First batch of UI changes for the new Contacts Search.

Change-Id: I625eae64dfb34ea2be8758f845197303c34a4581
diff --git a/res/drawable-hdpi-finger/bg_blk_search_contact.9.png b/res/drawable-hdpi-finger/bg_blk_search_contact.9.png
new file mode 100644
index 0000000..7b66b3b
--- /dev/null
+++ b/res/drawable-hdpi-finger/bg_blk_search_contact.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_search_dialog_default.9.png b/res/drawable-hdpi-finger/btn_search_dialog_default.9.png
new file mode 100644
index 0000000..f65fa14
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_search_dialog_default.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_search_dialog_pressed.9.png b/res/drawable-hdpi-finger/btn_search_dialog_pressed.9.png
new file mode 100644
index 0000000..17cc0dd
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_search_dialog_selected.9.png b/res/drawable-hdpi-finger/btn_search_dialog_selected.9.png
new file mode 100644
index 0000000..0509e14
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_search.png b/res/drawable-hdpi-finger/ic_btn_search.png
new file mode 100644
index 0000000..1859267
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_btn_search.png
Binary files differ
diff --git a/res/drawable/btn_search_dialog.xml b/res/drawable/btn_search_dialog.xml
new file mode 100644
index 0000000..b7f5187
--- /dev/null
+++ b/res/drawable/btn_search_dialog.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    
+   <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_default" />
+        
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_search_dialog_pressed" />
+    
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_selected" />
+    
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_default" />
+    
+    <item
+         android:drawable="@drawable/btn_search_dialog_default" />
+</selector>
diff --git a/res/layout-finger/contacts_list_content.xml b/res/layout-finger/contacts_list_content.xml
index cae4762..fe65da0 100644
--- a/res/layout-finger/contacts_list_content.xml
+++ b/res/layout-finger/contacts_list_content.xml
@@ -19,7 +19,11 @@
         android:layout_height="match_parent"
         android:orientation="vertical">
 
-    <com.android.contacts.FocusRequestingListView android:id="@android:id/list"
+    <include android:id="@+id/searchView" 
+        layout="@layout/search_bar"
+        android:visibility="gone"/>
+
+    <ListView android:id="@android:id/list"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:fastScrollEnabled="true"
diff --git a/res/layout-finger/search_bar.xml b/res/layout-finger/search_bar.xml
new file mode 100644
index 0000000..155c2ac
--- /dev/null
+++ b/res/layout-finger/search_bar.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/search_bar"
+    android:layout_width="match_parent"
+    android:layout_height="64dip"
+    android:orientation="vertical"
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants"
+    android:background="@drawable/bg_blk_search_contact">
+
+    <!-- Outer layout defines the entire search bar at the top of the screen -->
+    <LinearLayout
+        android:id="@+id/search_plate"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingLeft="4dip"
+        android:paddingRight="4dip"
+        android:paddingTop="6dip"
+        android:paddingBottom="0dip"        
+        >
+
+        <!-- Inner layout contains the app icon, button(s) and EditText -->
+        <LinearLayout
+            android:id="@+id/search_edit_frame"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView 
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" 
+                android:src="@drawable/ic_launcher_contacts"
+                android:layout_marginRight="7dip"
+                android:layout_gravity="center_vertical"
+                android:scaleType="centerInside" />
+              
+            <view
+                class="com.android.contacts.SearchEditText"
+                android:id="@+id/search_src_text"
+                android:layout_height="wrap_content"
+                android:layout_width="0dip"
+                android:layout_weight="1.0"
+                android:layout_marginLeft="4dip"
+                android:layout_marginBottom="0dip"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:inputType="textNoSuggestions"
+                android:imeOptions="actionGo|flagNoExtractUi"
+                android:hint="@string/search_bar_hint"
+                android:freezesText="true"
+            />
+
+            <ImageButton
+                android:id="@+id/search_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:background="@drawable/btn_search_dialog"
+                android:src="@drawable/ic_btn_search"
+                android:layout_marginLeft="4dip"
+                android:layout_marginBottom="4dip"               
+            />
+        </LinearLayout>
+        
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout-finger/total_contacts.xml b/res/layout-finger/total_contacts.xml
index e1588d4..54a804b 100644
--- a/res/layout-finger/total_contacts.xml
+++ b/res/layout-finger/total_contacts.xml
@@ -14,14 +14,37 @@
      limitations under the License.
 -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/totalContactsText"
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="25dip"
-    android:textColor="#ffbfbfbf"
-    android:textSize="14sp"
-    android:textStyle="normal"
+    android:layout_height="28dip"
     android:background="@drawable/infobar_dark"
