• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SmartDialCursorLoader(Context context)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    */
configureQuery(String query)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
loadInBackground()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
deliverResult(Cursor cursor)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
onStartLoading()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
onStopLoading()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
onReset()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
onCanceled(Cursor cursor)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 
releaseResources(Cursor cursor)170   private void releaseResources(Cursor cursor) {
171     if (cursor != null) {
172       cursor.close();
173     }
174   }
175 
setShowEmptyListForNullQuery(boolean show)176   public void setShowEmptyListForNullQuery(boolean show) {
177     showEmptyListForNullQuery = show;
178     if (nameMatcher != null) {
179       nameMatcher.setShouldMatchEmptyQuery(!show);
180     }
181   }
182 }
183