SearchView behavioral and visual changes

Align text suggestions and search string.
Close button only clears the field.
Back button clears focus, hides dropdown and keyboard.
Tweaked SearchDialog's use of SearchView to follow above patterns.
Fixed other little bugs.

Change-Id: I9d34c2ebe2b1b2ca887220894c19a26809db85f6
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 42eda02..8e2d360 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -168,6 +168,7 @@
         SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar);
         searchBar.setSearchDialog(this);
         mSearchView = (SearchView) findViewById(com.android.internal.R.id.search_view);
+        mSearchView.setIconified(false);
         mSearchView.setOnCloseListener(mOnCloseListener);
         mSearchView.setOnQueryTextListener(mOnQueryChangeListener);
         mSearchView.setOnSuggestionListener(mOnSuggestionSelectionListener);
@@ -633,31 +634,6 @@
         }
 
         /**
-         * Overrides the handling of the back key to move back to the previous
-         * sources or dismiss the search dialog, instead of dismissing the input
-         * method.
-         */
-        @Override
-        public boolean dispatchKeyEventPreIme(KeyEvent event) {
-            if (DBG)
-                Log.d(LOG_TAG, "onKeyPreIme(" + event + ")");
-            if (mSearchDialog != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-                KeyEvent.DispatcherState state = getKeyDispatcherState();
-                if (state != null) {
-                    if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
-                        state.startTracking(event, this);
-                        return true;
-                    } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()
-                            && state.isTracking(event)) {
-                        mSearchDialog.onBackPressed();
-                        return true;
-                    }
-                }
-            }
-            return super.dispatchKeyEventPreIme(event);
-        }
-
-        /**
          * Don't allow action modes in a SearchBar, it looks silly.
          */
         @Override