-    android:paddingLeft="7dp"
-    android:gravity="center"
-/>
\ No newline at end of file
+    android:paddingLeft="5dp"
+    android:paddingRight="7dp"
+    >
+
+    <TextView 
+        android:id="@+id/totalContactsText"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:textColor="#ffbfbfbf"
+        android:textSize="14sp"
+        android:textStyle="normal"
+        android:layout_alignParentLeft="true"
+        android:gravity="center_vertical"
+    />
+
+    <TextView 
+        android:id="@+id/searchForMoreText"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:textColor="#ffbfbfbf"
+        android:textSize="14sp"
+        android:textStyle="normal"
+        android:layout_alignParentRight="true"
+        android:gravity="center_vertical"
+        android:text="@string/search_more_hint"
+        android:visibility="gone"
+    />
+
+</RelativeLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7990406..e4730b2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -337,6 +337,9 @@
     <string name="noContactsWithPhoneNumbers">No contacts with phone numbers.</string>
 
     <!-- The text displayed when the contacts list is empty while displaying favorite contacts -->
+    <string name="noMatchingFilteredContacts">Tap the search button to perform a full search.</string>
+    
+    <!-- The text displayed when the contacts list is empty while displaying favorite contacts -->
     <string name="noFavorites">No favorites.</string>
 
     <!-- Title for group selection dialog. The dialog contains a list of contact groups that the
@@ -464,6 +467,12 @@
     <!-- Displayed at the top of the contacts showing the zero total number of contacts found when "Only contacts with phones" not selected -->
     <string name="listFoundAllContactsZero">Contact not found</string>
 
+    <!-- Displayed at the top of the contacts showing the total number of contacts found when typing search query -->
+    <plurals name="searchFoundContacts">
+        <item quantity="one">1 contact</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> contacts</item>
+    </plurals>
+
     <!-- The description text for the social activity stream tab. Space is limited for this string, so the shorter the better -->
     <string name="socialStreamIconLabel">Social</string>
 
@@ -1302,4 +1311,10 @@
     
     <!-- An allowable value for the "view names as" contact display option  -->
     <string name="display_options_view_family_name_first">Family name first</string>
+    
+    <!-- Gray hint displayed in the search field in Contacts when empty -->
+    <string name="search_bar_hint">Search contacts</string>
+    
+    <!-- Hint displayed underneath the search button in Contacts-->
+    <string name="search_more_hint">Search to find more</string>
 </resources>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 40e048f..3546d3c 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -78,11 +78,12 @@
 import android.provider.ContactsContract.Intents.Insert;
 import android.provider.ContactsContract.Intents.UI;
 import android.telephony.TelephonyManager;
+import android.text.Editable;
 import android.text.TextUtils;
+import android.text.TextWatcher;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextThemeWrapper;
-import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -90,18 +91,23 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.ViewGroup.LayoutParams;
+import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.AlphabetIndexer;
 import android.widget.ArrayAdapter;
 import android.widget.Filter;
+import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.QuickContactBadge;
 import android.widget.ResourceCursorAdapter;
 import android.widget.SectionIndexer;
+import android.widget.TabHost;
 import android.widget.TextView;
 import android.widget.AbsListView.OnScrollListener;
 
@@ -125,8 +131,8 @@
  * Displays a list of contacts. Usually is embedded into the ContactsActivity.
  */
 @SuppressWarnings("deprecation")
