AI 147564: Merge back from search branch to donut.  Notes:
  - all public apis and framework changes have been reviewed by relevant folks in our branch (e.g romainguy)
  - all new public apis are @hidden; they will still get reviewed by api council once we're in git
  - other than that, it's mostly GlobalSearch and search dialog stuff, a new apps provider, and some tweaks
  to the contacts provider that was reviewed by jham

Automated import of CL 147564
diff --git a/res/drawable/search_button_bg.9.png b/res/drawable/search_button_bg.9.png
new file mode 100644
index 0000000..b140b24
--- /dev/null
+++ b/res/drawable/search_button_bg.9.png
Binary files differ
diff --git a/res/drawable/search_button_voice.png b/res/drawable/search_button_voice.png
new file mode 100644
index 0000000..4c3d683
--- /dev/null
+++ b/res/drawable/search_button_voice.png
Binary files differ
diff --git a/res/drawable/search_floater.9.png b/res/drawable/search_floater.9.png
new file mode 100644
index 0000000..a2007ad
--- /dev/null
+++ b/res/drawable/search_floater.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget.xml b/res/drawable/textfield_searchwidget.xml
new file mode 100644
index 0000000..80f3dca
--- /dev/null
+++ b/res/drawable/textfield_searchwidget.xml
@@ -0,0 +1,31 @@
+<?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/textfield_searchwidget_default" />
+    
+    <item android:state_pressed="true"
+        android:drawable="@drawable/textfield_searchwidget_pressed" />
+    
+    <item android:state_enabled="true" android:state_focused="true"
+        android:drawable="@drawable/textfield_searchwidget_selected" />
+    
+    <item android:state_enabled="true"
+        android:drawable="@drawable/textfield_searchwidget_default" />
+    
+</selector>
diff --git a/res/drawable/textfield_searchwidget_default.9.png b/res/drawable/textfield_searchwidget_default.9.png
new file mode 100644
index 0000000..247abca
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_default.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_pressed.9.png b/res/drawable/textfield_searchwidget_pressed.9.png
new file mode 100644
index 0000000..d628cbc
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_pressed.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_selected.9.png b/res/drawable/textfield_searchwidget_selected.9.png
new file mode 100644
index 0000000..9e93527
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_selected.9.png
Binary files differ
diff --git a/res/layout/widget_search.xml b/res/layout/widget_search.xml
index 1db8488..841b155 100644
--- a/res/layout/widget_search.xml
+++ b/res/layout/widget_search.xml
@@ -14,52 +14,47 @@
      limitations under the License.
 -->
 
-<com.android.launcher.Search xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher.Search 
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+    android:id="@+id/widget_search"  
     android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="horizontal"
-    android:background="@drawable/search_bg"
-    android:gravity="center_vertical"
-    >
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="top">
 
-    <ImageView
-        android:layout_width="wrap_content"
+    <LinearLayout
+        android:id="@+id/search_plate"
+        android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        android:paddingRight="3dip"
-        android:src="@drawable/google_logo" />
-        
-    <com.android.launcher.SearchAutoCompleteTextView
-        android:id="@+id/input"
-        android:layout_width="0dip"
-        android:layout_weight="1"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="1dip"
-        android:hint="@string/search_hint"
-        android:focusableInTouchMode="false"
-        android:singleLine="true"
-        android:selectAllOnFocus="true"
-        android:completionThreshold="1"
-        android:inputType="textAutoComplete"
-        android:imeOptions="actionSearch"
-        android:lines="1"
-        android:dropDownWidth="fill_parent"
-        android:popupBackground="@drawable/spinner_dropdown_background"
+        android:orientation="horizontal"
+        android:paddingLeft="12dip"
+        android:paddingRight="12dip"
+        android:paddingTop="7dip"
+        android:paddingBottom="13dip"
+        android:background="@drawable/search_floater" >
+
+        <TextView
+            android:id="@+id/search_src_text"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.0"
+            android:editable="false"
+            android:inputType="text"
+            android:background="@drawable/textfield_searchwidget"
+            android:textAppearance="?android:attr/textAppearanceMediumInverse"
+            android:textColor="@android:color/primary_text_light"
         />
         
-     <ImageButton android:id="@+id/search_go_btn"
-         android:layout_marginLeft="5dip"
-         android:layout_width="wrap_content"
-         android:layout_height="wrap_content"
-         android:src="@*android:drawable/ic_btn_search"
-         style="@style/SearchButton"
-         />
-    
-     <ImageButton android:id="@+id/search_voice_btn"
-         android:layout_marginLeft="4dip"
-         android:layout_width="wrap_content"
-         android:layout_height="wrap_content"
-         android:src="@android:drawable/ic_btn_speak_now"
-         style="@style/SearchButton"
-         />
+        <ImageButton 
+            android:id="@+id/search_voice_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_marginLeft="8dip"
+            android:background="@*android:drawable/btn_search_dialog_voice"
+            android:src="@*android:drawable/ic_btn_speak_now"
+        />
+
+    </LinearLayout>
 
 </com.android.launcher.Search>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml
index ae2e2a6..2280b61 100644
--- a/res/xml/default_workspace.xml
+++ b/res/xml/default_workspace.xml
@@ -19,7 +19,7 @@
     <clock
         launcher:screen="2"
         launcher:x="1"
-        launcher:y="0" />
+        launcher:y="1" />
 
     <search
         launcher:screen="1"
diff --git a/src/com/android/launcher/Launcher.java b/src/com/android/launcher/Launcher.java
index 7dd3418..3bf96aa 100644
--- a/src/com/android/launcher/Launcher.java
+++ b/src/com/android/launcher/Launcher.java
@@ -20,6 +20,7 @@
 import android.app.AlertDialog;
 import android.app.Application;
 import android.app.Dialog;
+import android.app.IWallpaperService;
 import android.app.SearchManager;
 import android.app.StatusBarManager;
 import android.content.ActivityNotFoundException;
@@ -35,26 +36,24 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.MessageQueue;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.Message;
-import android.provider.*;
-import android.telephony.PhoneNumberUtils;
+import android.provider.LiveFolders;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -71,11 +70,11 @@
 import android.view.View.OnLongClickListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
+import android.widget.GridView;
+import android.widget.ListView;
+import android.widget.SlidingDrawer;
 import android.widget.TextView;
 import android.widget.Toast;
-import android.widget.GridView;
-import android.widget.SlidingDrawer;
-import android.app.IWallpaperService;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 
@@ -168,7 +167,6 @@
 
     private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
     private final ContentObserver mObserver = new FavoritesChangeObserver();
