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