-public class ContactsListActivity extends ListActivity implements
-        View.OnCreateContextMenuListener, View.OnClickListener {
+public class ContactsListActivity extends ListActivity implements View.OnCreateContextMenuListener,
+        View.OnClickListener, View.OnKeyListener, TextWatcher, TextView.OnEditorActionListener {
 
     public static class JoinContactActivity extends ContactsListActivity {
 
@@ -137,7 +143,11 @@
     private static final boolean ENABLE_ACTION_ICON_OVERLAYS = true;
 
     private static final String LIST_STATE_KEY = "liststate";
-    private static final String FOCUS_KEY = "focused";
+
+    /**
+     * Saved state key for the flag that indicates if the UI is in the search mode.
+     */
+    private static final String SEARCH_MODE_KEY = "searchMode";
 
     static final int MENU_ITEM_VIEW_CONTACT = 1;
     static final int MENU_ITEM_CALL = 2;
@@ -151,6 +161,7 @@
     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;
 
     private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
 
@@ -229,7 +240,9 @@
     /** Show all contacts and pick them when clicking, and allow creating a new contact */
     static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW;
     /** Show all phone numbers and pick them when clicking */
-    static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
+    // TODO fix and reenable search in phone number picker
+    static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE |
+            MODE_MASK_NO_FILTER;
     /** Show all phone numbers through the legacy provider and pick them when clicking */
     static final int MODE_LEGACY_PICK_PHONE =
             51 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
@@ -249,6 +262,15 @@
     static final int MODE_JOIN_CONTACT = 70 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE
             | MODE_MASK_NO_DATA | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
 
+    /** Run a search query in a PICK mode */
+    static final int MODE_QUERY_PICK = 75 | MODE_MASK_NO_FILTER | MODE_MASK_PICKER;
+
+    /**
+     * An action used to do perform search while in a contact picker.  It is initiated
+     * by the ContactListActivity itself.
+     */
+    private static final String ACTION_INTERNAL_SEARCH = "com.android.contacts.INTERNAL_SEARCH";
+
     /** Maximum number of suggestions shown for joining aggregates */
     static final int MAX_SUGGESTIONS = 4;
 
@@ -372,7 +394,6 @@
      * Used to keep track of the scroll state of the list.
      */
     private Parcelable mListState = null;
-    private boolean mListHasFocus;
 
     private String mShortcutAction;
 
@@ -470,6 +491,11 @@
     private boolean mHighlightWhenScrolling;
     private TextHighlightingAnimation mHighlightingAnimation;
 
+    // If true, the activity is in the "search mode" with the search UI displayed.
+    private boolean mSearchMode;
+    private View mSearchView;
+    private SearchEditText mSearchEditText;
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -590,6 +616,9 @@
                 mMode = MODE_QUERY;
                 mQueryData = getIntent().getStringExtra(SearchManager.QUERY);
             }
+        } else if (ACTION_INTERNAL_SEARCH.equals(action)) {
+            mMode = MODE_QUERY_PICK;
+            mQueryData = getIntent().getStringExtra(SearchManager.QUERY);
 
         // Since this is the filter activity it receives all intents
         // dispatched from the SearchManager for security reasons
@@ -661,41 +690,11 @@
             setContentView(R.layout.contacts_list_content);
         }
 
-        // Setup the UI
-        final ListView list = getListView();
-        mHighlightingAnimation =
-                new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
-
-        // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
-        // them when an A-Z headers is visible.
-        list.setDividerHeight(0);
-        list.setFocusable(true);
-        list.setOnCreateContextMenuListener(this);
-        if ((mMode & MODE_MASK_NO_FILTER) != MODE_MASK_NO_FILTER) {
-            list.setTextFilterEnabled(true);
-        }
-
-        if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
-            // Add the header for creating a new contact
-            final LayoutInflater inflater = getLayoutInflater();
-            View header = inflater.inflate(R.layout.create_new_contact, list, false);
-            list.addHeaderView(header);
-        }
-
-        // Set the proper empty string
-        setEmptyText();
-
-        mAdapter = new ContactItemListAdapter(this);
-        setListAdapter(mAdapter);
-        getListView().setOnScrollListener(mAdapter);
-
-        // We manually save/restore the listview state
-        list.setSaveEnabled(false);
+        setupListView();
 
         mQueryHandler = new QueryHandler(this);
         mJustCreated = true;
 
-        // TODO(jham) redesign this
         mSyncEnabled = true;
 //        // Check to see if sync is enabled
 //        final ContentResolver resolver = getContentResolver();
@@ -716,6 +715,61 @@
 //        }
     }
 