-    private final ContentObserver mAppWidgetResetObserver = new AppWidgetResetObserver();
 
     private LayoutInflater mInflater;
 
@@ -353,6 +351,9 @@
         if (mRestoring) {
             startLoaders();
         }
+
+        // Make sure that the search gadget (if any) is in its normal place.
+        stopSearch(false);
     }
 
     @Override
@@ -387,45 +388,29 @@
             boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
                     keyCode, event);
             if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
-                // something usable has been typed - dispatch it now.
-                final String str = mDefaultKeySsb.toString();
-
-                boolean isDialable = true;
-                final int count = str.length();
-                for (int i = 0; i < count; i++) {
-                    if (!PhoneNumberUtils.isReallyDialable(str.charAt(i))) {
-                        isDialable = false;
-                        break;
-                    }
-                }
-                Intent intent;
-                if (isDialable) {
-                    intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", str, null));
-                } else {
-                    intent = new Intent(Contacts.Intents.UI.FILTER_CONTACTS_ACTION);
-                    intent.putExtra(Contacts.Intents.UI.FILTER_TEXT_EXTRA_KEY, str);
-                }
-
-                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
-                try {
-                    startActivity(intent);
-                } catch (android.content.ActivityNotFoundException ex) {
-                    // Oh well... no one knows how to filter/dial. Life goes on.
-                }
-
-                mDefaultKeySsb.clear();
-                mDefaultKeySsb.clearSpans();
-                Selection.setSelection(mDefaultKeySsb, 0);
-
-                return true;
+                // something usable has been typed - start a search
+                // the typed text will be retrieved and cleared by 
+                // showSearchDialog()
+                // If there are multiple keystrokes before the search dialog takes focus,
+                // onSearchRequested() will be called for every keystroke,
+                // but it is idempotent, so it's fine.
+                return onSearchRequested();
             }
         }
 
         return handled;
     }
 
+    private String getTypedText() {
+        return mDefaultKeySsb.toString();
+    }
+
+    private void clearTypedText() {
+        mDefaultKeySsb.clear();
+        mDefaultKeySsb.clearSpans();
+        Selection.setSelection(mDefaultKeySsb, 0);
+    }
+
     /**
      * Restores the previous state, if it exists.
      *
@@ -495,7 +480,7 @@
         drawer.setOnDrawerCloseListener(drawerManager);
         drawer.setOnDrawerScrollListener(drawerManager);
 
-        grid.setTextFilterEnabled(true);
+        grid.setTextFilterEnabled(false);
         grid.setDragger(dragLayer);
         grid.setLauncher(this);
 
@@ -827,7 +812,6 @@
         sModel.abortLoaders();
 
         getContentResolver().unregisterContentObserver(mObserver);
-        getContentResolver().unregisterContentObserver(mAppWidgetResetObserver);
         unregisterReceiver(mApplicationsReceiver);
     }
 
@@ -840,13 +824,76 @@
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery, 
             Bundle appSearchData, boolean globalSearch) {
+
+        closeDrawer(false);
+        
+        // Slide the search widget to the top, if it's on the current screen,
+        // otherwise show the search dialog immediately.
+        Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+        if (searchWidget == null) {
+            showSearchDialog(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+        } else {
+            searchWidget.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+            // show the currently typed text in the search widget while sliding
+            searchWidget.setQuery(getTypedText());
+        }
+    }
+    
+    /**
+     * Show the search dialog immediately, without changing the search widget.
+     * See {@link Activity.startSearch()} for the arguments.
+     */
+    public void showSearchDialog(String initialQuery, boolean selectInitialQuery, 
+            Bundle appSearchData, boolean globalSearch) {
+        
+        if (initialQuery == null) {
+            // Use any text typed in the launcher as the initial query
+            initialQuery = getTypedText();
+            clearTypedText();
+        }
         if (appSearchData == null) {
             appSearchData = new Bundle();
             appSearchData.putString(SearchManager.SOURCE, "launcher-search");
         }
-        super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+        
+        final SearchManager searchManager =
+                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+
+        final Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+        if (searchWidget != null) {
+            // This gets called when the user leaves the search dialog to go back to
+            // the Launcher.
+            searchManager.setOnCancelListener(new SearchManager.OnCancelListener() {
+                public void onCancel() {
+                    searchManager.setOnCancelListener(null);
+                    stopSearch(true);
+                }            
+            });
+        }
+        
+        searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+            appSearchData, globalSearch); 
     }
 
+    /**
+     * Cancel search dialog if it is open.
+     * 
+     * @param animate Whether to animate the search gadget (if any) when restoring it
+     * to its original position.
+     */
+    public void stopSearch(boolean animate) {
+        // Close search dialog
+        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        if (searchManager.isVisible()) {
+            searchManager.stopSearch();
+        }
+        // Restore search widget to its normal position
+        Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+        if (searchWidget != null) {
+            searchWidget.stopSearch(false);
+        }
+    }
+    
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         if (mDesktopLocked) return false;
@@ -905,15 +952,16 @@
 
         return super.onOptionsItemSelected(item);
     }
+    
+    /**
+     * Indicates that we want global search for this activity by setting the globalSearch
+     * argument for {@link #startSearch} to true.
+     */
 
     @Override
     public boolean onSearchRequested() {
-        if (mWorkspace.snapToSearch()) {
-            closeDrawer(true);                // search widget: get drawer out of the way
-            return true;
-        } else {
-            return super.onSearchRequested(); // no search widget: use system search UI
-        }
+        startSearch(null, false, null, true); 
+        return true;
     }
 
     private void addItems() {
@@ -975,6 +1023,8 @@
     
         final View view = mInflater.inflate(info.layoutResource, null);
         view.setTag(info);
+        Search search = (Search) view.findViewById(R.id.widget_search);
+        search.setLauncher(this);
     
         mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
     }
@@ -1158,7 +1208,6 @@
     private void registerContentObservers() {
         ContentResolver resolver = getContentResolver();
         resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mObserver);
-        resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, true, mAppWidgetResetObserver);
     }
 
     @Override
@@ -1224,16 +1273,6 @@
         sModel.loadUserItems(false, this, false, false);
     }
 
-    /**
-     * When reset, we handle by calling {@link AppWidgetHost#startListening()}
-     * to make sure our callbacks are set correctly.
-     */
-    private void onAppWidgetReset() {
-        if (mAppWidgetHost != null) {
-            mAppWidgetHost.startListening();
-        }
-    }
-
     void onDesktopItemsLoaded() {
         if (mDestroyed) return;
         bindDesktopItems();
@@ -1250,8 +1289,6 @@
             return;
         }
 
