Initial Contribution
diff --git a/src/com/android/launcher/Search.java b/src/com/android/launcher/Search.java
new file mode 100644
index 0000000..69e26ac
--- /dev/null
+++ b/src/com/android/launcher/Search.java
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2008 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.launcher;
+
+import android.app.ISearchManager;
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+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.Button;
+import android.widget.CursorAdapter;
+import android.widget.Filter;
+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 {
+
+    private final String TAG = "SearchGadget";
+
+    private AutoCompleteTextView mSearchText;
+    private Button mGoButton;
+    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;
+
+    /**
+     * Used to inflate the Workspace from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attribtues set containing the Workspace's customization values.
+     */
+    public Search(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+    
+    /**
+     * Implements OnClickListener (for button)
+     */
+    public void onClick(View v) {
+        query();
+    }
+
+    private void query() {
+        String query = mSearchText.getText().toString();
+        if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
+            return;
+        }
+        sendLaunchIntent(Intent.ACTION_SEARCH, null, query, null, 0, null, mSearchable);
+    }
+    
+    /**
+     * 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);
+    }
+    
+    /**
+     * 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) {
+            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)
+     */
+    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) {
+        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);
+    }
+
+    @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 = (Button) findViewById(R.id.go);
+        mGoButton.setOnClickListener(this);
+        mGoButton.setOnKeyListener(this);
+        
+        mSearchText.setOnLongClickListener(this);
+        mGoButton.setOnLongClickListener(this);
+        
+        // disable the button since we start out w/empty input
+        mGoButton.setEnabled(false);
+        mGoButton.setFocusable(false);
+        
+        configureSuggestions();
+    }
+    
+    /** The rest of the class deals with providing search suggestions */
+    
+    /**
+     * Set up the suggestions provider mechanism
+     */
+    private void configureSuggestions() {
+        // get SearchableInfo
+        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;
+        
+        mSearchText.setOnItemClickListener(this);
+        mSearchText.setOnItemSelectedListener(this);
+        
+        // attach the suggestions adapter
+        mSuggestionsAdapter = new SuggestionsAdapter(mContext, 
+                com.android.internal.R.layout.search_dropdown_item_1line, null,
+                SuggestionsAdapter.ONE_LINE_FROM, SuggestionsAdapter.ONE_LINE_TO, mSearchable);
+        mSearchText.setAdapter(mSuggestionsAdapter);
+    }
+    
+    /**
+     * Implements OnItemClickListener
+     */
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+//      Log.d(TAG, "onItemClick() position " + position);
+        launchSuggestion(mSuggestionsAdapter, position);
+    }
+    
+    /** 
+     * Implements OnItemSelectedListener
+     */
+     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);
+                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());
+        }
+    }
+        
+    /**
+     * This class provides the filtering-based interface to suggestions providers.
+     */
+    private static class SuggestionsAdapter extends SimpleCursorAdapter {
+        public final static String[] ONE_LINE_FROM =   { SearchManager.SUGGEST_COLUMN_TEXT_1 };
+        public final static int[] ONE_LINE_TO =        { com.android.internal.R.id.text1 };
+        
+        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.
+         */
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String query = (constraint == null) ? "" : constraint.toString();
+            return getSuggestions(mSearchable, query);
+        }
+        
+        /**
+         * 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;
+            }
+        }
+
+    }
+}