+    private void setupListView() {
+        final ListView list = getListView();
+        mHighlightingAnimation =
+                new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
+
+        // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
+        // them when an A-Z headers is visible.
+        list.setDividerHeight(0);
+        list.setFocusable(true);
+        list.setOnCreateContextMenuListener(this);
+
+        if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
+            // Add the header for creating a new contact
+            final LayoutInflater inflater = getLayoutInflater();
+            View header = inflater.inflate(R.layout.create_new_contact, list, false);
+            list.addHeaderView(header);
+        }
+
+        // Set the proper empty string
+        setEmptyText();
+
+        mAdapter = new ContactItemListAdapter(this);
+        setListAdapter(mAdapter);
+        list.setOnScrollListener(mAdapter);
+        list.setOnKeyListener(this);
+
+        // We manually save/restore the listview state
+        list.setSaveEnabled(false);
+    }
+
+    /**
+     * Configures search UI.
+     */
+    private void setupSearchView() {
+        if (mSearchView != null) {
+            return;
+        }
+
+        mSearchView = findViewById(R.id.searchView);
+        mSearchEditText = (SearchEditText)mSearchView.findViewById(R.id.search_src_text);
+        mSearchEditText.addTextChangedListener(this);
+        mSearchEditText.setOnEditorActionListener(this);
+
+        ImageButton searchButton = (ImageButton)mSearchView.findViewById(R.id.search_btn);
+        searchButton.setOnClickListener(this);
+    }
+
+    private boolean isPickerMode() {
+        return mMode == MODE_PICK_CONTACT
+                || mMode == MODE_PICK_OR_CREATE_CONTACT
+                || mMode == MODE_LEGACY_PICK_PERSON
+                || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON
+                || mMode == MODE_QUERY_PICK;
+    }
+
     private String getContactDisplayName(long contactId) {
         String contactName = null;
         Cursor c = getContentResolver().query(
@@ -749,12 +803,20 @@
 
     /** {@inheritDoc} */
     public void onClick(View v) {
-        if (v.getId() == R.id.call_button) {
-            final int position = (Integer) v.getTag();
-            Cursor c = mAdapter.getCursor();
-            if (c != null) {
-                c.moveToPosition(position);
-                callContact(c);
+        int id = v.getId();
+        switch (id) {
+            case R.id.call_button: {
+                final int position = (Integer)v.getTag();
+                Cursor c = mAdapter.getCursor();
+                if (c != null) {
+                    c.moveToPosition(position);
+                    callContact(c);
+                }
+                break;
+            }
+            case R.id.search_btn: {
+                doSearch();
+                break;
             }
         }
     }
@@ -765,15 +827,15 @@
         }
 
         TextView empty = (TextView) findViewById(R.id.emptyText);
-        int gravity = Gravity.NO_GRAVITY;
 
-        if (mDisplayOnlyPhones) {
+        if (mSearchMode) {
+            empty.setText(getText(R.string.noMatchingFilteredContacts));
+        } else if (mDisplayOnlyPhones) {
             empty.setText(getText(R.string.noContactsWithPhoneNumbers));
-            gravity = Gravity.CENTER;
         } else if (mMode == MODE_STREQUENT || mMode == MODE_STARRED) {
             empty.setText(getText(R.string.noFavoritesHelpText));
         } else if (mMode == MODE_QUERY) {
-             empty.setText(getText(R.string.noMatchingContacts));
+            empty.setText(getText(R.string.noMatchingContacts));
         } else {
             boolean hasSim = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
                     .hasIccCard();
@@ -792,7 +854,6 @@
                 }
             }
         }
-        empty.setGravity(gravity);
     }
 
     private void buildUserGroupUri(String group) {
@@ -834,16 +895,8 @@
             setDefaultMode();
         }
 
-        // See if we were invoked with a filter
-        if (parent != null && parent instanceof DialtactsActivity) {
-            String filterText = ((DialtactsActivity) parent).getAndClearFilterText();
-            if (filterText != null && filterText.length() > 0) {
-                getListView().setFilterText(filterText);
-                // Don't start a new query since it will conflict with the filter
-                runQuery = false;
-            } else if (mJustCreated) {
-                getListView().clearTextFilter();
-            }
+        if (mSearchMode) {
+            startSearchMode(false);
         }
 
         if (mJustCreated && runQuery) {
@@ -854,6 +907,14 @@
         mJustCreated = false;
     }
 
+    private String getTextFilter() {
+        return mSearchEditText.getText().toString();
+    }
+
+    private void setTextFilter(String filterText) {
+        mSearchEditText.setText(filterText);
+    }
+
     @Override
     protected void onRestart() {
         super.onRestart();
@@ -861,7 +922,7 @@
         // The cursor was killed off in onStop(), so we need to get a new one here
         // We do not perform the query if a filter is set on the list because the
         // filter will cause the query to happen anyway
-        if (TextUtils.isEmpty(getListView().getTextFilter())) {
+        if (TextUtils.isEmpty(getTextFilter())) {
             startQuery();
         } else {
             // Run the filtered query on the adapter
@@ -874,7 +935,7 @@
         super.onSaveInstanceState(icicle);
         // Save list state in the bundle so we can restore it after the QueryHandler has run
         icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
-        icicle.putBoolean(FOCUS_KEY, mList.hasFocus());
+        icicle.putBoolean(SEARCH_MODE_KEY, mSearchMode);
     }
 
     @Override
@@ -882,17 +943,13 @@
         super.onRestoreInstanceState(icicle);
         // Retrieve list state. This will be applied after the QueryHandler has run
         mListState = icicle.getParcelable(LIST_STATE_KEY);
-        mListHasFocus = icicle.getBoolean(FOCUS_KEY);
+        mSearchMode = icicle.getBoolean(SEARCH_MODE_KEY);
     }
 
     @Override
     protected void onStop() {
         super.onStop();
 
-        // We don't want the list to display the empty state, since when we resume it will still
-        // be there and show up while the new query is happening. After the async query finished
-        // in response to onRestart() setLoading(false) will be called.
-        mAdapter.setLoading(true);
         mAdapter.setSuggestionsCursor(null);
         mAdapter.changeCursor(null);
         mAdapter.clearImageFetching();
@@ -935,7 +992,7 @@
                 return true;
             }
             case R.id.menu_search: {
-                startSearch(null, false, null, false);
+                startSearchMode(true);
                 return true;
             }
             case R.id.menu_add: {
@@ -959,6 +1016,113 @@
         return false;
     }
 
+    /**
+     * Displays and initializes the search UI at the top of the activity.  If
+     * this activity is part of a tab activity, also removes the tabs.
+     *
+     * @param showKeyboard a flag indicating whether the soft keyboard should be
+     *            auto shown automatically.
+     */
+    private void startSearchMode(boolean showKeyboard) {
+        setupSearchView();
+        View tabs = findTabWidget();
+        if (tabs != null) {
+            tabs.setVisibility(View.GONE);
+        }
+
+        mList.setFocusable(false);
+        mSearchEditText.setAutoShowKeyboard(showKeyboard);
+        mSearchEditText.requestFocus();
+        mSearchView.setVisibility(View.VISIBLE);
+        mSearchMode = true;
+        setEmptyText();
+    }
+
+    /**
+     * Hides the search UI and shows the tabs if they were hidden before.
+     */
+    private void stopSearchMode() {
+
+        // In case the list view owns the soft keyboard at this point, hide the keyboard
+        InputMethodManager inputManager = (InputMethodManager)getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        inputManager.hideSoftInputFromWindow(mList.getWindowToken(), 0);
+
+        // In case the search text view owns the soft keyboard, do the same
+        mSearchEditText.hideKeyboard();
+        mSearchView.setVisibility(View.GONE);
+
+        View tabs = findTabWidget();
+        if (tabs != null) {
+            tabs.setVisibility(View.VISIBLE);
+        }
+
+        mSearchMode = false;
+        setEmptyText();
+
+        // This will trigger a query
+        setTextFilter(null);
+
+        mList.setFocusable(true);
+    }
+
+    /**
+     * If this activity is hosted by a tab activity, the method returns the
+     * TabWidget from the TabHost activity; otherwise it returns null.
+     */
+    private View findTabWidget() {
+        View start = getListView();
+        ViewParent parent = start.getParent();
+        while (parent != null) {
+            if (parent instanceof TabHost) {
+                return ((TabHost)parent).getTabWidget();
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
+    /**
+     * Performs filtering of the list based on the search query entered in the
+     * search text edit.
+     */
+    protected void onSearchTextChanged() {
+        Filter filter = mAdapter.getFilter();
+        filter.filter(getTextFilter());
+    }
+
+    /**
+     * Closes search UI if shown, otherwise follows the default "back" behavior.
+     */
+    @Override
+    public void onBackPressed() {
+        if (mSearchMode) {
+            stopSearchMode();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    /**
+     * Starts a new activity that will run a search query and display search results.
+     */
+    private void doSearch() {
+        String query = getTextFilter();
+        if (TextUtils.isEmpty(query)) {
+            return;
+        }
+
+        Intent intent = new Intent(this, getClass());
+        intent.putExtra(SearchManager.QUERY, query);
+        if (isPickerMode()) {
+            intent.setAction(ACTION_INTERNAL_SEARCH);
+            startActivityForResult(intent, SUBACTIVITY_SEARCH);
+        } else {
+            intent.setAction(Intent.ACTION_SEARCH);
+            startActivity(intent);
+        }
+    }
+
     @Override
     protected Dialog onCreateDialog(int id) {
         switch (id) {
@@ -1100,13 +1264,12 @@
     }
 
     @Override
-    protected void onActivityResult(int requestCode, int resultCode,
-            Intent data) {
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         switch (requestCode) {
             case SUBACTIVITY_NEW_CONTACT:
                 if (resultCode == RESULT_OK) {
                     returnPickerResult(null, data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
-                            data.getData(), 0);
+                            data.getData());
                 }
                 break;
 
@@ -1120,6 +1283,13 @@
                 // Mark as just created so we re-run the view query
                 mJustCreated = true;
                 break;
+
+            case SUBACTIVITY_SEARCH:
+                if (resultCode == RESULT_OK) {
+                    returnPickerResult(null, data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
+                            data.getData());
+                }
+                break;
         }
     }
 
@@ -1220,6 +1390,47 @@
         return super.onContextItemSelected(item);
     }
 
+
+    /**
+     * Event handler for the use case where the user starts typing without
+     * bringing up the search UI first.
+     */
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0) {
+            int unicodeChar = event.getUnicodeChar();
+            if (unicodeChar != 0) {
+                setTextFilter(new String(new int[]{unicodeChar}, 0, 1));
+                startSearchMode(false);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Event handler for search UI.
+     */
+    public void afterTextChanged(Editable s) {
+        onSearchTextChanged();
+    }
+
+    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_GO) {
+            doSearch();
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         switch (keyCode) {
@@ -1229,6 +1440,7 @@
                 }
                 break;
             }
+
             case KeyEvent.KEYCODE_DEL: {
                 final int position = getListView().getSelectedItemPosition();
                 if (position != ListView.INVALID_POSITION) {
@@ -1238,6 +1450,19 @@
                 }
                 break;
             }
+
+            case KeyEvent.KEYCODE_SEARCH: {
+                if ((mMode & MODE_MASK_NO_FILTER) == 0) {
+                    if (mSearchMode) {
+                        stopSearchMode();
+                    } else {
+                        startSearchMode(true);
+                    }
+                    return true;
+                } else {
+                    return false;
+                }
+            }
         }
 
         return super.onKeyDown(keyCode, event);
@@ -1325,34 +1550,25 @@
                     mJoinModeShowAllContacts = false;
                     startQuery();
                 } else {
-                    returnPickerResult(null, null, uri, id);
+                    returnPickerResult(null, null, uri);
                 }
             } else if (mMode == MODE_QUERY_PICK_TO_VIEW) {
                 // Started with query that should launch to view contact
                 final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                 startActivity(intent);
                 finish();
-            } else if (mMode == MODE_PICK_CONTACT
-                    || mMode == MODE_PICK_OR_CREATE_CONTACT
-                    || mMode == MODE_LEGACY_PICK_PERSON
-                    || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
-                if (mShortcutAction != null) {
-                    Cursor c = (Cursor) mAdapter.getItem(position);
-                    returnPickerResult(c, c.getString(getSummaryDisplayNameColumnIndex()), uri, id);
-                } else {
-                    returnPickerResult(null, null, uri, id);
-                }
+            } else if (isPickerMode()) {
+                Cursor c = (Cursor) mAdapter.getItem(position);
+                returnPickerResult(c, c.getString(getSummaryDisplayNameColumnIndex()), uri);
             } else if (mMode == MODE_PICK_PHONE) {
-                if (mShortcutAction != null) {
-                    Cursor c = (Cursor) mAdapter.getItem(position);
-                    returnPickerResult(c, c.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX), uri, id);
-                } else {
-                    returnPickerResult(null, null, uri, id);
-                }
+                Cursor c = (Cursor) mAdapter.getItem(position);
+                long contactId = c.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
+                returnPickerResult(c, c.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX),
+                        ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId));
             } else if (mMode == MODE_PICK_POSTAL
                     || mMode == MODE_LEGACY_PICK_POSTAL
                     || mMode == MODE_LEGACY_PICK_PHONE) {
-                returnPickerResult(null, null, uri, id);
+                returnPickerResult(null, null, uri);
             }
         } else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
                 && position == 0) {
@@ -1364,10 +1580,10 @@
     }
 
     /**
-     * @param uri In most cases, this should be a lookup {@link Uri}, possibly
+     * @param contactUri In most cases, this should be a lookup {@link Uri}, possibly
      *            generated through {@link Contacts#getLookupUri(long, String)}.
      */
