diff options
author | 2015-04-13 17:49:17 -0700 | |
---|---|---|
committer | 2015-04-16 11:33:54 -0700 | |
commit | 44a175732dc4b872515f978b986ef7b357fe2f00 (patch) | |
tree | be4c2e92514d7f5ac8306149ba469607ffb0f0e7 /java/src/com/android/inputmethod/latin/ContactsManager.java | |
parent | 40f0f61bb365b5073f1d9fdb56a393c5df5ef4b0 (diff) | |
download | latinime-44a175732dc4b872515f978b986ef7b357fe2f00.tar.gz latinime-44a175732dc4b872515f978b986ef7b357fe2f00.tar.xz latinime-44a175732dc4b872515f978b986ef7b357fe2f00.zip |
Add affinity model for contact names.
This allows us to:
1. Rank contacts and only add the top N names to the keyboard LM.
2. Avoid adding duplicate names.
Note: The affinity calcualuation is limited by the fact that some apps
currently do not update the TIMES_CONTACTED counter. To better handle
this case, the new measure also takes into account whether or not a
name is in the visible contacts group.
Bug: 20053274
Change-Id: I2741cb8958667d4a294aba8c437a45cec4b42dc7
Diffstat (limited to 'java/src/com/android/inputmethod/latin/ContactsManager.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/ContactsManager.java | 99 |
1 files changed, 93 insertions, 6 deletions
diff --git a/java/src/com/android/inputmethod/latin/ContactsManager.java b/java/src/com/android/inputmethod/latin/ContactsManager.java index 7a971cf75..13503ff45 100644 --- a/java/src/com/android/inputmethod/latin/ContactsManager.java +++ b/java/src/com/android/inputmethod/latin/ContactsManager.java @@ -21,11 +21,17 @@ import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.ContactsContract.Contacts; +import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.StringUtils; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** @@ -38,6 +44,63 @@ public class ContactsManager { private static final String TAG = "ContactsManager"; /** + * Use at most this many of the highest affinity contacts. + */ + public static final int MAX_CONTACT_NAMES = 200; + + protected static class RankedContact { + public final String mName; + public final long mLastContactedTime; + public final int mTimesContacted; + public final boolean mInVisibleGroup; + + private float mAffinity = 0.0f; + + RankedContact(final Cursor cursor) { + mName = cursor.getString( + ContactsDictionaryConstants.NAME_INDEX); + mTimesContacted = cursor.getInt( + ContactsDictionaryConstants.TIMES_CONTACTED_INDEX); + mLastContactedTime = cursor.getLong( + ContactsDictionaryConstants.LAST_TIME_CONTACTED_INDEX); + mInVisibleGroup = cursor.getInt( + ContactsDictionaryConstants.IN_VISIBLE_GROUP_INDEX) == 1; + } + + float getAffinity() { + return mAffinity; + } + + /** + * Calculates the affinity with the contact based on: + * - How many times it has been contacted + * - How long since the last contact. + * - Whether the contact is in the visible group (i.e., Contacts list). + * + * Note: This affinity is limited by the fact that some apps currently do not update the + * LAST_TIME_CONTACTED or TIMES_CONTACTED counters. As a result, a frequently messaged + * contact may still have 0 affinity. + */ + void computeAffinity(final int maxTimesContacted, final long currentTime) { + final float timesWeight = ((float) mTimesContacted + 1) / (maxTimesContacted + 1); + final long timeSinceLastContact = Math.min( + Math.max(0, currentTime - mLastContactedTime), + TimeUnit.MILLISECONDS.convert(180, TimeUnit.DAYS)); + final float lastTimeWeight = (float) Math.pow(0.5, + timeSinceLastContact / (TimeUnit.MILLISECONDS.convert(10, TimeUnit.DAYS))); + final float visibleWeight = mInVisibleGroup ? 1.0f : 0.0f; + mAffinity = (timesWeight + lastTimeWeight + visibleWeight) / 3; + } + } + + private static class AffinityComparator implements Comparator<RankedContact> { + @Override + public int compare(RankedContact contact1, RankedContact contact2) { + return Float.compare(contact2.getAffinity(), contact1.getAffinity()); + } + } + + /** * Interface to implement for classes interested in getting notified for updates * to Contacts content provider. */ @@ -82,14 +145,18 @@ public class ContactsManager { * Returns all the valid names in the Contacts DB. Callers should also * call {@link #updateLocalState(ArrayList)} after they are done with result * so that the manager can cache local state for determining updates. + * + * These names are sorted by their affinity to the user, with favorite + * contacts appearing first. */ public ArrayList<String> getValidNames(final Uri uri) { - final ArrayList<String> names = new ArrayList<>(); // Check all contacts since it's not possible to find out which names have changed. // This is needed because it's possible to receive extraneous onChange events even when no // name has changed. final Cursor cursor = mContext.getContentResolver().query(uri, ContactsDictionaryConstants.PROJECTION, null, null, null); + final ArrayList<RankedContact> contacts = new ArrayList<>(); + int maxTimesContacted = 0; if (cursor != null) { try { if (cursor.moveToFirst()) { @@ -97,7 +164,12 @@ public class ContactsManager { final String name = cursor.getString( ContactsDictionaryConstants.NAME_INDEX); if (isValidName(name)) { - names.add(name); + final int timesContacted = cursor.getInt( + ContactsDictionaryConstants.TIMES_CONTACTED_INDEX); + if (timesContacted > maxTimesContacted) { + maxTimesContacted = timesContacted; + } + contacts.add(new RankedContact(cursor)); } cursor.moveToNext(); } @@ -106,7 +178,16 @@ public class ContactsManager { cursor.close(); } } - return names; + final long currentTime = System.currentTimeMillis(); + for (RankedContact contact : contacts) { + contact.computeAffinity(maxTimesContacted, currentTime); + } + Collections.sort(contacts, new AffinityComparator()); + final HashSet<String> names = new HashSet<>(); + for (int i = 0; i < contacts.size() && names.size() < MAX_CONTACT_NAMES; ++i) { + names.add(contacts.get(i).mName); + } + return new ArrayList<>(names); } /** @@ -134,10 +215,16 @@ public class ContactsManager { } private static boolean isValidName(final String name) { - if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) { - return true; + if (TextUtils.isEmpty(name) || name.indexOf(Constants.CODE_COMMERCIAL_AT) != -1) { + return false; + } + final boolean hasSpace = name.indexOf(Constants.CODE_SPACE) != -1; + if (!hasSpace) { + // Only allow an isolated word if it does not contain a hyphen. + // This helps to filter out mailing lists. + return name.indexOf(Constants.CODE_DASH) == -1; } - return false; + return true; } /** |