Home | History | Annotate | Download | only in dialpad
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dialer.dialpad;
     18 
     19 import android.content.AsyncTaskLoader;
     20 import android.content.Context;
     21 import android.content.Loader.ForceLoadContentObserver;
     22 import android.database.Cursor;
     23 import android.database.MatrixCursor;
     24 import android.net.Uri;
     25 import android.util.Log;
     26 
     27 import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
     28 import com.android.contacts.common.util.PermissionsUtil;
     29 import com.android.dialer.database.DialerDatabaseHelper;
     30 import com.android.dialer.database.DialerDatabaseHelper.ContactNumber;
     31 import com.android.dialerbind.DatabaseHelperManager;
     32 
     33 import java.util.ArrayList;
     34 
     35 /**
     36  * Implements a Loader<Cursor> class to asynchronously load SmartDial search results.
     37  */
     38 public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> {
     39 
     40     private final String TAG = SmartDialCursorLoader.class.getSimpleName();
     41     private final boolean DEBUG = false;
     42 
     43     private final Context mContext;
     44 
     45     private Cursor mCursor;
     46 
     47     private String mQuery;
     48     private SmartDialNameMatcher mNameMatcher;
     49 
     50     private ForceLoadContentObserver mObserver;
     51 
     52     public SmartDialCursorLoader(Context context) {
     53         super(context);
     54         mContext = context;
     55     }
     56 
     57     /**
     58      * Configures the query string to be used to find SmartDial matches.
     59      * @param query The query string user typed.
     60      */
     61     public void configureQuery(String query) {
     62         if (DEBUG) {
     63             Log.v(TAG, "Configure new query to be " + query);
     64         }
     65         mQuery = SmartDialNameMatcher.normalizeNumber(query, SmartDialPrefix.getMap());
     66 
     67         /** Constructs a name matcher object for matching names. */
     68         mNameMatcher = new SmartDialNameMatcher(mQuery, SmartDialPrefix.getMap());
     69     }
     70 
     71     /**
     72      * Queries the SmartDial database and loads results in background.
     73      * @return Cursor of contacts that matches the SmartDial query.
     74      */
     75     @Override
     76     public Cursor loadInBackground() {
     77         if (DEBUG) {
     78             Log.v(TAG, "Load in background " + mQuery);
     79         }
     80 
     81         if (!PermissionsUtil.hasContactsPermissions(mContext)) {
     82             return new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY);
     83         }
     84 
     85         /** Loads results from the database helper. */
     86         final DialerDatabaseHelper dialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(
     87                 mContext);
     88         final ArrayList<ContactNumber> allMatches = dialerDatabaseHelper.getLooseMatches(mQuery,
     89                 mNameMatcher);
     90 
     91         if (DEBUG) {
     92             Log.v(TAG, "Loaded matches " + String.valueOf(allMatches.size()));
     93         }
     94 
     95         /** Constructs a cursor for the returned array of results. */
     96         final MatrixCursor cursor = new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY);
     97         Object[] row = new Object[PhoneQuery.PROJECTION_PRIMARY.length];
     98         for (ContactNumber contact : allMatches) {
     99             row[PhoneQuery.PHONE_ID] = contact.dataId;
    100             row[PhoneQuery.PHONE_NUMBER] = contact.phoneNumber;
    101             row[PhoneQuery.CONTACT_ID] = contact.id;
    102             row[PhoneQuery.LOOKUP_KEY] = contact.lookupKey;
    103             row[PhoneQuery.PHOTO_ID] = contact.photoId;
    104             row[PhoneQuery.DISPLAY_NAME] = contact.displayName;
    105             row[PhoneQuery.CARRIER_PRESENCE] = contact.carrierPresence;
    106             cursor.addRow(row);
    107         }
    108         return cursor;
    109     }
    110 
    111     @Override
    112     public void deliverResult(Cursor cursor) {
    113         if (isReset()) {
    114             /** The Loader has been reset; ignore the result and invalidate the data. */
    115             releaseResources(cursor);
    116             return;
    117         }
    118 
    119         /** Hold a reference to the old data so it doesn't get garbage collected. */
    120         Cursor oldCursor = mCursor;
    121         mCursor = cursor;
    122 
    123         if (mObserver == null) {
    124             mObserver = new ForceLoadContentObserver();
    125             mContext.getContentResolver().registerContentObserver(
    126                     DialerDatabaseHelper.SMART_DIAL_UPDATED_URI, true, mObserver);
    127         }
    128 
    129         if (isStarted()) {
    130             /** If the Loader is in a started state, deliver the results to the client. */
    131             super.deliverResult(cursor);
    132         }
    133 
    134         /** Invalidate the old data as we don't need it any more. */
    135         if (oldCursor != null && oldCursor != cursor) {
    136             releaseResources(oldCursor);
    137         }
    138     }
    139 
    140     @Override
    141     protected void onStartLoading() {
    142         if (mCursor != null) {
    143             /** Deliver any previously loaded data immediately. */
    144             deliverResult(mCursor);
    145         }
    146         if (mCursor == null) {
    147             /** Force loads every time as our results change with queries. */
    148             forceLoad();
    149         }
    150     }
    151 
    152     @Override
    153     protected void onStopLoading() {
    154         /** The Loader is in a stopped state, so we should attempt to cancel the current load. */
    155         cancelLoad();
    156     }
    157 
    158     @Override
    159     protected void onReset() {
    160         /** Ensure the loader has been stopped. */
    161         onStopLoading();
    162 
    163         if (mObserver != null) {
    164             mContext.getContentResolver().unregisterContentObserver(mObserver);
    165             mObserver = null;
    166         }
    167 
    168         /** Release all previously saved query results. */
    169         if (mCursor != null) {
    170             releaseResources(mCursor);
    171             mCursor = null;
    172         }
    173     }
    174 
    175     @Override
    176     public void onCanceled(Cursor cursor) {
    177         super.onCanceled(cursor);
    178 
    179         if (mObserver != null) {
    180             mContext.getContentResolver().unregisterContentObserver(mObserver);
    181             mObserver = null;
    182         }
    183 
    184         /** The load has been canceled, so we should release the resources associated with 'data'.*/
    185         releaseResources(cursor);
    186     }
    187 
    188     private void releaseResources(Cursor cursor) {
    189         if (cursor != null) {
    190             cursor.close();
    191         }
    192     }
    193 }
    194