| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dialer.dialpad; |
| |
| import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG; |
| |
| import android.os.AsyncTask; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.Contacts; |
| import android.telephony.PhoneNumberUtils; |
| import android.util.Log; |
| |
| import com.android.contacts.common.preference.ContactsPreferences; |
| import com.android.contacts.common.util.StopWatch; |
| import com.android.dialer.dialpad.SmartDialCache.ContactNumber; |
| |
| import com.google.common.base.Objects; |
| import com.google.common.collect.Lists; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * This task searches through the provided cache to return the top 3 contacts(ranked by confidence) |
| * that match the query, then passes it back to the {@link SmartDialLoaderCallback} through a |
| * callback function. |
| */ |
| public class SmartDialLoaderTask extends AsyncTask<String, Integer, List<SmartDialEntry>> { |
| |
| public interface SmartDialLoaderCallback { |
| void setSmartDialAdapterEntries(List<SmartDialEntry> list, String query); |
| } |
| |
| static private final boolean DEBUG = false; |
| |
| private static final int MAX_ENTRIES = 3; |
| |
| private final SmartDialCache mContactsCache; |
| |
| private final SmartDialLoaderCallback mCallback; |
| |
| private final String mQuery; |
| |
| /** |
| * See {@link ContactsPreferences#getDisplayOrder()}. |
| * {@link ContactsContract.Preferences#DISPLAY_ORDER_PRIMARY} (first name first) |
| * {@link ContactsContract.Preferences#DISPLAY_ORDER_ALTERNATIVE} (last name first) |
| */ |
| private final SmartDialNameMatcher mNameMatcher; |
| |
| public SmartDialLoaderTask(SmartDialLoaderCallback callback, String query, |
| SmartDialCache cache) { |
| this.mCallback = callback; |
| this.mNameMatcher = new SmartDialNameMatcher(PhoneNumberUtils.normalizeNumber(query)); |
| this.mContactsCache = cache; |
| this.mQuery = query; |
| } |
| |
| @Override |
| protected List<SmartDialEntry> doInBackground(String... params) { |
| return getContactMatches(); |
| } |
| |
| @Override |
| protected void onPostExecute(List<SmartDialEntry> result) { |
| if (mCallback != null) { |
| mCallback.setSmartDialAdapterEntries(result, mQuery); |
| } |
| } |
| |
| /** |
| * Loads all visible contacts with phone numbers and check if their display names match the |
| * query. Return at most {@link #MAX_ENTRIES} {@link SmartDialEntry}'s for the matching |
| * contacts. |
| */ |
| private ArrayList<SmartDialEntry> getContactMatches() { |
| |
| final SmartDialTrie trie = mContactsCache.getContacts(); |
| if (DEBUG) { |
| Log.d(LOG_TAG, "Size of cache: " + trie.size()); |
| } |
| |
| final StopWatch stopWatch = DEBUG ? StopWatch.start("Start Match") : null; |
| final ArrayList<ContactNumber> allMatches = trie.getAllWithPrefix(mNameMatcher.getQuery()); |
| if (DEBUG) { |
| stopWatch.lap("Find matches"); |
| } |
| // Sort matches in order of ascending contact affinity (lower is better) |
| Collections.sort(allMatches, new SmartDialCache.ContactAffinityComparator()); |
| if (DEBUG) { |
| stopWatch.lap("Sort"); |
| } |
| final Set<ContactMatch> duplicates = new HashSet<ContactMatch>(); |
| final ArrayList<SmartDialEntry> candidates = Lists.newArrayList(); |
| for (ContactNumber contact : allMatches) { |
| final ContactMatch contactMatch = new ContactMatch(contact.lookupKey, contact.id); |
| // Don't add multiple contact numbers from the same contact into suggestions if |
| // there are multiple matches. Instead, just keep the highest priority number |
| // instead. |
| if (duplicates.contains(contactMatch)) { |
| continue; |
| } |
| duplicates.add(contactMatch); |
| final boolean matches = mNameMatcher.matches(contact.displayName); |
| candidates.add(new SmartDialEntry( |
| contact.displayName, |
| Contacts.getLookupUri(contact.id, contact.lookupKey), |
| contact.phoneNumber, |
| mNameMatcher.getMatchPositions(), |
| mNameMatcher.matchesNumber(contact.phoneNumber, mNameMatcher.getQuery()) |
| )); |
| if (candidates.size() >= MAX_ENTRIES) { |
| break; |
| } |
| } |
| if (DEBUG) { |
| stopWatch.stopAndLog(LOG_TAG + " Match Complete", 0); |
| } |
| return candidates; |
| } |
| |
| private class ContactMatch { |
| public final String lookupKey; |
| public final long id; |
| |
| public ContactMatch(String lookupKey, long id) { |
| this.lookupKey = lookupKey; |
| this.id = id; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(lookupKey, id); |
| } |
| |
| @Override |
| public boolean equals(Object object) { |
| if (this == object) { |
| return true; |
| } |
| if (object instanceof ContactMatch) { |
| ContactMatch that = (ContactMatch) object; |
| return Objects.equal(this.lookupKey, that.lookupKey) |
| && Objects.equal(this.id, that.id); |
| } |
| return false; |
| } |
| } |
| } |