Merge "Only show populated fields and have an Add button instead"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 23e7cb0..762d66d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -448,6 +448,11 @@
             />
         </activity>
 
+        <!-- Interstitial activity that shows a phone disambig dialog -->
+        <activity android:name="CallContactActivity"
+            android:theme="@android:style/Theme.Translucent">
+        </activity>
+
         <!-- Makes .ContactsListActivity the search target for any activity in Contacts -->
         <meta-data
             android:name="android.app.default_searchable"
diff --git a/res/layout-finger/contacts_search_content.xml b/res/layout-finger/contacts_search_content.xml
index 680a891..480a8aa 100644
--- a/res/layout-finger/contacts_search_content.xml
+++ b/res/layout-finger/contacts_search_content.xml
@@ -26,9 +26,8 @@
     <include android:id="@+id/searchView"
         layout="@layout/search_bar"/>
 
-    <view
-        class="com.android.contacts.ContactEntryListView" 
-        android:id="@android:id/list"
+    <FrameLayout
+        android:id="@+id/list_container"
         android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1"
diff --git a/src/com/android/contacts/CallContactActivity.java b/src/com/android/contacts/CallContactActivity.java
new file mode 100644
index 0000000..181e9b7
--- /dev/null
+++ b/src/com/android/contacts/CallContactActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 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.contacts;
+
+import com.android.contacts.list.CallOrSmsInitiator;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.DialogInterface.OnDismissListener;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+
+/**
+ * An interstitial activity used when the user selects a QSB search suggestion using
+ * a call button.
+ */
+public class CallContactActivity extends Activity implements OnDismissListener {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Uri data = getIntent().getData();
+        if (data == null) {
+            finish();
+        }
+
+        if (Contacts.CONTENT_ITEM_TYPE.equals(getContentResolver().getType(data))) {
+            CallOrSmsInitiator initiator = new CallOrSmsInitiator(this);
+            initiator.setOnDismissListener(this);
+            initiator.initiateCall(data);
+        } else {
+            startActivity(new Intent(Intent.ACTION_CALL_PRIVILEGED, data));
+            finish();
+        }
+    }
+
+    public void onDismiss(DialogInterface dialog) {
+        finish();
+    }
+}
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index a015d62..bfb6a96 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -35,6 +35,8 @@
 import com.android.contacts.ui.ContactsPreferencesActivity;
 import com.android.contacts.util.AccountSelectionUtil;
 import com.android.contacts.widget.ContextMenuAdapter;
+import com.android.contacts.widget.SearchEditText;
+import com.android.contacts.widget.SearchEditText.OnFilterTextListener;
 
 import android.accounts.Account;
 import android.app.Activity;
@@ -81,33 +83,25 @@
 
     private static final String TAG = "ContactsListActivity";
 
-    private static final boolean ENABLE_ACTION_ICON_OVERLAYS = true;
-
-    private static final String SHORTCUT_ACTION_KEY = "shortcutAction";
-
     private static final int SUBACTIVITY_NEW_CONTACT = 1;
     private static final int SUBACTIVITY_VIEW_CONTACT = 2;
     private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
     private static final int SUBACTIVITY_SEARCH = 4;
     protected static final int SUBACTIVITY_FILTER = 5;
 