-    private void returnPickerResult(Cursor c, String name, Uri uri, long id) {
+    private void returnPickerResult(Cursor c, String name, Uri contactUri) {
         final Intent intent = new Intent();
 
         if (mShortcutAction != null) {
@@ -1378,13 +1594,13 @@
                 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                         Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
 
-                shortcutIntent.setData(uri);
+                shortcutIntent.setData(contactUri);
                 shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_MODE,
                         ContactsContract.QuickContact.MODE_LARGE);
                 shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_EXCLUDE_MIMES,
                         (String[]) null);
 
-                final Bitmap icon = framePhoto(loadContactPhoto(id, null));
+                final Bitmap icon = framePhoto(loadContactPhoto(contactUri, null));
                 if (icon != null) {
                     intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaleToAppIconSize(icon));
                 } else {
@@ -1410,17 +1626,16 @@
                 Uri phoneUri = Uri.fromParts(scheme, number, null);
                 shortcutIntent = new Intent(mShortcutAction, phoneUri);
 
-                // Find the Contacts._ID for this phone number
-                long contactId = c.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
                 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
-                        generatePhoneNumberIcon(contactId, type, resid));
+                        generatePhoneNumberIcon(contactUri, type, resid));
             }
             shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
             intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
             intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
             setResult(RESULT_OK, intent);
         } else {
-            setResult(RESULT_OK, intent.setData(uri));
+            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
+            setResult(RESULT_OK, intent.setData(contactUri));
         }
         finish();
     }
