diff options
Diffstat (limited to 'src/com/android/dialer/dialpad/SmartDialLoaderTask.java')
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialLoaderTask.java | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java new file mode 100644 index 000000000..ec99d8a14 --- /dev/null +++ b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.dialpad; + +import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG; + +import com.google.common.collect.Lists; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.util.StopWatch; + +import java.util.ArrayList; +import java.util.List; + +/** + * AsyncTask that performs one of two functions depending on which constructor is used. + * If {@link #SmartDialLoaderTask(Context context, int nameDisplayOrder)} is used, the task + * caches all contacts with a phone number into the static variable {@link #sContactsCache}. + * If {@link #SmartDialLoaderTask(SmartDialLoaderCallback callback, String query)} is used, the + * task searches through the cache to return the top 3 contacts(ranked by confidence) that match + * the query, then passes it back to the {@link SmartDialLoaderCallback} through a callback + * function. + */ +// TODO: Make the cache a singleton class and refactor to fix possible concurrency issues in the +// future +public class SmartDialLoaderTask extends AsyncTask<String, Integer, List<SmartDialEntry>> { + + private class Contact { + final String mDisplayName; + final String mStrippedDisplayName; + final String mLookupKey; + final long mId; + + public Contact(long id, String displayName, String lookupKey) { + mDisplayName = displayName; + mStrippedDisplayName = SmartDialNameMatcher.stripDiacritics(displayName); + mLookupKey = lookupKey; + mId = id; + } + } + + public interface SmartDialLoaderCallback { + void setSmartDialAdapterEntries(List<SmartDialEntry> list); + } + + static private final boolean DEBUG = true; // STOPSHIP change to false. + + private static final int MAX_ENTRIES = 3; + + private static List<Contact> sContactsCache; + + private final boolean mCacheOnly; + + private final SmartDialLoaderCallback mCallback; + + private final Context mContext; + /** + * See {@link ContactsPreferences#getDisplayOrder()}. + * {@link ContactsContract.Preferences#DISPLAY_ORDER_PRIMARY} (first name first) + * {@link ContactsContract.Preferences#DISPLAY_ORDER_ALTERNATIVE} (last name first) + */ + private final int mNameDisplayOrder; + + private final SmartDialNameMatcher mNameMatcher; + + // cache only constructor + private SmartDialLoaderTask(Context context, int nameDisplayOrder) { + this.mNameDisplayOrder = nameDisplayOrder; + this.mContext = context; + // we're just caching contacts so no need to initialize a SmartDialNameMatcher or callback + this.mNameMatcher = null; + this.mCallback = null; + this.mCacheOnly = true; + } + + public SmartDialLoaderTask(SmartDialLoaderCallback callback, String query) { + this.mCallback = callback; + this.mContext = null; + this.mCacheOnly = false; + this.mNameDisplayOrder = 0; + this.mNameMatcher = new SmartDialNameMatcher(PhoneNumberUtils.normalizeNumber(query)); + } + + @Override + protected List<SmartDialEntry> doInBackground(String... params) { + if (mCacheOnly) { + cacheContacts(); + return Lists.newArrayList(); + } + + return getContactMatches(); + } + + @Override + protected void onPostExecute(List<SmartDialEntry> result) { + if (mCallback != null) { + mCallback.setSmartDialAdapterEntries(result); + } + } + + /** Query used for loadByContactName */ + private interface ContactQuery { + Uri URI = Contacts.CONTENT_URI.buildUpon() + // Visible contact only + //.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, "0") + .build(); + String[] PROJECTION = new String[] { + Contacts._ID, + Contacts.DISPLAY_NAME, + Contacts.LOOKUP_KEY + }; + String[] PROJECTION_ALTERNATIVE = new String[] { + Contacts._ID, + Contacts.DISPLAY_NAME_ALTERNATIVE, + Contacts.LOOKUP_KEY + }; + + int COLUMN_ID = 0; + int COLUMN_DISPLAY_NAME = 1; + int COLUMN_LOOKUP_KEY = 2; + + String SELECTION = + //Contacts.IN_VISIBLE_GROUP + "=1 and " + + Contacts.HAS_PHONE_NUMBER + "=1"; + + String ORDER_BY = Contacts.LAST_TIME_CONTACTED + " DESC"; + } + + public static void startCacheContactsTaskIfNeeded(Context context, int displayOrder) { + if (sContactsCache != null) { + // contacts have already been cached, just return + return; + } + final SmartDialLoaderTask task = + new SmartDialLoaderTask(context, displayOrder); + task.execute(); + } + + /** + * Caches the contacts into an in memory array list. This is called once at startup and should + * not be cancelled. + */ + private void cacheContacts() { + final StopWatch stopWatch = DEBUG ? StopWatch.start("SmartDial Cache") : null; + if (sContactsCache != null) { + // contacts have already been cached, just return + stopWatch.stopAndLog("SmartDial Already Cached", 0); + return; + } + + final Cursor c = mContext.getContentResolver().query(ContactQuery.URI, + (mNameDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) + ? ContactQuery.PROJECTION : ContactQuery.PROJECTION_ALTERNATIVE, + ContactQuery.SELECTION, null, + ContactQuery.ORDER_BY); + if (c == null) { + stopWatch.stopAndLog("Query Failuregi", 0); + return; + } + sContactsCache = Lists.newArrayListWithCapacity(c.getCount()); + try { + c.moveToPosition(-1); + while (c.moveToNext()) { + final String displayName = c.getString(ContactQuery.COLUMN_DISPLAY_NAME); + final long id = c.getLong(ContactQuery.COLUMN_ID); + final String lookupKey = c.getString(ContactQuery.COLUMN_LOOKUP_KEY); + sContactsCache.add(new Contact(id, displayName, lookupKey)); + } + } finally { + c.close(); + if (DEBUG) { + stopWatch.stopAndLog("SmartDial Cache", 0); + } + } + } + + /** + * Loads all visible contacts with phone numbers and check if their display names match the + * query. Return at most {@link #MAX_ENTRIES} {@link SmartDialEntry}'s for the matching + * contacts. + */ + private ArrayList<SmartDialEntry> getContactMatches() { + final StopWatch stopWatch = DEBUG ? StopWatch.start(LOG_TAG + " Start Match") : null; + if (sContactsCache == null) { + // contacts should have been cached by this point in time, but in case they + // are not, we go ahead and cache them into memory. + if (DEBUG) { + Log.d(LOG_TAG, "empty cache"); + } + cacheContacts(); + // TODO: if sContactsCache is still null at this point we should try to recache + } + if (DEBUG) { + Log.d(LOG_TAG, "Size of cache: " + sContactsCache.size()); + } + final ArrayList<SmartDialEntry> outList = Lists.newArrayList(); + if (sContactsCache == null) { + return outList; + } + int count = 0; + for (int i = 0; i < sContactsCache.size(); i++) { + final Contact contact = sContactsCache.get(i); + final String strippedDisplayName = contact.mStrippedDisplayName; + + if (!mNameMatcher.matches(strippedDisplayName)) { + continue; + } + // Matched; create SmartDialEntry. + @SuppressWarnings("unchecked") + final SmartDialEntry entry = new SmartDialEntry( + contact.mDisplayName, + Contacts.getLookupUri(contact.mId, contact.mLookupKey), + (ArrayList<SmartDialMatchPosition>) mNameMatcher.getMatchPositions().clone() + ); + outList.add(entry); + count++; + if (count >= MAX_ENTRIES) { + break; + } + } + if (DEBUG) { + stopWatch.stopAndLog(LOG_TAG + " Match Complete", 0); + } + return outList; + } +} |