-    public static final String AUTHORITIES_FILTER_KEY = "authorities";
-
-    static final String[] RAW_CONTACTS_PROJECTION = new String[] {
+    private static final String[] RAW_CONTACTS_PROJECTION = new String[] {
         RawContacts._ID, //0
         RawContacts.CONTACT_ID, //1
         RawContacts.ACCOUNT_TYPE, //2
     };
 
-    static final String KEY_PICKER_MODE = "picker_mode";
-
     private Uri mSelectedContactUri;
 
     private ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
     private int  mWritableSourcesCnt;
     private int  mReadOnlySourcesCnt;
 
-    final String[] sLookupProjection = new String[] {
+    private final String[] sLookupProjection = new String[] {
             Contacts.LOOKUP_KEY
     };
     private class DeleteClickListener implements DialogInterface.OnClickListener {
@@ -128,6 +122,7 @@
     private boolean mSearchInitiated;
 
     private ContactsRequest mRequest;
+    private SearchEditText mSearchEditText;
 
     public ContactsListActivity() {
         mIntentResolver = new ContactsIntentResolver(this);
@@ -164,11 +159,41 @@
 
         onCreateFragment();
 
+        int listFragmentContainerId;
+        if (mRequest.isSearchMode()) {
+            setContentView(R.layout.contacts_search_content);
+            listFragmentContainerId = R.id.list_container;
+            setupSearchUI();
+        } else {
+            listFragmentContainerId = android.R.id.content;
+        }
         FragmentTransaction transaction = openFragmentTransaction();
-        transaction.add(mListFragment, android.R.id.content);
+        transaction.add(mListFragment, listFragmentContainerId);
         transaction.commit();
     }
 
+    private void setupSearchUI() {
+        mSearchEditText = (SearchEditText)findViewById(R.id.search_src_text);
+        mSearchEditText.setText(mRequest.getQueryString());
+        mSearchEditText.setOnFilterTextListener(new OnFilterTextListener() {
+            public void onFilterChange(String queryString) {
+                mListFragment.setQueryString(queryString);
+            }
+
+            public void onCancelSearch() {
+                finish();
+            }
+        });
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mRequest.isSearchMode()) {
+            mSearchEditText.requestFocus();
+        }
+    }
+
     /**
      * Creates the fragment based on the current request.
      */
@@ -466,7 +491,7 @@
             }
             case R.id.menu_accounts: {
                 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
-                intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
+                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
                     ContactsContract.AUTHORITY
                 });
                 startActivity(intent);
diff --git a/src/com/android/contacts/PhoneDisambigDialog.java b/src/com/android/contacts/PhoneDisambigDialog.java
index 391ded0..3b32b57 100644
--- a/src/com/android/contacts/PhoneDisambigDialog.java
+++ b/src/com/android/contacts/PhoneDisambigDialog.java
@@ -89,6 +89,10 @@
         mDialog = dialogBuilder.create();
     }
 
+    public void setOnDismissListener(DialogInterface.OnDismissListener dismissListener) {
+        mDialog.setOnDismissListener(dismissListener);
+    }
+
     /**
      * Show the dialog.
      */
diff --git a/src/com/android/contacts/list/CallOrSmsInitiator.java b/src/com/android/contacts/list/CallOrSmsInitiator.java
index 1511931..b5aabee 100644
--- a/src/com/android/contacts/list/CallOrSmsInitiator.java
+++ b/src/com/android/contacts/list/CallOrSmsInitiator.java
@@ -17,9 +17,12 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.PhoneDisambigDialog;
+import com.android.internal.widget.RotarySelector.OnDialTriggerListener;
 
 import android.content.AsyncQueryHandler;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.ContactsContract.Contacts;
@@ -48,6 +51,7 @@
 
     private static final String PHONE_NUMBER_SELECTION = Data.MIMETYPE + "='"
             + Phone.CONTENT_ITEM_TYPE + "' AND " + Phone.NUMBER + " NOT NULL";
+    private OnDismissListener mDismissListener;
 
     public CallOrSmsInitiator(Context context) {
         this.mContext = context;
@@ -59,6 +63,10 @@
         };
     }
 