@@ -1456,17 +1671,17 @@
      * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
      * number, and if there is a photo also adds the call action icon.
      *
-     * @param contactId The person the phone number belongs to
+     * @param lookupUri The person the phone number belongs to
      * @param type The type of the phone number
      * @param actionResId The ID for the action resource
      * @return The bitmap for the icon
      */
-    private Bitmap generatePhoneNumberIcon(long contactId, int type, int actionResId) {
+    private Bitmap generatePhoneNumberIcon(Uri lookupUri, int type, int actionResId) {
         final Resources r = getResources();
         boolean drawPhoneOverlay = true;
         final float scaleDensity = getResources().getDisplayMetrics().scaledDensity;
 
-        Bitmap photo = loadContactPhoto(contactId, null);
+        Bitmap photo = loadContactPhoto(lookupUri, null);
         if (photo == null) {
             // If there isn't a photo use the generic phone action icon instead
             Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
@@ -1611,8 +1826,10 @@
                 } else if (mQueryMode == QUERY_MODE_TEL) {
                     return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(mQueryData));
                 }
+                return Contacts.CONTENT_URI;
             }
-            case MODE_QUERY: {
+            case MODE_QUERY:
+            case MODE_QUERY_PICK: {
                 return getContactFilterUri(mQueryData);
             }
             case MODE_GROUP: {
@@ -1690,6 +1907,7 @@
             case MODE_FREQUENT:
             case MODE_STARRED:
             case MODE_QUERY:
+            case MODE_QUERY_PICK:
             case MODE_DEFAULT:
             case MODE_INSERT_OR_EDIT_CONTACT:
             case MODE_GROUP:
@@ -1727,12 +1945,13 @@
         return CONTACTS_SUMMARY_PROJECTION;
     }
 
-    private Bitmap loadContactPhoto(long contactId, BitmapFactory.Options options) {
+    private Bitmap loadContactPhoto(Uri lookupUri, BitmapFactory.Options options) {
         Cursor cursor = null;
         Bitmap bm = null;
 
         try {
-            Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+            // TODO we should have a "photo" directory under the lookup URI itself
+            Uri contactUri = Contacts.lookupContact(getContentResolver(), lookupUri);
             Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
             cursor = getContentResolver().query(photoUri, new String[] {Photo.PHOTO},
                     null, null, null);
@@ -1860,7 +2079,8 @@
                         getSortOrder(projection));
                 break;
 
-            case MODE_QUERY: {
+            case MODE_QUERY:
+            case MODE_QUERY_PICK: {
                 mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
                         projection, null, null,
                         getSortOrder(projection));
@@ -2152,17 +2372,12 @@
                     cursor = activity.getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
                 }
 
-                activity.mAdapter.setLoading(false);
-                activity.getListView().clearTextFilter();
+//                activity.setTextFilter(null);
                 activity.mAdapter.changeCursor(cursor);
 
                 // Now that the cursor is populated again, it's possible to restore the list state
                 if (activity.mListState != null) {
                     activity.mList.onRestoreInstanceState(activity.mListState);
-                    if (activity.mListHasFocus) {
-                        activity.mList.requestFocus();
-                    }
-                    activity.mListHasFocus = false;
                     activity.mListState = null;
                 }
             } else {
@@ -2391,7 +2606,7 @@
          */
         @Override
         protected void onContentChanged() {
-            CharSequence constraint = getListView().getTextFilter();
+            CharSequence constraint = getTextFilter();
             if (!TextUtils.isEmpty(constraint)) {
                 // Reset the filter state then start an async filter operation
                 Filter filter = getFilter();
@@ -2413,7 +2628,7 @@
                 // if the list is empty, so always claim the list is not empty.
                 return false;
             } else {
-                if (mLoading) {
+                if (mCursor == null || mLoading) {
                     // We don't want the empty state to show when loading.
                     return false;
                 } else {
@@ -2491,17 +2706,25 @@
             return v;
         }
 
+
         private View getTotalContactCountView(ViewGroup parent) {
             final LayoutInflater inflater = getLayoutInflater();
-            TextView totalContacts = (TextView) inflater.inflate(R.layout.total_contacts,
-                    parent, false);
+            View view = inflater.inflate(R.layout.total_contacts, parent, false);
+
+            TextView totalContacts = (TextView) view.findViewById(R.id.totalContactsText);
+            TextView searchForMore = (TextView) view.findViewById(R.id.searchForMoreText);
 
             String text;
             int count = getRealCount();
 
-            if (mMode == MODE_QUERY || !TextUtils.isEmpty(getListView().getTextFilter())) {
+            if (mMode == MODE_QUERY || mMode == MODE_QUERY_PICK) {
                 text = getQuantityText(count, R.string.listFoundAllContactsZero,
                         R.plurals.listFoundAllContacts);
+                searchForMore.setVisibility(View.GONE);
+            } else if (mSearchMode && !TextUtils.isEmpty(getTextFilter())) {
+                text = getQuantityText(count, R.string.listFoundAllContactsZero,
+                        R.plurals.searchFoundContacts);
+                searchForMore.setVisibility(View.VISIBLE);
             } else {
                 if (mDisplayOnlyPhones) {
                     text = getQuantityText(count, R.string.listTotalPhoneContactsZero,
@@ -2510,9 +2733,10 @@
                     text = getQuantityText(count, R.string.listTotalAllContactsZero,
                             R.plurals.listTotalAllContacts);
                 }
+                searchForMore.setVisibility(View.GONE);
             }
             totalContacts.setText(text);
-            return totalContacts;
+            return view;
         }
 
         // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
@@ -2804,6 +3028,7 @@
 
         @Override
         public void changeCursor(Cursor cursor) {
+            setLoading(false);
 
             // Get the split between starred and frequent items, if the mode is strequent
             mFrequentSeparatorPos = ListView.INVALID_POSITION;
diff --git a/src/com/android/contacts/SearchEditText.java b/src/com/android/contacts/SearchEditText.java
new file mode 100644
index 0000000..74a1d30
--- /dev/null
+++ b/src/com/android/contacts/SearchEditText.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 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 android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+/**
+ * A custom text editor that optionally automatically brings up the soft
+ * keyboard when first focused.
+ */
+public class SearchEditText extends EditText {
+
+    private boolean mAutoShowKeyboard;
+
+    public SearchEditText(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Automatically show the soft keyboard when the field gets focus.  This is a
+     * single-shot setting - it is reset as soon as the keyboard is shown.
+     */
+    public void setAutoShowKeyboard(boolean flag) {
+        mAutoShowKeyboard = flag;
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (hasWindowFocus && mAutoShowKeyboard) {
+            showKeyboard();
+        }
+    }
+
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+        if (focused && mAutoShowKeyboard) {
+            showKeyboard();
+        }
+    }
+
+    /**
+     * Explicitly brings up the soft keyboard if necessary.
+     */
+    private void showKeyboard() {
+        InputMethodManager inputManager = (InputMethodManager)getContext().getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        inputManager.showSoftInput(this, 0);
+        mAutoShowKeyboard = false;
+    }
+
+    public void hideKeyboard() {
+        InputMethodManager inputManager = (InputMethodManager)getContext().getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        inputManager.hideSoftInputFromWindow(getWindowToken(), 0);
+    }
+}