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.app.dialpad;
     18 
     19 import android.content.AsyncTaskLoader;
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.database.MatrixCursor;
     23 import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
     24 import com.android.dialer.common.LogUtil;
     25 import com.android.dialer.database.Database;
     26 import com.android.dialer.database.DialerDatabaseHelper;
     27 import com.android.dialer.database.DialerDatabaseHelper.ContactNumber;
     28 import com.android.dialer.smartdial.SmartDialNameMatcher;
     29 import com.android.dialer.smartdial.SmartDialPrefix;
     30 import com.android.dialer.util.PermissionsUtil;
     31 import java.util.ArrayList;
     32 
     33 /** Implements a Loader<Cursor> class to asynchronously load SmartDial search results. */
     34 public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> {
     35 
     36   private static final String TAG = "SmartDialCursorLoader";
     37   private static final boolean DEBUG = false;
     38 
     39   private final Context mContext;
     40 
     41   private Cursor mCursor;
     42 
     43   private String mQuery;
     44   private SmartDialNameMatcher mNameMatcher;
     45 
     46   private boolean mShowEmptyListForNullQuery = true;
     47 
     48   public SmartDialCursorLoader(Context context) {
     49     super(context);
     50     mContext = context;
     51   }
     52 
     53   /**
     54    * Configures the query string to be used to find SmartDial matches.
     55    *
     56    * @param query The query string user typed.
     57    */
     58   public void configureQuery(String query) {
     59     if (DEBUG) {
     60       LogUtil.v(TAG, "Configure new query to be " + query);
     61     }
     62     mQuery = SmartDialNameMatcher.normalizeNumber(query, SmartDialPrefix.getMap());
     63 
     64     /** Constructs a name matcher object for matching names. */
     65     mNameMatcher = new SmartDialNameMatcher(mQuery, SmartDialPrefix.getMap());
     66     mNameMatcher.setShouldMatchEmptyQuery(!mShowEmptyListForNullQuery);
     67   }
     68 
     69   /**
     70    * Queries the SmartDial database and loads results in background.
     71    *
     72    * @return Cursor of contacts that matches the SmartDial query.
     73    */
     74   @Override
     75   public Cursor loadInBackground() {
     76     if (DEBUG) {
     77       LogUtil.v(TAG, "Load in background " + mQuery);
     78     }
     79 
     80     if (!PermissionsUtil.hasContactsReadPermissions(mContext)) {
     81       return new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY);
     82     }
     83 
     84     /** Loads results from the database helper. */
     85     final DialerDatabaseHelper dialerDatabaseHelper =
     86         Database.get(mContext).getDatabaseHelper(mContext);
     87     final ArrayList<ContactNumber> allMatches =
     88         dialerDatabaseHelper.getLooseMatches(mQuery, mNameMatcher);
     89 
     90     if (DEBUG) {
     91       LogUtil.v(TAG, "Loaded matches " + allMatches.size());
     92     }
     93 
     94     /** Constructs a cursor for the returned array of results. */
     95     final MatrixCursor cursor = new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY);
     96     Object[] row = new Object[PhoneQuery.PROJECTION_PRIMARY.length];
     97     for (ContactNumber contact : allMatches) {
     98       row[PhoneQuery.PHONE_ID] = contact.dataId;
     99       row[PhoneQuery.PHONE_NUMBER] = contact.phoneNumber;
    100       row[PhoneQuery.CONTACT_ID] = contact.id;
    101       row[PhoneQuery.LOOKUP_KEY] = contact.lookupKey;
    102       row[PhoneQuery.PHOTO_ID] = contact.photoId;
    103       row[PhoneQuery.DISPLAY_NAME] = contact.displayName;
    104       row[PhoneQuery.CARRIER_PRESENCE] = contact.carrierPresence;
    105       cursor.addRow(row);
    106     }
    107     return cursor;
    108   }
    109 
    110   @Override
    111   public void deliverResult(Cursor cursor) {
    112     if (isReset()) {
    113       /** The Loader has been reset; ignore the result and invalidate the data. */
    114       releaseResources(cursor);
    115       return;
    116     }
    117 
    118     /** Hold a reference to the old data so it doesn't get garbage collected. */
    119     Cursor oldCursor = mCursor;
    120     mCursor = cursor;
    121 
    122     if (isStarted()) {
    123       /** If the Loader is in a started state, deliver the results to the client. */
    124       super.deliverResult(cursor);
    125     }
    126 
    127     /** Invalidate the old data as we don't need it any more. */
    128     if (oldCursor != null && oldCursor != cursor) {
    129       releaseResources(oldCursor);
    130     }
    131   }
    132 
    133   @Override
    134   protected void onStartLoading() {
    135     if (mCursor != null) {
    136       /** Deliver any previously loaded data immediately. */
    137       deliverResult(mCursor);
    138     }
    139     if (mCursor == null) {
    140       /** Force loads every time as our results change with queries. */
    141       forceLoad();
    142     }
    143   }
    144 
    145   @Override
    146   protected void onStopLoading() {
    147     /** The Loader is in a stopped state, so we should attempt to cancel the current load. */
    148     cancelLoad();
    149   }
    150 
    151   @Override
    152   protected void onReset() {
    153     /** Ensure the loader has been stopped. */
    154     onStopLoading();
    155 
    156     /** Release all previously saved query results. */
    157     if (mCursor != null) {
    158       releaseResources(mCursor);
    159       mCursor = null;
    160     }
    161   }
    162 
    163   @Override
    164   public void onCanceled(Cursor cursor) {
    165     super.onCanceled(cursor);
    166 
    167     /** The load has been canceled, so we should release the resources associated with 'data'. */
    168     releaseResources(cursor);
    169   }
    170 
    171   private void releaseResources(Cursor cursor) {
    172     if (cursor != null) {
    173       cursor.close();
    174     }
    175   }
    176 
    177   public void setShowEmptyListForNullQuery(boolean show) {
    178     mShowEmptyListForNullQuery = show;
    179     if (mNameMatcher != null) {
    180       mNameMatcher.setShouldMatchEmptyQuery(!show);
    181     }
    182   }
    183 }
    184