-        mAllAppsGrid.setAdapter(drawerAdapter);
-
         final Workspace workspace = mWorkspace;
         int count = workspace.getChildCount();
         for (int i = 0; i < count; i++) {
@@ -1275,7 +1312,7 @@
             mBinder.mTerminate = true;
         }
         
-        mBinder = new DesktopBinder(this, shortcuts, appWidgets);
+        mBinder = new DesktopBinder(this, shortcuts, appWidgets, drawerAdapter);
         mBinder.startBindingItems();
     }
 
@@ -1317,6 +1354,9 @@
                     final View view = mInflater.inflate(R.layout.widget_search,
                             (ViewGroup) workspace.getChildAt(screen), false);
                     
+                    Search search = (Search) view.findViewById(R.id.widget_search);
+                    search.setLauncher(this);
+                    
                     final Widget widget = (Widget) item;
                     view.setTag(widget);
                     
@@ -1329,7 +1369,7 @@
 
         if (end >= count) {
             finishBindDesktopItems();
-            binder.startBindingAppWidgetsWhenIdle();
+            binder.startBindingDrawer();
         } else {
             binder.obtainMessage(DesktopBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget();
         }
@@ -1376,6 +1416,12 @@
         mDrawer.unlock();
     }
     
+    private void bindDrawer(Launcher.DesktopBinder binder,
+            ApplicationsAdapter drawerAdapter) {
+        mAllAppsGrid.setAdapter(drawerAdapter);
+        binder.startBindingAppWidgetsWhenIdle();
+    }
+    
     private void bindAppWidgets(Launcher.DesktopBinder binder,
             LinkedList<LauncherAppWidgetInfo> appWidgets) {
         
@@ -1386,14 +1432,10 @@
             final LauncherAppWidgetInfo item = appWidgets.removeFirst();
             
             final int appWidgetId = item.appWidgetId;
-            final AppWidgetProviderInfo appWidgetInfo =
-                    mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+            final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
             item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
             
-            if (LOGD) {
-                d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s",
-                        appWidgetId, appWidgetInfo));
-            }
+            if (LOGD) d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s", appWidgetId, appWidgetInfo));
             
             item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
             item.hostView.setTag(item);
@@ -1891,21 +1933,6 @@
     }
 
     /**
-     * Receives notifications when the {@link AppWidgetHost} has been reset,
-     * usually only when the {@link LauncherProvider} database is first created.
-     */
-    private class AppWidgetResetObserver extends ContentObserver {
-        public AppWidgetResetObserver() {
-            super(new Handler());
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            onAppWidgetReset();
-        }
-    }
-
-    /**
      * Receives intents from other applications to change the wallpaper.
      */
     private static class WallpaperIntentReceiver extends BroadcastReceiver {
@@ -1996,21 +2023,25 @@
     private static class DesktopBinder extends Handler implements MessageQueue.IdleHandler {
         static final int MESSAGE_BIND_ITEMS = 0x1;
         static final int MESSAGE_BIND_APPWIDGETS = 0x2;
+        static final int MESSAGE_BIND_DRAWER = 0x3;
         
         // Number of items to bind in every pass
         static final int ITEMS_COUNT = 6;
 
         private final ArrayList<ItemInfo> mShortcuts;
         private final LinkedList<LauncherAppWidgetInfo> mAppWidgets;
+        private final ApplicationsAdapter mDrawerAdapter;
         private final WeakReference<Launcher> mLauncher;
         
-        public volatile boolean mTerminate = false;
+        public boolean mTerminate = false;
 
         DesktopBinder(Launcher launcher, ArrayList<ItemInfo> shortcuts,
-                ArrayList<LauncherAppWidgetInfo> appWidgets) {
+                ArrayList<LauncherAppWidgetInfo> appWidgets,
+                ApplicationsAdapter drawerAdapter) {
 
             mLauncher = new WeakReference<Launcher>(launcher);
             mShortcuts = shortcuts;
+            mDrawerAdapter = drawerAdapter;
             
             // Sort widgets so active workspace is bound first
             final int currentScreen = launcher.mWorkspace.getCurrentScreen();
@@ -2030,6 +2061,10 @@
         public void startBindingItems() {
             obtainMessage(MESSAGE_BIND_ITEMS, 0, mShortcuts.size()).sendToTarget();
         }
+
+        public void startBindingDrawer() {
+            obtainMessage(MESSAGE_BIND_DRAWER).sendToTarget();
+        }
         
         public void startBindingAppWidgetsWhenIdle() {
             // Ask for notification when message queue becomes idle
@@ -2059,6 +2094,10 @@
                     launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2);
                     break;
                 }
+                case MESSAGE_BIND_DRAWER: {
+                    launcher.bindDrawer(this, mDrawerAdapter);
+                    break;
+                }
                 case MESSAGE_BIND_APPWIDGETS: {
                     launcher.bindAppWidgets(this, mAppWidgets);
                     break;
@@ -2067,3 +2106,4 @@
         }
     }
 }
+
diff --git a/src/com/android/launcher/Search.java b/src/com/android/launcher/Search.java
index 71ab7ef..3d09479 100644
--- a/src/com/android/launcher/Search.java
+++ b/src/com/android/launcher/Search.java
@@ -16,71 +16,59 @@
 
 package com.android.launcher;
 
-import android.app.ISearchManager;
-import android.app.SearchManager;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
+import android.content.res.Configuration;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.server.search.SearchableInfo;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnClickListener;
 import android.view.View.OnKeyListener;
 import android.view.View.OnLongClickListener;
-import android.widget.AdapterView;
-import android.widget.AutoCompleteTextView;
-import android.widget.CursorAdapter;
-import android.widget.Filter;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.ImageButton;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.SimpleCursorAdapter;
 import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
 