diff --git a/core/java/android/content/SearchRecentSuggestionsProvider.java b/core/java/android/content/SearchRecentSuggestionsProvider.java
index 3d89e92..e1a8d21 100644
--- a/core/java/android/content/SearchRecentSuggestionsProvider.java
+++ b/core/java/android/content/SearchRecentSuggestionsProvider.java
@@ -186,6 +186,9 @@
 
             mSuggestionProjection = new String [] {
                     "0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
+                    "'android.resource://system/"
+                            + com.android.internal.R.drawable.ic_menu_recent_history + "' AS "
+                            + SearchManager.SUGGEST_COLUMN_ICON_1,
                     "display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
                     "display2 AS " + SearchManager.SUGGEST_COLUMN_TEXT_2,
                     "query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
@@ -196,6 +199,9 @@
 
             mSuggestionProjection = new String [] {
                     "0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
+                    "'android.resource://system/"
+                            + com.android.internal.R.drawable.ic_menu_recent_history + "' AS "
+                            + SearchManager.SUGGEST_COLUMN_ICON_1,
                     "display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
                     "query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
                     "_id"
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index f3bda43..b2d1a1e 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -18,8 +18,6 @@
 
 import static android.widget.SuggestionsAdapter.getColumnString;
 
-import com.android.internal.R;
-
 import android.app.PendingIntent;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
@@ -39,10 +37,14 @@
 import android.os.Bundle;
 import android.speech.RecognizerIntent;
 import android.text.Editable;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.style.ImageSpan;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -51,6 +53,8 @@
 import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.TextView.OnEditorActionListener;
 
+import com.android.internal.R;
+
 import java.util.WeakHashMap;
 
 /**
@@ -87,6 +91,8 @@
     private View mSearchEditFrame;
     private View mVoiceButton;
     private SearchAutoComplete mQueryTextView;
+    private View mDropDownAnchor;
+    private ImageView mSearchHintIcon;
     private boolean mSubmitButtonEnabled;
     private CharSequence mQueryHint;
     private boolean mQueryRefinement;
@@ -195,6 +201,7 @@
         mSubmitButton = findViewById(R.id.search_go_btn);
         mCloseButton = (ImageView) findViewById(R.id.search_close_btn);
         mVoiceButton = findViewById(R.id.search_voice_btn);
+        mSearchHintIcon = (ImageView) findViewById(R.id.search_mag_icon);
 
         mSearchButton.setOnClickListener(mOnClickListener);
         mCloseButton.setOnClickListener(mOnClickListener);
@@ -244,7 +251,20 @@
         mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
         mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
+        mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor());
+        if (mDropDownAnchor != null) {
+            mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    adjustDropDownSizeAndPosition();
+                }
+
+            });
+        }
+
         updateViewsVisibility(mIconifiedByDefault);
+        updateQueryHint();
     }
 
     /**
@@ -263,7 +283,7 @@
         }
         // Cache the voice search capability
         mVoiceButtonEnabled = hasVoiceSearch();
-        updateViewsVisibility(mIconifiedByDefault);
+        updateViewsVisibility(isIconified());
     }
 
     /**
@@ -300,7 +320,6 @@
         mQueryTextView.clearFocus();
         setImeVisibility(false);
         mClearingFocus = false;
-        updateViewsVisibility(mIconifiedByDefault);
     }
 
     /**
@@ -555,6 +574,7 @@
         mSearchButton.setVisibility(visCollapsed);
         updateSubmitButton(hasText);
         mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
+        mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
         updateCloseButton();
         updateVoiceButton(!hasText);
         updateSubmitArea();
@@ -822,9 +842,29 @@
         return result;
     }
 
+    private int getSearchIconId() {
+        TypedValue outValue = new TypedValue();
+        getContext().getTheme().resolveAttribute(com.android.internal.R.attr.searchViewSearchIcon,
+                outValue, true);
+        return outValue.resourceId;
+    }
+
+    private CharSequence getDecoratedHint(CharSequence hintText) {
+        // If the field is always expanded, then don't add the search icon to the hint
+        if (!mIconifiedByDefault) return hintText;
+
+        SpannableStringBuilder ssb = new SpannableStringBuilder("   "); // for the icon
+        ssb.append(hintText);
+        Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId());
+        int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
+        searchIcon.setBounds(0, 0, textSize, textSize);
+        ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return ssb;
+    }
+
     private void updateQueryHint() {
         if (mQueryHint != null) {
-            mQueryTextView.setHint(mQueryHint);
+            mQueryTextView.setHint(getDecoratedHint(mQueryHint));
         } else if (mSearchable != null) {
             CharSequence hint = null;
             int hintId = mSearchable.getHintId();
@@ -832,8 +872,10 @@
                 hint = getContext().getString(hintId);
             }
             if (hint != null) {
-                mQueryTextView.setHint(hint);
+                mQueryTextView.setHint(getDecoratedHint(hint));
             }
+        } else {
+            mQueryTextView.setHint(getDecoratedHint(""));
         }
     }
 
@@ -922,9 +964,13 @@
         CharSequence text = mQueryTextView.getText();
         if (TextUtils.isEmpty(text)) {
             if (mIconifiedByDefault) {
-                // query field already empty, hide the keyboard and remove focus
-                clearFocus();
-                setImeVisibility(false);
+                // If the app doesn't override the close behavior
+                if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
+                    // hide the keyboard and remove focus
+                    clearFocus();
+                    // collapse the search field
+                    updateViewsVisibility(true);
+                }
             }
         } else {
             mQueryTextView.setText("");
@@ -932,10 +978,6 @@
             setImeVisibility(true);
         }
 
-        if (mIconifiedByDefault && (mOnCloseListener == null || !mOnCloseListener.onClose())) {
-            updateViewsVisibility(mIconifiedByDefault);
-            setImeVisibility(false);
-        }
     }
 
     private void onSearchClicked() {
@@ -975,6 +1017,28 @@
         updateFocusedState(mQueryTextView.hasFocus());
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+    }
+
+    private void adjustDropDownSizeAndPosition() {
+        if (mDropDownAnchor.getWidth() > 1) {
+            Resources res = getContext().getResources();
+            int anchorPadding = mSearchPlate.getPaddingLeft();
+            Rect dropDownPadding = new Rect();
+            int iconOffset = mIconifiedByDefault
+                    ? res.getDimensionPixelSize(R.dimen.dropdownitem_icon_width)
+                    + res.getDimensionPixelSize(R.dimen.dropdownitem_text_padding_left)
+                    : 0;
+            mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
+            mQueryTextView.setDropDownHorizontalOffset(-(dropDownPadding.left + iconOffset)
+                    + anchorPadding);
+            mQueryTextView.setDropDownWidth(mDropDownAnchor.getWidth() + dropDownPadding.left
+                    + dropDownPadding.right + iconOffset - (anchorPadding));
+        }
+    }
+
     private boolean onItemClicked(int position, int actionKey, String actionMsg) {
         if (mOnSuggestionListener == null
                 || !mOnSuggestionListener.onSuggestionClick(position)) {
@@ -1393,5 +1457,32 @@
         public boolean enoughToFilter() {
             return mThreshold <= 0 || super.enoughToFilter();
         }
+
+        @Override
+        public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // special case for the back key, we do not even try to send it
+                // to the drop down list but instead, consume it immediately
+                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+                    KeyEvent.DispatcherState state = getKeyDispatcherState();
+                    if (state != null) {
+                        state.startTracking(event, this);
+                    }
+                    return true;
+                } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                    KeyEvent.DispatcherState state = getKeyDispatcherState();
+                    if (state != null) {
+                        state.handleUpEvent(event);
+                    }
+                    if (event.isTracking() && !event.isCanceled()) {
+                        mSearchView.clearFocus();
+                        mSearchView.setImeVisibility(false);
+                        return true;
+                    }
+                }
+            }
+            return super.onKeyPreIme(keyCode, event);
+        }
+
     }
 }
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 2cfc016..9e32c9a 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -16,8 +16,6 @@
 
 package android.widget;
 
-import com.android.internal.R;
-
 import android.app.SearchDialog;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
@@ -47,6 +45,8 @@
 import android.view.ViewGroup;
 import android.view.View.OnClickListener;
 
+import com.android.internal.R;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -88,8 +88,8 @@
     private int mIconName2Col = INVALID_INDEX;
     private int mFlagsCol = INVALID_INDEX;
 
-    private final Runnable mStartSpinnerRunnable;
-    private final Runnable mStopSpinnerRunnable;
+    // private final Runnable mStartSpinnerRunnable;
+    // private final Runnable mStopSpinnerRunnable;
 
     /**
      * The amount of time we delay in the filter when the user presses the delete key.
@@ -113,17 +113,18 @@
 
         mOutsideDrawablesCache = outsideDrawablesCache;
         
-        mStartSpinnerRunnable = new Runnable() {
-                public void run() {
-                // mSearchView.setWorking(true); // TODO:
-                }
-            };
 
-        mStopSpinnerRunnable = new Runnable() {
-            public void run() {
-                // mSearchView.setWorking(false); // TODO:
-            }
-        };
+        // mStartSpinnerRunnable = new Runnable() {
+        // public void run() {
+        // // mSearchView.setWorking(true); // TODO:
+        // }
+        // };
+        //
+        // mStopSpinnerRunnable = new Runnable() {
+        // public void run() {
+        // // mSearchView.setWorking(false); // TODO:
+        // }
+        // };
 
         // delay 500ms when deleting
         getFilter().setDelayer(new Filter.Delayer() {
@@ -341,10 +342,10 @@
         }
 
         if (views.mIcon1 != null) {
-            setViewDrawable(views.mIcon1, getIcon1(cursor));
+            setViewDrawable(views.mIcon1, getIcon1(cursor), View.INVISIBLE);
         }
         if (views.mIcon2 != null) {
-            setViewDrawable(views.mIcon2, getIcon2(cursor));
+            setViewDrawable(views.mIcon2, getIcon2(cursor), View.GONE);
         }
         if (mQueryRefinement == REFINE_ALL
                 || (mQueryRefinement == REFINE_BY_ENTRY
@@ -414,13 +415,13 @@
      * Sets the drawable in an image view, makes sure the view is only visible if there
      * is a drawable.
      */
-    private void setViewDrawable(ImageView v, Drawable drawable) {
+    private void setViewDrawable(ImageView v, Drawable drawable, int nullVisibility) {
         // Set the icon even if the drawable is null, since we need to clear any
         // previous icon.
         v.setImageDrawable(drawable);
 
         if (drawable == null) {
-            v.setVisibility(View.GONE);
+            v.setVisibility(nullVisibility);
         } else {
             v.setVisibility(View.VISIBLE);
 
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index 790ac6b..f6b5b53 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -66,7 +66,6 @@
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:maxWidth="600dip"
-            android:iconifiedByDefault="false"
             android:layout_gravity="center_vertical"
             />
 
diff --git a/core/res/res/layout/search_dropdown_item_icons_2line.xml b/core/res/res/layout/search_dropdown_item_icons_2line.xml
index 53906f9..acef2cc 100644
--- a/core/res/res/layout/search_dropdown_item_icons_2line.xml
+++ b/core/res/res/layout/search_dropdown_item_icons_2line.xml
@@ -19,21 +19,21 @@
 -->
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:paddingLeft="4dip"
-    android:paddingRight="2dip"
+    android:paddingLeft="@dimen/dropdownitem_text_padding_left"
+    android:paddingRight="4dip"
     android:layout_width="match_parent"
     android:layout_height="?android:attr/searchResultListItemHeight" >
 
     <!-- Icons come first in the layout, since their placement doesn't depend on
          the placement of the text views. -->
     <ImageView android:id="@android:id/icon1"
-        android:layout_width="48dip"
+        android:layout_width="@dimen/dropdownitem_icon_width"
         android:layout_height="48dip"
         android:scaleType="centerInside"
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
         android:layout_alignParentBottom="true"
-        android:visibility="gone" />
+        android:visibility="invisible" />
 
     <ImageView android:id="@+id/edit_query"
         android:layout_width="48dip"
diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml
index fee27eb..6b70d8d 100644
--- a/core/res/res/layout/search_view.xml
+++ b/core/res/res/layout/search_view.xml
@@ -22,6 +22,8 @@
     android:id="@+id/search_bar"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:paddingLeft="8dip"
+    android:paddingRight="8dip"
     android:orientation="horizontal"
     >
 
@@ -30,7 +32,7 @@
         android:id="@+id/search_badge"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_gravity="center_vertical"
+        android:gravity="center_vertical"
         android:layout_marginBottom="2dip"
         android:drawablePadding="0dip"
         android:textAppearance="?android:attr/textAppearanceMedium"
@@ -54,12 +56,21 @@
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:layout_gravity="center_vertical"
-        android:layout_marginLeft="8dip"
-        android:layout_marginRight="8dip"
         android:layout_marginTop="4dip"
         android:layout_marginBottom="4dip"
         android:orientation="horizontal">
 
+        <ImageView
+            android:id="@+id/search_mag_icon"
+            android:layout_width="@dimen/dropdownitem_icon_width"
+            android:layout_height="wrap_content"
+            android:scaleType="centerInside"
+            android:layout_marginLeft="@dimen/dropdownitem_text_padding_left"
+            android:layout_gravity="center_vertical"
+            android:src="?android:attr/searchViewSearchIcon"
+            android:visibility="gone"
+        />
+
         <!-- Inner layout contains the app icon, button(s) and EditText -->
         <LinearLayout
             android:id="@+id/search_plate"
@@ -70,14 +81,6 @@
             android:orientation="horizontal"
             android:background="?android:attr/searchViewTextField">
 
-            <ImageView
-                android:id="@+id/search_app_icon"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_gravity="center_vertical"
-                android:src="?android:attr/searchViewSearchIcon"
-            />
-
             <view class="android.widget.SearchView$SearchAutoComplete"
                 android:id="@+id/search_src_text"
                 android:layout_height="36dip"
@@ -85,8 +88,8 @@
                 android:layout_weight="1"
                 android:minWidth="@dimen/search_view_text_min_width"
                 android:layout_gravity="bottom"
-                android:paddingLeft="8dip"
-                android:paddingRight="6dip"
+                android:paddingLeft="@dimen/dropdownitem_text_padding_left"
+                android:paddingRight="@dimen/dropdownitem_text_padding_right"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:background="@null"
@@ -100,7 +103,7 @@
 
             <ImageView
                 android:id="@+id/search_close_btn"
-                android:layout_width="wrap_content"
+                android:layout_width="@dimen/dropdownitem_icon_width"
                 android:layout_height="match_parent"
                 android:paddingLeft="8dip"
                 android:paddingRight="8dip"
@@ -131,7 +134,7 @@
                 android:visibility="gone"
                 android:focusable="true"
             />
-    
+
             <ImageView
                 android:id="@+id/search_voice_btn"
                 android:layout_width="wrap_content"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 73f636f..2ba4e66 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -141,4 +141,13 @@
     <!-- The default gap between components in a layout. -->
     <dimen name="default_gap">16dip</dimen>
 
+    <!-- Text padding for dropdown items -->
+    <dimen name="dropdownitem_text_padding_left">6dip</dimen>
+
+    <!-- Text padding for dropdown items -->
+    <dimen name="dropdownitem_text_padding_right">6dip</dimen>
+
+    <!-- Width of the icon in a dropdown list -->
+    <dimen name="dropdownitem_icon_width">48dip</dimen>
+
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5b5e7c3..d647467 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -563,8 +563,8 @@
 
     <style name="Widget.DropDownItem">
         <item name="android:textAppearance">@style/TextAppearance.Widget.DropDownItem</item>
-        <item name="android:paddingLeft">6dip</item>
-        <item name="android:paddingRight">6dip</item>
+        <item name="android:paddingLeft">@dimen/dropdownitem_text_padding_left</item>
+        <item name="android:paddingRight">@dimen/dropdownitem_text_padding_right</item>
         <item name="android:gravity">center_vertical</item>
     </style>