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 SmartDialCursorLoader(Context context)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 */ configureQuery(String query)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 loadInBackground()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 deliverResult(Cursor cursor)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 onStartLoading()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 onStopLoading()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 onReset()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 onCanceled(Cursor cursor)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 releaseResources(Cursor cursor)188 private void releaseResources(Cursor cursor) { 189 if (cursor != null) { 190 cursor.close(); 191 } 192 } 193 } 194