-public class Search extends LinearLayout implements OnClickListener, OnKeyListener,
-        OnLongClickListener, TextWatcher, OnItemClickListener, OnItemSelectedListener {
+public class Search extends LinearLayout 
+        implements OnClickListener, OnKeyListener, OnLongClickListener {
+
+    // Speed at which the widget slides up/down, in pixels/ms.
+    private static final float ANIMATION_VELOCITY = 1.0f;
 
     private final String TAG = "SearchWidget";
 
-    private AutoCompleteTextView mSearchText;
-    private ImageButton mGoButton;
+    private Launcher mLauncher;
+
+    private TextView mSearchText;
     private ImageButton mVoiceButton;
-    private OnLongClickListener mLongClickListener;
-    
-    // Support for suggestions
-    private SuggestionsAdapter mSuggestionsAdapter;
-    private SearchableInfo mSearchable;
-    private String mSuggestionAction = null;
-    private Uri mSuggestionData = null;
-    private String mSuggestionQuery = null;
-    private int mItemSelected = -1;
-    
+
+    /** The animation that morphs the search widget to the search dialog. */
+    private Animation mMorphAnimation;
+
+    /** The animation that morphs the search widget back to its normal position. */
+    private Animation mUnmorphAnimation;
+
+    // These four are passed to Launcher.startSearch() when the search widget
+    // has finished morphing. They are instance variables to make it possible to update
+    // them while the widget is morphing.
+    private String mInitialQuery;
+    private boolean mSelectInitialQuery;    
+    private Bundle mAppSearchData;
+    private boolean mGlobalSearch;
+
     // For voice searching
     private Intent mVoiceSearchIntent;
 
-    private Rect mTempRect = new Rect();
-    private boolean mRestoreFocus = false;
-
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -89,293 +77,235 @@
      */
     public Search(Context context, AttributeSet attrs) {
         super(context, attrs);
+
+        Interpolator interpolator = new AccelerateDecelerateInterpolator();
+
+        mMorphAnimation = new ToParentOriginAnimation();
+        // no need to apply transformation before the animation starts,
+        // since the gadget is already in its normal place.
+        mMorphAnimation.setFillBefore(false);
+        // stay in the top position after the animation finishes
+        mMorphAnimation.setFillAfter(true);
+        mMorphAnimation.setInterpolator(interpolator);
+        mMorphAnimation.setAnimationListener(new Animation.AnimationListener() {
+            // The amount of time before the animation ends to show the search dialog.
+            private static final long TIME_BEFORE_ANIMATION_END = 80;
+            
+            // The runnable which we'll pass to our handler to show the search dialog.
+            private final Runnable mShowSearchDialogRunnable = new Runnable() {
+                public void run() {
+                    showSearchDialog();
+                }
+            };
+            
+            public void onAnimationEnd(Animation animation) { }
+            public void onAnimationRepeat(Animation animation) { }
+            public void onAnimationStart(Animation animation) {
+                // Make the search dialog show up ideally *just* as the animation reaches
+                // the top, to aid the illusion that the widget becomes the search dialog.
+                // Otherwise, there is a short delay when the widget reaches the top before
+                // the search dialog shows. We do this roughly 80ms before the animation ends.
+                getHandler().postDelayed(
+                        mShowSearchDialogRunnable,
+                        Math.max(mMorphAnimation.getDuration() - TIME_BEFORE_ANIMATION_END, 0));
+            }
+        });
+
+        mUnmorphAnimation = new FromParentOriginAnimation();
+        // stay in the top position until the animation starts
+        mUnmorphAnimation.setFillBefore(true);
+        // no need to apply transformation after the animation finishes,
+        // since the gadget is now back in its normal place.
+        mUnmorphAnimation.setFillAfter(false);
+        mUnmorphAnimation.setInterpolator(interpolator);
+        mUnmorphAnimation.setAnimationListener(new Animation.AnimationListener(){
+            public void onAnimationEnd(Animation animation) {
+                clearAnimation();
+            }
+            public void onAnimationRepeat(Animation animation) { }
+            public void onAnimationStart(Animation animation) { }
+        });
         
         mVoiceSearchIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
         mVoiceSearchIntent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                 android.speech.RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
     }
-    
+
     /**
-     * Implements OnClickListener (for button)
+     * Implements OnClickListener.
      */
     public void onClick(View v) {
-        if (v == mGoButton) {
-            query();
-        } else if (v == mVoiceButton) {
-            try {
-                getContext().startActivity(mVoiceSearchIntent);
-            } catch (ActivityNotFoundException ex) {
-                // Should not happen, since we check the availability of
-                // voice search before showing the button. But just in case...
-                Log.w(TAG, "Could not find voice search activity");
+        if (v == mVoiceButton) {
+            startVoiceSearch();
+        } else {
+            mLauncher.onSearchRequested();
+        }
+    }
+
+    private void startVoiceSearch() {
+        try {
+            getContext().startActivity(mVoiceSearchIntent);
+        } catch (ActivityNotFoundException ex) {
+            // Should not happen, since we check the availability of
+            // voice search before showing the button. But just in case...
+            Log.w(TAG, "Could not find voice search activity");
+        }
+    }
+
+    /**
+     * Sets the query text. The query field is not editable, instead we forward
+     * the key events to the launcher, which keeps track of the text, 
+     * calls setQuery() to show it, and gives it to the search dialog.
+     */
+    public void setQuery(String query) {
+        mSearchText.setText(query, TextView.BufferType.NORMAL);
+    }
+
+    /**
+     * Morph the search gadget to the search dialog.
+     * See {@link Activity.startSearch()} for the arguments.
+     */
+    public void startSearch(String initialQuery, boolean selectInitialQuery, 
+            Bundle appSearchData, boolean globalSearch) {
+        mInitialQuery = initialQuery;
+        mSelectInitialQuery = selectInitialQuery;
+        mAppSearchData = appSearchData;
+        mGlobalSearch = globalSearch;
+        
+        // Call up the keyboard before we actually call the search dialog so that it
+        // (hopefully) animates in at about the same time as the widget animation, and
+        // so that it becomes available as soon as possible. Only do this if a hard
+        // keyboard is not currently available.
+        if (getContext().getResources().getConfiguration().hardKeyboardHidden ==
+                Configuration.HARDKEYBOARDHIDDEN_YES) {
+            // Make sure the text field is not focusable, so it's not responsible for
+            // causing the whole view to shift up to accommodate the keyboard.
+            mSearchText.setFocusable(false);
+            
+            InputMethodManager inputManager = (InputMethodManager)
+                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+            inputManager.showSoftInputUnchecked(0, null);
+        }
+        
+        if (isAtTop()) {
+            showSearchDialog();
+        } else {
+            // Start the animation, unless it has already started.
+            if (getAnimation() != mMorphAnimation) {
+                mMorphAnimation.setDuration(getAnimationDuration());
+                startAnimation(mMorphAnimation);
             }
         }
     }
 
-    private void query() {
-        String query = mSearchText.getText().toString();
-        if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
-            return;
+    /**
+     * Shows the system search dialog immediately, without any animation.
+     */
+    private void showSearchDialog() {
+        mLauncher.showSearchDialog(
+                mInitialQuery, mSelectInitialQuery, mAppSearchData, mGlobalSearch);
+    }
+
+    /**
+     * Restore the search gadget to its normal position.
+     * 
+     * @param animate Whether to animate the movement of the gadget.
+     */
+    public void stopSearch(boolean animate) {
+        setQuery("");
+        
+        // Set the search field back to focusable after making it unfocusable in
+        // startSearch, so that the home screen doesn't try to shift around when the
+        // keyboard comes up.
+        mSearchText.setFocusable(true);
+        // Only restore if we are not already restored.
+        if (getAnimation() == mMorphAnimation) {
+            if (animate && !isAtTop()) {
+                mUnmorphAnimation.setDuration(getAnimationDuration());
+                startAnimation(mUnmorphAnimation);
+            } else {
+                clearAnimation();
+            }
         }
-        Bundle appData = new Bundle();
-        appData.putString(SearchManager.SOURCE, "launcher-widget");
-        sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, 0, null, mSearchable);
-        clearQuery();
+    }
+
+    private boolean isAtTop() {
+        return getTop() == 0;
+    }
+
+    private int getAnimationDuration() {
+        return (int) (getTop() / ANIMATION_VELOCITY);
+    }
+
+    /**
+     * Modify clearAnimation() to invalidate the parent. This works around
+     * an issue where the region where the end of the animation placed the view
+     * was not redrawn after clearing the animation.
+     */
+    @Override
+    public void clearAnimation() {
+        Animation animation = getAnimation();
+        if (animation != null) {
+            super.clearAnimation();
+            if (animation.hasEnded() 
+                    && animation.getFillAfter()
+                    && animation.willChangeBounds()) {
+                ((View) getParent()).invalidate();
+            } else {
+                invalidate();
+            }
+        }
     }
     
-    /**
-     * Assemble a search intent and send it.
-     * 
-     * This is copied from SearchDialog.
-     *
-     * @param action The intent to send, typically Intent.ACTION_SEARCH
-     * @param data The data for the intent
-     * @param query The user text entered (so far)
-     * @param appData The app data bundle (if supplied)
-     * @param actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will
-     * be sent here.  Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
-     * @param actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the
-     * corresponding tag message will be sent here.  Pass null for no actionKey message.
-     * @param si Reference to the current SearchableInfo.  Passed here so it can be used even after
-     * we've called dismiss(), which attempts to null mSearchable.
-     */
-    private void sendLaunchIntent(final String action, final Uri data, final String query,
-            final Bundle appData, int actionKey, final String actionMsg, final SearchableInfo si) {
-        Intent launcher = new Intent(action);
-        launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        if (query != null) {
-            launcher.putExtra(SearchManager.QUERY, query);
-        }
-
-        if (data != null) {
-            launcher.setData(data);
-        }
-
-        if (appData != null) {
-            launcher.putExtra(SearchManager.APP_DATA, appData);
-        }
-
-        // add launch info (action key, etc.)
-        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
-            launcher.putExtra(SearchManager.ACTION_KEY, actionKey);
-            launcher.putExtra(SearchManager.ACTION_MSG, actionMsg);
-        }
-
-        // attempt to enforce security requirement (no 3rd-party intents)
-        if (si != null) {
-            launcher.setComponent(si.mSearchActivity);
-        }
-
-        getContext().startActivity(launcher);
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        if (!hasWindowFocus && hasFocus()) {
-            mRestoreFocus = true;
-        }
-
-        super.onWindowFocusChanged(hasWindowFocus);
-
-        if (hasWindowFocus && mRestoreFocus) {
-            if (isInTouchMode()) {
-                final AutoCompleteTextView searchText = mSearchText;
-                searchText.setSelectAllOnFocus(false);
-                searchText.requestFocusFromTouch();
-                searchText.setSelectAllOnFocus(true);
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (!event.isSystem() && 
+                (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
+            // Forward key events to Launcher, which will forward text 
+            // to search dialog
+            switch (event.getAction()) {
+                case KeyEvent.ACTION_DOWN:
+                    return mLauncher.onKeyDown(keyCode, event);
+                case KeyEvent.ACTION_MULTIPLE:
+                    return mLauncher.onKeyMultiple(keyCode, event.getRepeatCount(), event);
+                case KeyEvent.ACTION_UP:
+                    return mLauncher.onKeyUp(keyCode, event);
             }
-            mRestoreFocus = false;
         }
-    }
-
-    /**
-     * Implements TextWatcher (for EditText)
-     */
-    public void beforeTextChanged(CharSequence s, int start, int before, int after) { 
-    }
-
-    /**
-     * Implements TextWatcher (for EditText)
-     */
-    public void onTextChanged(CharSequence s, int start, int before, int after) {
-        // enable the button if we have one or more non-space characters
-        boolean enabled = TextUtils.getTrimmedLength(mSearchText.getText()) != 0;
-        mGoButton.setEnabled(enabled);
-        mGoButton.setFocusable(enabled);
-    }
-
-    /**
-     * Implements TextWatcher (for EditText)
-     */
-    public void afterTextChanged(Editable s) {
-    }
-
-    /**
-     * Implements OnKeyListener (for EditText and for button)
-     * 
-     * This plays some games with state in order to "soften" the strength of suggestions
-     * presented.  Suggestions should not be used unless the user specifically navigates to them
-     * (or clicks them, in which case it's obvious).  This is not the way that AutoCompleteTextBox
-     * normally works.
-     */
-    public final boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (v == mSearchText) {
-            boolean searchTrigger = (keyCode == KeyEvent.KEYCODE_ENTER || 
-                    keyCode == KeyEvent.KEYCODE_SEARCH ||
-                    keyCode == KeyEvent.KEYCODE_DPAD_CENTER);
-            if (event.getAction() == KeyEvent.ACTION_UP) {
-//              Log.d(TAG, "onKey() ACTION_UP isPopupShowing:" + mSearchText.isPopupShowing());
-                if (!mSearchText.isPopupShowing()) {
-                    if (searchTrigger) {
-                        query();
-                        return true;
-                    }
-                }
-            } else {
-//              Log.d(TAG, "onKey() ACTION_DOWN isPopupShowing:" + mSearchText.isPopupShowing() +
-//                      " mItemSelected="+ mItemSelected);
-                if (searchTrigger && mItemSelected < 0) {
-                    query();
-                    return true;
-                }
-            }
-        } else if (v == mGoButton || v == mVoiceButton) {
-            boolean handled = false;
-            if (!event.isSystem() && 
-                    (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
-                if (mSearchText.requestFocus()) {
-                    handled = mSearchText.dispatchKeyEvent(event);
-                }
-            }
-            return handled;
-        }
-
         return false;
     }
-    
-    @Override
-    public void setOnLongClickListener(OnLongClickListener l) {
-        super.setOnLongClickListener(l);
-        mLongClickListener = l;
-    }
-    
+
     /**
-     * Implements OnLongClickListener (for button)
+     * Implements OnLongClickListener to pass long clicks on child views 
+     * to the widget. This makes it possible to pick up the widget by long
+     * clicking on the text field or a button.
      */
     public boolean onLongClick(View v) {
-        // Pretend that a long press on a child view is a long press on the search widget
-        if (mLongClickListener != null) {
-            return mLongClickListener.onLongClick(this);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // Request focus unless the user tapped on the voice search button
-        final int x = (int) ev.getX();
-        final int y = (int) ev.getY();
-        final Rect frame = mTempRect;
-        mVoiceButton.getHitRect(frame);
-        if (!frame.contains(x, y)) {
-            requestFocusFromTouch();
-        }
-        return super.onInterceptTouchEvent(ev);
-    }
-    
-    /**
-     * In order to keep things simple, the external trigger will clear the query just before
-     * focusing, so as to give you a fresh query.  This way we eliminate any sources of
-     * accidental query launching.
-     */
-    public void clearQuery() {
-        mSearchText.setText(null);
+        return performLongClick();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mSearchText = (AutoCompleteTextView) findViewById(R.id.input);
-        // TODO: This can be confusing when the user taps the text field to give the focus
-        // (it is not necessary but I ran into this issue several times myself)
-        // mTitleInput.setOnClickListener(this);
-        mSearchText.setOnKeyListener(this);
-        mSearchText.addTextChangedListener(this);
-
-        mGoButton = (ImageButton) findViewById(R.id.search_go_btn);
+        mSearchText = (TextView) findViewById(R.id.search_src_text);
         mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
-        mGoButton.setOnClickListener(this);
+
+        mSearchText.setOnKeyListener(this);
+
+        mSearchText.setOnClickListener(this);
         mVoiceButton.setOnClickListener(this);
-        mGoButton.setOnKeyListener(this);
-        mVoiceButton.setOnKeyListener(this);
-        
+        setOnClickListener(this);        
+
         mSearchText.setOnLongClickListener(this);
-        mGoButton.setOnLongClickListener(this);
         mVoiceButton.setOnLongClickListener(this);
-        
-        // disable the button since we start out w/empty input
-        mGoButton.setEnabled(false);
-        mGoButton.setFocusable(false);
-        
-        configureSearchableInfo();
-        configureSuggestions();
+
         configureVoiceSearchButton();
     }
-    
-    /**
-     * Cache of popup padding value after read from {@link Resources}.
-     */
-    private static float mPaddingInset = -1;
-    
-    /**
-     * When our size is changed, pass down adjusted width and offset values to
-     * correctly center the {@link AutoCompleteTextView} popup and include our
-     * padding.
-     */
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        if (changed) {
-            if (mPaddingInset == -1) {
-                mPaddingInset = getResources().getDimension(R.dimen.search_widget_inset);
-            }
-            
-            // Fill entire width of widget, minus padding inset
-            float paddedWidth = getWidth() - (mPaddingInset * 2);
-            float paddedOffset = -(mSearchText.getLeft() - mPaddingInset);
-                
-            mSearchText.setDropDownWidth((int) paddedWidth);
-            mSearchText.setDropDownHorizontalOffset((int) paddedOffset);
-        }
-    }
-    
-    /**
-     * Read the searchable info from the search manager
-     */
-    private void configureSearchableInfo() {
-        ISearchManager sms;
-        SearchableInfo searchable;
-        sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
-        try {
-            // TODO null isn't the published use of this API, but it works when global=true
-            // TODO better implementation:  defer all of this, let Home set it up 
-            searchable = sms.getSearchableInfo(null, true);
-        } catch (RemoteException e) {
-            searchable = null;
-        }
-        if (searchable == null) {
-            // no suggestions so just get out (no need to continue)
-            return;
-        }
-        mSearchable = searchable;
-    }
-    
+
     /**
      * If appropriate & available, configure voice search
      * 
@@ -384,346 +314,45 @@
      * voice search.
      */
     private void configureVoiceSearchButton() {
-        boolean voiceSearchVisible = false;
-        if (mSearchable.getVoiceSearchEnabled() && mSearchable.getVoiceSearchLaunchWebSearch()) {
-            // Enable the voice search button if there is an activity that can handle it
-            PackageManager pm = getContext().getPackageManager();
-            ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
-                    PackageManager.MATCH_DEFAULT_ONLY);
-            voiceSearchVisible = ri != null;
-        }
-        
+        // Enable the voice search button if there is an activity that can handle it
+        PackageManager pm = getContext().getPackageManager();
+        ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        boolean voiceSearchVisible = ri != null;
+
         // finally, set visible state of voice search button, as appropriate
         mVoiceButton.setVisibility(voiceSearchVisible ? View.VISIBLE : View.GONE);
     }
-     
-    /** The rest of the class deals with providing search suggestions */
-    
+
     /**
-     * Set up the suggestions provider mechanism
+     * Sets the {@link Launcher} that this gadget will call on to display the search dialog. 
      */
-    private void configureSuggestions() {
-        // get SearchableInfo
-        
-        mSearchText.setOnItemClickListener(this);
-        mSearchText.setOnItemSelectedListener(this);
-        
-        // attach the suggestions adapter
-        mSuggestionsAdapter = new SuggestionsAdapter(mContext, 
-                com.android.internal.R.layout.search_dropdown_item_2line, null,
-                SuggestionsAdapter.TWO_LINE_FROM, SuggestionsAdapter.TWO_LINE_TO, mSearchable);
-        mSearchText.setAdapter(mSuggestionsAdapter);
+    public void setLauncher(Launcher launcher) {
+        mLauncher = launcher;
     }
-    
-    /**
-     * Remove internal cursor references when detaching from window which
-     * prevents {@link Context} leaks.
-     */
-    @Override
-    public void onDetachedFromWindow() {
-        if (mSuggestionsAdapter != null) {
-            mSuggestionsAdapter.changeCursor(null);
-            mSuggestionsAdapter = null;
-        }
-    }
-    
-    /**
-     * Implements OnItemClickListener
-     */
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-//      Log.d(TAG, "onItemClick() position " + position);
-        launchSuggestion(mSuggestionsAdapter, position);
-    }
-    
+
     /** 
-     * Implements OnItemSelectedListener
+     * Moves the view to the top left corner of its parent.
      */
-     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-//       Log.d(TAG, "onItemSelected() position " + position);
-         mItemSelected = position;
-     }
-
-     /** 
-      * Implements OnItemSelectedListener
-      */
-     public void onNothingSelected(AdapterView<?> parent) {
-//       Log.d(TAG, "onNothingSelected()");
-         mItemSelected = -1;
-     }
-
-    /**
-     * Code to launch a suggestion query.  
-     * 
-     * This is copied from SearchDialog.
-     * 
-     * @param ca The CursorAdapter containing the suggestions
-     * @param position The suggestion we'll be launching from
-     * 
-     * @return Returns true if a successful launch, false if could not (e.g. bad position)
-     */
-    private boolean launchSuggestion(CursorAdapter ca, int position) {
-        if (ca != null) {
-            Cursor c = ca.getCursor();
-            if ((c != null) && c.moveToPosition(position)) {
-                setupSuggestionIntent(c, mSearchable);
-                
-                SearchableInfo si = mSearchable;
-                String suggestionAction = mSuggestionAction;
-                Uri suggestionData = mSuggestionData;
-                String suggestionQuery = mSuggestionQuery;
-                sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, null,
-                                    KeyEvent.KEYCODE_UNKNOWN, null, si);
-                clearQuery();
-                return true;
-            }
-        }
-        return false;
-    }
-    
-    /**
-     * When a particular suggestion has been selected, perform the various lookups required
-     * to use the suggestion.  This includes checking the cursor for suggestion-specific data,
-     * and/or falling back to the XML for defaults;  It also creates REST style Uri data when
-     * the suggestion includes a data id.
-     * 
-     * NOTE:  Return values are in member variables mSuggestionAction, mSuggestionData and
-     * mSuggestionQuery.
-     * 
-     * This is copied from SearchDialog.
-     * 
-     * @param c The suggestions cursor, moved to the row of the user's selection
-     * @param si The searchable activity's info record
-     */
-    void setupSuggestionIntent(Cursor c, SearchableInfo si) {
-        try {
-            // use specific action if supplied, or default action if supplied, or fixed default
-            mSuggestionAction = null;
-            int column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
-            if (column >= 0) {
-                final String action = c.getString(column);
-                if (action != null) {
-                    mSuggestionAction = action;
-                }
-            }
-            if (mSuggestionAction == null) {
-                mSuggestionAction = si.getSuggestIntentAction();
-            }
-            if (mSuggestionAction == null) {
-                mSuggestionAction = Intent.ACTION_SEARCH;
-            }
-            
-            // use specific data if supplied, or default data if supplied
-            String data = null;
-            column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
-            if (column >= 0) {
-                final String rowData = c.getString(column);
-                if (rowData != null) {
-                    data = rowData;
-                }
-            }
-            if (data == null) {
-                data = si.getSuggestIntentData();
-            }
-            
-            // then, if an ID was provided, append it.
-            if (data != null) {
-                column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
-                if (column >= 0) {
-                    final String id = c.getString(column);
-                    if (id != null) {
-                        data = data + "/" + Uri.encode(id);
-                    }
-                }
-            }
-            mSuggestionData = (data == null) ? null : Uri.parse(data);
-            
-            mSuggestionQuery = null;
-            column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
-            if (column >= 0) {
-                final String query = c.getString(column);
-                if (query != null) {
-                    mSuggestionQuery = query;
-                }
-            }
-        } catch (RuntimeException e ) {
-            int rowNum;
-            try {                       // be really paranoid now
-                rowNum = c.getPosition();
-            } catch (RuntimeException e2 ) {
-                rowNum = -1;
-            }
-            Log.w(TAG, "Search Suggestions cursor at row " + rowNum + 
-                            " returned exception" + e.toString());
-        }
-    }
-
-    SearchAutoCompleteTextView getSearchInputField() {
-        return (SearchAutoCompleteTextView) mSearchText;
-    }
-
-    /**
-     * This class provides the filtering-based interface to suggestions providers.
-     * It is hardwired in a couple of places to support GoogleSearch - for example, it supports
-     * two-line suggestions, but it does not support icons.
-     */
-    private static class SuggestionsAdapter extends SimpleCursorAdapter {
-        public final static String[] TWO_LINE_FROM =    {SearchManager.SUGGEST_COLUMN_TEXT_1,
-                                                         SearchManager.SUGGEST_COLUMN_TEXT_2 };
-        public final static int[] TWO_LINE_TO =         {com.android.internal.R.id.text1, 
-                                                         com.android.internal.R.id.text2};
-        
-        private final String TAG = "SuggestionsAdapter";
-        
-        Filter mFilter;
-        SearchableInfo mSearchable;
-        private Resources mProviderResources;
-        String[] mFromStrings;
-
-        public SuggestionsAdapter(Context context, int layout, Cursor c,
-                String[] from, int[] to, SearchableInfo searchable) {
-            super(context, layout, c, from, to);
-            mFromStrings = from;
-            mSearchable = searchable;
-            
-            // set up provider resources (gives us icons, etc.)
-            Context activityContext = mSearchable.getActivityContext(mContext);
-            Context providerContext = mSearchable.getProviderContext(mContext, activityContext);
-            mProviderResources = providerContext.getResources();
-        }
-        
-        /**
-         * Use the search suggestions provider to obtain a live cursor.  This will be called
-         * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
-         * The results will be processed in the UI thread and changeCursor() will be called.
-         */
+    private class ToParentOriginAnimation extends Animation {
         @Override
-        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
-            String query = (constraint == null) ? "" : constraint.toString();
-            return getSuggestions(mSearchable, query);
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            float dx = -getLeft() * interpolatedTime;
+            float dy = -getTop() * interpolatedTime;
+            t.getMatrix().setTranslate(dx, dy);
         }
-        
-        /**
-         * Overriding this allows us to write the selected query back into the box.
-         * NOTE:  This is a vastly simplified version of SearchDialog.jamQuery() and does
-         * not universally support the search API.  But it is sufficient for Google Search.
-         */
-        @Override
-        public CharSequence convertToString(Cursor cursor) {
-            CharSequence result = null;
-            if (cursor != null) {
-                int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
-                if (column >= 0) {
-                    final String query = cursor.getString(column);
-                    if (query != null) {
-                        result = query;
-                    }
-                }
-            }
-            return result;
-        }
-
-        /**
-         * Get the query cursor for the search suggestions.
-         * 
-         * TODO this is functionally identical to the version in SearchDialog.java.  Perhaps it 
-         * could be hoisted into SearchableInfo or some other shared spot.
-         * 
-         * @param query The search text entered (so far)
-         * @return Returns a cursor with suggestions, or null if no suggestions 
-         */
-        private Cursor getSuggestions(final SearchableInfo searchable, final String query) {
-            Cursor cursor = null;
-            if (searchable.getSuggestAuthority() != null) {
-                try {
-                    StringBuilder uriStr = new StringBuilder("content://");
-                    uriStr.append(searchable.getSuggestAuthority());
-
-                    // if content path provided, insert it now
-                    final String contentPath = searchable.getSuggestPath();
-                    if (contentPath != null) {
-                        uriStr.append('/');
-                        uriStr.append(contentPath);
-                    }
-
-                    // append standard suggestion query path 
-                    uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY);
-
-                    // inject query, either as selection args or inline
-                    String[] selArgs = null;
-                    if (searchable.getSuggestSelection() != null) {    // use selection if provided
-                        selArgs = new String[] {query};
-                    } else {
-                        uriStr.append('/');                             // no sel, use REST pattern
-                        uriStr.append(Uri.encode(query));
-                    }
-
-                    // finally, make the query
-                    cursor = mContext.getContentResolver().query(
-                                                        Uri.parse(uriStr.toString()), null, 
-                                                        searchable.getSuggestSelection(), selArgs,
-                                                        null);
-                } catch (RuntimeException e) {
-                    Log.w(TAG, "Search Suggestions query returned exception " + e.toString());
-                    cursor = null;
-                }
-            }
-            
-            return cursor;
-        }
-
-        /**
-         * Overriding this allows us to affect the way that an icon is loaded.  Specifically,
-         * we can be more controlling about the resource path (and allow icons to come from other
-         * packages).
-         * 
-         * TODO: This is 100% identical to the version in SearchDialog.java
-         *
-         * @param v ImageView to receive an image
-         * @param value the value retrieved from the cursor
-         */
-        @Override
-        public void setViewImage(ImageView v, String value) {
-            int resID;
-            Drawable img = null;
-
-            try {
-                resID = Integer.parseInt(value);
-                if (resID != 0) {
-                    img = mProviderResources.getDrawable(resID);
-                }
-            } catch (NumberFormatException nfe) {
-                // img = null;
-            } catch (NotFoundException e2) {
-                // img = null;
-            }
-            
-            // finally, set the image to whatever we've gotten
-            v.setImageDrawable(img);
-        }
-        
-        /**
-         * This method is overridden purely to provide a bit of protection against
-         * flaky content providers.
-         * 
-         * TODO: This is 100% identical to the version in SearchDialog.java
-         * 
-         * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
-         */
-        @Override 
-        public View getView(int position, View convertView, ViewGroup parent) {
-            try {
-                return super.getView(position, convertView, parent);
-            } catch (RuntimeException e) {
-                Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString());
-                // what can I return here?
-                View v = newView(mContext, mCursor, parent);
-                if (v != null) {
-                    TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1);
-                    tv.setText(e.toString());
-                }
-                return v;
-            }
-        }
-
     }
