From 1be0178a11b1cc3b06867b14446e1e041e97a82c Mon Sep 17 00:00:00 2001 From: Yorke Lee Date: Wed, 7 Nov 2012 15:11:44 -0800 Subject: Add smart dialling capabilities to dialer Layout changes to dialpad fragment to make space for smart dial suggestions. This feature does not appear in landscape mode. SmartDialTextView automatically resizes text to fit within the bounds of the view. SmartDialAdapter highlights certain portions of the matching name based on match positions provided by SmartDialNameMatcher. SmartDialLoaderTask is an AsyncTask that caches all contacts with a phone number into memory, or matches all contact names against a provided query to return a list of matches (maximum of 3). SmartDialNameMatcher contains utility functions to remove accents from accented characters and normalize a phone number. It also contains the matching logic that determines if a contact's display name matches a numeric query. Added some tests for SmartDialNameMatcher Bug: 6977981 Change-Id: I43e1a70d8d0d46e02fc67fe1caaec9a1769124f5 --- .../android/dialer/dialpad/DialpadFragment.java | 103 ++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) (limited to 'src/com/android/dialer/dialpad/DialpadFragment.java') diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java index 2b77b4619..3af7f9bec 100644 --- a/src/com/android/dialer/dialpad/DialpadFragment.java +++ b/src/com/android/dialer/dialpad/DialpadFragment.java @@ -31,6 +31,7 @@ import android.graphics.BitmapFactory; import android.media.AudioManager; import android.media.ToneGenerator; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; @@ -39,6 +40,7 @@ import android.provider.Contacts.Intents.Insert; import android.provider.Contacts.People; import android.provider.Contacts.Phones; import android.provider.Contacts.PhonesColumns; +import android.provider.ContactsContract.Contacts; import android.provider.Settings; import android.telephony.PhoneNumberUtils; import android.telephony.PhoneStateListener; @@ -59,6 +61,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.EditText; @@ -68,9 +71,12 @@ import android.widget.PopupMenu; import android.widget.TextView; import com.android.contacts.common.CallUtil; +import com.android.contacts.common.activity.TransactionSafeActivity; +import com.android.dialer.interactions.PhoneNumberInteraction; import com.android.contacts.common.GeoUtil; import com.android.contacts.common.util.PhoneNumberFormatter; import com.android.contacts.common.util.StopWatch; +import com.android.contacts.common.preference.ContactsPreferences; import com.android.dialer.DialtactsActivity; import com.android.dialer.R; import com.android.dialer.SpecialCharSequenceMgr; @@ -79,6 +85,9 @@ import com.android.internal.telephony.ITelephony; import com.android.phone.common.CallLogAsync; import com.android.phone.common.HapticFeedback; +import java.util.ArrayList; +import java.util.List; + /** * Fragment that displays a twelve-key phone dialpad. */ @@ -87,7 +96,8 @@ public class DialpadFragment extends Fragment View.OnLongClickListener, View.OnKeyListener, AdapterView.OnItemClickListener, TextWatcher, PopupMenu.OnMenuItemClickListener, - DialpadImageButton.OnPressedListener { + DialpadImageButton.OnPressedListener, + SmartDialLoaderTask.SmartDialLoaderCallback { private static final String TAG = DialpadFragment.class.getSimpleName(); private static final boolean DEBUG = DialtactsActivity.DEBUG; @@ -104,6 +114,8 @@ public class DialpadFragment extends Fragment /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */ private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF; + private ContactsPreferences mContactsPrefs; + /** * View (usually FrameLayout) containing mDigits field. This can be null, in which mDigits * isn't enclosed by the container. @@ -131,6 +143,15 @@ public class DialpadFragment extends Fragment private ListView mDialpadChooser; private DialpadChooserAdapter mDialpadChooserAdapter; + /** Will be set only if the view has the smart dialing section. */ + private AbsListView mSmartDialList; + + /** + * Adapter for {@link #mSmartDialList}. + * Will be set only if the view has the smart dialing section. + */ + private SmartDialAdapter mSmartDialAdapter; + /** * Regular expression prohibiting manual phone call. Can be empty, which means "no rule". */ @@ -149,6 +170,7 @@ public class DialpadFragment extends Fragment // Vibration (haptic feedback) for dialer key presses. private final HapticFeedback mHaptic = new HapticFeedback(); + private boolean mNeedToCacheSmartDial = false; /** Identifier for the "Add Call" intent extra. */ private static final String ADD_CALL_MODE_KEY = "add_call_mode"; @@ -246,14 +268,16 @@ public class DialpadFragment extends Fragment } updateDialAndDeleteButtonEnabledState(); + loadSmartDialEntries(); } @Override public void onCreate(Bundle state) { super.onCreate(state); + mContactsPrefs = new ContactsPreferences(getActivity()); mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); - + mNeedToCacheSmartDial = true; try { mHaptic.init(getActivity(), getResources().getBoolean(R.bool.config_enable_dialer_key_vibration)); @@ -269,6 +293,13 @@ public class DialpadFragment extends Fragment if (state != null) { mDigitsFilledByIntent = state.getBoolean(PREF_DIGITS_FILLED_BY_INTENT); } + + // Start caching contacts to use for smart dialling only if the dialpad fragment is visible + if (getUserVisibleHint()) { + SmartDialLoaderTask.startCacheContactsTaskIfNeeded( + getActivity(), mContactsPrefs.getDisplayOrder()); + mNeedToCacheSmartDial = false; + } } @Override @@ -334,6 +365,14 @@ public class DialpadFragment extends Fragment mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser); mDialpadChooser.setOnItemClickListener(this); + // Smart dial + mSmartDialList = (AbsListView) fragmentView.findViewById(R.id.dialpad_smartdial_list); + if (mSmartDialList != null) { + mSmartDialAdapter = new SmartDialAdapter(getActivity()); + mSmartDialList.setAdapter(mSmartDialAdapter); + mSmartDialList.setOnItemClickListener(new OnSmartDialItemClick()); + } + configureScreenFromIntent(getActivity().getIntent()); return fragmentView; @@ -1115,6 +1154,11 @@ public class DialpadFragment extends Fragment } } + private String getCallOrigin() { + return (getActivity() instanceof DialtactsActivity) ? + ((DialtactsActivity) getActivity()).getCallOrigin() : null; + } + private void handleDialButtonClickWithEmptyDigits() { if (phoneIsCdma() && phoneIsOffhook()) { // This is really CDMA specific. On GSM is it possible @@ -1637,4 +1681,59 @@ public class DialpadFragment extends Fragment intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true); return intent; } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (isVisibleToUser && mNeedToCacheSmartDial) { + SmartDialLoaderTask.startCacheContactsTaskIfNeeded( + getActivity(), mContactsPrefs.getDisplayOrder()); + mNeedToCacheSmartDial = false; + } + } + + private String mLastDigitsForSmartDial; + + private void loadSmartDialEntries() { + if (mSmartDialAdapter == null) { + // No smart dial views. Landscape? + return; + } + + // Update only when the digits have changed. + final String digits = SmartDialNameMatcher.normalizeNumber(mDigits.getText().toString()); + if (TextUtils.equals(digits, mLastDigitsForSmartDial)) { + return; + } + mLastDigitsForSmartDial = digits; + + if (digits.length() < 2) { + mSmartDialAdapter.clear(); + } else { + final SmartDialLoaderTask task = new SmartDialLoaderTask(this, digits); + // don't execute this in serial, otherwise we have to wait too long for results + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new String[] {}); + } + } + + @Override + public void setSmartDialAdapterEntries(List data) { + if (data == null || data.isEmpty()) { + // No results found. Keep the last results. + return; + } + mSmartDialAdapter.setEntries(data); + } + + private class OnSmartDialItemClick implements AdapterView.OnItemClickListener { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + final SmartDialEntry entry = (SmartDialEntry) view.getTag(); + if (entry == null) return; // just in case. + + mClearDigitsOnStop = true; + PhoneNumberInteraction.startInteractionForPhoneCall( + (TransactionSafeActivity) getActivity(), entry.contactUri, getCallOrigin()); + } + } } -- cgit v1.2.3