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