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