+    public void setOnDismissListener(DialogInterface.OnDismissListener dismissListener) {
+        this.mDismissListener = dismissListener;
+    }
+
     protected void onPhoneNumberQueryComplete(int token, Object cookie, Cursor cursor) {
         if (cursor == null || cursor.getCount() == 0) {
             cursor.close();
@@ -89,6 +97,9 @@
         if (phone == null) {
             // Display dialog to choose a number to call.
             PhoneDisambigDialog phoneDialog = new PhoneDisambigDialog(mContext, cursor, mSendSms);
+            if (mDismissListener != null) {
+                phoneDialog.setOnDismissListener(mDismissListener);
+            }
             phoneDialog.show();
         } else {
             if (mSendSms) {
@@ -96,6 +107,9 @@
             } else {
                 ContactsUtils.initiateCall(mContext, phone);
             }
+            if (mDismissListener != null) {
+                mDismissListener.onDismiss(null);
+            }
         }
     }
 
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index c894ba7..1f8822e 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -22,8 +22,6 @@
 import com.android.contacts.R;
 import com.android.contacts.ui.ContactsPreferences;
 import com.android.contacts.widget.ContextMenuAdapter;
-import com.android.contacts.widget.SearchEditText;
-import com.android.contacts.widget.SearchEditText.OnCloseListener;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -36,14 +34,12 @@
 import android.content.Context;
 import android.content.IContentService;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.preference.PreferenceManager;
 import android.provider.ContactsContract;
 import android.provider.Settings;
 import android.provider.ContactsContract.ProviderStatus;
@@ -51,7 +47,6 @@
 import android.text.Editable;
 import android.text.Html;
 import android.text.TextUtils;
-import android.text.TextWatcher;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -70,16 +65,13 @@
 import android.widget.TextView;
 import android.widget.AbsListView.OnScrollListener;
 import android.widget.AdapterView.OnItemClickListener;
-import android.widget.TextView.OnEditorActionListener;
 
 /**
  * Common base class for various contact-related list fragments.
  */
 public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter>
         extends LoaderManagingFragment<Cursor>
-        implements OnItemClickListener,
-        OnScrollListener, TextWatcher, OnEditorActionListener, OnCloseListener,
-        OnFocusChangeListener, OnTouchListener {
+        implements OnItemClickListener, OnScrollListener, OnFocusChangeListener, OnTouchListener {
 
     private static final String TAG = "ContactEntryListFragment";
 
@@ -107,7 +99,6 @@
 
     private ContextMenuAdapter mContextMenuAdapter;
     private ContactPhotoLoader mPhotoLoader;
-    private SearchEditText mSearchEditText;
     private ContactListEmptyView mEmptyView;
     private ProviderStatusLoader mProviderStatusLoader;
     private ContactsPreferences mContactsPrefs;
@@ -132,6 +123,7 @@
         return mAdapter;
     }
 
+    @Override
     public View getView() {
         return mView;
     }
@@ -221,7 +213,6 @@
 
     public void setSearchMode(boolean flag) {
         mSearchMode = flag;
-        configureSearchEditText();
     }
 
     public boolean isSearchMode() {
@@ -230,7 +221,6 @@
 
     public void setSearchResultsMode(boolean flag) {
         mSearchResultsMode = flag;
-        configureSearchEditText();
     }
 
     public boolean isSearchResultsMode() {
@@ -242,9 +232,12 @@
     }
 
     public void setQueryString(String queryString) {
-        mQueryString = queryString;
-        if (mAdapter != null) {
-            mAdapter.setQueryString(queryString);
+        if (!TextUtils.equals(mQueryString, queryString)) {
+            mQueryString = queryString;
+            if (mAdapter != null) {
+                mAdapter.setQueryString(queryString);
+                reloadData();
+            }
         }
     }
 
@@ -324,7 +317,7 @@
             mListState = savedState.getParcelable(LIST_STATE_KEY);
         }
     }
-    
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
@@ -372,7 +365,6 @@
         }
 
         configurePhotoLoader();
-        configureSearchEditText();
         configureSearchResultText();
         return mView;
     }
@@ -401,15 +393,6 @@
             }
         }
     }
