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