| /* |
| * 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.base.Preconditions; |
| import com.google.common.collect.Lists; |
| |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.Contacts; |
| import android.util.Log; |
| |
| import com.android.contacts.common.util.StopWatch; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Cache object used to cache Smart Dial contacts that handles various states of the cache: |
| * 1) Cache has been populated |
| * 2) Cache task is currently running |
| * 3) Cache task failed |
| */ |
| public class SmartDialCache { |
| |
| public static class Contact { |
| public final String displayName; |
| public final String lookupKey; |
| public final long id; |
| |
| public Contact(long id, String displayName, String lookupKey) { |
| this.displayName = displayName; |
| this.lookupKey = lookupKey; |
| this.id = id; |
| } |
| } |
| |
| /** 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.HAS_PHONE_NUMBER + "=1"; |
| |
| String ORDER_BY = Contacts.LAST_TIME_CONTACTED + " DESC"; |
| } |
| |
| // mContactsCache and mCachingStarted need to be volatile because we check for their status |
| // in cacheIfNeeded from the UI thread, to decided whether or not to fire up a caching thread. |
| private List<Contact> mContactsCache; |
| private volatile boolean mNeedsRecache = true; |
| private final int mNameDisplayOrder; |
| private final Context mContext; |
| private final Object mLock = new Object(); |
| |
| private static final boolean DEBUG = true; // STOPSHIP change to false. |
| |
| public SmartDialCache(Context context, int nameDisplayOrder) { |
| mNameDisplayOrder = nameDisplayOrder; |
| Preconditions.checkNotNull(context, "Context must not be null"); |
| mContext = context.getApplicationContext(); |
| } |
| |
| /** |
| * Performs a database query, iterates through the returned cursor and saves the retrieved |
| * contacts to a local cache. |
| */ |
| private void cacheContacts(Context context) { |
| synchronized(mLock) { |
| // In extremely rare edge cases, getContacts() might be called and start caching |
| // between the time mCachingThread is added to the thread pool and it starts |
| // running. If so, at this point in time mContactsCache will no longer be null |
| // since it is populated by getContacts. We thus no longer have to perform any |
| // caching. |
| if (mContactsCache != null) { |
| if (DEBUG) { |
| Log.d(LOG_TAG, "Contacts already cached"); |
| } |
| return; |
| } |
| final StopWatch stopWatch = DEBUG ? StopWatch.start("SmartDial Cache") : null; |
| final Cursor c = context.getContentResolver().query(ContactQuery.URI, |
| (mNameDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) |
| ? ContactQuery.PROJECTION : ContactQuery.PROJECTION_ALTERNATIVE, |
| ContactQuery.SELECTION, null, |
| ContactQuery.ORDER_BY); |
| if (c == null) { |
| Log.w(LOG_TAG, "SmartDial query received null for cursor"); |
| if (DEBUG) { |
| stopWatch.stopAndLog("Query Failure", 0); |
| } |
| return; |
| } |
| try { |
| mContactsCache = Lists.newArrayListWithCapacity(c.getCount()); |
| 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); |
| mContactsCache.add(new Contact(id, displayName, lookupKey)); |
| } |
| } finally { |
| c.close(); |
| if (DEBUG) { |
| stopWatch.stopAndLog("SmartDial Cache", 0); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the list of cached contacts. If the caching task has not started or been completed, |
| * the method blocks till the caching process is complete before returning the full list of |
| * cached contacts. This means that this method should not be called from the UI thread. |
| * |
| * @return List of already cached contacts, or an empty list if the caching failed for any |
| * reason. |
| */ |
| public List<Contact> getContacts() { |
| synchronized(mLock) { |
| if (mContactsCache == null) { |
| cacheContacts(mContext); |
| mNeedsRecache = false; |
| return (mContactsCache == null) ? new ArrayList<Contact>() : mContactsCache; |
| } else { |
| return mContactsCache; |
| } |
| } |
| } |
| |
| /** |
| * Only start a new caching task if {@link #mContactsCache} is null and there is no caching |
| * task that is currently running |
| */ |
| public void cacheIfNeeded() { |
| if (mNeedsRecache) { |
| mNeedsRecache = false; |
| startCachingThread(); |
| } |
| } |
| |
| private void startCachingThread() { |
| new Thread(new Runnable() { |
| @Override |
| public void run() { |
| cacheContacts(mContext); |
| } |
| }).start(); |
| } |
| |
| } |