-    protected void configureSearchEditText() {
-        if (isSearchMode() && mView != null) {
-            mSearchEditText = (SearchEditText)mView.findViewById(R.id.search_src_text);
-            mSearchEditText.setText(getQueryString());
-            mSearchEditText.addTextChangedListener(this);
-            mSearchEditText.setOnEditorActionListener(this);
-            mSearchEditText.setOnCloseListener(this);
-        }
-    }
 
     protected void configureAdapter() {
         if (mAdapter != null) {
@@ -461,9 +444,6 @@
         if (isPhotoLoaderEnabled()) {
             mPhotoLoader.resume();
         }
-        if (isSearchMode()) {
-            mSearchEditText.requestFocus();
-        }
     }
 
     @Override
@@ -491,35 +471,6 @@
     }
 
     /**
-     * Event handler for search UI.
-     */
-    public void afterTextChanged(Editable s) {
-        String query = s.toString().trim();
-        setQueryString(query);
-        reloadData();
-    }
-
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-    }
-
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-    }
-
-    /**
-     * Event handler for search UI.
-     */
-    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-        if (actionId == EditorInfo.IME_ACTION_DONE) {
-            hideSoftKeyboard();
-            if (TextUtils.isEmpty(getQueryString())) {
-                finish();
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
      * Dismisses the soft keyboard when the list takes focus.
      */
     public void onFocusChange(View view, boolean hasFocus) {
diff --git a/src/com/android/contacts/list/ContactsIntentResolver.java b/src/com/android/contacts/list/ContactsIntentResolver.java
index beab168..8388daa 100644
--- a/src/com/android/contacts/list/ContactsIntentResolver.java
+++ b/src/com/android/contacts/list/ContactsIntentResolver.java
@@ -16,25 +16,20 @@
 
 package com.android.contacts.list;
 
+import com.android.contacts.CallContactActivity;
 import com.android.contacts.ContactsSearchManager;
 import com.android.contacts.R;
 
 import android.app.Activity;
 import android.app.SearchManager;
-import android.content.ContentUris;
 import android.content.Intent;
-import android.content.UriMatcher;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.ContactsContract;
 import android.provider.Contacts.ContactMethods;
 import android.provider.Contacts.People;
 import android.provider.Contacts.Phones;
 import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Intents;
-import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.Intents.UI;
@@ -42,22 +37,14 @@
 import android.util.Log;
 
 /**
- * Maintains contact list configuration, which is a transient object that
- * deals with intents, saved instance configuration etc.
+ * Parses a Contacts intent, extracting all relevant parts and packaging them
+ * as a {@link ContactsRequest} object.
  */
 @SuppressWarnings("deprecation")
 public class ContactsIntentResolver {
 
     private static final String TAG = "ContactsListActivity";
 
-    // Uri matcher for contact id
-    private static final int CONTACTS_ID = 1001;
-    private static final UriMatcher sContactsIdMatcher;
-    static {
-        sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-        sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
-    }
-
     private final Activity mContext;
 
     public ContactsIntentResolver(Activity context) {
@@ -69,8 +56,6 @@
         request.setDisplayOnlyVisible(true);
 
         String action = intent.getAction();
-        String component = intent.getComponent().getClassName();
-        String type = intent.getType();
 
         Log.i(TAG, "Called with action: " + action);
 
@@ -96,11 +81,11 @@
         } else if (UI.LIST_GROUP_ACTION.equals(action)) {
             request.setActionCode(ContactsRequest.ACTION_GROUP);
             String groupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
-            if (TextUtils.isEmpty(groupName)) {
+            if (!TextUtils.isEmpty(groupName)) {
+                request.setGroupName(groupName);
+            } else {
                 Log.e(TAG, "Intent missing a required extra: " + UI.GROUP_NAME_EXTRA_KEY);
                 request.setValid(false);
-            } else {
-                request.setGroupName(groupName);
             }
         } else if (Intent.ACTION_PICK.equals(action)) {
             final String resolvedType = intent.resolveType(mContext);
@@ -121,6 +106,7 @@
                 request.setLegacyCompatibilityMode(true);
             }
         } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
+            String component = intent.getComponent().getClassName();
             if (component.equals("alias.DialShortcut")) {
                 request.setActionCode(ContactsRequest.ACTION_CREATE_SHORTCUT_CALL);
                 request.setActivityTitle(mContext.getString(R.string.callShortcutActivityTitle));
@@ -132,6 +118,7 @@
                 request.setActivityTitle(mContext.getString(R.string.shortcutActivityTitle));
             }
         } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
+            String type = intent.getType();
             if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
                 request.setActionCode(ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT);
             } else if (Phone.CONTENT_ITEM_TYPE.equals(type)) {
@@ -211,40 +198,25 @@
         // dispatched from the SearchManager for security reasons
         // so we need to re-dispatch from here to the intended target.
         } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
-            // TODO show the disambig dialog instead of guessing the number
             Uri data = intent.getData();
-            Uri telUri = null;
-            if (sContactsIdMatcher.match(data) == CONTACTS_ID) {
-                long contactId = Long.valueOf(data.getLastPathSegment());
-                final Cursor cursor = queryPhoneNumbers(contactId);
-                if (cursor != null) {
-                    if (cursor.getCount() == 1 && cursor.moveToFirst()) {
-                        int phoneNumberIndex = cursor.getColumnIndex(Phone.NUMBER);
-                        String phoneNumber = cursor.getString(phoneNumberIndex);
-                        telUri = Uri.parse("tel:" + phoneNumber);
-                    }
-                    cursor.close();
-                }
-            }
             // See if the suggestion was clicked with a search action key (call button)
-            Intent newIntent;
-            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG)) && telUri != null) {
-                newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, telUri);
+            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
+                Intent newIntent = new Intent(mContext, CallContactActivity.class);
+                newIntent.setData(data);
+                request.setRedirectIntent(newIntent);
             } else {
-                newIntent = new Intent(Intent.ACTION_VIEW, data);
+                request.setRedirectIntent(new Intent(Intent.ACTION_VIEW, data));
             }
-            request.setRedirectIntent(newIntent);
         } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
-            Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
-            request.setRedirectIntent(newIntent);
+            request.setRedirectIntent(new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData()));
         } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
             // TODO actually support this in EditContactActivity.
             String number = intent.getData().getSchemeSpecificPart();
             Intent newIntent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
             newIntent.putExtra(Intents.Insert.PHONE, number);
             request.setRedirectIntent(newIntent);