+
+    /** 
+     * Moves the view from the top left corner of its parent.
+     */
+    private class FromParentOriginAnimation extends Animation {
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            float dx = -getLeft() * (1.0f - interpolatedTime);
+            float dy = -getTop() * (1.0f - interpolatedTime);
+            t.getMatrix().setTranslate(dx, dy);
+        }
+    }
+
 }
diff --git a/src/com/android/launcher/Workspace.java b/src/com/android/launcher/Workspace.java
index 359767a..c76fb7e 100644
--- a/src/com/android/launcher/Workspace.java
+++ b/src/com/android/launcher/Workspace.java
@@ -1027,7 +1027,7 @@
         }
         return result;
     }
-    
+
     /**
      * Find a search widget on the given screen
      */
@@ -1041,102 +1041,14 @@
         }
         return null;
     }
-    
+
     /**
-     * Focuses on the search widget on the specified screen,
-     * if there is one.  Also clears the current search selection so we don't 
+     * Gets the first search widget on the current screen, if there is one.
+     * Returns <code>null</code> otherwise.
      */
-    private boolean focusOnSearch(int screen) {
-        CellLayout currentScreen = (CellLayout) getChildAt(screen);
-        final Search searchWidget = findSearchWidget(currentScreen);
-        if (searchWidget != null) {
-            // This is necessary when focus on search is requested from the menu
-            // If the workspace was not in touch mode before the menu is invoked
-            // and the user clicks "Search" by touching the menu item, the following
-            // happens:
-            //
-            // - We request focus from touch on the search widget
-            // - The search widget gains focus
-            // - The window focus comes back to Home's window
-            // - The touch mode change is propagated to Home's window
-            // - The search widget is not focusable in touch mode and ViewRoot
-            //   clears its focus
-            //
-            // Forcing focusable in touch mode ensures the search widget will
-            // keep the focus no matter what happens.
-            //
-            // Note: the search input field disables focusable in touch mode
-            // after the window gets the focus back, see SearchAutoCompleteTextView
-            final SearchAutoCompleteTextView input = searchWidget.getSearchInputField();
-            input.setFocusableInTouchMode(true);
-            input.showKeyboardOnNextFocus();
-
-            if (isInTouchMode()) {
-                searchWidget.requestFocusFromTouch();
-            } else {
-                searchWidget.requestFocus();
-            }
-            searchWidget.clearQuery();
-            return true;
-        }
-        return false;
-    }
-    
-    /**
-     * Snap to the nearest screen with a search widget and give it focus
-     * 
-     * @return True if a search widget was found
-     */
-    public boolean snapToSearch() {
-        // The screen we are searching
-        int current = mCurrentScreen;
-        
-        // first position scanned so far
-        int first = current;
-
-        // last position scanned so far
-        int last = current;
-
-        // True if we should move down on the next iteration
-        boolean next = false;
-
-        // True when we have looked at the first item in the data
-        boolean hitFirst;
-
-        // True when we have looked at the last item in the data
-        boolean hitLast;
-        
-        final int count = getChildCount();
-
-        while (true) {
-            if (focusOnSearch(current)) {
-                return true;
-            }
-
-            hitLast = last == count - 1;
-            hitFirst = first == 0;
-
-            if (hitLast && hitFirst) {
-                // Looked at everything
-                break;
-            }
-
-            if (hitFirst || (next && !hitLast)) {
-                // Either we hit the top, or we are trying to move down
-                last++;
-                current = last;
-                // Try going up next time
-                next = false;
-            } else {
-                // Either we hit the bottom, or we are trying to move up
-                first--;
-                current = first;
-                // Try going down next time
-                next = true;
-            }
-
-        }
-        return false;
+    public Search findSearchWidgetOnCurrentScreen() {
+        CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
+        return findSearchWidget(currentScreen);
     }
 
     public Folder getFolderForTag(Object tag) {