-        }
 
+        }
         // Allow the title to be set to a custom String using an extra on the intent
         String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
         if (title != null) {
@@ -253,20 +225,4 @@
 
         return request;
     }
-
-    // TODO replace with a disabmig dialog
-    @Deprecated
-    private Cursor queryPhoneNumbers(long contactId) {
-        Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
-        Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
-
-        Cursor c = mContext.getContentResolver().query(dataUri,
-                new String[] {Phone._ID, Phone.NUMBER, Phone.IS_SUPER_PRIMARY,
-                RawContacts.ACCOUNT_TYPE, Phone.TYPE, Phone.LABEL},
-                Data.MIMETYPE + "=?", new String[] {Phone.CONTENT_ITEM_TYPE}, null);
-        if (c != null && c.moveToFirst()) {
-            return c;
-        }
-        return null;
-    }
 }
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 45380bc..13d60e1 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -124,9 +124,7 @@
 
     @Override
     protected View inflateView(LayoutInflater inflater, ViewGroup container) {
-        if (isSearchMode()) {
-            return inflater.inflate(R.layout.contacts_search_content, null);
-        } else if (isSearchResultsMode()) {
+        if (isSearchResultsMode()) {
             return inflater.inflate(R.layout.contacts_list_search_results, null);
         } else {
             return inflater.inflate(R.layout.contacts_list_content, null);
diff --git a/src/com/android/contacts/widget/SearchEditText.java b/src/com/android/contacts/widget/SearchEditText.java
index 1a50976..45001a5 100644
--- a/src/com/android/contacts/widget/SearchEditText.java
+++ b/src/com/android/contacts/widget/SearchEditText.java
@@ -18,31 +18,40 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.text.Editable;
 import android.text.TextUtils;
+import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
 
 /**
  * A custom text editor that helps automatically dismiss the activity along with the soft
  * keyboard.
  */
-public class SearchEditText extends EditText {
-
+public class SearchEditText extends EditText implements OnEditorActionListener, TextWatcher {
     private boolean mMagnifyingGlassShown = true;
-    private Drawable mMagnifyingGlass;
-    private OnCloseListener mListener;
 
-    public interface OnCloseListener {
-        void onClose();
+    private Drawable mMagnifyingGlass;
+    private OnFilterTextListener mListener;
+
+    public interface OnFilterTextListener {
+        void onFilterChange(String queryString);
+        void onCancelSearch();
     }
 
     public SearchEditText(Context context, AttributeSet attrs) {
         super(context, attrs);
+        addTextChangedListener(this);
+        setOnEditorActionListener(this);
         mMagnifyingGlass = getCompoundDrawables()[2];
     }
 
-    public void setOnCloseListener(OnCloseListener listener) {
+    public void setOnFilterTextListener(OnFilterTextListener listener) {
         this.mListener = listener;
     }
 
@@ -71,9 +80,51 @@
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK && TextUtils.isEmpty(getText()) && mListener != null) {
-            mListener.onClose();
+            mListener.onCancelSearch();
             return true;
         }
         return false;
     }
+
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    /**
+     * Event handler for search UI.
+     */
+    public void afterTextChanged(Editable s) {
+        if (mListener != null) {
+            mListener.onFilterChange(trim(s));
+        }
+    }
+
+    private String trim(Editable s) {
+        return s.toString().trim();
+    }
+
+    /**
+     * Event handler for search UI.
+     */
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            hideSoftKeyboard();
+            if (TextUtils.isEmpty(trim(getText())) && mListener != null) {
+                mListener.onCancelSearch();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void hideSoftKeyboard() {
+        // Hide soft keyboard, if visible
+        InputMethodManager inputMethodManager = (InputMethodManager)
+                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+    }
+
 }
diff --git a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
index f941dd6..b2c3072 100644
--- a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
+++ b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
@@ -489,7 +489,8 @@
     private long findArbitraryContactWithPhoneNumber() {
         final Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI,
                 new String[] { Contacts._ID },
-                Contacts.HAS_PHONE_NUMBER + "!=0", null, Contacts._ID + " LIMIT 1");
+                Contacts.HAS_PHONE_NUMBER + "!=0 AND " + Contacts.STARRED + "!=0" ,
+                null, "RANDOM() LIMIT 1");
         try {
             if (cursor.moveToFirst()) {
                 return cursor.getLong(0);