Merge change 534 into donut

* changes:
  Make sure to map undefined OES functions to their non-OES counterpart if it exists.
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index a0cdb63..3d89ad7 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -16,24 +16,24 @@
 
 package android.app;
 
+import static android.app.SuggestionsAdapter.getColumnString;
+
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 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.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.server.search.SearchableInfo;
 import android.speech.RecognizerIntent;
@@ -45,7 +45,10 @@
 import android.util.Log;
 import android.view.Gravity;
 import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
@@ -53,17 +56,14 @@
 import android.widget.AdapterView;
 import android.widget.AutoCompleteTextView;
 import android.widget.Button;
-import android.widget.CursorAdapter;
 import android.widget.ImageButton;
-import android.widget.ImageView;
 import android.widget.ListView;
-import android.widget.SimpleCursorAdapter;
 import android.widget.TextView;
-import android.widget.WrapperListAdapter;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.AdapterView.OnItemSelectedListener;
 
-import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.WeakHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
@@ -75,59 +75,66 @@
 public class SearchDialog extends Dialog implements OnItemClickListener, OnItemSelectedListener {
 
     // Debugging support
-    final static String LOG_TAG = "SearchDialog";
-    private static final int DBG_LOG_TIMING = 0;
-    final static int DBG_JAM_THREADING = 0;
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "SearchDialog";
+    private static final boolean DBG_LOG_TIMING = false;
 
-    // interaction with runtime
-    IntentFilter mCloseDialogsFilter;
-    IntentFilter mPackageFilter;
-    
     private static final String INSTANCE_KEY_COMPONENT = "comp";
     private static final String INSTANCE_KEY_APPDATA = "data";
     private static final String INSTANCE_KEY_GLOBALSEARCH = "glob";
     private static final String INSTANCE_KEY_DISPLAY_QUERY = "dQry";
     private static final String INSTANCE_KEY_DISPLAY_SEL_START = "sel1";
     private static final String INSTANCE_KEY_DISPLAY_SEL_END = "sel2";
-    private static final String INSTANCE_KEY_USER_QUERY = "uQry";
-    private static final String INSTANCE_KEY_SUGGESTION_QUERY = "sQry";
     private static final String INSTANCE_KEY_SELECTED_ELEMENT = "slEl";
     private static final int INSTANCE_SELECTED_BUTTON = -2;
     private static final int INSTANCE_SELECTED_QUERY = -1;
-
+    
+    // interaction with runtime
+    private IntentFilter mCloseDialogsFilter;
+    private IntentFilter mPackageFilter;
+    
     // views & widgets
     private TextView mBadgeLabel;
-    private AutoCompleteTextView mSearchTextField;
+    private SearchAutoComplete mSearchAutoComplete;
     private Button mGoButton;
     private ImageButton mVoiceButton;
+    private View mSearchPlate;
 
     // interaction with searchable application
+    private SearchableInfo mSearchable;
     private ComponentName mLaunchComponent;
     private Bundle mAppSearchData;
     private boolean mGlobalSearchMode;
     private Context mActivityContext;
+    
+    // stack of previous searchables, to support the BACK key after
+    // SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.
+    // The top of the stack (= previous searchable) is the last element of the list,
+    // since adding and removing is efficient at the end of an ArrayList.
+    private ArrayList<ComponentName> mPreviousComponents;
 
-    // interaction with the search manager service
-    private SearchableInfo mSearchable;
-    
-    // support for suggestions 
-    private String mUserQuery = null;
-    private int mUserQuerySelStart;
-    private int mUserQuerySelEnd;
-    private boolean mLeaveJammedQueryOnRefocus = false;
-    private String mPreviousSuggestionQuery = null;
-    private int mPresetSelection = -1;
-    private String mSuggestionAction = null;
-    private Uri mSuggestionData = null;
-    private String mSuggestionQuery = null;
-    
     // For voice searching
     private Intent mVoiceWebSearchIntent;
     private Intent mVoiceAppSearchIntent;
 
     // support for AutoCompleteTextView suggestions display
     private SuggestionsAdapter mSuggestionsAdapter;
-
+    
+    // Whether to rewrite queries when selecting suggestions
+    // TODO: This is disabled because of problems with persistent selections
+    // causing non-user-initiated rewrites.
+    private static final boolean REWRITE_QUERIES = false;
+    
+    // The query entered by the user. This is not changed when selecting a suggestion
+    // that modifies the contents of the text field. But if the user then edits
+    // the suggestion, the resulting string is saved.
+    private String mUserQuery;
+    
+    // A weak map of drawables we've gotten from other packages, so we don't load them
+    // more than once.
+    private final WeakHashMap<String, Drawable> mOutsideDrawablesCache =
+            new WeakHashMap<String, Drawable>();
+    
     /**
      * Constructor - fires it up and makes it look like the search UI.
      * 
@@ -153,25 +160,29 @@
         theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT);
         WindowManager.LayoutParams lp = theWindow.getAttributes();
-        lp.setTitle("Search Dialog");
         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
         theWindow.setAttributes(lp);
 
         // get the view elements for local access
         mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
-        mSearchTextField = (AutoCompleteTextView) 
+        mSearchAutoComplete = (SearchAutoComplete)
                 findViewById(com.android.internal.R.id.search_src_text);
         mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
         mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
+        mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
         
         // attach listeners
-        mSearchTextField.addTextChangedListener(mTextWatcher);
-        mSearchTextField.setOnKeyListener(mTextKeyListener);
+        mSearchAutoComplete.addTextChangedListener(mTextWatcher);
+        mSearchAutoComplete.setOnKeyListener(mTextKeyListener);
+        mSearchAutoComplete.setOnItemClickListener(this);
+        mSearchAutoComplete.setOnItemSelectedListener(this);
         mGoButton.setOnClickListener(mGoButtonClickListener);
         mGoButton.setOnKeyListener(mButtonsKeyListener);
         mVoiceButton.setOnClickListener(mVoiceButtonClickListener);
         mVoiceButton.setOnKeyListener(mButtonsKeyListener);
 
+        mSearchAutoComplete.setSearchDialog(this);
+        
         // pre-hide all the extraneous elements
         mBadgeLabel.setVisibility(View.GONE);
 
@@ -199,7 +210,7 @@
     /**
      * Set up the search dialog
      * 
-     * @param Returns true if search dialog launched, false if not
+     * @return true if search dialog launched, false if not
      */
     public boolean show(String initialQuery, boolean selectInitialQuery,
             ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
@@ -208,75 +219,65 @@
             // in this case, just discard the "show" request
             return true;
         }
-
-        // Get searchable info from search manager and use to set up other elements of UI
-        // Do this first so we can get out quickly if there's nothing to search
-        ISearchManager sms;
-        sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
-        try {
-            mSearchable = sms.getSearchableInfo(componentName, globalSearch);
-        } catch (RemoteException e) {
-            mSearchable = null;
+        
+        // set up the searchable and show the dialog
+        if (!show(componentName, appSearchData, globalSearch)) {
+            return false;
         }
+        
+        // finally, load the user's initial text (which may trigger suggestions)
+        setUserQuery(initialQuery);
+        if (selectInitialQuery) {
+            mSearchAutoComplete.selectAll();
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Sets up the search dialog and shows it.
+     * 
+     * @return <code>true</code> if search dialog launched
+     */
+    private boolean show(ComponentName componentName, Bundle appSearchData, 
+            boolean globalSearch) {
+        
+        if (DBG) { 
+            Log.d(LOG_TAG, "show(" + componentName + ", " 
+                    + appSearchData + ", " + globalSearch + ")");
+        }
+        
+        mSearchable = SearchManager.getSearchableInfo(componentName, globalSearch);
         if (mSearchable == null) {
             // unfortunately, we can't log here.  it would be logspam every time the user
             // clicks the "search" key on a non-search app
             return false;
         }
         
-        // OK, we're going to show ourselves
-        super.show();
-
-        setupSearchableInfo();
-        
         mLaunchComponent = componentName;
         mAppSearchData = appSearchData;
-        mGlobalSearchMode = globalSearch;
+        // Using globalSearch here is just an optimization, just calling
+        // isDefaultSearchable() should always give the same result.
+        mGlobalSearchMode = globalSearch || SearchManager.isDefaultSearchable(mSearchable); 
+        mActivityContext = mSearchable.getActivityContext(getContext());
+        
+        // show the dialog. this will call onStart().
+        if (!isShowing()) {
+            show();
+        }
 
+        updateUI();
+        
+        return true;
+    }
+    
+    @Override
+    protected void onStart() {
+        super.onStart();
+        
         // receive broadcasts
         getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter);
         getContext().registerReceiver(mBroadcastReceiver, mPackageFilter);
-        
-        // configure the autocomplete aspects of the input box
-        mSearchTextField.setOnItemClickListener(this);
-        mSearchTextField.setOnItemSelectedListener(this);
-
-        // This conversion is necessary to force a preload of the EditText and thus force
-        // suggestions to be presented (even for an empty query)
-        if (initialQuery == null) {
-            initialQuery = "";     // This forces the preload to happen, triggering suggestions
-        }
-
-        // attach the suggestions adapter, if suggestions are available
-        // The existence of a suggestions authority is the proxy for "suggestions available here"
-        if (mSearchable.getSuggestAuthority() == null) {
-            mSuggestionsAdapter = null;
-            mSearchTextField.setAdapter(mSuggestionsAdapter);
-            mSearchTextField.setText(initialQuery);
-        } else {
-            mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable, 
-                    mSearchTextField);
-            mSearchTextField.setAdapter(mSuggestionsAdapter);
-
-            // finally, load the user's initial text (which may trigger suggestions)
-            mSuggestionsAdapter.setNonUserQuery(false);
-            mSearchTextField.setText(initialQuery);
-        }
-        
-        if (selectInitialQuery) {
-            mSearchTextField.selectAll();
-        } else {
-            mSearchTextField.setSelection(initialQuery.length());
-        }
-        return true;
-    }
-
-    /**
-     * The default show() for this Dialog is not supported.
-     */
-    @Override
-    public void show() {
-        return;
     }
 
     /**
@@ -289,6 +290,8 @@
     public void onStop() {
         super.onStop();
         
+        // TODO: Removing the listeners means that they never get called, since 
+        // Dialog.dismissDialog() calls onStop() before sendDismissMessage().
         setOnCancelListener(null);
         setOnDismissListener(null);
         
@@ -299,26 +302,36 @@
             // This is OK - it just means we didn't have any registered
         }
         
-        // close any leftover cursor
-        if (mSuggestionsAdapter != null) {
-            mSuggestionsAdapter.changeCursor(null);
-        }
+        closeSuggestionsAdapter();
         
         // dump extra memory we're hanging on to
         mLaunchComponent = null;
         mAppSearchData = null;
         mSearchable = null;
-        mSuggestionAction = null;
-        mSuggestionData = null;
-        mSuggestionQuery = null;
         mActivityContext = null;
-        mPreviousSuggestionQuery = null;
         mUserQuery = null;
+        mPreviousComponents = null;
+    }
+    
+    /**
+     * Closes and gets rid of the suggestions adapter.
+     */
+    private void closeSuggestionsAdapter() {
+        // remove the adapter from the autocomplete first, to avoid any updates
+        // when we drop the cursor
+        mSearchAutoComplete.setAdapter((SuggestionsAdapter)null);
+        // close any leftover cursor
+        if (mSuggestionsAdapter != null) {
+            mSuggestionsAdapter.changeCursor(null);
+        }
+        mSuggestionsAdapter = null;
     }
     
     /**
      * Save the minimal set of data necessary to recreate the search
      * 
+     * TODO: go through this and make sure that it saves everything that is needed
+     * 
      * @return A bundle with the state of the dialog.
      */
     @Override
@@ -331,16 +344,14 @@
         bundle.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode);
         
         // UI state
-        bundle.putString(INSTANCE_KEY_DISPLAY_QUERY, mSearchTextField.getText().toString());
-        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_START, mSearchTextField.getSelectionStart());
-        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_END, mSearchTextField.getSelectionEnd());
-        bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
-        bundle.putString(INSTANCE_KEY_SUGGESTION_QUERY, mPreviousSuggestionQuery);
+        bundle.putString(INSTANCE_KEY_DISPLAY_QUERY, mSearchAutoComplete.getText().toString());
+        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_START, mSearchAutoComplete.getSelectionStart());
+        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_END, mSearchAutoComplete.getSelectionEnd());
         
         int selectedElement = INSTANCE_SELECTED_QUERY;
         if (mGoButton.isFocused()) {
             selectedElement = INSTANCE_SELECTED_BUTTON;
-        } else if (mSearchTextField.isPopupShowing()) {
+        } else if (mSearchAutoComplete.isPopupShowing()) {
             selectedElement = 0; // TODO mSearchTextField.getListSelection()    // 0..n
         }
         bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement);
@@ -350,6 +361,8 @@
 
     /**
      * Restore the state of the dialog from a previously saved bundle.
+     * 
+     * TODO: go through this and make sure that it saves everything that is saved
      *
      * @param savedInstanceState The state of the dialog previously saved by
      *     {@link #onSaveInstanceState()}.
@@ -365,26 +378,17 @@
         String displayQuery = savedInstanceState.getString(INSTANCE_KEY_DISPLAY_QUERY);
         int querySelStart = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_START, -1);
         int querySelEnd = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_END, -1);
-        String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
         int selectedElement = savedInstanceState.getInt(INSTANCE_KEY_SELECTED_ELEMENT);
-        String suggestionQuery = savedInstanceState.getString(INSTANCE_KEY_SUGGESTION_QUERY);
         
         // show the dialog.  skip any show/hide animation, we want to go fast.
         // send the text that actually generates the suggestions here;  we'll replace the display
         // text as necessary in a moment.
-        if (!show(suggestionQuery, false, launchComponent, appSearchData, globalSearch)) {
+        if (!show(displayQuery, false, launchComponent, appSearchData, globalSearch)) {
             // for some reason, we couldn't re-instantiate
             return;
         }
         
-        if (mSuggestionsAdapter != null) {
-            mSuggestionsAdapter.setNonUserQuery(true);
-        }
-        mSearchTextField.setText(displayQuery);
-        // TODO because the new query is (not) processed in another thread, we can't just
-        // take away this flag (yet).  The better solution here is going to require a new API
-        // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions.
-//      mSuggestionsAdapter.setNonUserQuery(false);
+        mSearchAutoComplete.setText(displayQuery);
         
         // clean up the selection state
         switch (selectedElement) {
@@ -395,21 +399,19 @@
             break;
         case INSTANCE_SELECTED_QUERY:
             if (querySelStart >= 0 && querySelEnd >= 0) {
-                mSearchTextField.requestFocus();
-                mSearchTextField.setSelection(querySelStart, querySelEnd);
+                mSearchAutoComplete.requestFocus();
+                mSearchAutoComplete.setSelection(querySelStart, querySelEnd);
             }
             break;
         default:
-            // defer selecting a list element until suggestion list appears
-            mPresetSelection = selectedElement;
-            // TODO mSearchTextField.setListSelection(selectedElement)
+            // TODO: defer selecting a list element until suggestion list appears
+//            mSearchAutoComplete.setListSelection(selectedElement)
             break;
         }
     }
     
     /**
-     * Hook for updating layout on a rotation
-     * 
+     * Called after resources have changed, e.g. after screen rotation or locale change.
      */
     public void onConfigurationChanged(Configuration newConfig) {
         if (isShowing()) {
@@ -419,15 +421,13 @@
             updateQueryHint();
         } 
     }
-
+    
     /**
-     * Use SearchableInfo record (from search manager service) to preconfigure the UI in various
-     * ways.
+     * Update the UI according to the info in the current value of {@link #mSearchable}.
      */
-    private void setupSearchableInfo() {
+    private void updateUI() {
         if (mSearchable != null) {
-            mActivityContext = mSearchable.getActivityContext(getContext());
-            
+            updateSearchAutoComplete();
             updateSearchButton();
             updateSearchBadge();
             updateQueryHint();
@@ -449,24 +449,38 @@
                     inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
                 }
             }
-            mSearchTextField.setInputType(inputType);
-            mSearchTextField.setImeOptions(mSearchable.getImeOptions());
+            mSearchAutoComplete.setInputType(inputType);
+            mSearchAutoComplete.setImeOptions(mSearchable.getImeOptions());
+        }
+    }
+    
+    /**
+     * Updates the auto-complete text view.
+     */
+    private void updateSearchAutoComplete() {
+        // close any existing suggestions adapter
+        closeSuggestionsAdapter();
+        
+        mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
+        mSearchAutoComplete.setThreshold(0);  // always allow zero-query suggestions
+
+        if (mGlobalSearchMode) {
+            mSearchAutoComplete.setDropDownAlwaysVisible(true);  // fill space until results come in
+            mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
+        } else {
+            mSearchAutoComplete.setDropDownAlwaysVisible(false);
+            mSearchAutoComplete.setDropDownDismissedOnCompletion(true);
+        }
+
+        // attach the suggestions adapter, if suggestions are available
+        // The existence of a suggestions authority is the proxy for "suggestions available here"
+        if (mSearchable.getSuggestAuthority() != null) {
+            mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable, 
+                    mOutsideDrawablesCache);
+            mSearchAutoComplete.setAdapter(mSuggestionsAdapter);
         }
     }
 
-    /**
-     * The list of installed packages has just changed.  This means that our current context
-     * may no longer be valid.  This would only happen if a package is installed/removed exactly
-     * when the search bar is open.  So for now we're just going to close the search
-     * bar.  
-     * 
-     * Anything fancier would require some checks to see if the user's context was still valid.
-     * Which would be messier.
-     */
-    public void onPackageListChange() {
-        cancel();
-    }
-    
     /**    
      * Update the text in the search button.  Note: This is deprecated functionality, for 
      * 1.0 compatibility only.
@@ -481,26 +495,40 @@
             iconLabel = getContext().getResources().
                     getDrawable(com.android.internal.R.drawable.ic_btn_search);
         }
-        mGoButton.setText(textLabel);  
+        mGoButton.setText(textLabel);
         mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null);
     }
     
     /**
-     * Setup the search "Badge" if request by mode flags.
+     * Setup the search "Badge" if requested by mode flags.
      */
     private void updateSearchBadge() {
         // assume both hidden
         int visibility = View.GONE;
         Drawable icon = null;
-        String text = null;
+        CharSequence text = null;
         
         // optionally show one or the other.
         if (mSearchable.mBadgeIcon) {
             icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId());
             visibility = View.VISIBLE;
+            if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId());
         } else if (mSearchable.mBadgeLabel) {
             text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
             visibility = View.VISIBLE;
+            if (DBG) Log.d(LOG_TAG, "Using badge label: " + mSearchable.getLabelId());
+        } else if (!mGlobalSearchMode) {
+            // Get the localized name of the application which we are doing search in.
+            try {
+                PackageManager pm = getContext().getPackageManager();
+                ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
+                text = pm.getApplicationLabel(info.applicationInfo);
+                visibility = View.VISIBLE;
+                if (DBG) Log.d(LOG_TAG, "Using application label: " + text);
+            } catch (NameNotFoundException e) {
+                // app not found, fine, don't use its name for the label
+                Log.w(LOG_TAG, mLaunchComponent + " not found.");
+            }
         }
         
         mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
@@ -520,7 +548,7 @@
                     hint = mActivityContext.getString(hintId);
                 }
             }
-            mSearchTextField.setHint(hint);
+            mSearchAutoComplete.setHint(hint);
         }
     }
 
@@ -548,63 +576,129 @@
         mVoiceButton.setVisibility(visibility);
     }
     
+    /*
+     * Menu.
+     */
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Show search settings menu item if anyone handles the intent for it
+        Intent settingsIntent = new Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS);
+        PackageManager pm = getContext().getPackageManager();
+        ActivityInfo activityInfo = settingsIntent.resolveActivityInfo(pm, 0);
+        if (activityInfo != null) {
+            settingsIntent.setClassName(activityInfo.applicationInfo.packageName,
+                    activityInfo.name);
+            String label = getActivityLabel(activityInfo);
+            menu.add(Menu.NONE, Menu.NONE, Menu.NONE, label)
+                    .setIcon(android.R.drawable.ic_menu_preferences)
+                    .setAlphabeticShortcut('P')
+                    .setIntent(settingsIntent);
+            return true;
+        }
+        return super.onCreateOptionsMenu(menu);
+    }
+    
+    // TODO: shouldn't this be in PackageManager?
+    private String getActivityLabel(ActivityInfo activityInfo) {
+        PackageManager pm = getContext().getPackageManager();
+        try {
+            int labelRes = activityInfo.labelRes;
+            if (labelRes == 0) {
+                return null;
+            }
+            Resources r = pm.getResourcesForApplication(activityInfo.applicationInfo);
+            return r.getString(labelRes);
+        } catch (NameNotFoundException ex) {
+            return null;
+        }
+    }
+    
     /**
      * Listeners of various types
      */
 
     /**
+     * {@link Dialog#onTouchEvent(MotionEvent)} will cancel the dialog only when the
+     * touch is outside the window. But the window includes space for the drop-down,
+     * so we also cancel on taps outside the search bar when the drop-down is not showing.
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        // cancel if the drop-down is not showing and the touch event was outside the search plate
+        if (!mSearchAutoComplete.isPopupShowing() && isOutOfBounds(mSearchPlate, event)) {
+            if (DBG) Log.d(LOG_TAG, "Pop-up not showing and outside of search plate.");
+            cancel();
+            return true;
+        }
+        // Let Dialog handle events outside the window while the pop-up is showing.
+        return super.onTouchEvent(event);
+    }
+    
+    private boolean isOutOfBounds(View v, MotionEvent event) {
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
+        return (x < -slop) || (y < -slop)
+                || (x > (v.getWidth()+slop))
+                || (y > (v.getHeight()+slop));
+    }
+    
+    /**
      * Dialog's OnKeyListener implements various search-specific functionality
      *
      * @param keyCode This is the keycode of the typed key, and is the same value as
-     * found in the KeyEvent parameter.
+     *        found in the KeyEvent parameter.
      * @param event The complete event record for the typed key
      *
      * @return Return true if the event was handled here, or false if not.
      */
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-        case KeyEvent.KEYCODE_BACK:
-            cancel();
+        if (DBG) Log.d(LOG_TAG, "onKeyDown(" + keyCode + "," + event + ")");
+        
+        // handle back key to go back to previous searchable, etc.
+        if (handleBackKey(keyCode, event)) {
             return true;
-        case KeyEvent.KEYCODE_SEARCH:
-            if (TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0) {
-                launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
+        }
+        
+        // search or cancel on search key
+        if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+            if (!mSearchAutoComplete.isEmpty()) {
+                launchQuerySearch();
             } else {
                 cancel();
             }
             return true;
-        default:
-            SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
-            if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) {
-                launchQuerySearch(keyCode, actionKey.mQueryActionMsg);
-                return true;
-            }
-            break;
         }
+
+        // if it's an action specified by the searchable activity, launch the
+        // entered query with the action key
+        SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+        if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) {
+            launchQuerySearch(keyCode, actionKey.mQueryActionMsg);
+            return true;
+        }
+        
         return false;
     }
-
+    
     /**
      * Callback to watch the textedit field for empty/non-empty
      */
     private TextWatcher mTextWatcher = new TextWatcher() {
 
-        public void beforeTextChanged(CharSequence s, int start, int
-                before, int after) { }
+        public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
 
         public void onTextChanged(CharSequence s, int start,
                 int before, int after) {
-            if (DBG_LOG_TIMING == 1) {
+            if (DBG_LOG_TIMING) {
                 dbgLogTiming("onTextChanged()");
             }
             updateWidgetState();
-            // Only do suggestions if actually typed by user
-            if ((mSuggestionsAdapter != null) && !mSuggestionsAdapter.getNonUserQuery()) {
-                mPreviousSuggestionQuery = s.toString();
-                mUserQuery = mSearchTextField.getText().toString();
-                mUserQuerySelStart = mSearchTextField.getSelectionStart();
-                mUserQuerySelEnd = mSearchTextField.getSelectionEnd();
+            if (!mSearchAutoComplete.isPerformingCompletion()) {
+                // The user changed the query, remember it.
+                mUserQuery = s == null ? "" : s.toString();
             }
         }
 
@@ -616,64 +710,34 @@
      */
     private void updateWidgetState() {
         // enable the button if we have one or more non-space characters
-        boolean enabled =
-            TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0;
-
+        boolean enabled = !mSearchAutoComplete.isEmpty();
         mGoButton.setEnabled(enabled);
         mGoButton.setFocusable(enabled);
     }
 
-    private final static String[] ONE_LINE_FROM =       {SearchManager.SUGGEST_COLUMN_TEXT_1 };
-    private final static String[] ONE_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1,
-                                                         SearchManager.SUGGEST_COLUMN_ICON_1,
-                                                         SearchManager.SUGGEST_COLUMN_ICON_2};
-    private final static String[] TWO_LINE_FROM =       {SearchManager.SUGGEST_COLUMN_TEXT_1,
-                                                         SearchManager.SUGGEST_COLUMN_TEXT_2 };
-    private final static String[] TWO_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1,
-                                                         SearchManager.SUGGEST_COLUMN_TEXT_2,
-                                                         SearchManager.SUGGEST_COLUMN_ICON_1,
-                                                         SearchManager.SUGGEST_COLUMN_ICON_2 };
-    
-    private final static int[] ONE_LINE_TO =       {com.android.internal.R.id.text1};
-    private final static int[] ONE_LINE_ICONS_TO = {com.android.internal.R.id.text1,
-                                                    com.android.internal.R.id.icon1, 
-                                                    com.android.internal.R.id.icon2};
-    private final static int[] TWO_LINE_TO =       {com.android.internal.R.id.text1, 
-                                                    com.android.internal.R.id.text2};
-    private final static int[] TWO_LINE_ICONS_TO = {com.android.internal.R.id.text1, 
-                                                    com.android.internal.R.id.text2,
-                                                    com.android.internal.R.id.icon1, 
-                                                    com.android.internal.R.id.icon2};
-    
-    /**
-     * Safely retrieve the suggestions cursor adapter from the ListView
-     * 
-     * @param adapterView The ListView containing our adapter
-     * @result The CursorAdapter that we installed, or null if not set
-     */
-    private static CursorAdapter getSuggestionsAdapter(AdapterView<?> adapterView) {
-        CursorAdapter result = null;
-        if (adapterView != null) {
-            Object ad = adapterView.getAdapter();
-            if (ad instanceof CursorAdapter) {
-                result = (CursorAdapter) ad;
-            } else if (ad instanceof WrapperListAdapter) {
-                result = (CursorAdapter) ((WrapperListAdapter)ad).getWrappedAdapter();
-            }
-        }
-        return result;
-    }
-
     /**
      * React to typing in the GO search button by refocusing to EditText. 
      * Continue typing the query.
      */
     View.OnKeyListener mButtonsKeyListener = new View.OnKeyListener() {
         public boolean onKey(View v, int keyCode, KeyEvent event) {
-            // also guard against possible race conditions (late arrival after dismiss)
-            if (mSearchable != null) {
-                return refocusingKeyListener(v, keyCode, event);
+            // guard against possible race conditions
+            if (mSearchable == null) {
+                return 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)) {
+                // restore focus and give key to EditText ...
+                if (mSearchAutoComplete.requestFocus()) {
+                    return mSearchAutoComplete.dispatchKeyEvent(event);
+                }
+            }
+
             return false;
         }
     };
@@ -683,10 +747,11 @@
      */
     View.OnClickListener mGoButtonClickListener = new View.OnClickListener() {
         public void onClick(View v) {
-            // also guard against possible race conditions (late arrival after dismiss)
-            if (mSearchable != null) {
-                launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
+            // guard against possible race conditions
+            if (mSearchable == null) {
+                return;
             }
+            launchQuerySearch();
         }
     };
     
@@ -695,14 +760,16 @@
      */
     View.OnClickListener mVoiceButtonClickListener = new View.OnClickListener() {
         public void onClick(View v) {
+            // guard against possible race conditions
+            if (mSearchable == null) {
+                return;
+            }
             try {
                 if (mSearchable.getVoiceSearchLaunchWebSearch()) {
                     getContext().startActivity(mVoiceWebSearchIntent);
-                    dismiss();
                 } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
                     Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent);
                     getContext().startActivity(appSearchIntent);
-                    dismiss();
                 }
             } catch (ActivityNotFoundException e) {
                 // Should not happen, since we check the availability of
@@ -778,136 +845,56 @@
      */
     View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
         public boolean onKey(View v, int keyCode, KeyEvent event) {
-            if (keyCode == KeyEvent.KEYCODE_BACK) {
-                cancel();
-                return true;
+            // guard against possible race conditions
+            if (mSearchable == null) {
+                return false;
             }
-            // also guard against possible race conditions (late arrival after dismiss)
-            if (mSearchable != null && 
-                    TextUtils.getTrimmedLength(mSearchTextField.getText()) > 0) {
-                if (DBG_LOG_TIMING == 1) {
-                    dbgLogTiming("doTextKey()");
+
+            if (DBG_LOG_TIMING) dbgLogTiming("doTextKey()");
+            if (DBG) { 
+                Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event 
+                        + "), selection: " + mSearchAutoComplete.getListSelection());
+            }
+            
+            // If a suggestion is selected, handle enter, search key, and action keys 
+            // as presses on the selected suggestion
+            if (mSearchAutoComplete.isPopupShowing() && 
+                    mSearchAutoComplete.getListSelection() != ListView.INVALID_POSITION) {
+                return onSuggestionsKey(v, keyCode, event);
+            }
+
+            // If there is text in the query box, handle enter, and action keys
+            // The search key is handled by the dialog's onKeyDown(). 
+            if (!mSearchAutoComplete.isEmpty()) {
+                if (keyCode == KeyEvent.KEYCODE_ENTER 
+                        && event.getAction() == KeyEvent.ACTION_UP) {
+                    v.cancelLongPress();
+                    launchQuerySearch();                    
+                    return true;
                 }
-                // dispatch "typing in the list" first
-                if (mSearchTextField.isPopupShowing() && 
-                        mSearchTextField.getListSelection() != ListView.INVALID_POSITION) {
-                     return onSuggestionsKey(v, keyCode, event);
-                }
-                // otherwise, dispatch an "edit view" key
-                switch (keyCode) {
-                case KeyEvent.KEYCODE_ENTER:
-                    if (event.getAction() == KeyEvent.ACTION_UP) {
-                        v.cancelLongPress();
-                        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);                    
+                if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                    SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+                    if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) {
+                        launchQuerySearch(keyCode, actionKey.mQueryActionMsg);
                         return true;
                     }
-                    break;
-                case KeyEvent.KEYCODE_DPAD_DOWN:                    
-                    // capture the EditText state, so we can restore the user entry later
-                    mUserQuery = mSearchTextField.getText().toString();
-                    mUserQuerySelStart = mSearchTextField.getSelectionStart();
-                    mUserQuerySelEnd = mSearchTextField.getSelectionEnd();
-                    // pass through - we're just watching here
-                    break;
-                default:
-                    if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                        SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
-                        if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) {
-                            launchQuerySearch(keyCode, actionKey.mQueryActionMsg);
-                            return true;
-                        }
-                    }
-                    break;
                 }
             }
             return false;
         }
     };
-
+        
     /**
-     * React to the user typing while the suggestions are focused.  First, check for action
-     * keys.  If not handled, try refocusing regular characters into the EditText.  In this case,
-     * replace the query text (start typing fresh text).
-     */
-    private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
-        boolean handled = false;
-        // also guard against possible race conditions (late arrival after dismiss)
-        if (mSearchable != null) {
-            handled = doSuggestionsKey(v, keyCode, event);
-        }
-        return handled;
-    }
-    
-    /**
-     * Per UI design, we're going to "steer" any typed keystrokes back into the EditText
-     * box, even if the user has navigated the focus to the dropdown or to the GO button.
-     * 
-     * @param v The view into which the keystroke was typed
-     * @param keyCode keyCode of entered key
-     * @param event Full KeyEvent record of entered key
-     */
-    private boolean refocusingKeyListener(View v, int keyCode, KeyEvent event) {
-        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)) {
-            // restore focus and give key to EditText ...
-            // but don't replace the user's query
-            mLeaveJammedQueryOnRefocus = true;
-            if (mSearchTextField.requestFocus()) {
-                handled = mSearchTextField.dispatchKeyEvent(event);
-            }
-            mLeaveJammedQueryOnRefocus = false;
-        }
-        return handled;
-    }
-    
-    /**
-     * Update query text based on transitions in and out of suggestions list.
-     */
-    /*
-     * TODO - figure out if this logic is required for the autocomplete text view version
-
-    OnFocusChangeListener mSuggestFocusListener = new OnFocusChangeListener() {
-        public void onFocusChange(View v, boolean hasFocus) {
-            // also guard against possible race conditions (late arrival after dismiss)
-            if (mSearchable == null) {
-                return;
-            }
-            // Update query text based on navigation in to/out of the suggestions list
-            if (hasFocus) {
-                // Entering the list view - record selection point from user's query
-                mUserQuery = mSearchTextField.getText().toString();
-                mUserQuerySelStart = mSearchTextField.getSelectionStart();
-                mUserQuerySelEnd = mSearchTextField.getSelectionEnd();
-                // then update the query to match the entered selection
-                jamSuggestionQuery(true, mSuggestionsList, 
-                                    mSuggestionsList.getSelectedItemPosition());
-            } else {
-                // Exiting the list view
-                
-                if (mSuggestionsList.getSelectedItemPosition() < 0) {
-                    // Direct exit - Leave new suggestion in place (do nothing)
-                } else {
-                    // Navigation exit - restore user's query text
-                    if (!mLeaveJammedQueryOnRefocus) {
-                        jamSuggestionQuery(false, null, -1);
-                    }
-                }
-            }
-
-        }
-    };
-    */
-    
-    /**
-     * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent.  It's an indication that
-     * we should close ourselves immediately, in order to allow a higher-priority UI to take over
+     * When the ACTION_CLOSE_SYSTEM_DIALOGS intent is received, we should close ourselves 
+     * immediately, in order to allow a higher-priority UI to take over
      * (e.g. phone call received).
+     * 
+     * When a package is added, removed or changed, our current context
+     * may no longer be valid.  This would only happen if a package is installed/removed exactly
+     * when the search bar is open.  So for now we're just going to close the search
+     * bar.  
+     * Anything fancier would require some checks to see if the user's context was still valid.
+     * Which would be messier.
      */
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -918,7 +905,7 @@
             } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)
                     || Intent.ACTION_PACKAGE_REMOVED.equals(action)
                     || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                onPackageListChange();
+                cancel();
             }
         }
     };
@@ -938,58 +925,45 @@
     }
     
     /**
-     * Various ways to launch searches
+     * React to the user typing while in the suggestions list. First, check for action
+     * keys. If not handled, try refocusing regular characters into the EditText. 
      */
-
-    /**
-     * React to the user clicking the "GO" button.  Hide the UI and launch a search.
-     *
-     * @param actionKey Pass a keycode if the launch was triggered by an action key.  Pass
-     * KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
-     * @param actionMsg Pass the suggestion-provided message if the launch was triggered by an
-     * action key.  Pass null for no actionKey message.
-     */
-    private void launchQuerySearch(int actionKey, final String actionMsg)  {
-        final String query = mSearchTextField.getText().toString();
-        final Bundle appData = mAppSearchData;
-        final SearchableInfo si = mSearchable;      // cache briefly (dismiss() nulls it)
-        dismiss();
-        sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, actionKey, actionMsg, si);
-    }
-
-    /**
-     * React to the user typing an action key while in the suggestions list
-     */
-    private boolean doSuggestionsKey(View v, int keyCode, KeyEvent event) {
-        // Exit early in case of race condition
+    private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
+        // guard against possible race conditions (late arrival after dismiss)
+        if (mSearchable == null) {
+            return false;
+        }
         if (mSuggestionsAdapter == null) {
             return false;
         }
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            if (DBG_LOG_TIMING == 1) {
-                dbgLogTiming("doSuggestionsKey()");
+            if (DBG_LOG_TIMING) {
+                dbgLogTiming("onSuggestionsKey()");
             }
             
             // First, check for enter or search (both of which we'll treat as a "click")
             if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
-                int position = mSearchTextField.getListSelection();
-                return launchSuggestion(mSuggestionsAdapter, position);
+                int position = mSearchAutoComplete.getListSelection();
+                return launchSuggestion(position);
             }
             
             // Next, check for left/right moves, which we use to "return" the user to the edit view
             if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
-                // give "focus" to text editor, but don't restore the user's original query
+                // give "focus" to text editor, with cursor at the beginning if
+                // left key, at end if right key
+                // TODO: Reverse left/right for right-to-left languages, e.g. Arabic
                 int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 
-                        0 : mSearchTextField.length();
-                mSearchTextField.setSelection(selPoint);
-                mSearchTextField.setListSelection(0);
-                mSearchTextField.clearListSelection();
+                        0 : mSearchAutoComplete.length();
+                mSearchAutoComplete.setSelection(selPoint);
+                mSearchAutoComplete.setListSelection(0);
+                mSearchAutoComplete.clearListSelection();
                 return true;
             }
             
             // Next, check for an "up and out" move
-            if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchTextField.getListSelection()) {
-                jamSuggestionQuery(false, null, -1);
+            if (keyCode == KeyEvent.KEYCODE_DPAD_UP 
+                    && 0 == mSearchAutoComplete.getListSelection()) {
+                restoreUserQuery();
                 // let ACTV complete the move
                 return false;
             }
@@ -999,222 +973,239 @@
             if ((actionKey != null) && 
                     ((actionKey.mSuggestActionMsg != null) || 
                      (actionKey.mSuggestActionMsgColumn != null))) {
-                //   launch suggestion using action key column
-                int position = mSearchTextField.getListSelection();
-                if (position >= 0) {
+                // launch suggestion using action key column
+                int position = mSearchAutoComplete.getListSelection();
+                if (position != ListView.INVALID_POSITION) {
                     Cursor c = mSuggestionsAdapter.getCursor();
                     if (c.moveToPosition(position)) {
                         final String actionMsg = getActionKeyMessage(c, actionKey);
                         if (actionMsg != null && (actionMsg.length() > 0)) {
-                            // shut down search bar and launch the activity
-                            // cache everything we need because dismiss releases mems
-                            setupSuggestionIntent(c, mSearchable);
-                            final String query = mSearchTextField.getText().toString();
-                            final Bundle appData =  mAppSearchData;
-                            SearchableInfo si = mSearchable;
-                            String suggestionAction = mSuggestionAction;
-                            Uri suggestionData = mSuggestionData;
-                            String suggestionQuery = mSuggestionQuery;
-                            dismiss();
-                            sendLaunchIntent(suggestionAction, suggestionData,
-                                    suggestionQuery, appData,
-                                             keyCode, actionMsg, si);
-                            return true;
+                            return launchSuggestion(position, keyCode, actionMsg);
                         }
                     }
                 }
             }
         }
         return false;
-    }    
-
+    }
+    
     /**
-     * Set or reset the user query to follow the selections in the suggestions
-     * 
-     * @param jamQuery True means to set the query, false means to reset it to the user's choice
+     * Launch a search for the text in the query text field.
      */
-    private void jamSuggestionQuery(boolean jamQuery, AdapterView<?> parent, int position) {
-        // quick check against race conditions
-        if (mSearchable == null) {
-            return;
-        }
-        
-        mSuggestionsAdapter.setNonUserQuery(true);       // disables any suggestions processing
-        if (jamQuery) {
-            CursorAdapter ca = getSuggestionsAdapter(parent);
-            Cursor c = ca.getCursor();
-            if (c.moveToPosition(position)) {
-                setupSuggestionIntent(c, mSearchable);
-                String jamText = null;
-
-                // Simple heuristic for selecting text with which to rewrite the query.
-                if (mSuggestionQuery != null) {
-                    jamText = mSuggestionQuery;
-                } else if (mSearchable.mQueryRewriteFromData && (mSuggestionData != null)) {
-                    jamText = mSuggestionData.toString();
-                } else if (mSearchable.mQueryRewriteFromText) {
-                    try {
-                        int column = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1);
-                        jamText = c.getString(column);
-                    } catch (RuntimeException e) {
-                        // no work here, jamText is null
-                    }
-                }
-                if (jamText != null) {
-                    mSearchTextField.setText(jamText);
-                    /* mSearchTextField.selectAll(); */ // this didn't work anyway in the old UI
-                    // TODO this is only needed in the model where we have a selection in the ACTV
-                    // and in the dropdown at the same time.
-                    mSearchTextField.setSelection(jamText.length());
-                }
-            }
-        } else {
-            // reset user query
-            mSearchTextField.setText(mUserQuery);
-            try {
-                mSearchTextField.setSelection(mUserQuerySelStart, mUserQuerySelEnd);
-            } catch (IndexOutOfBoundsException e) {
-                // In case of error, just select all
-                Log.e(LOG_TAG, "Caught IndexOutOfBoundsException while setting selection.  " +
-                        "start=" + mUserQuerySelStart + " end=" + mUserQuerySelEnd +
-                        " text=\"" + mUserQuery + "\"");
-                mSearchTextField.selectAll();
-            }
-        }
-        // TODO because the new query is (not) processed in another thread, we can't just
-        // take away this flag (yet).  The better solution here is going to require a new API
-        // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions.
-//      mSuggestionsAdapter.setNonUserQuery(false);
+    protected void launchQuerySearch()  {
+        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
     }
 
     /**
-     * Assemble a search intent and send it.
+     * Launch a search for the text in the query text field.
      *
-     * @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.
+     * @param actionKey The key code of the action key that was pressed,
+     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+     * @param actionMsg The message for the action key that was pressed,
+     *        or <code>null</code> if none.
      */
-    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);
-
-        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)
-        launcher.setComponent(si.mSearchActivity);
-
-        getContext().startActivity(launcher);
+    protected void launchQuerySearch(int actionKey, String actionMsg)  {
+        String query = mSearchAutoComplete.getText().toString();
+        Intent intent = createIntent(Intent.ACTION_SEARCH, null, query, null, 
+                actionKey, actionMsg);
+        launchIntent(intent);
     }
-
+    
     /**
-     * Shared code for launching a query from a suggestion.
-     * @param ca The cursor adapter containing the suggestions
-     * @param position The suggestion we'll be launching from
-     * @return true if a successful launch, false if could not (e.g. bad position)
+     * Launches an intent based on a suggestion.
+     * 
+     * @param position The index of the suggestion to create the intent from.
+     * @return true if a successful launch, false if could not (e.g. bad position).
      */
-    private boolean launchSuggestion(CursorAdapter ca, int position) {
-        Cursor c = ca.getCursor();
+    protected boolean launchSuggestion(int position) {
+        return launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
+    }
+    
+    /**
+     * Launches an intent based on a suggestion.
+     * 
+     * @param position The index of the suggestion to create the intent from.
+     * @param actionKey The key code of the action key that was pressed,
+     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+     * @param actionMsg The message for the action key that was pressed,
+     *        or <code>null</code> if none.
+     * @return true if a successful launch, false if could not (e.g. bad position).
+     */
+    protected boolean launchSuggestion(int position, int actionKey, String actionMsg) {
+        Cursor c = mSuggestionsAdapter.getCursor();
         if ((c != null) && c.moveToPosition(position)) {
-            setupSuggestionIntent(c, mSearchable);
-            
-            final Bundle appData =  mAppSearchData;
-            SearchableInfo si = mSearchable;
-            String suggestionAction = mSuggestionAction;
-            Uri suggestionData = mSuggestionData;
-            String suggestionQuery = mSuggestionQuery;
-            dismiss();
-            sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, appData,
-                                KeyEvent.KEYCODE_UNKNOWN, null, si);
+            Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
+            launchIntent(intent);
             return true;
         }
         return false;
     }
     
     /**
+     * Launches an intent. Also dismisses the search dialog if not in global search mode.
+     */
+    private void launchIntent(Intent intent) {
+        if (intent == null) {
+            return;
+        }
+        if (handleSpecialIntent(intent)){
+            return;
+        }
+        if (!mGlobalSearchMode) {
+            dismiss();
+        }
+        getContext().startActivity(intent);
+    }
+    
+    /**
+     * Handles the special intent actions declared in {@link SearchManager}.
+     * 
+     * @return <code>true</code> if the intent was handled.
+     */
+    private boolean handleSpecialIntent(Intent intent) {
+        String action = intent.getAction();
+        if (SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.equals(action)) {
+            handleChangeSourceIntent(intent);
+            return true;
+        } else if (SearchManager.INTENT_ACTION_CURSOR_RESPOND.equals(action)) {
+            handleCursorRespondIntent(intent);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Handles SearchManager#INTENT_ACTION_CHANGE_SOURCE.
+     */
+    private void handleChangeSourceIntent(Intent intent) {
+        Uri dataUri = intent.getData();
+        if (dataUri == null) {
+            Log.w(LOG_TAG, "SearchManager.INTENT_ACTION_CHANGE_SOURCE without intent data.");
+            return;
+        }
+        ComponentName componentName = ComponentName.unflattenFromString(dataUri.toString());
+        if (componentName == null) {
+            Log.w(LOG_TAG, "Invalid ComponentName: " + dataUri);
+            return;
+        }
+        if (DBG) Log.d(LOG_TAG, "Switching to " + componentName);
+        
+        ComponentName previous = mLaunchComponent;
+        if (!show(componentName, mAppSearchData, false)) {
+            Log.w(LOG_TAG, "Failed to switch to source " + componentName);
+            return;
+        }
+        pushPreviousComponent(previous);
+
+        String query = intent.getStringExtra(SearchManager.QUERY);
+        setUserQuery(query);
+    }
+    
+    /**
+     * Handles {@link SearchManager#INTENT_ACTION_CURSOR_RESPOND}.
+     */
+    private void handleCursorRespondIntent(Intent intent) {
+        Cursor c = mSuggestionsAdapter.getCursor();
+        if (c != null) {
+            c.respond(intent.getExtras());
+        }
+    }
+    
+    /**
+     * Saves the previous component that was searched, so that we can go
+     * back to it.
+     */
+    private void pushPreviousComponent(ComponentName componentName) {
+        if (mPreviousComponents == null) {
+            mPreviousComponents = new ArrayList<ComponentName>();
+        }
+        mPreviousComponents.add(componentName);
+    }
+    
+    /**
+     * Pops the previous component off the stack and returns it.
+     * 
+     * @return The component name, or <code>null</code> if there was
+     *         no previous component.
+     */
+    private ComponentName popPreviousComponent() {
+        if (mPreviousComponents == null) {
+            return null;
+        }
+        int size = mPreviousComponents.size();
+        if (size == 0) {
+            return null;
+        }
+        return mPreviousComponents.remove(size - 1);
+    }
+    
+    /**
+     * Goes back to the previous component that was searched, if any.
+     * 
+     * @return <code>true</code> if there was a previous component that we could go back to.
+     */
+    private boolean backToPreviousComponent() {
+        ComponentName previous = popPreviousComponent();
+        if (previous == null) {
+            return false;
+        }
+        if (!show(previous, mAppSearchData, false)) {
+            Log.w(LOG_TAG, "Failed to switch to source " + previous);
+            return false;
+        }
+        
+        // must touch text to trigger suggestions
+        // TODO: should this be the text as it was when the user left
+        // the source that we are now going back to?
+        String query = mSearchAutoComplete.getText().toString();
+        setUserQuery(query);
+        
+        return true;
+    }
+    
+    /**
      * 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.
-     * 
      * @param c The suggestions cursor, moved to the row of the user's selection
-     * @param si The searchable activity's info record
+     * @param actionKey The key code of the action key that was pressed,
+     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+     * @param actionMsg The message for the action key that was pressed,
+     *        or <code>null</code> if none.
+     * @return An intent for the suggestion at the cursor's position.
      */
-    void setupSuggestionIntent(Cursor c, SearchableInfo si) {
+    private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
         try {
             // use specific action if supplied, or default action if supplied, or fixed default
-            mSuggestionAction = null;
-            int mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
-            if (mColumn >= 0) {
-                final String action = c.getString(mColumn);
-                if (action != null) {
-                    mSuggestionAction = action;
-                }
+            String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+            if (action == null) {
+                action = mSearchable.getSuggestIntentAction();
             }
-            if (mSuggestionAction == null) {
-                mSuggestionAction = si.getSuggestIntentAction();
-            }
-            if (mSuggestionAction == null) {
-                mSuggestionAction = Intent.ACTION_SEARCH;
+            if (action == null) {
+                action = Intent.ACTION_SEARCH;
             }
             
             // use specific data if supplied, or default data if supplied
-            String data = null;
-            mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
-            if (mColumn >= 0) {
-                final String rowData = c.getString(mColumn);
-                if (rowData != null) {
-                    data = rowData;
-                }
-            }
+            String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
             if (data == null) {
-                data = si.getSuggestIntentData();
+                data = mSearchable.getSuggestIntentData();
             }
-            
             // then, if an ID was provided, append it.
             if (data != null) {
-                mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
-                if (mColumn >= 0) {
-                    final String id = c.getString(mColumn);
-                    if (id != null) {
-                        data = data + "/" + Uri.encode(id);
-                    }
+                String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
+                if (id != null) {
+                    data = data + "/" + Uri.encode(id);
                 }
             }
-            mSuggestionData = (data == null) ? null : Uri.parse(data);
+            Uri dataUri = (data == null) ? null : Uri.parse(data);
+
+            String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
             
-            mSuggestionQuery = null;
-            mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
-            if (mColumn >= 0) {
-                final String query = c.getString(mColumn);
-                if (query != null) {
-                    mSuggestionQuery = query;
-                }
-            }
+            String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
+
+            return createIntent(action, dataUri, query, extraData, actionKey, actionMsg);
         } catch (RuntimeException e ) {
             int rowNum;
             try {                       // be really paranoid now
@@ -1224,10 +1215,49 @@
             }
             Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum + 
                             " returned exception" + e.toString());
+            return null;
         }
     }
     
     /**
+     * Constructs an intent from the given information and the search dialog state.
+     * 
+     * @param action Intent action.
+     * @param data Intent data, or <code>null</code>.
+     * @param query Intent query, or <code>null</code>.
+     * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
+     * @param actionKey The key code of the action key that was pressed,
+     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+     * @param actionMsg The message for the action key that was pressed,
+     *        or <code>null</code> if none.
+     * @return The intent.
+     */
+    private Intent createIntent(String action, Uri data, String query, String extraData,
+            int actionKey, String actionMsg) {
+        // Now build the Intent
+        Intent intent = new Intent(action);
+        if (data != null) {
+            intent.setData(data);
+        }
+        if (query != null) {
+            intent.putExtra(SearchManager.QUERY, query);
+        }
+        if (extraData != null) {
+            intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
+        }
+        if (mAppSearchData != null) {
+            intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
+        }
+        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
+            intent.putExtra(SearchManager.ACTION_KEY, actionKey);
+            intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
+        }
+        // attempt to enforce security requirement (no 3rd-party intents)
+        intent.setComponent(mSearchable.mSearchActivity);
+        return intent;
+    }
+    
+    /**
      * For a given suggestion and a given cursor row, get the action message.  If not provided
      * by the specific row/column, also check for a single definition (for the action key).
      * 
@@ -1236,17 +1266,12 @@
      * 
      * @return Returns a string, or null if no action key message for this suggestion
      */
-    private String getActionKeyMessage(Cursor c, final SearchableInfo.ActionKeyInfo actionKey) {
+    private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
         String result = null;
         // check first in the cursor data, for a suggestion-specific message
         final String column = actionKey.mSuggestActionMsgColumn;
         if (column != null) {
-            try {
-                int colId = c.getColumnIndexOrThrow(column);
-                result = c.getString(colId);
-            } catch (RuntimeException e) {
-                // OK - result is already null
-            }
+            result = SuggestionsAdapter.getColumnString(c, column);
         }
         // If the cursor didn't give us a message, see if there's a single message defined
         // for the actionkey (for all suggestions)
@@ -1257,343 +1282,178 @@
     }
         
     /**
-     * Local subclass for AutoCompleteTextView
-     * 
-     * This exists entirely to override the threshold method.  Otherwise we just use the class
-     * as-is.
+     * Local subclass for AutoCompleteTextView.
      */
     public static class SearchAutoComplete extends AutoCompleteTextView {
 
+        private int mThreshold;
+        private SearchDialog mSearchDialog;
+        
         public SearchAutoComplete(Context context) {
             super(null);
+            mThreshold = getThreshold();
         }
         
         public SearchAutoComplete(Context context, AttributeSet attrs) {
             super(context, attrs);
+            mThreshold = getThreshold();
         }
 
         public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
             super(context, attrs, defStyle);
+            mThreshold = getThreshold();
+        }
+
+        private void setSearchDialog(SearchDialog searchDialog) {
+            mSearchDialog = searchDialog;
+        }
+        
+        @Override
+        public void setThreshold(int threshold) {
+            super.setThreshold(threshold);
+            mThreshold = threshold;
+        }
+
+        /**
+         * Returns true if the text field is empty, or contains only whitespace.
+         */
+        private boolean isEmpty() {
+            return TextUtils.getTrimmedLength(getText()) == 0;
         }
         
         /**
-         * We never allow ACTV to automatically replace the text, since we use "jamSuggestionQuery"
-         * to do that.  There's no point in letting ACTV do this here, because in the search UI,
-         * as soon as we click a suggestion, we're going to start shutting things down.
+         * Clears the entered text.
+         */
+        private void clear() {
+            setText("");
+        }
+        
+        /**
+         * We override this method to avoid replacing the query box text
+         * when a suggestion is clicked.
          */
         @Override
-        public void replaceText(CharSequence text) {
+        protected void replaceText(CharSequence text) {
         }
         
         /**
-         * We always return true, so that the effective threshold is "zero".  This allows us
-         * to provide "null" suggestions such as "just show me some recent entries".
+         * We override this method so that we can allow a threshold of zero, which ACTV does not.
          */
         @Override
         public boolean enoughToFilter() {
-            return true;
+            return mThreshold <= 0 || super.enoughToFilter();
+        }
+        
+        /**
+         * {@link AutoCompleteTextView#onKeyPreIme(int, KeyEvent)}) dismisses the drop-down on BACK,
+         * so we must override this method to modify the BACK behavior.
+         */
+        @Override
+        public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+            return mSearchDialog.handleBackKey(keyCode, event);
         }
     }
     
-    /**
-     * Support for AutoCompleteTextView-based suggestions
-     */
-    /**
-     * 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 {
-        private final String TAG = "SuggestionsAdapter";
-        
-        SearchableInfo mSearchable;
-        private Resources mProviderResources;
-        
-        // These private variables are shared by the filter thread and must be protected
-        private WeakReference<Cursor> mRecentCursor = new WeakReference<Cursor>(null);
-        private boolean mNonUserQuery = false;
-        private AutoCompleteTextView mParentView;
-
-        public SuggestionsAdapter(Context context, SearchableInfo searchable,
-                AutoCompleteTextView actv) {
-            super(context, -1, null, null, null);
-            mSearchable = searchable;
-            mParentView = actv;
-            
-            // set up provider resources (gives us icons, etc.)
-            Context activityContext = mSearchable.getActivityContext(mContext);
-            Context providerContext = mSearchable.getProviderContext(mContext, activityContext);
-            mProviderResources = providerContext.getResources();
+    protected boolean handleBackKey(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
+            mSearchAutoComplete.dismissDropDown();
+            if (backToPreviousComponent()) {
+                return true;
+            }
+            if (!mSearchAutoComplete.isEmpty()) {
+                mSearchAutoComplete.clear();
+                return true;
+            }
+            cancel();
+            return true;
         }
-        
-        /**
-         * Set this field (temporarily!) to disable suggestions updating.  This allows us
-         * to change the string in the text view without changing the suggestions list.
-         */
-        public void setNonUserQuery(boolean nonUserQuery) {
-            synchronized (this) {
-                mNonUserQuery = nonUserQuery;
-            }
-        }
-        
-        public boolean getNonUserQuery() {
-            synchronized (this) {
-                return mNonUserQuery;
-            }
-        }
-
-        /**
-         * 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.
-         * 
-         * In order to provide the Search Mgr functionality of seeing your query change as you
-         * scroll through the list, we have to be able to jam new text into the string without
-         * retriggering the suggestions.  We do that here via the "nonUserQuery" flag.  In that
-         * case we simply return the existing cursor.
-         * 
-         * TODO: Dianne suggests that this should simply be promoted into an AutoCompleteTextView
-         * behavior (perhaps optionally).
-         * 
-         * TODO: The "nonuserquery" logic has a race condition because it happens in another thread.
-         * This also needs to be fixed.
-         */
-        @Override
-        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
-            String query = (constraint == null) ? "" : constraint.toString();
-            Cursor c = null;
-            synchronized (this) {
-                if (mNonUserQuery) {
-                    c = mRecentCursor.get();
-                    mNonUserQuery = false;
-                }
-            }
-            if (c == null) {
-                c = getSuggestions(mSearchable, query);
-                synchronized (this) {
-                    mRecentCursor = new WeakReference<Cursor>(c);
-                }
-            }
-            return c;
-        }
-        
-        /**
-         * Overriding changeCursor() allows us to change not only the cursor, but by sampling
-         * the cursor's columns, the actual display characteristics of the list.
-         */
-        @Override
-        public void changeCursor(Cursor c) {
-            
-            // first, check for various conditions that disqualify this cursor
-            if ((c == null) || (c.getCount() == 0)) {
-                // no cursor, or cursor with no data
-                changeCursorAndColumns(null, null, null);
-                if (c != null) {
-                    c.close();
-                }
-                return;
-            }
-
-            // check cursor before trying to create list views from it
-            int colId = c.getColumnIndex("_id");
-            int col1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
-            int col2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
-            int colIc1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
-            int colIc2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
-
-            boolean minimal = (colId >= 0) && (col1 >= 0);
-            boolean hasIcons = (colIc1 >= 0) && (colIc2 >= 0);
-            boolean has2Lines = col2 >= 0;
-
-            if (minimal) {
-                int layout;
-                String[] from;
-                int[] to;
-
-                if (hasIcons) {
-                    if (has2Lines) {
-                        layout = com.android.internal.R.layout.search_dropdown_item_icons_2line;
-                        from = TWO_LINE_ICONS_FROM;
-                        to = TWO_LINE_ICONS_TO;
-                    } else {
-                        layout = com.android.internal.R.layout.search_dropdown_item_icons_1line;
-                        from = ONE_LINE_ICONS_FROM;
-                        to = ONE_LINE_ICONS_TO;
-                    }
-                } else {
-                    if (has2Lines) {
-                        layout = com.android.internal.R.layout.search_dropdown_item_2line;
-                        from = TWO_LINE_FROM;
-                        to = TWO_LINE_TO;
-                    } else {
-                        layout = com.android.internal.R.layout.search_dropdown_item_1line;
-                        from = ONE_LINE_FROM;
-                        to = ONE_LINE_TO;
-                    }
-                }
-                // Force the underlying ListView to discard and reload all layouts
-                // (Note, this should be optimized for cases where layout/cursor remain same)
-                mParentView.resetListAndClearViews();
-                // Now actually set up the cursor, columns, and the list view
-                changeCursorAndColumns(c, from, to);
-                setViewResource(layout);          
-            } else {
-                // Provide some help for developers instead of just silently discarding
-                Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns.");
-                changeCursorAndColumns(null, null, null);
-                c.close();
-            }
-            if ((colIc1 >= 0) != (colIc2 >= 0)) {
-                Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns.");
-            }    
-        }
-        
-        /**
-         * 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;
-            }
-        }
-
+        return false;
     }
     
     /**
      * Implements OnItemClickListener
      */
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        // Log.d(LOG_TAG, "onItemClick() position " + position);
-        launchSuggestion(mSuggestionsAdapter, position);
+        if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
+        launchSuggestion(position);
     }
-    
+
     /** 
      * Implements OnItemSelectedListener
      */
      public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-         // Log.d(LOG_TAG, "onItemSelected() position " + position);
-         jamSuggestionQuery(true, parent, position);
+         if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
+         // A suggestion has been selected, rewrite the query if possible,
+         // otherwise the restore the original query.
+         if (REWRITE_QUERIES) {
+             rewriteQueryFromSuggestion(position);
+         }
      }
 
      /** 
       * Implements OnItemSelectedListener
       */
      public void onNothingSelected(AdapterView<?> parent) {
-         // Log.d(LOG_TAG, "onNothingSelected()");
+         if (DBG) Log.d(LOG_TAG, "onNothingSelected()");
+     }
+     
+     /**
+      * Query rewriting.
+      */
+
+     private void rewriteQueryFromSuggestion(int position) {
+         Cursor c = mSuggestionsAdapter.getCursor();
+         if (c == null) {
+             return;
+         }
+         if (c.moveToPosition(position)) {
+             // Get the new query from the suggestion.
+             CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
+             if (newQuery != null) {
+                 // The suggestion rewrites the query.
+                 if (DBG) Log.d(LOG_TAG, "Rewriting query to '" + newQuery + "'");
+                 // Update the text field, without getting new suggestions.
+                 setQuery(newQuery);
+             } else {
+                 // The suggestion does not rewrite the query, restore the user's query.
+                 if (DBG) Log.d(LOG_TAG, "Suggestion gives no rewrite, restoring user query.");
+                 restoreUserQuery();
+             }
+         } else {
+             // We got a bad position, restore the user's query.
+             Log.w(LOG_TAG, "Bad suggestion position: " + position);
+             restoreUserQuery();
+         }
+     }
+     
+     /** 
+      * Restores the query entered by the user if needed.
+      */
+     private void restoreUserQuery() {
+         if (DBG) Log.d(LOG_TAG, "Restoring query to '" + mUserQuery + "'");
+         setQuery(mUserQuery);
+     }
+     
+     /**
+      * Sets the text in the query box, without updating the suggestions.
+      */
+     private void setQuery(CharSequence query) {
+         mSearchAutoComplete.setText(query, false);
+         if (query != null) {
+             mSearchAutoComplete.setSelection(query.length());
+         }
+     }
+     
+     /**
+      * Sets the text in the query box, updating the suggestions.
+      */
+     private void setUserQuery(String query) {
+         if (query == null) {
+             query = "";
+         }
+         mUserQuery = query;
+         mSearchAutoComplete.setText(query);
+         mSearchAutoComplete.setSelection(query.length());
      }
 
     /**
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index ecdd3f8..3e9f3dd 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -17,12 +17,17 @@
 package android.app;
 
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.Configuration;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.server.search.SearchableInfo;
 import android.view.KeyEvent;
 
 /**
@@ -439,20 +444,18 @@
  *     
  *     <tr><th>{@link #SUGGEST_COLUMN_ICON_1}</th>
  *         <td>If your cursor includes this column, then all suggestions will be provided in an
- *             icons+text format.  This value should be a reference (resource ID) of the icon to
+ *             icons+text format.  This value should be a reference to the icon to
  *             draw on the left side, or it can be null or zero to indicate no icon in this row.
- *             You must provide both cursor columns, or neither.
  *             </td>
- *         <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_2}</td>
+ *         <td align="center">No.</td>
  *     </tr>
  *     
  *     <tr><th>{@link #SUGGEST_COLUMN_ICON_2}</th>
  *         <td>If your cursor includes this column, then all suggestions will be provided in an
- *             icons+text format.  This value should be a reference (resource ID) of the icon to
+ *             icons+text format.  This value should be a reference to the icon to
  *             draw on the right side, or it can be null or zero to indicate no icon in this row.
- *             You must provide both cursor columns, or neither.
  *             </td>
- *         <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_1}</td>
+ *         <td align="center">No.</td>
  *     </tr>
  *     
  *     <tr><th>{@link #SUGGEST_COLUMN_INTENT_ACTION}</th>
@@ -1155,6 +1158,13 @@
     public final static String ACTION_KEY = "action_key";
     
     /**
+     * Intent extra data key: This key will be used for the extra populated by the
+     * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
+     * {@hide}
+     */
+    public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
+    
+    /**
      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
      * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
      * to obtain the action message that was defined for a particular search action key and/or
@@ -1195,21 +1205,59 @@
     public final static String SUGGEST_COLUMN_TEXT_2 = "suggest_text_2";
     /**
      * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
-     *  then all suggestions will be provided in format that includes space for two small icons,
+     *  then all suggestions will be provided in a format that includes space for two small icons,
      *  one at the left and one at the right of each suggestion.  The data in the column must
-     *  be a a resource ID for the icon you wish to have displayed.  If you include this column,
-     *  you must also include {@link #SUGGEST_COLUMN_ICON_2}.
+     *  be a resource ID of a drawable, or a URI in one of the following formats:
+     *
+     * <ul>
+     * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+     * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
+     * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+     * </ul>
+     *
+     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 
+     * for more information on these schemes. 
      */
     public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1";
     /**
      * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
-     *  then all suggestions will be provided in format that includes space for two small icons,
+     *  then all suggestions will be provided in a format that includes space for two small icons,
      *  one at the left and one at the right of each suggestion.  The data in the column must
-     *  be a a resource ID for the icon you wish to have displayed.  If you include this column,
-     *  you must also include {@link #SUGGEST_COLUMN_ICON_1}.
+     *  be a resource ID of a drawable, or a URI in one of the following formats:
+     *
+     * <ul>
+     * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+     * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
+     * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+     * </ul>
+     *
+     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 
+     * for more information on these schemes. 
      */
     public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
     /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
+     *  then all suggestions will be provided in a format that includes space for two small icons,
+     *  one at the left and one at the right of each suggestion.  The data in the column must
+     *  be a blob that contains a bitmap.
+     * 
+     * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_1} column.
+     *
+     * @hide
+     */
+    public final static String SUGGEST_COLUMN_ICON_1_BITMAP = "suggest_icon_1_bitmap";
+    /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
+     *  then all suggestions will be provided in a format that includes space for two small icons,
+     *  one at the left and one at the right of each suggestion.  The data in the column must
+     *  be a blob that contains a bitmap.
+     * 
+     * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_2} column.
+     *
+     * @hide
+     */
+    public final static String SUGGEST_COLUMN_ICON_2_BITMAP = "suggest_icon_2_bitmap";
+    /**
      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
      * this element exists at the given row, this is the action that will be used when
      * forming the suggestion's intent.  If the element is not provided, the action will be taken
@@ -1230,6 +1278,14 @@
      */
     public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data";
     /**
+     * Column name for suggestions cursor.  <i>Optional.</i>  This column allows suggestions
+     *  to provide additional arbitrary data which will be included as an extra under the key
+     *  {@link #EXTRA_DATA_KEY}.
+     * 
+     * @hide pending API council approval
+     */
+    public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
+    /**
      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
      * this element exists at the given row, then "/" and this value will be appended to the data
      * field in the Intent.  This should only be used if the data field has already been set to an
@@ -1244,6 +1300,54 @@
      */
     public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query";
 
+    /**
+     * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
+     * the search dialog will switch to a different suggestion source when the
+     * suggestion is clicked. 
+     * 
+     * {@link #SUGGEST_COLUMN_INTENT_DATA} must contain
+     * the flattened {@link ComponentName} of the activity which is to be searched.
+     * 
+     * TODO: Should {@link #SUGGEST_COLUMN_INTENT_DATA} instead contain a URI in the format
+     * used by {@link android.provider.Applications}?
+     * 
+     * TODO: This intent should be protected by the same permission that we use
+     * for replacing the global search provider.
+     * 
+     * The query text field will be set to the value of {@link #SUGGEST_COLUMN_QUERY}.
+     * 
+     * @hide Pending API council approval.
+     */
+    public final static String INTENT_ACTION_CHANGE_SEARCH_SOURCE 
+            = "android.search.action.CHANGE_SEARCH_SOURCE";
+    
+    /**
+     * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
+     * the search dialog will call {@link Cursor#respond(Bundle)} when the
+     * suggestion is clicked. 
+     * 
+     * The {@link Bundle} argument will be constructed
+     * in the same way as the "extra" bundle included in an Intent constructed 
+     * from the suggestion.
+     * 
+     * @hide Pending API council approval.
+     */
+    public final static String INTENT_ACTION_CURSOR_RESPOND
+            = "android.search.action.CURSOR_RESPOND";
+    
+    /**
+     * Intent action for starting the global search settings activity.
+     * The global search provider should handle this intent.
+     * 
+     * @hide Pending API council approval.
+     */
+    public final static String INTENT_ACTION_SEARCH_SETTINGS 
+            = "android.search.action.SEARCH_SETTINGS";
+    
+    /**
+     * Reference to the shared system search service.
+     */
+    private static ISearchManager sService = getSearchManagerService();
 
     private final Context mContext;
     private final Handler mHandler;
@@ -1257,12 +1361,6 @@
         mContext = context;
         mHandler = handler;
     }
-    private static ISearchManager mService;
-
-    static {
-        mService = ISearchManager.Stub.asInterface(
-                    ServiceManager.getService(Context.SEARCH_SERVICE));
-    }
     
     /**
      * Launch search UI.
@@ -1459,5 +1557,93 @@
             mSearchDialog.onConfigurationChanged(newConfig);
         }
     }
-      
+    
+    private static ISearchManager getSearchManagerService() {
+        return ISearchManager.Stub.asInterface(
+            ServiceManager.getService(Context.SEARCH_SERVICE));
+    }
+    
+    /**
+     * Gets information about a searchable activity. This method is static so that it can
+     * be used from non-Activity contexts.
+     *
+     * @param componentName The activity to get searchable information for.
+     * @param globalSearch If <code>false</code>, return information about the given activity.
+     *        If <code>true</code>, return information about the global search activity. 
+     * @return Searchable information, or <code>null</code> if the activity is not searchable.
+     * 
+     * @hide because SearchableInfo is not part of the API.
+     */
+    public static SearchableInfo getSearchableInfo(ComponentName componentName, 
+            boolean globalSearch) {
+        try {
+            return sService.getSearchableInfo(componentName, globalSearch);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+    
+    /**
+     * Checks whether the given searchable is the default searchable.
+     * 
+     * @hide because SearchableInfo is not part of the API.
+     */
+    public static boolean isDefaultSearchable(SearchableInfo searchable) {
+        SearchableInfo defaultSearchable = SearchManager.getSearchableInfo(null, true);
+        return defaultSearchable != null 
+                && defaultSearchable.mSearchActivity.equals(searchable.mSearchActivity);
+    }
+    
+    /**
+     * Gets a cursor with search suggestions. This method is static so that it can
+     * be used from non-Activity context.
+     *
+     * @param searchable Information about how to get the suggestions.
+     * @param query The search text entered (so far).
+     * @return a cursor with suggestions, or <code>null</null> the suggestion query failed. 
+     * 
+     * @hide because SearchableInfo is not part of the API.
+     */
+    public static Cursor getSuggestions(Context context, SearchableInfo searchable, String query) {
+        if (searchable == null) {
+            return null;
+        }
+
+        String authority = searchable.getSuggestAuthority();
+        if (authority == null) {
+            return null;
+        }
+
+        Uri.Builder uriBuilder = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority);
+
+        // if content path provided, insert it now
+        final String contentPath = searchable.getSuggestPath();
+        if (contentPath != null) {
+            uriBuilder.appendEncodedPath(contentPath);
+        }
+
+        // append standard suggestion query path 
+        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
+
+        // get the query selection, may be null
+        String selection = searchable.getSuggestSelection();
+        // inject query, either as selection args or inline
+        String[] selArgs = null;
+        if (selection != null) {    // use selection if provided
+            selArgs = new String[] { query };
+        } else {                    // no selection, use REST pattern
+            uriBuilder.appendPath(query);
+        }
+
+        Uri uri = uriBuilder
+                .query("")     // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+                .fragment("")  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+                .build();
+
+        // finally, make the query
+        return context.getContentResolver().query(uri, null, selection, selArgs, null);
+    }
+     
 }
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
new file mode 100644
index 0000000..af2a321
--- /dev/null
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.server.search.SearchableInfo;
+import android.text.Html;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import java.io.FileNotFoundException;
+import java.util.WeakHashMap;
+
+/**
+ * Provides the contents for the suggestion drop-down list.in {@link SearchDialog}.
+ * 
+ * @hide
+ */
+class SuggestionsAdapter extends ResourceCursorAdapter {
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "SuggestionsAdapter";
+    
+    private SearchableInfo mSearchable;
+    private Context mProviderContext;
+    private WeakHashMap<String, Drawable> mOutsideDrawablesCache;
+
+    // Cached column indexes, updated when the cursor changes. 
+    private int mFormatCol;
+    private int mText1Col;
+    private int mText2Col;
+    private int mIconName1Col;
+    private int mIconName2Col;
+    private int mIconBitmap1Col;
+    private int mIconBitmap2Col;
+    
+    public SuggestionsAdapter(Context context, SearchableInfo searchable,
+            WeakHashMap<String, Drawable> outsideDrawablesCache) {
+        super(context,
+                com.android.internal.R.layout.search_dropdown_item_icons_2line,
+                null,   // no initial cursor
+                true);  // auto-requery
+        mSearchable = searchable;
+        
+        // set up provider resources (gives us icons, etc.)
+        Context activityContext = mSearchable.getActivityContext(mContext);
+        mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
+        
+        mOutsideDrawablesCache = outsideDrawablesCache;
+    }
+    
+    /**
+     * Overridden to always return <code>false</code>, since we cannot be sure that
+     * suggestion sources return stable IDs.
+     */
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+    /**
+     * 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) {
+        if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
+        String query = (constraint == null) ? "" : constraint.toString();
+        try {
+            return SearchManager.getSuggestions(mContext, mSearchable, query);
+        } catch (RuntimeException e) {
+            Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
+            return null;
+        }
+    }
+    
+    /**
+     * Cache columns.
+     */
+    @Override
+    public void changeCursor(Cursor c) {
+        if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
+        super.changeCursor(c);
+        if (c != null) {
+            mFormatCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
+            mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
+            mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
+            mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
+            mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
+            mIconBitmap1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1_BITMAP);
+            mIconBitmap2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2_BITMAP);
+        }
+    }
+    
+    /**
+     * Tags the view with cached child view look-ups.
+     */
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        View v = super.newView(context, cursor, parent);
+        v.setTag(new ChildViewCache(v));
+        return v;
+    }
+    
+    /**
+     * Cache of the child views of drop-drown list items, to avoid looking up the children
+     * each time the contents of a list item are changed.
+     */
+    private final static class ChildViewCache {
+        public final TextView mText1;
+        public final TextView mText2;
+        public final ImageView mIcon1;
+        public final ImageView mIcon2;
+        
+        public ChildViewCache(View v) {
+            mText1 = (TextView) v.findViewById(com.android.internal.R.id.text1);
+            mText2 = (TextView) v.findViewById(com.android.internal.R.id.text2);
+            mIcon1 = (ImageView) v.findViewById(com.android.internal.R.id.icon1);
+            mIcon2 = (ImageView) v.findViewById(com.android.internal.R.id.icon2);
+        }
+    }
+    
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        ChildViewCache views = (ChildViewCache) view.getTag();
+        boolean isHtml = false;
+        if (mFormatCol >= 0) {
+            String format = cursor.getString(mFormatCol);
+            isHtml = "html".equals(format);    
+        }
+        setViewText(cursor, views.mText1, mText1Col, isHtml);
+        setViewText(cursor, views.mText2, mText2Col, isHtml);
+        setViewIcon(cursor, views.mIcon1, mIconBitmap1Col, mIconName1Col);
+        setViewIcon(cursor, views.mIcon2, mIconBitmap2Col, mIconName2Col);
+    }
+    
+    private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) {
+        if (v == null) {
+            return;
+        }
+        CharSequence text = null;
+        if (textCol >= 0) {
+            String str = cursor.getString(textCol);
+            text = (str != null && isHtml) ? Html.fromHtml(str) : str;
+        }
+        // Set the text even if it's null, since we need to clear any previous text.
+        v.setText(text);
+        
+        if (TextUtils.isEmpty(text)) {
+            v.setVisibility(View.GONE);
+        } else {
+            v.setVisibility(View.VISIBLE);
+        }
+    }
+    
+    private void setViewIcon(Cursor cursor, ImageView v, int iconBitmapCol, int iconNameCol) {
+        if (v == null) {
+            return;
+        }
+        Drawable drawable = null;
+        // First try the bitmap column
+        if (iconBitmapCol >= 0) {
+            byte[] data = cursor.getBlob(iconBitmapCol);
+            if (data != null) {
+                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                if (bitmap != null) {
+                    drawable = new BitmapDrawable(bitmap);
+                }
+            }
+        }
+        // If there was no bitmap, try the icon resource column.
+        if (drawable == null && iconNameCol >= 0) {
+            String value = cursor.getString(iconNameCol);
+            drawable = getDrawableFromResourceValue(value);
+        }
+        // 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);
+        } else {
+            v.setVisibility(View.VISIBLE);
+        }
+    }
+    
+    /**
+     * Gets the text to show in the query field when a suggestion is selected.
+     * 
+     * @param cursor The Cursor to read the suggestion data from. The Cursor should already 
+     *        be moved to the suggestion that is to be read from.
+     * @return The text to show, or <code>null</code> if the query should not be
+     *         changed when selecting this suggestion.
+     */
+    @Override
+    public CharSequence convertToString(Cursor cursor) {
+        if (cursor == null) {
+            return null;
+        }
+        
+        String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
+        if (query != null) {
+            return query;
+        }
+        
+        if (mSearchable.mQueryRewriteFromData) {
+            String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
+            if (data != null) {
+                return data;
+            }
+        }
+        
+        if (mSearchable.mQueryRewriteFromText) {
+            String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1);
+            if (text1 != null) {
+                return text1;
+            }
+        }
+        
+        return null;
+    }
+    
+    /**
+     * This method is overridden purely to provide a bit of protection against
+     * flaky content providers.
+     * 
+     * @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(LOG_TAG, "Search suggestions cursor threw exception.", e);
+            // Put exception string in item title
+            View v = newView(mContext, mCursor, parent);
+            if (v != null) {
+                ChildViewCache views = (ChildViewCache) v.getTag(); 
+                TextView tv = views.mText1;
+                tv.setText(e.toString());
+            }
+            return v;
+        }
+    }
+    
+    /**
+     * Gets a drawable given a value provided by a suggestion provider.
+     * 
+     * This value could be just the string value of a resource id
+     * (e.g., "2130837524"), in which case we will try to retrieve a drawable from
+     * the provider's resources. If the value is not an integer, it is
+     * treated as a Uri and opened with  
+     * {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
+     *
+     * All resources and URIs are read using the suggestion provider's context.
+     *
+     * If the string is not formatted as expected, or no drawable can be found for
+     * the provided value, this method returns null.
+     * 
+     * @param drawableId a string like "2130837524",
+     *        "android.resource://com.android.alarmclock/2130837524",
+     *        or "content://contacts/photos/253".
+     * @return a Drawable, or null if none found
+     */
+    private Drawable getDrawableFromResourceValue(String drawableId) {
+        if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
+            return null;
+        }
+        
+        // First, check the cache.
+        Drawable drawable = mOutsideDrawablesCache.get(drawableId);
+        if (drawable != null) return drawable;
+
+        try {
+            // Not cached, try using it as a plain resource ID in the provider's context.
+            int resourceId = Integer.parseInt(drawableId);
+            drawable = mProviderContext.getResources().getDrawable(resourceId);
+        } catch (NumberFormatException nfe) {
+            // The id was not an integer resource id.
+            // Let the ContentResolver handle content, android.resource and file URIs.
+            try {
+                Uri uri = Uri.parse(drawableId);
+                drawable = Drawable.createFromStream(
+                        mProviderContext.getContentResolver().openInputStream(uri),
+                        null);
+            } catch (FileNotFoundException fnfe) {
+                // drawable = null;
+            }
+                    
+            // If we got a drawable for this resource id, then stick it in the
+            // map so we don't do this lookup again.
+            if (drawable != null) {
+                mOutsideDrawablesCache.put(drawableId, drawable);
+            }
+        } catch (NotFoundException nfe) {
+            // Resource could not be found
+            // drawable = null;
+        }
+        
+        return drawable;
+    }
+    
+    /**
+     * Gets the value of a string column by name.
+     * 
+     * @param cursor Cursor to read the value from.
+     * @param columnName The name of the column to read.
+     * @return The value of the given column, or <code>null</null>
+     *         if the cursor does not contain the given column.
+     */
+    public static String getColumnString(Cursor cursor, String columnName) {
+        int col = cursor.getColumnIndex(columnName);
+        if (col == -1) {
+            return null;
+        }
+        return cursor.getString(col);
+    }
+
+}
diff --git a/core/java/android/provider/Applications.java b/core/java/android/provider/Applications.java
new file mode 100644
index 0000000..0b0ce58
--- /dev/null
+++ b/core/java/android/provider/Applications.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.app.SearchManager;
+import android.net.Uri;
+import android.widget.SimpleCursorAdapter;
+
+/**
+ * <p>The Applications provider gives information about installed applications.</p>
+ * 
+ * <p>This provider provides the following columns:
+ * 
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *
+ *     <thead>
+ *     <tr><th>Column Name</th> <th>Description</th> </tr>
+ *     </thead>
+ *
+ * <tbody>
+ * <tr><th>{@link SearchManager#SUGGEST_COLUMN_TEXT_1}</th>
+ *     <td>The application name.</td>
+ * </tr>
+ * 
+ * <tr><th>{@link SearchManager#SUGGEST_COLUMN_INTENT_COMPONENT}</th>
+ *     <td>The component to be used when forming the intent.</td>
+ * </tr>
+ * 
+ * <tr><th>{@link SearchManager#SUGGEST_COLUMN_ICON_1}</th>
+ *     <td>The application's icon resource id, prepended by its package name and
+ *         separated by a colon, e.g., "com.android.alarmclock:2130837524". The
+ *         package name is required for an activity interpreting this value to
+ *         be able to correctly access the icon drawable, for example, in an override of
+ *         {@link SimpleCursorAdapter#setViewImage(android.widget.ImageView, String)}.</td>
+ * </tr>
+ * 
+ * <tr><th>{@link SearchManager#SUGGEST_COLUMN_ICON_2}</th>
+ *     <td><i>Unused - column provided to conform to the {@link SearchManager} stipulation
+ *            that all providers provide either both or neither of
+ *            {@link SearchManager#SUGGEST_COLUMN_ICON_1} and
+ *            {@link SearchManager#SUGGEST_COLUMN_ICON_2}.</td>
+ * </tr>
+ * 
+ * @hide pending API council approval - should be unhidden at the same time as
+ *       {@link SearchManager#SUGGEST_COLUMN_INTENT_COMPONENT}
+ */
+public class Applications {
+    private static final String TAG = "Applications";
+
+    /**
+     * The content authority for this provider.
+     *
+     * @hide
+     */
+    public static final String AUTHORITY = "applications";
+
+    /**
+     * The content:// style URL for this provider
+     *
+     * @hide
+     */
+    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+
+    /**
+     * no public constructor since this is a utility class
+     */
+    private Applications() {}
+}
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index fe15553..eaace6b 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -22,8 +22,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Handler;
-import android.util.Config;
 
 /**
  * This is a simplified version of the Search Manager service.  It no longer handles
@@ -36,7 +36,6 @@
         // general debugging support
     private static final String TAG = "SearchManagerService";
     private static final boolean DEBUG = false;
-    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
     
         // configuration choices
     private static final boolean IMMEDIATE_SEARCHABLES_UPDATE = true;
@@ -45,9 +44,10 @@
     private final Context mContext;
     private final Handler mHandler;
     private boolean mSearchablesDirty;
+    private Searchables mSearchables;
     
     /**
-     * Initialize the Search Manager service in the provided system context.
+     * Initializes the Search Manager service in the provided system context.
      * Only one instance of this object should be created!
      *
      * @param context to use for accessing DB, window manager, etc.
@@ -55,6 +55,8 @@
     public SearchManagerService(Context context)  {     
         mContext = context;
         mHandler = new Handler();
+        mSearchablesDirty = true;
+        mSearchables = new Searchables(context);
         
         // Setup the infrastructure for updating and maintaining the list
         // of searchable activities.
@@ -64,7 +66,6 @@
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
-        mSearchablesDirty = true;
         
         // After startup settles down, preload the searchables list,
         // which will reduce the delay when the search UI is invoked.
@@ -109,34 +110,41 @@
     };
 
     /**
-     * Update the list of searchables, either at startup or in response to
+     * Updates the list of searchables, either at startup or in response to
      * a package add/remove broadcast message.
      */
     private void updateSearchables() {
-        SearchableInfo.buildSearchableList(mContext);
+        mSearchables.buildSearchableList();
         mSearchablesDirty = false;
         
-        // TODO This is a hack.  This shouldn't be hardcoded here, it's probably
-        // a policy.
-//      ComponentName defaultSearch = new ComponentName( 
-//              "com.android.contacts", 
-//              "com.android.contacts.ContactsListActivity" );
-        ComponentName defaultSearch = new ComponentName( 
-                "com.android.googlesearch", 
-                "com.android.googlesearch.GoogleSearch" );
-        SearchableInfo.setDefaultSearchable(mContext, defaultSearch);
+        // TODO SearchableInfo should be the source of truth about whether a searchable exists.
+        // As it stands, if the package exists but is misconfigured in some way, then this
+        // would fail, and needs to be fixed.
+        ComponentName defaultSearch = new ComponentName(
+                "com.android.globalsearch", 
+                "com.android.globalsearch.GlobalSearch");
+        
+        try {
+            mContext.getPackageManager().getActivityInfo(defaultSearch, 0);
+        } catch (NameNotFoundException e) {
+            defaultSearch = new ComponentName(
+                    "com.android.googlesearch",
+                    "com.android.googlesearch.GoogleSearch");
+        }
+        
+        mSearchables.setDefaultSearchable(defaultSearch);
     }
 
     /**
-     * Return the searchableinfo for a given activity
+     * Returns the SearchableInfo for a given activity
      *
      * @param launchActivity The activity from which we're launching this search.
-     * @return Returns a SearchableInfo record describing the parameters of the search,
-     * or null if no searchable metadata was available.
      * @param globalSearch If false, this will only launch the search that has been specifically
      * defined by the application (which is usually defined as a local search).  If no default 
      * search is defined in the current application or activity, no search will be launched.
      * If true, this will always launch a platform-global (e.g. web-based) search instead.
+     * @return Returns a SearchableInfo record describing the parameters of the search,
+     * or null if no searchable metadata was available.
      */
     public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) {
         // final check.  however we should try to avoid this, because
@@ -146,11 +154,12 @@
         }
         SearchableInfo si = null;
         if (globalSearch) {
-            si = SearchableInfo.getDefaultSearchable();
+            si = mSearchables.getDefaultSearchable();
         } else {
-            si = SearchableInfo.getSearchableInfo(mContext, launchActivity);
+            si = mSearchables.getSearchableInfo(launchActivity);
         }
 
         return si;
     }
+
 }
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 0c04839..22abd1b 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -21,14 +21,11 @@
 
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.InputType;
@@ -38,9 +35,6 @@
 import android.view.inputmethod.EditorInfo;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
 
 public final class SearchableInfo implements Parcelable {
 
@@ -50,19 +44,12 @@
     // set this flag to 1 to prevent any apps from providing suggestions
     final static int DBG_INHIBIT_SUGGESTIONS = 0;
 
-    // static strings used for XML lookups, etc.
+    // static strings used for XML lookups.
     // TODO how should these be documented for the developer, in a more structured way than 
     // the current long wordy javadoc in SearchManager.java ?
-    private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
     private static final String MD_LABEL_SEARCHABLE = "android.app.searchable";
-    private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
     private static final String MD_XML_ELEMENT_SEARCHABLE = "searchable";
     private static final String MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY = "actionkey";
-
-    // class maintenance and general shared data
-    private static HashMap<ComponentName, SearchableInfo> sSearchablesMap = null;
-    private static ArrayList<SearchableInfo> sSearchablesList = null;
-    private static SearchableInfo sDefaultSearchable = null;
     
     // true member variables - what we know about the searchability
     // TO-DO replace public with getters
@@ -86,7 +73,6 @@
     private String mSuggestIntentData = null;
     private ActionKeyInfo mActionKeyList = null;
     private String mSuggestProviderPackage = null;
-    private Context mCacheActivityContext = null;   // use during setup only - don't hold memory!
     
     // Flag values for Searchable_voiceSearchMode
     private static int VOICE_SEARCH_SHOW_BUTTON = 1;
@@ -97,37 +83,7 @@
     private int mVoicePromptTextId;         // voicePromptText
     private int mVoiceLanguageId;           // voiceLanguage
     private int mVoiceMaxResults;           // voiceMaxResults
-    
-    /**
-     * Set the default searchable activity (when none is specified).
-     */
-    public static void setDefaultSearchable(Context context, 
-                                            ComponentName activity) {
-        synchronized (SearchableInfo.class) {
-            SearchableInfo si = null;
-            if (activity != null) {
-                si = getSearchableInfo(context, activity);
-                if (si != null) {
-                    // move to front of list
-                    sSearchablesList.remove(si);
-                    sSearchablesList.add(0, si);
-                }
-            }
-            sDefaultSearchable = si;
-        }
-    }
-    
-    /**
-     * Provides the system-default search activity, which you can use
-     * whenever getSearchableInfo() returns null;
-     * 
-     * @return Returns the system-default search activity, null if never defined
-     */
-    public static SearchableInfo getDefaultSearchable() {
-        synchronized (SearchableInfo.class) {
-            return sDefaultSearchable;
-        }
-    }
+
     
     /**
      * Retrieve the authority for obtaining search suggestions.
@@ -193,9 +149,16 @@
      * @return Returns a context related to the searchable activity
      */
     public Context getActivityContext(Context context) {
+        return createActivityContext(context, mSearchActivity);
+    }
+    
+    /**
+     * Creates a context for another activity.
+     */
+    private static Context createActivityContext(Context context, ComponentName activity) {
         Context theirContext = null;
         try {
-            theirContext = context.createPackageContext(mSearchActivity.getPackageName(), 0);
+            theirContext = context.createPackageContext(activity.getPackageName(), 0);
         } catch (PackageManager.NameNotFoundException e) {
             // unexpected, but we deal with this by null-checking theirContext
         } catch (java.lang.SecurityException e) {
@@ -234,242 +197,68 @@
     }
     
     /**
-     * Factory.  Look up, or construct, based on the activity.
-     * 
-     * The activities fall into three cases, based on meta-data found in 
-     * the manifest entry:
-     * <ol>
-     * <li>The activity itself implements search.  This is indicated by the
-     * presence of a "android.app.searchable" meta-data attribute.
-     * The value is a reference to an XML file containing search information.</li>
-     * <li>A related activity implements search.  This is indicated by the
-     * presence of a "android.app.default_searchable" meta-data attribute.
-     * The value is a string naming the activity implementing search.  In this
-     * case the factory will "redirect" and return the searchable data.</li>
-     * <li>No searchability data is provided.  We return null here and other
-     * code will insert the "default" (e.g. contacts) search.
-     * 
-     * TODO: cache the result in the map, and check the map first.
-     * TODO: it might make sense to implement the searchable reference as
-     * an application meta-data entry.  This way we don't have to pepper each
-     * and every activity.
-     * TODO: can we skip the constructor step if it's a non-searchable?
-     * TODO: does it make sense to plug the default into a slot here for 
-     * automatic return?  Probably not, but it's one way to do it.
-     *
-     * @param activity The name of the current activity, or null if the 
-     * activity does not define any explicit searchable metadata.
-     */
-    public static SearchableInfo getSearchableInfo(Context context, 
-                                                   ComponentName activity) {
-        // Step 1.  Is the result already hashed?  (case 1)
-        SearchableInfo result;
-        synchronized (SearchableInfo.class) {
-            result = sSearchablesMap.get(activity);
-            if (result != null) return result;
-        }
-        
-        // Step 2.  See if the current activity references a searchable.
-        // Note:  Conceptually, this could be a while(true) loop, but there's
-        // no point in implementing reference chaining here and risking a loop.  
-        // References must point directly to searchable activities.
-       
-        ActivityInfo ai = null;
-        XmlPullParser xml = null;
-        try {
-            ai = context.getPackageManager().
-                       getActivityInfo(activity, PackageManager.GET_META_DATA );
-            String refActivityName = null;
-            
-            // First look for activity-specific reference
-            Bundle md = ai.metaData;
-            if (md != null) {
-                refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
-            }
-            // If not found, try for app-wide reference
-            if (refActivityName == null) {
-                md = ai.applicationInfo.metaData;
-                if (md != null) {
-                    refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
-                }
-            }
-            
-            // Irrespective of source, if a reference was found, follow it.
-            if (refActivityName != null)
-            {
-                // An app or activity can declare that we should simply launch 
-                // "system default search" if search is invoked.
-                if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
-                    return getDefaultSearchable();
-                }
-                String pkg = activity.getPackageName();
-                ComponentName referredActivity;
-                if (refActivityName.charAt(0) == '.') {
-                    referredActivity = new ComponentName(pkg, pkg + refActivityName);
-                } else {
-                    referredActivity = new ComponentName(pkg, refActivityName);
-                }
-
-                // Now try the referred activity, and if found, cache
-                // it against the original name so we can skip the check
-                synchronized (SearchableInfo.class) {
-                    result = sSearchablesMap.get(referredActivity);
-                    if (result != null) {
-                        sSearchablesMap.put(activity, result);
-                        return result;
-                    }
-                }
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // case 3: no metadata
-        }
- 
-        // Step 3.  None found. Return null.
-        return null;
-        
-    }
-    
-    /**
-     * Super-factory.  Builds an entire list (suitable for display) of 
-     * activities that are searchable, by iterating the entire set of 
-     * ACTION_SEARCH intents.  
-     * 
-     * Also clears the hash of all activities -> searches which will
-     * refill as the user clicks "search".
-     * 
-     * This should only be done at startup and again if we know that the
-     * list has changed.
-     * 
-     * TODO: every activity that provides a ACTION_SEARCH intent should
-     * also provide searchability meta-data.  There are a bunch of checks here
-     * that, if data is not found, silently skip to the next activity.  This
-     * won't help a developer trying to figure out why their activity isn't
-     * showing up in the list, but an exception here is too rough.  I would
-     * like to find a better notification mechanism.
-     * 
-     * TODO: sort the list somehow?  UI choice.
-     * 
-     * @param context a context we can use during this work
-     */
-    public static void buildSearchableList(Context context) {
-        
-        // create empty hash & list
-        HashMap<ComponentName, SearchableInfo> newSearchablesMap 
-                                = new HashMap<ComponentName, SearchableInfo>();
-        ArrayList<SearchableInfo> newSearchablesList
-                                = new ArrayList<SearchableInfo>();
-
-        // use intent resolver to generate list of ACTION_SEARCH receivers
-        final PackageManager pm = context.getPackageManager();
-        List<ResolveInfo> infoList;
-        final Intent intent = new Intent(Intent.ACTION_SEARCH);
-        infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
-        
-        // analyze each one, generate a Searchables record, and record
-        if (infoList != null) {
-            int count = infoList.size();
-            for (int ii = 0; ii < count; ii++) {
-                // for each component, try to find metadata
-                ResolveInfo info = infoList.get(ii);
-                ActivityInfo ai = info.activityInfo;
-                XmlResourceParser xml = ai.loadXmlMetaData(context.getPackageManager(), 
-                                                       MD_LABEL_SEARCHABLE);
-                if (xml == null) {
-                    continue;
-                }
-                ComponentName cName = new ComponentName(
-                        info.activityInfo.packageName, 
-                        info.activityInfo.name);
-                
-                SearchableInfo searchable = getActivityMetaData(context, xml, cName);
-                xml.close();
-                
-                if (searchable != null) {
-                    // no need to keep the context any longer.  setup time is over.
-                    searchable.mCacheActivityContext  = null;
-                    
-                    newSearchablesList.add(searchable);
-                    newSearchablesMap.put(cName, searchable);
-                }
-            }
-        }
-        
-        // record the final values as a coherent pair
-        synchronized (SearchableInfo.class) {
-            sSearchablesList = newSearchablesList;
-            sSearchablesMap = newSearchablesMap;
-        }
-    }
-    
-    /**
      * Constructor
      * 
      * Given a ComponentName, get the searchability info
      * and build a local copy of it.  Use the factory, not this.
      * 
-     * @param context runtime context
+     * @param activityContext runtime context for the activity that the searchable info is about.
      * @param attr The attribute set we found in the XML file, contains the values that are used to
      * construct the object.
      * @param cName The component name of the searchable activity
      */
-    private SearchableInfo(Context context, AttributeSet attr, final ComponentName cName) {
+    private SearchableInfo(Context activityContext, AttributeSet attr, final ComponentName cName) {
         // initialize as an "unsearchable" object
         mSearchable = false;
         mSearchActivity = cName;
         
-        // to access another activity's resources, I need its context.
-        // BE SURE to release the cache sometime after construction - it's a large object to hold
-        mCacheActivityContext = getActivityContext(context);
-        if (mCacheActivityContext != null) {
-            TypedArray a = mCacheActivityContext.obtainStyledAttributes(attr,
-                    com.android.internal.R.styleable.Searchable);
-            mSearchMode = a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0);
-            mLabelId = a.getResourceId(com.android.internal.R.styleable.Searchable_label, 0);
-            mHintId = a.getResourceId(com.android.internal.R.styleable.Searchable_hint, 0);
-            mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0);
-            mSearchButtonText = a.getResourceId(
-                    com.android.internal.R.styleable.Searchable_searchButtonText, 0);
-            mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType, 
-                    InputType.TYPE_CLASS_TEXT |
-                    InputType.TYPE_TEXT_VARIATION_NORMAL);
-            mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions, 
-                    EditorInfo.IME_ACTION_SEARCH);
+        TypedArray a = activityContext.obtainStyledAttributes(attr,
+                com.android.internal.R.styleable.Searchable);
+        mSearchMode = a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0);
+        mLabelId = a.getResourceId(com.android.internal.R.styleable.Searchable_label, 0);
+        mHintId = a.getResourceId(com.android.internal.R.styleable.Searchable_hint, 0);
+        mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0);
+        mSearchButtonText = a.getResourceId(
+                com.android.internal.R.styleable.Searchable_searchButtonText, 0);
+        mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType, 
+                InputType.TYPE_CLASS_TEXT |
+                InputType.TYPE_TEXT_VARIATION_NORMAL);
+        mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions, 
+                EditorInfo.IME_ACTION_SEARCH);
 
-            setSearchModeFlags();
-            if (DBG_INHIBIT_SUGGESTIONS == 0) {
-                mSuggestAuthority = a.getString(
-                        com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
-                mSuggestPath = a.getString(
-                        com.android.internal.R.styleable.Searchable_searchSuggestPath);
-                mSuggestSelection = a.getString(
-                        com.android.internal.R.styleable.Searchable_searchSuggestSelection);
-                mSuggestIntentAction = a.getString(
-                        com.android.internal.R.styleable.Searchable_searchSuggestIntentAction);
-                mSuggestIntentData = a.getString(
-                        com.android.internal.R.styleable.Searchable_searchSuggestIntentData);
-            }
-            mVoiceSearchMode = 
-                a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0);
-            // TODO this didn't work - came back zero from YouTube
-            mVoiceLanguageModeId = 
-                a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0);
-            mVoicePromptTextId = 
-                a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0);
-            mVoiceLanguageId = 
-                a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0);
-            mVoiceMaxResults = 
-                a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0);
+        setSearchModeFlags();
+        if (DBG_INHIBIT_SUGGESTIONS == 0) {
+            mSuggestAuthority = a.getString(
+                    com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
+            mSuggestPath = a.getString(
+                    com.android.internal.R.styleable.Searchable_searchSuggestPath);
+            mSuggestSelection = a.getString(
+                    com.android.internal.R.styleable.Searchable_searchSuggestSelection);
+            mSuggestIntentAction = a.getString(
+                    com.android.internal.R.styleable.Searchable_searchSuggestIntentAction);
+            mSuggestIntentData = a.getString(
+                    com.android.internal.R.styleable.Searchable_searchSuggestIntentData);
+        }
+        mVoiceSearchMode = 
+            a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0);
+        // TODO this didn't work - came back zero from YouTube
+        mVoiceLanguageModeId = 
+            a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0);
+        mVoicePromptTextId = 
+            a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0);
+        mVoiceLanguageId = 
+            a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0);
+        mVoiceMaxResults = 
+            a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0);
 
-            a.recycle();
+        a.recycle();
 
-            // get package info for suggestions provider (if any)
-            if (mSuggestAuthority != null) {
-                ProviderInfo pi =
-                    context.getPackageManager().resolveContentProvider(mSuggestAuthority,
-                            0);
-                if (pi != null) {
-                    mSuggestProviderPackage = pi.packageName;
-                }
+        // get package info for suggestions provider (if any)
+        if (mSuggestAuthority != null) {
+            PackageManager pm = activityContext.getPackageManager();
+            ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority, 0);
+            if (pi != null) {
+                mSuggestProviderPackage = pi.packageName;
             }
         }
 
@@ -496,7 +285,7 @@
     /**
      * Private class used to hold the "action key" configuration
      */
-    public class ActionKeyInfo implements Parcelable {
+    public static class ActionKeyInfo implements Parcelable {
         
         public int mKeyCode = 0;
         public String mQueryActionMsg;
@@ -506,14 +295,15 @@
         
         /**
          * Create one object using attributeset as input data.
-         * @param context runtime context
+         * @param activityContext runtime context of the activity that the action key information
+         *        is about.
          * @param attr The attribute set we found in the XML file, contains the values that are used to
          * construct the object.
          * @param next We'll build these up using a simple linked list (since there are usually
          * just zero or one).
          */
-        public ActionKeyInfo(Context context, AttributeSet attr, ActionKeyInfo next) {
-            TypedArray a = mCacheActivityContext.obtainStyledAttributes(attr,
+        public ActionKeyInfo(Context activityContext, AttributeSet attr, ActionKeyInfo next) {
+            TypedArray a = activityContext.obtainStyledAttributes(attr,
                     com.android.internal.R.styleable.SearchableActionKey);
 
             mKeyCode = a.getInt(
@@ -584,6 +374,20 @@
         return null;
     }
     
+    public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) {
+        // for each component, try to find metadata
+        XmlResourceParser xml = 
+                activityInfo.loadXmlMetaData(context.getPackageManager(), MD_LABEL_SEARCHABLE);
+        if (xml == null) {
+            return null;
+        }
+        ComponentName cName = new ComponentName(activityInfo.packageName, activityInfo.name);
+        
+        SearchableInfo searchable = getActivityMetaData(context, xml, cName);
+        xml.close();
+        return searchable;
+    }
+    
     /**
      * Get the metadata for a given activity
      * 
@@ -598,6 +402,7 @@
     private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml,
             final ComponentName cName)  {
         SearchableInfo result = null;
+        Context activityContext = createActivityContext(context, cName);
         
         // in order to use the attributes mechanism, we have to walk the parser
         // forward through the file until it's reading the tag of interest.
@@ -608,7 +413,7 @@
                     if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) {
                         AttributeSet attr = Xml.asAttributeSet(xml);
                         if (attr != null) {
-                            result = new SearchableInfo(context, attr, cName);
+                            result = new SearchableInfo(activityContext, attr, cName);
                             // if the constructor returned a bad object, exit now.
                             if (! result.mSearchable) {
                                 return null;
@@ -621,7 +426,7 @@
                         }
                         AttributeSet attr = Xml.asAttributeSet(xml);
                         if (attr != null) {
-                            ActionKeyInfo keyInfo = result.new ActionKeyInfo(context, attr, 
+                            ActionKeyInfo keyInfo = new ActionKeyInfo(activityContext, attr, 
                                     result.mActionKeyList);
                             // only add to list if it is was useable
                             if (keyInfo.mKeyCode != 0) {
@@ -637,6 +442,7 @@
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
+        
         return result;
     }
     
@@ -757,16 +563,6 @@
     }
     
     /**
-     * Return the list of searchable activities, for use in the drop-down.
-     */
-    public static ArrayList<SearchableInfo> getSearchablesList() {
-        synchronized (SearchableInfo.class) {
-            ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(sSearchablesList);
-            return result;
-        }
-    }
-    
-    /**
      * Support for parcelable and aidl operations.
      */
     public static final Parcelable.Creator<SearchableInfo> CREATOR
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
new file mode 100644
index 0000000..ba75d21
--- /dev/null
+++ b/core/java/android/server/search/Searchables.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.search;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class maintains the information about all searchable activities. 
+ */
+public class Searchables {
+
+    // static strings used for XML lookups, etc.
+    // TODO how should these be documented for the developer, in a more structured way than 
+    // the current long wordy javadoc in SearchManager.java ?
+    private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
+    private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
+    
+    private Context mContext;
+    
+    private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
+    private ArrayList<SearchableInfo> mSearchablesList = null;
+    private SearchableInfo mDefaultSearchable = null;
+    
+    /**
+     * 
+     * @param context Context to use for looking up activities etc.
+     */
+    public Searchables (Context context) {
+        mContext = context;
+    }
+    
+    /**
+     * Look up, or construct, based on the activity.
+     * 
+     * The activities fall into three cases, based on meta-data found in 
+     * the manifest entry:
+     * <ol>
+     * <li>The activity itself implements search.  This is indicated by the
+     * presence of a "android.app.searchable" meta-data attribute.
+     * The value is a reference to an XML file containing search information.</li>
+     * <li>A related activity implements search.  This is indicated by the
+     * presence of a "android.app.default_searchable" meta-data attribute.
+     * The value is a string naming the activity implementing search.  In this
+     * case the factory will "redirect" and return the searchable data.</li>
+     * <li>No searchability data is provided.  We return null here and other
+     * code will insert the "default" (e.g. contacts) search.
+     * 
+     * TODO: cache the result in the map, and check the map first.
+     * TODO: it might make sense to implement the searchable reference as
+     * an application meta-data entry.  This way we don't have to pepper each
+     * and every activity.
+     * TODO: can we skip the constructor step if it's a non-searchable?
+     * TODO: does it make sense to plug the default into a slot here for 
+     * automatic return?  Probably not, but it's one way to do it.
+     *
+     * @param activity The name of the current activity, or null if the 
+     * activity does not define any explicit searchable metadata.
+     */
+    public SearchableInfo getSearchableInfo(ComponentName activity) {
+        // Step 1.  Is the result already hashed?  (case 1)
+        SearchableInfo result;
+        synchronized (this) {
+            result = mSearchablesMap.get(activity);
+            if (result != null) return result;
+        }
+        
+        // Step 2.  See if the current activity references a searchable.
+        // Note:  Conceptually, this could be a while(true) loop, but there's
+        // no point in implementing reference chaining here and risking a loop.  
+        // References must point directly to searchable activities.
+       
+        ActivityInfo ai = null;
+        try {
+            ai = mContext.getPackageManager().
+                       getActivityInfo(activity, PackageManager.GET_META_DATA );
+            String refActivityName = null;
+            
+            // First look for activity-specific reference
+            Bundle md = ai.metaData;
+            if (md != null) {
+                refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+            }
+            // If not found, try for app-wide reference
+            if (refActivityName == null) {
+                md = ai.applicationInfo.metaData;
+                if (md != null) {
+                    refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+                }
+            }
+            
+            // Irrespective of source, if a reference was found, follow it.
+            if (refActivityName != null)
+            {
+                // An app or activity can declare that we should simply launch 
+                // "system default search" if search is invoked.
+                if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
+                    return getDefaultSearchable();
+                }
+                String pkg = activity.getPackageName();
+                ComponentName referredActivity;
+                if (refActivityName.charAt(0) == '.') {
+                    referredActivity = new ComponentName(pkg, pkg + refActivityName);
+                } else {
+                    referredActivity = new ComponentName(pkg, refActivityName);
+                }
+
+                // Now try the referred activity, and if found, cache
+                // it against the original name so we can skip the check
+                synchronized (this) {
+                    result = mSearchablesMap.get(referredActivity);
+                    if (result != null) {
+                        mSearchablesMap.put(activity, result);
+                        return result;
+                    }
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // case 3: no metadata
+        }
+ 
+        // Step 3.  None found. Return null.
+        return null;
+        
+    }
+    
+    /**
+     * Set the default searchable activity (when none is specified).
+     */
+    public synchronized void setDefaultSearchable(ComponentName activity) {
+        SearchableInfo si = null;
+        if (activity != null) {
+            si = getSearchableInfo(activity);
+            if (si != null) {
+                // move to front of list
+                mSearchablesList.remove(si);
+                mSearchablesList.add(0, si);
+            }
+        }
+        mDefaultSearchable = si;
+    }
+    
+    /**
+     * Provides the system-default search activity, which you can use
+     * whenever getSearchableInfo() returns null;
+     * 
+     * @return Returns the system-default search activity, null if never defined
+     */
+    public synchronized SearchableInfo getDefaultSearchable() {
+        return mDefaultSearchable;
+    }
+    
+    public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
+        return searchable == mDefaultSearchable;
+    }
+   
+    /**
+     * Builds an entire list (suitable for display) of 
+     * activities that are searchable, by iterating the entire set of 
+     * ACTION_SEARCH intents.  
+     * 
+     * Also clears the hash of all activities -> searches which will
+     * refill as the user clicks "search".
+     * 
+     * This should only be done at startup and again if we know that the
+     * list has changed.
+     * 
+     * TODO: every activity that provides a ACTION_SEARCH intent should
+     * also provide searchability meta-data.  There are a bunch of checks here
+     * that, if data is not found, silently skip to the next activity.  This
+     * won't help a developer trying to figure out why their activity isn't
+     * showing up in the list, but an exception here is too rough.  I would
+     * like to find a better notification mechanism.
+     * 
+     * TODO: sort the list somehow?  UI choice.
+     */
+    public void buildSearchableList() {
+        
+        // create empty hash & list
+        HashMap<ComponentName, SearchableInfo> newSearchablesMap 
+                                = new HashMap<ComponentName, SearchableInfo>();
+        ArrayList<SearchableInfo> newSearchablesList
+                                = new ArrayList<SearchableInfo>();
+
+        // use intent resolver to generate list of ACTION_SEARCH receivers
+        final PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> infoList;
+        final Intent intent = new Intent(Intent.ACTION_SEARCH);
+        infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+        
+        // analyze each one, generate a Searchables record, and record
+        if (infoList != null) {
+            int count = infoList.size();
+            for (int ii = 0; ii < count; ii++) {
+                // for each component, try to find metadata
+                ResolveInfo info = infoList.get(ii);
+                ActivityInfo ai = info.activityInfo;
+                SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
+                if (searchable != null) {
+                    newSearchablesList.add(searchable);
+                    newSearchablesMap.put(searchable.mSearchActivity, searchable);
+                }
+            }
+        }
+        
+        // record the final values as a coherent pair
+        synchronized (this) {
+            mSearchablesList = newSearchablesList;
+            mSearchablesMap = newSearchablesMap;
+        }
+    }
+    
+    /**
+     * Returns the list of searchable activities.
+     */
+    public synchronized ArrayList<SearchableInfo> getSearchablesList() {
+        ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
+        return result;
+    }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 772ad89..b408f27 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -983,18 +983,6 @@
         mSelectorRect.setEmpty();
         invalidate();
     }
-    
-    /**
-     * The list is empty and we need to change the layout, so *really* clear everything out.
-     * @hide - for AutoCompleteTextView & SearchDialog only
-     */
-    /* package */ void resetListAndClearViews() {
-        rememberSyncState();
-        removeAllViewsInLayout();
-        mRecycler.clear();
-        mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
-        requestLayout();
-    }
 
     @Override
     protected int computeVerticalScrollExtent() {
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index dfb971e..e3186b0 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -110,6 +110,10 @@
     private final DropDownItemClickListener mDropDownItemClickListener =
             new DropDownItemClickListener();
 
+    private boolean mDropDownAlwaysVisible = false;
+
+    private boolean mDropDownDismissedOnCompletion = true;
+
     private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
     private boolean mOpenBefore;
 
@@ -211,6 +215,8 @@
      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
      * 
      * @return the width for the drop down list
+     * 
+     * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
      */
     public int getDropDownWidth() {
         return mDropDownWidth;
@@ -222,6 +228,8 @@
      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
      * 
      * @param width the width to use
+     * 
+     * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
      */
     public void setDropDownWidth(int width) {
         mDropDownWidth = width;
@@ -231,6 +239,8 @@
      * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
      *  
      * @return the view's id, or {@link View#NO_ID} if none specified
+     * 
+     * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
      */
     public int getDropDownAnchor() {
         return mDropDownAnchorId;
@@ -242,13 +252,173 @@
      * loading a view which is not yet instantiated.</p>
      * 
      * @param id the id to anchor the drop down list view to
+     * 
+     * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor 
      */
     public void setDropDownAnchor(int id) {
         mDropDownAnchorId = id;
         mDropDownAnchorView = null;
     }
+    
+    /**
+     * <p>Gets the background of the auto-complete drop-down list.</p>
+     * 
+     * @return the background drawable
+     * 
+     * @attr ref android.R.styleable#PopupWindow_popupBackground
+     *
+     * @hide Pending API council approval
+     */
+    public Drawable getDropDownBackground() {
+        return mPopup.getBackground();
+    }
+    
+    /**
+     * <p>Sets the background of the auto-complete drop-down list.</p>
+     * 
+     * @param d the drawable to set as the background
+     * 
+     * @attr ref android.R.styleable#PopupWindow_popupBackground
+     *
+     * @hide Pending API council approval
+     */
+    public void setDropDownBackgroundDrawable(Drawable d) {
+        mPopup.setBackgroundDrawable(d);
+    }
+    
+    /**
+     * <p>Sets the background of the auto-complete drop-down list.</p>
+     * 
+     * @param id the id of the drawable to set as the background
+     * 
+     * @attr ref android.R.styleable#PopupWindow_popupBackground
+     *
+     * @hide Pending API council approval
+     */
+    public void setDropDownBackgroundResource(int id) {
+        mPopup.setBackgroundDrawable(getResources().getDrawable(id));
+    }
 
     /**
+     * <p>Sets the animation style of the auto-complete drop-down list.</p>
+     *
+     * <p>If the drop-down is showing, calling this method will take effect only
+     * the next time the drop-down is shown.</p>
+     *
+     * @param animationStyle animation style to use when the drop-down appears
+     *      and disappears.  Set to -1 for the default animation, 0 for no
+     *      animation, or a resource identifier for an explicit animation.
+     *
+     * @hide Pending API council approval
+     */
+    public void setDropDownAnimationStyle(int animationStyle) {
+        mPopup.setAnimationStyle(animationStyle);
+    }
+
+    /**
+     * <p>Returns the animation style that is used when the drop-down list appears and disappears
+     * </p>
+     *
+     * @return the animation style that is used when the drop-down list appears and disappears
+     *
+     * @hide Pending API council approval
+     */
+    public int getDropDownAnimationStyle() {
+        return mPopup.getAnimationStyle();
+    }
+    
+    /**
+     * <p>Sets the vertical offset used for the auto-complete drop-down list.</p>
+     * 
+     * @param offset the vertical offset
+     *
+     * @hide Pending API council approval
+     */
+    public void setDropDownVerticalOffset(int offset) {
+        mDropDownVerticalOffset = offset;
+    }
+    
+    /**
+     * <p>Gets the vertical offset used for the auto-complete drop-down list.</p>
+     * 
+     * @return the vertical offset
+     *
+     * @hide Pending API council approval
+     */
+    public int getDropDownVerticalOffset() {
+        return mDropDownVerticalOffset;
+    }
+    
+    /**
+     * <p>Sets the horizontal offset used for the auto-complete drop-down list.</p>
+     * 
+     * @param offset the horizontal offset
+     *
+     * @hide Pending API council approval
+     */
+    public void setDropDownHorizontalOffset(int offset) {
+        mDropDownHorizontalOffset = offset;
+    }
+    
+    /**
+     * <p>Gets the horizontal offset used for the auto-complete drop-down list.</p>
+     * 
+     * @return the horizontal offset
+     *
+     * @hide Pending API council approval
+     */
+    public int getDropDownHorizontalOffset() {
+        return mDropDownHorizontalOffset;
+    }
+
+    /**
+     * @return Whether the drop-down is visible as long as there is {@link #enoughToFilter()}
+     *
+     * @hide Pending API council approval
+     */
+    public boolean isDropDownAlwaysVisible() {
+        return mDropDownAlwaysVisible;
+    }
+
+    /**
+     * Sets whether the drop-down should remain visible as long as there is there is
+     * {@link #enoughToFilter()}.  This is useful if an unknown number of results are expected
+     * to show up in the adapter sometime in the future.
+     *
+     * The drop-down will occupy the entire screen below {@link #getDropDownAnchor} regardless
+     * of the size or content of the list.  {@link #getDropDownBackground()} will fill any space
+     * that is not used by the list.
+     *
+     * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
+     *
+     * @hide Pending API council approval
+     */
+    public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
+        mDropDownAlwaysVisible = dropDownAlwaysVisible;
+    }
+   
+    /**
+     * Checks whether the drop-down is dismissed when a suggestion is clicked.
+     * 
+     * @hide Pending API council approval
+     */
+    public boolean isDropDownDismissedOnCompletion() {
+        return mDropDownDismissedOnCompletion;
+    }
+    
+    /**
+     * Sets whether the drop-down is dismissed when a suggestion is clicked. This is 
+     * true by default.
+     * 
+     * @param dropDownDismissedOnCompletion Whether to dismiss the drop-down.
+     * 
+     * @hide Pending API council approval
+     */
+    public void setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion) {
+        mDropDownDismissedOnCompletion = dropDownDismissedOnCompletion;
+    }
+ 
+    /**
      * <p>Returns the number of characters the user must type before the drop
      * down list is shown.</p>
      *
@@ -628,16 +798,6 @@
         }
         return ListView.INVALID_POSITION;
     }
-    
-    /**
-     * We're changing the adapter and its views so really, really clear everything out
-     * @hide - for SearchDialog only
-     */
-    public void resetListAndClearViews() {
-        if (mDropDownList != null) {
-            mDropDownList.resetListAndClearViews();
-        }
-    }
 
     /**
      * <p>Starts filtering the content of the drop down list. The filtering
@@ -709,7 +869,9 @@
             }
         }
 
-        dismissDropDown();
+        if (mDropDownDismissedOnCompletion) {
+            dismissDropDown();
+        }
     }
     
     /**
@@ -721,6 +883,42 @@
     }
 
     /**
+     * Like {@link #setText(CharSequence)}, except that it can disable filtering.
+     *
+     * @param filter If <code>false</code>, no filtering will be performed
+     *        as a result of this call.
+     * 
+     * @hide Pending API council approval.
+     */
+    public void setText(CharSequence text, boolean filter) {
+        if (filter) {
+            setText(text);
+        } else {
+            mBlockCompletion = true;
+            setText(text);
+            mBlockCompletion = false;
+        }
+    }
+    
+    /**
+     * Like {@link #setTextKeepState(CharSequence)}, except that it can disable filtering.
+     *
+     * @param filter If <code>false</code>, no filtering will be performed
+     *        as a result of this call.
+     * 
+     * @hide Pending API council approval.
+     */
+    public void setTextKeepState(CharSequence text, boolean filter) {
+        if (filter) {
+            setTextKeepState(text);
+        } else {
+            mBlockCompletion = true;
+            setTextKeepState(text);
+            mBlockCompletion = false;
+        }
+    }
+    
+    /**
      * <p>Performs the text completion by replacing the current text by the
      * selected item. Subclasses should override this method to avoid replacing
      * the whole content of the edit box.</p>
@@ -734,6 +932,7 @@
         Selection.setSelection(spannable, spannable.length());
     }
 
+    /** {@inheritDoc} */
     public void onFilterComplete(int count) {
         if (mAttachCount <= 0) return;
 
@@ -744,7 +943,7 @@
          * to filter.
          */
 
-        if (count > 0 && enoughToFilter()) {
+        if ((count > 0 || mDropDownAlwaysVisible) && enoughToFilter()) {
             if (hasFocus() && hasWindowFocus()) {
                 showDropDown();
             }
@@ -809,22 +1008,6 @@
     }
     
     /**
-     * Set the horizontal offset with respect to {@link #setDropDownAnchor(int)}
-     * @hide pending API council review
-     */
-    public void setDropDownHorizontalOffset(int horizontalOffset) {
-        mDropDownHorizontalOffset = horizontalOffset;
-    }
-    
-    /**
-     * Set the vertical offset with respect to {@link #setDropDownAnchor(int)}
-     * @hide pending API council review
-     */
-    public void setDropDownVerticalOffset(int verticalOffset) {
-        mDropDownVerticalOffset = verticalOffset;
-    }
-
-    /**
      * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
      * the id is NO_ID or we can't find a view for the given id, we return this TextView as
      * the default anchoring point.</p>
@@ -856,10 +1039,9 @@
                     mDropDownVerticalOffset, widthSpec, height);
         } else {
             if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
-                mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT);
+                mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT, 0);
             } else {
-                mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
+                mPopup.setWindowLayoutMode(0, 0);
                 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
                     mPopup.setWidth(getDropDownAnchorView().getWidth());
                 } else {
@@ -966,8 +1148,10 @@
         final int maxHeight = mPopup.getMaxAvailableHeight(this, mDropDownVerticalOffset);
         //otherHeights += dropDownView.getPaddingTop() + dropDownView.getPaddingBottom();
 
-        return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
+        final int measuredHeight = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
                 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
+
+        return mDropDownAlwaysVisible ? maxHeight : measuredHeight;
     }
 
     private View getHintView(Context context) {
diff --git a/core/java/com/android/internal/util/TypedProperties.java b/core/java/com/android/internal/util/TypedProperties.java
new file mode 100644
index 0000000..48479e3
--- /dev/null
+++ b/core/java/com/android/internal/util/TypedProperties.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * A {@code Map} that publishes a set of typed properties, defined by
+ * zero or more {@code Reader}s containing textual definitions and assignments.
+ */
+public class TypedProperties extends HashMap<String, Object> {
+    /**
+     * Instantiates a {@link java.io.StreamTokenizer} and sets its syntax tables
+     * appropriately for the {@code TypedProperties} file format.
+     *
+     * @param r The {@code Reader} that the {@code StreamTokenizer} will read from
+     * @return a newly-created and initialized {@code StreamTokenizer}
+     */
+    static StreamTokenizer initTokenizer(Reader r) {
+        StreamTokenizer st = new StreamTokenizer(r);
+
+        // Treat everything we don't specify as "ordinary".
+        st.resetSyntax();
+
+        /* The only non-quoted-string words we'll be reading are:
+         * - property names: [._$a-zA-Z0-9]
+         * - type names (case insensitive): [a-zA-Z]
+         * - number literals: [-0-9.eExXA-Za-z]  ('x' for 0xNNN hex literals. "NaN", "Infinity")
+         * - "true" or "false" (case insensitive): [a-zA-Z]
+         */
+        st.wordChars('0', '9');
+        st.wordChars('A', 'Z');
+        st.wordChars('a', 'z');
+        st.wordChars('_', '_');
+        st.wordChars('$', '$');
+        st.wordChars('.', '.');
+        st.wordChars('-', '-');
+        st.wordChars('+', '+');
+
+        // Single-character tokens
+        st.ordinaryChar('=');
+
+        // Other special characters
+        st.whitespaceChars(' ', ' ');
+        st.whitespaceChars('\t', '\t');
+
+        st.quoteChar('"');
+
+        st.commentChar('#');
+
+        st.eolIsSignificant(true);
+
+        return st;
+    }
+
+
+    /**
+     * An unchecked exception that is thrown when encountering a syntax
+     * or semantic error in the input.
+     */
+    public static class ParseException extends IllegalArgumentException {
+        ParseException(StreamTokenizer state, String expected) {
+            super("expected " + expected + ", saw " + state.toString());
+        }
+    }
+
+    // A sentinel instance used to indicate a null string.
+    static final String NULL_STRING = new String("<TypedProperties:NULL_STRING>");
+
+    // Constants used to represent the supported types.
+    static final int TYPE_UNSET = 'x';
+    static final int TYPE_BOOLEAN = 'Z';
+    static final int TYPE_BYTE = 'I' | 1 << 8;
+    // TYPE_CHAR: character literal syntax not supported; use short.
+    static final int TYPE_SHORT = 'I' | 2 << 8;
+    static final int TYPE_INT = 'I' | 4 << 8;
+    static final int TYPE_LONG = 'I' | 8 << 8;
+    static final int TYPE_FLOAT = 'F' | 4 << 8;
+    static final int TYPE_DOUBLE = 'F' | 8 << 8;
+    static final int TYPE_STRING = 'L' | 's' << 8;
+    static final int TYPE_ERROR = -1;
+
+    /**
+     * Converts a case-insensitive string to an internal type constant.
+     *
+     * @param typeName the type name to convert
+     * @return the type constant that corresponds to {@code typeName},
+     *         or {@code TYPE_ERROR} if the type is unknown
+     */
+    static int interpretType(String typeName) {
+        if ("unset".equalsIgnoreCase(typeName)) {
+            return TYPE_UNSET;
+        } else if ("boolean".equalsIgnoreCase(typeName)) {
+            return TYPE_BOOLEAN;
+        } else if ("byte".equalsIgnoreCase(typeName)) {
+            return TYPE_BYTE;
+        } else if ("short".equalsIgnoreCase(typeName)) {
+            return TYPE_SHORT;
+        } else if ("int".equalsIgnoreCase(typeName)) {
+            return TYPE_INT;
+        } else if ("long".equalsIgnoreCase(typeName)) {
+            return TYPE_LONG;
+        } else if ("float".equalsIgnoreCase(typeName)) {
+            return TYPE_FLOAT;
+        } else if ("double".equalsIgnoreCase(typeName)) {
+            return TYPE_DOUBLE;
+        } else if ("string".equalsIgnoreCase(typeName)) {
+            return TYPE_STRING;
+        }
+        return TYPE_ERROR;
+    }
+
+    /**
+     * Consumes EOL tokens.
+     * Returns when a non-EOL token is found.
+     *
+     * @param st The {@code StreamTokenizer} to read tokens from
+     * @return &gt; 0 if an EOL token was seen, &lt; 0 if EOF was seen,
+     *         0 if no tokens were consumed
+     */
+    static int eatEols(StreamTokenizer st) throws IOException {
+        int token;
+        boolean eolSeen = false;
+        do {
+            token = st.nextToken();
+            if (token == StreamTokenizer.TT_EOF) {
+                return -1;
+            } else if (token == StreamTokenizer.TT_EOL) {
+                eolSeen = true;
+            }
+        } while (token == StreamTokenizer.TT_EOL);
+        st.pushBack();
+        return eolSeen ? 1 : 0;
+    }
+
+    /**
+     * Parses the data in the reader.
+     *
+     * @param r The {@code Reader} containing input data to parse
+     * @param map The {@code Map} to insert parameter values into
+     * @throws ParseException if the input data is malformed
+     * @throws IOException if there is a problem reading from the {@code Reader}
+     */
+    static void parse(Reader r, Map<String, Object> map) throws ParseException, IOException {
+        final StreamTokenizer st = initTokenizer(r);
+
+        /* A property name must be a valid fully-qualified class + package name.
+         * We don't support Unicode, though.
+         */
+        final String identifierPattern = "[a-zA-Z_$][0-9a-zA-Z_$]*";
+        final Pattern propertyNamePattern =
+            Pattern.compile("(" + identifierPattern + "\\.)*" + identifierPattern);
+
+
+        boolean eolNeeded = false;
+        while (true) {
+            int token;
+
+            // Eat one or more EOL, or quit on EOF.
+            int eolStatus = eatEols(st);
+            if (eolStatus < 0) {
+                // EOF occurred.
+                break;
+            } else if (eolNeeded && eolStatus == 0) {
+                throw new ParseException(st, "end of line or end of file");
+            }
+
+            // Read the property name.
+            token = st.nextToken();
+            if (token != StreamTokenizer.TT_WORD) {
+                throw new ParseException(st, "property name");
+            }
+            final String propertyName = st.sval;
+            if (!propertyNamePattern.matcher(propertyName).matches()) {
+                throw new ParseException(st, "valid property name");
+            }
+            st.sval = null;
+
+            // Read the type.
+            token = st.nextToken();
+            if (token != StreamTokenizer.TT_WORD) {
+                throw new ParseException(st, "type name");
+            }
+            final int type = interpretType(st.sval);
+            if (type == TYPE_ERROR) {
+                throw new ParseException(st, "valid type name");
+            }
+            st.sval = null;
+
+            if (type == TYPE_UNSET) {
+                map.remove(propertyName);
+            } else {
+                // Expect '='.
+                token = st.nextToken();
+                if (token != '=') {
+                    throw new ParseException(st, "'='");
+                }
+
+                // Read a value of the appropriate type, and insert into the map.
+                final Object value = parseValue(st, type);
+                final Object oldValue = map.remove(propertyName);
+                if (oldValue != null) {
+                    // TODO: catch the case where a string is set to null and then
+                    //       the same property is defined with a different type.
+                    if (value.getClass() != oldValue.getClass()) {
+                        throw new ParseException(st,
+                            "(property previously declared as a different type)");
+                    }
+                }
+                map.put(propertyName, value);
+            }
+
+            // Require that we see at least one EOL before the next token.
+            eolNeeded = true;
+        }
+    }
+
+    /**
+     * Parses the next token in the StreamTokenizer as the specified type.
+     *
+     * @param st The token source
+     * @param type The type to interpret next token as
+     * @return a Boolean, Number subclass, or String representing the value.
+     *         Null strings are represented by the String instance NULL_STRING
+     * @throws IOException if there is a problem reading from the {@code StreamTokenizer}
+     */
+    static Object parseValue(StreamTokenizer st, final int type) throws IOException {
+        final int token = st.nextToken();
+
+        if (type == TYPE_BOOLEAN) {
+            if (token != StreamTokenizer.TT_WORD) {
+                throw new ParseException(st, "boolean constant");
+            }
+
+            if ("true".equalsIgnoreCase(st.sval)) {
+                return Boolean.TRUE;
+            } else if ("false".equalsIgnoreCase(st.sval)) {
+                return Boolean.FALSE;
+            }
+
+            throw new ParseException(st, "boolean constant");
+        } else if ((type & 0xff) == 'I') {
+            if (token != StreamTokenizer.TT_WORD) {
+                throw new ParseException(st, "integer constant");
+            }
+
+            /* Parse the string.  Long.decode() handles C-style integer constants
+             * ("0x" -> hex, "0" -> octal).  It also treats numbers with a prefix of "#" as
+             * hex, but our syntax intentionally does not list '#' as a word character.
+             */
+            long value;
+            try {
+                value = Long.decode(st.sval);
+            } catch (NumberFormatException ex) {
+                throw new ParseException(st, "integer constant");
+            }
+
+            // Ensure that the type can hold this value, and return.
+            int width = (type >> 8) & 0xff;
+            switch (width) {
+            case 1:
+                if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
+                    throw new ParseException(st, "8-bit integer constant");
+                }
+                return new Byte((byte)value);
+            case 2:
+                if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+                    throw new ParseException(st, "16-bit integer constant");
+                }
+                return new Short((short)value);
+            case 4:
+                if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
+                    throw new ParseException(st, "32-bit integer constant");
+                }
+                return new Integer((int)value);
+            case 8:
+                if (value < Long.MIN_VALUE || value > Long.MAX_VALUE) {
+                    throw new ParseException(st, "64-bit integer constant");
+                }
+                return new Long(value);
+            default:
+                throw new IllegalStateException(
+                    "Internal error; unexpected integer type width " + width);
+            }
+        } else if ((type & 0xff) == 'F') {
+            if (token != StreamTokenizer.TT_WORD) {
+                throw new ParseException(st, "float constant");
+            }
+
+            // Parse the string.
+            /* TODO: Maybe just parse as float or double, losing precision if necessary.
+             *       Parsing as double and converting to float can change the value
+             *       compared to just parsing as float.
+             */
+            double value;
+            try {
+                /* TODO: detect if the string representation loses precision
+                 *       when being converted to a double.
+                 */
+                value = Double.parseDouble(st.sval);
+            } catch (NumberFormatException ex) {
+                throw new ParseException(st, "float constant");
+            }
+
+            // Ensure that the type can hold this value, and return.
+            if (((type >> 8) & 0xff) == 4) {
+                // This property is a float; make sure the value fits.
+                double absValue = Math.abs(value);
+                if (absValue != 0.0 && !Double.isInfinite(value) && !Double.isNaN(value)) {
+                    if (absValue < Float.MIN_VALUE || absValue > Float.MAX_VALUE) {
+                        throw new ParseException(st, "32-bit float constant");
+                    }
+                }
+                return new Float((float)value);
+            } else {
+                // This property is a double; no need to truncate.
+                return new Double(value);
+            }
+        } else if (type == TYPE_STRING) {
+            // Expect a quoted string or the word "null".
+            if (token == '"') {
+                return st.sval;
+            } else if (token == StreamTokenizer.TT_WORD && "null".equalsIgnoreCase(st.sval)) {
+                return NULL_STRING;
+            }
+            throw new ParseException(st, "double-quoted string or 'null'");
+        }
+
+        throw new IllegalStateException("Internal error; unknown type " + type);
+    }
+
+
+    /**
+     * Creates an empty TypedProperties instance.
+     */
+    public TypedProperties() {
+        super();
+    }
+
+    /**
+     * Loads zero or more properties from the specified Reader.
+     * Properties that have already been loaded are preserved unless
+     * the new Reader overrides or unsets earlier values for the
+     * same properties.
+     *
+     * File syntax:
+     *
+     *     &lt;property-name&gt; &lt;type&gt; = &lt;value&gt;
+     *     &lt;property-name&gt; unset
+     *
+     *     '#' is a comment character; it and anything appearing after it
+     *     on the line is ignored.
+     *
+     *     Blank lines are ignored.
+     *
+     *     The only required whitespace is between the property name
+     *     and the type.
+     *
+     *     Property assignments may not be split across multiple lines.
+     *
+     *     &lt;property-name&gt; is a valid fully-qualified class name
+     *     (one or more valid identifiers separated by dot characters).
+     *
+     *     &lt;type&gt; is one of {boolean, byte, short, int, long,
+     *     float, double, string}, and is case-insensitive.
+     *
+     *     &lt;value&gt; depends on the type:
+     *     - boolean: one of {true, false} (case-insensitive)
+     *     - byte, short, int, long: a valid Java integer constant
+     *       (including non-base-10 constants like 0xabc and 074)
+     *       whose value does not overflow the type.  NOTE: these are
+     *       interpreted as Java integer values, so they are all signed.
+     *     - float, double: a valid Java floating-point constant.
+     *       If the type is float, the value must fit in 32 bits.
+     *     - string: a double-quoted string value, or the word {@code null}.
+     *       NOTE: the contents of the string must be 7-bit clean ASCII;
+     *       C-style octal escapes are recognized, but Unicode escapes are not.
+     *
+     *
+     * @param r The Reader to load properties from
+     * @throws IOException if an error occurs when reading the data
+     * @throws IllegalArgumentException if the data is malformed
+     */
+    public void load(Reader r) throws IOException {
+        parse(r, this);
+    }
+
+    @Override
+    public Object get(Object key) {
+        Object value = super.get(key);
+        if (value == NULL_STRING) {
+            return null;
+        }
+        return value;
+    }
+
+    /*
+     * Getters with explicit defaults
+     */
+
+    /**
+     * An unchecked exception that is thrown if a {@code get&lt;TYPE&gt;()} method
+     * is used to retrieve a parameter whose type does not match the method name.
+     */
+    public static class TypeException extends IllegalArgumentException {
+        TypeException(String property, Object value, String requestedType) {
+            super(property + " has type " + value.getClass().getName() +
+                ", not " + requestedType);
+        }
+    }
+
+    /**
+     * Returns the value of a boolean property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a boolean
+     */
+    public boolean getBoolean(String property, boolean def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value instanceof Boolean) {
+            return ((Boolean)value).booleanValue();
+        }
+        throw new TypeException(property, value, "boolean");
+    }
+
+    /**
+     * Returns the value of a byte property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a byte
+     */
+    public byte getByte(String property, byte def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value instanceof Byte) {
+            return ((Byte)value).byteValue();
+        }
+        throw new TypeException(property, value, "byte");
+    }
+
+    /**
+     * Returns the value of a short property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a short
+     */
+    public short getShort(String property, short def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value instanceof Short) {
+            return ((Short)value).shortValue();
+        }
+        throw new TypeException(property, value, "short");
+    }
+
+    /**
+     * Returns the value of an integer property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not an integer
+     */
+    public int getInt(String property, int def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value instanceof Integer) {
+            return ((Integer)value).intValue();
+        }
+        throw new TypeException(property, value, "int");
+    }
+
+    /**
+     * Returns the value of a long property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a long
+     */
+    public long getLong(String property, long def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value instanceof Long) {
+            return ((Long)value).longValue();
+        }
+        throw new TypeException(property, value, "long");
+    }
+
+    /**
+     * Returns the value of a float property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a float
+     */
+    public float getFloat(String property, float def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value instanceof Float) {
+            return ((Float)value).floatValue();
+        }
+        throw new TypeException(property, value, "float");
+    }
+
+    /**
+     * Returns the value of a double property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a double
+     */
+    public double getDouble(String property, double def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value instanceof Double) {
+            return ((Double)value).doubleValue();
+        }
+        throw new TypeException(property, value, "double");
+    }
+
+    /**
+     * Returns the value of a string property, or the default if the property
+     * has not been defined.
+     *
+     * @param property The name of the property to return
+     * @param def The default value to return if the property is not set
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a string
+     */
+    public String getString(String property, String def) {
+        Object value = super.get(property);
+        if (value == null) {
+            return def;
+        }
+        if (value == NULL_STRING) {
+            return null;
+        } else if (value instanceof String) {
+            return (String)value;
+        }
+        throw new TypeException(property, value, "string");
+    }
+
+    /*
+     * Getters with implicit defaults
+     */
+
+    /**
+     * Returns the value of a boolean property, or false
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a boolean
+     */
+    public boolean getBoolean(String property) {
+        return getBoolean(property, false);
+    }
+
+    /**
+     * Returns the value of a byte property, or 0
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a byte
+     */
+    public byte getByte(String property) {
+        return getByte(property, (byte)0);
+    }
+
+    /**
+     * Returns the value of a short property, or 0
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a short
+     */
+    public short getShort(String property) {
+        return getShort(property, (short)0);
+    }
+
+    /**
+     * Returns the value of an integer property, or 0
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not an integer
+     */
+    public int getInt(String property) {
+        return getInt(property, 0);
+    }
+
+    /**
+     * Returns the value of a long property, or 0
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a long
+     */
+    public long getLong(String property) {
+        return getLong(property, 0L);
+    }
+
+    /**
+     * Returns the value of a float property, or 0.0
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a float
+     */
+    public float getFloat(String property) {
+        return getFloat(property, 0.0f);
+    }
+
+    /**
+     * Returns the value of a double property, or 0.0
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a double
+     */
+    public double getDouble(String property) {
+        return getDouble(property, 0.0);
+    }
+
+    /**
+     * Returns the value of a String property, or ""
+     * if the property has not been defined.
+     *
+     * @param property The name of the property to return
+     * @return the value of the property
+     * @throws TypeException if the property is set and is not a string
+     */
+    public String getString(String property) {
+        return getString(property, "");
+    }
+}
diff --git a/core/res/res/drawable/btn_global_search.xml b/core/res/res/drawable/btn_global_search.xml
new file mode 100644
index 0000000..531f07e
--- /dev/null
+++ b/core/res/res/drawable/btn_global_search.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- TODO Need different assets for some of these button states. -->
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_global_search_normal" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_global_search_normal" />
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_default_pressed" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_selected" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_global_search_normal" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_global_search_normal" />
+    <item
+         android:drawable="@drawable/btn_global_search_normal" />
+</selector>
diff --git a/core/res/res/drawable/btn_global_search_normal.9.png b/core/res/res/drawable/btn_global_search_normal.9.png
new file mode 100644
index 0000000..9b7d3e5
--- /dev/null
+++ b/core/res/res/drawable/btn_global_search_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_search_dialog.xml b/core/res/res/drawable/btn_search_dialog.xml
new file mode 100644
index 0000000..b7f5187
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    
+   <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_default" />
+        
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_search_dialog_pressed" />
+    
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_selected" />
+    
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_default" />
+    
+    <item
+         android:drawable="@drawable/btn_search_dialog_default" />
+</selector>
diff --git a/core/res/res/drawable/btn_search_dialog_default.9.png b/core/res/res/drawable/btn_search_dialog_default.9.png
new file mode 100644
index 0000000..ec39178
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog_default.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_search_dialog_pressed.9.png b/core/res/res/drawable/btn_search_dialog_pressed.9.png
new file mode 100644
index 0000000..5f52fef
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_search_dialog_selected.9.png b/core/res/res/drawable/btn_search_dialog_selected.9.png
new file mode 100644
index 0000000..9fc2fde
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_search_dialog_voice.xml b/core/res/res/drawable/btn_search_dialog_voice.xml
new file mode 100644
index 0000000..748aaf5
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog_voice.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    
+   <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_voice_default" />
+        
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_search_dialog_voice_pressed" />
+    
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_voice_selected" />
+    
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_search_dialog_voice_default" />
+    
+    <item
+         android:drawable="@drawable/btn_search_dialog_voice_default" />
+</selector>
diff --git a/core/res/res/drawable/btn_search_dialog_voice_default.9.png b/core/res/res/drawable/btn_search_dialog_voice_default.9.png
new file mode 100644
index 0000000..2a3366c
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_search_dialog_voice_pressed.9.png b/core/res/res/drawable/btn_search_dialog_voice_pressed.9.png
new file mode 100644
index 0000000..57d7a74
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_search_dialog_voice_selected.9.png b/core/res/res/drawable/btn_search_dialog_voice_selected.9.png
new file mode 100644
index 0000000..db3187e
--- /dev/null
+++ b/core/res/res/drawable/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/search_dropdown_background.9.png b/core/res/res/drawable/search_dropdown_background.9.png
new file mode 100755
index 0000000..a6923b7
--- /dev/null
+++ b/core/res/res/drawable/search_dropdown_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/search_plate_global.9.png b/core/res/res/drawable/search_plate_global.9.png
new file mode 100644
index 0000000..126054b
--- /dev/null
+++ b/core/res/res/drawable/search_plate_global.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_search.xml b/core/res/res/drawable/textfield_search.xml
new file mode 100644
index 0000000..2923368
--- /dev/null
+++ b/core/res/res/drawable/textfield_search.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_search_default" />
+        
+    <item android:state_pressed="true"
+        android:drawable="@drawable/textfield_search_pressed" />
+        
+    <item android:state_enabled="true" android:state_focused="true"
+        android:drawable="@drawable/textfield_search_selected" />
+    
+    <item android:drawable="@drawable/textfield_search_default" />
+    
+</selector>
+
diff --git a/core/res/res/drawable/textfield_search_default.9.png b/core/res/res/drawable/textfield_search_default.9.png
new file mode 100755
index 0000000..7dc5b27
--- /dev/null
+++ b/core/res/res/drawable/textfield_search_default.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_search_pressed.9.png b/core/res/res/drawable/textfield_search_pressed.9.png
new file mode 100644
index 0000000..da00c25
--- /dev/null
+++ b/core/res/res/drawable/textfield_search_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_search_selected.9.png b/core/res/res/drawable/textfield_search_selected.9.png
new file mode 100755
index 0000000..a9fd3b2
--- /dev/null
+++ b/core/res/res/drawable/textfield_search_selected.9.png
Binary files differ
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 5e296c5..4c5c456 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -23,7 +23,7 @@
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:layout_height="wrap_content"
     android:layout_width="fill_parent"
-    android:paddingLeft="14dip"
+    android:paddingLeft="10dip"
     android:paddingRight="15dip">
 
     <!-- Activity icon when presenting dialog -->
@@ -42,13 +42,13 @@
             android:textAppearance="?android:attr/textAppearanceLargeInverse"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:paddingLeft="6dip" />
+            android:paddingLeft="10dip" />
         <!-- Extended activity info to distinguish between duplicate activity names -->
         <TextView android:id="@android:id/text2"
             android:textAppearance="?android:attr/textAppearanceMediumInverse"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:paddingLeft="6dip" />
+            android:paddingLeft="10dip" />
     </LinearLayout>
 </LinearLayout>
 
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index ef347da..6155626 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -26,79 +26,67 @@
     android:orientation="vertical" 
     android:focusable="true"
     android:descendantFocusability="afterDescendants">
-    <!-- android:paddingBottom="14dip"  TODO MUST FIX - it's a hack to get the popup to show -->
+    <!-- android:paddingBottom="200dip"  TODO MUST FIX - it's a hack to get the popup to show -->
 
     <!-- Outer layout defines the entire search bar at the top of the screen -->
-    <!-- Bottom padding of 16 is due to the graphic, with 9 extra pixels of drop
-         shadow, plus the desired padding of "8" against the user-visible (grey)
-         pixels, minus "1" to correct for positioning of the edittext & button. -->
     <LinearLayout
         android:id="@+id/search_plate"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:paddingLeft="8dip"
-        android:paddingRight="8dip"
-        android:paddingTop="6dip"
+        android:paddingLeft="12dip"
+        android:paddingRight="12dip"
+        android:paddingTop="7dip"
         android:paddingBottom="16dip"
-        android:baselineAligned="false"
-        android:background="@android:drawable/search_plate"
-        android:addStatesFromChildren="true" >
+        android:background="@drawable/search_plate_global" >
 
         <!-- This is actually used for the badge icon *or* the badge label (or neither) -->
         <TextView 
             android:id="@+id/search_badge"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:paddingLeft="2dip"
+            android:layout_marginBottom="2dip"
             android:drawablePadding="0dip"
             android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorPrimary" />
+            android:textColor="?android:attr/textColorPrimaryInverse" />
 
         <!-- Inner layout contains the button(s) and EditText -->
-        <!-- The layout_marginTop of "1" corrects for the extra 1 pixel of padding at the top of 
-             textfield_selected.9.png.  The "real" margin as displayed is "2". -->
-        <!-- The layout_marginBottom of "-5" corrects for the spacing we see at the 
-             bottom of the edittext and button images.  The "real" margin as displayed is "8" -->
         <LinearLayout
             android:id="@+id/search_edit_frame"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="1dip"
-            android:layout_marginBottom="-5dip"
-            android:orientation="horizontal"
-            android:addStatesFromChildren="true"
-            android:gravity="center_vertical"
-            android:baselineAligned="false" >
-
+            android:orientation="horizontal">
+            
             <view class="android.app.SearchDialog$SearchAutoComplete"
                 android:id="@+id/search_src_text"
+                android:background="@drawable/textfield_search"
                 android:layout_height="wrap_content"
                 android:layout_width="0dip"
                 android:layout_weight="1.0"
                 android:paddingLeft="8dip"
                 android:paddingRight="6dip"
+                android:singleLine="true"
                 android:inputType="text|textAutoComplete"
                 android:dropDownWidth="fill_parent"
                 android:dropDownAnchor="@id/search_plate"
-                android:dropDownVerticalOffset="-15dip"
+                android:dropDownVerticalOffset="-9dip"
+                android:popupBackground="@android:drawable/search_dropdown_background"
                 />
-                <!-- android:focusableInTouchMode="false" -->
-                <!-- android:singleLine="true" -->
-                <!-- android:selectAllOnFocus="true" -->
                 
             <!-- This button can switch between text and icon "modes" -->
             <Button 
                 android:id="@+id/search_go_btn"
-                android:layout_marginLeft="1dip"
+                android:background="@drawable/btn_search_dialog"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:drawableLeft="@android:drawable/ic_btn_search"
+                android:layout_height="fill_parent"
             />
 
-            <ImageButton android:id="@+id/search_voice_btn"
+            <ImageButton
+                android:id="@+id/search_voice_btn"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="fill_parent"
+                android:layout_marginLeft="8dip"
+                android:background="@drawable/btn_search_dialog_voice"
                 android:src="@android:drawable/ic_btn_speak_now"
             />
         </LinearLayout>
diff --git a/core/res/res/layout/search_dropdown_item_1line.xml b/core/res/res/layout/search_dropdown_item_1line.xml
index 3827206..bf3dd48 100644
--- a/core/res/res/layout/search_dropdown_item_1line.xml
+++ b/core/res/res/layout/search_dropdown_item_1line.xml
@@ -20,7 +20,7 @@
 <TextView xmlns:android="http://schemas.android.com/apk/res/android" 
     android:id="@android:id/text1"
     style="?android:attr/dropDownItemStyle"
-    android:textAppearance="?android:attr/textAppearanceMediumInverse"
+    android:textAppearance="?android:attr/textAppearanceSearchResultTitle"
     android:singleLine="true"
     android:layout_width="fill_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight" />
+    android:layout_height="?android:attr/searchResultListItemHeight" />
\ No newline at end of file
diff --git a/core/res/res/layout/search_dropdown_item_2line.xml b/core/res/res/layout/search_dropdown_item_2line.xml
index 96d6005..5546b6636 100644
--- a/core/res/res/layout/search_dropdown_item_2line.xml
+++ b/core/res/res/layout/search_dropdown_item_2line.xml
@@ -20,15 +20,16 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
     android:layout_width="fill_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:layout_height="?android:attr/searchResultListItemHeight"
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:baselineAligned="false"
     >
     
     <TwoLineListItem
-        android:paddingTop="2dip"
-        android:paddingBottom="2dip"
+        android:paddingTop="1dip"
+        android:paddingBottom="1dip"
+        android:gravity="center_vertical"
         android:layout_width="0dip"
         android:layout_weight="1"
         android:layout_height="wrap_content"
@@ -37,7 +38,7 @@
         <TextView
             android:id="@android:id/text1"
             style="?android:attr/dropDownItemStyle"
-            android:textAppearance="?android:attr/textAppearanceMediumInverse"
+            android:textAppearance="?android:attr/textAppearanceSearchResultTitle"
             android:singleLine="true"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content" />
@@ -45,7 +46,7 @@
         <TextView
             android:id="@android:id/text2"
             style="?android:attr/dropDownItemStyle"
-            android:textAppearance="?android:attr/textAppearanceSmallInverse"
+            android:textAppearance="?android:attr/textAppearanceSearchResultSubtitle"
             android:textColor="?android:attr/textColorSecondaryInverse"
             android:singleLine="true"
             android:layout_width="fill_parent"
diff --git a/core/res/res/layout/search_dropdown_item_icons_1line.xml b/core/res/res/layout/search_dropdown_item_icons_1line.xml
index c0713d5..4f65d74 100644
--- a/core/res/res/layout/search_dropdown_item_icons_1line.xml
+++ b/core/res/res/layout/search_dropdown_item_icons_1line.xml
@@ -22,31 +22,33 @@
     <!-- of the text element in apps/common/res/layout/simple_dropdown_item_1line.xml -->
     
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:paddingLeft="4dip"
+    android:paddingRight="2dip"
     android:layout_width="fill_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:layout_height="?android:attr/searchResultListItemHeight"
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:baselineAligned="false"
     >
     
     <ImageView android:id="@android:id/icon1"
-        android:layout_width="32dip"
-        android:layout_height="32dip"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
         android:layout_gravity="center_vertical"
-        android:scaleType="fitCenter" />
+        android:scaleType="centerInside" />
 
     <TextView android:id="@android:id/text1"
         style="?android:attr/dropDownItemStyle"
-        android:textAppearance="?android:attr/textAppearanceMediumInverse"
+        android:textAppearance="?android:attr/textAppearanceSearchResultTitle"
         android:singleLine="true"
         android:layout_height="wrap_content"
         android:layout_width="0dip"
         android:layout_weight="1"  />
 
     <ImageView android:id="@android:id/icon2"
-        android:layout_width="32dip"
-        android:layout_height="32dip"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
         android:layout_gravity="center_vertical"
-        android:scaleType="fitCenter" />
+        android:scaleType="centerInside" />
 
 </LinearLayout>
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 ad1c905..0d07490 100644
--- a/core/res/res/layout/search_dropdown_item_icons_2line.xml
+++ b/core/res/res/layout/search_dropdown_item_icons_2line.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+/* 
 **
 ** Copyright 2008, The Android Open Source Project
 **
@@ -18,56 +18,62 @@
 */
 -->
 
-    <!-- NOTE: The appearance of the inner text element must match the appearance -->
-    <!-- of the text element in apps/common/res/layout/simple_dropdown_item_2line.xml -->
-    
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:paddingLeft="4dip"
+    android:paddingRight="2dip"
     android:layout_width="fill_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight"
-    android:orientation="horizontal"
-    android:gravity="center_vertical"
-    android:baselineAligned="false"
-    >
-    
-    <ImageView android:id="@android:id/icon1"
-        android:layout_width="32dip"
-        android:layout_height="32dip"
-        android:layout_gravity="center_vertical"
-        android:scaleType="fitCenter" />
+    android:layout_height="?android:attr/searchResultListItemHeight" >
 
-    <TwoLineListItem
-        android:paddingTop="2dip"
-        android:paddingBottom="2dip"
-        android:layout_width="0dip"
-        android:layout_weight="1"
-        android:layout_height="wrap_content"
-        android:mode="twoLine" >
-    
-        <TextView
-            android:id="@android:id/text1"
-            style="?android:attr/dropDownItemStyle"
-            android:textAppearance="?android:attr/textAppearanceMediumInverse"
-            android:singleLine="true"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content" />
-    
-        <TextView
-            android:id="@android:id/text2"
-            style="?android:attr/dropDownItemStyle"
-            android:textAppearance="?android:attr/textAppearanceSmallInverse"
-            android:textColor="?android:attr/textColorSecondaryInverse"
-            android:singleLine="true"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@android:id/text1"
-            android:layout_alignLeft="@android:id/text1" />
-    
-    </TwoLineListItem>
+    <!-- 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_height="48dip"
+        android:scaleType="centerInside"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentBottom="true"
+        android:visibility="gone" />
 
     <ImageView android:id="@android:id/icon2"
-        android:layout_width="32dip"
-        android:layout_height="32dip"
-        android:layout_gravity="center_vertical"
-        android:scaleType="fitCenter" />
+        android:layout_width="48dip"
+        android:layout_height="48dip"
+        android:scaleType="centerInside"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentBottom="true"
+        android:visibility="gone" />
 
-</LinearLayout>
+    <!-- The subtitle comes before the title, since the height of the title depends on whether the
+         subtitle is visible or gone. --> 
+    <TextView android:id="@android:id/text2"
+        style="?android:attr/dropDownItemStyle"
+        android:textAppearance="?android:attr/textAppearanceSearchResultSubtitle"
+        android:singleLine="true"
+        android:layout_width="fill_parent"
+        android:layout_height="29dip" 
+        android:paddingBottom="4dip"
+        android:gravity="top"
+        android:layout_toRightOf="@android:id/icon1"
+        android:layout_toLeftOf="@android:id/icon2"
+        android:layout_alignWithParentIfMissing="true" 
+        android:layout_alignParentBottom="true"
+        android:visibility="gone" />
+    
+    <!-- The title is placed above the subtitle, if there is one. If there is no
+         subtitle, it fills the parent. -->
+    <TextView android:id="@android:id/text1"
+        style="?android:attr/dropDownItemStyle"
+        android:textAppearance="?android:attr/textAppearanceSearchResultTitle"
+        android:singleLine="true"
+        android:layout_width="fill_parent"
+        android:layout_height="29dip"
+        android:paddingTop="4dip"
+        android:gravity="center_vertical"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@android:id/icon1"
+        android:layout_toLeftOf="@android:id/icon2"
+        android:layout_above="@android:id/text2"
+        android:layout_alignWithParentIfMissing="true" />
+    
+</RelativeLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b98558e..972953b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -94,6 +94,11 @@
         <!-- Text color, typeface, size, and style for "small" inverse text. Defaults to secondary inverse text color. -->
         <attr name="textAppearanceSmallInverse" format="reference" />
 
+        <!-- Text color, typeface, size, and style for system search result title. Defaults to primary inverse text color. @hide -->	
+        <attr name="textAppearanceSearchResultTitle" format="reference" />	
+        <!-- Text color, typeface, size, and style for system search result subtitle. Defaults to primary inverse text color. @hide -->	
+        <attr name="textAppearanceSearchResultSubtitle" format="reference" />
+
         <!-- Text color, typeface, size, and style for the text inside of a button. -->
         <attr name="textAppearanceButton" format="reference" />
         
@@ -147,6 +152,8 @@
         <!-- The preferred list item height -->
         <attr name="listPreferredItemHeight" format="dimension" />
         <!-- The drawable for the list divider -->
+	<!-- The list item height for search results. @hide -->
+	<attr name="searchResultListItemHeight" format="dimension" />
         <attr name="listDivider" format="reference" />
         <!-- TextView style for list separators. -->
         <attr name="listSeparatorTextViewStyle" format="reference" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index d7b654e..a436f61 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -138,6 +138,7 @@
     </style>
 
     <!-- Window animations that are applied to the search bar overlay window.
+	Previously used, but currently unused.
          {@hide Pending API council approval} -->
     <style name="Animation.SearchBar">
         <item name="windowEnterAnimation">@anim/search_bar_enter</item>
@@ -574,6 +575,24 @@
         <item name="android:textColor">@android:color/primary_text_light_disable_only</item>
     </style>
 
+    <!-- @hide -->	
+     <style name="TextAppearance.SearchResult">	
+         <item name="android:textStyle">normal</item>	
+         <item name="android:textColor">?textColorPrimaryInverse</item>	
+         <item name="android:textColorHint">?textColorHintInverse</item>	
+     </style>	
+     	
+     <!-- @hide -->	
+     <style name="TextAppearance.SearchResult.Title">	
+         <item name="android:textSize">16sp</item>	
+     </style>	
+     	
+     <!-- @hide -->	
+     <style name="TextAppearance.SearchResult.Subtitle">	
+         <item name="android:textSize">13sp</item>	
+         <item name="android:textColor">?textColorSecondaryInverse</item>	
+     </style>	
+
     <style name="TextAppearance.WindowTitle">
         <item name="android:textColor">#fff</item>
         <item name="android:textSize">14sp</item>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 6b3d740..dfd2391 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -57,6 +57,12 @@
         <item name="textAppearanceLargeInverse">@android:style/TextAppearance.Large.Inverse</item>
         <item name="textAppearanceMediumInverse">@android:style/TextAppearance.Medium.Inverse</item>
         <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Small.Inverse</item>
+
+        <!-- @hide -->
+        <item name="textAppearanceSearchResultTitle">@android:style/TextAppearance.SearchResult.Title</item>
+        
+        <!-- @hide -->
+        <item name="textAppearanceSearchResultSubtitle">@android:style/TextAppearance.SearchResult.Subtitle</item>
         
         <item name="textAppearanceButton">@android:style/TextAppearance.Widget.Button</item>
         
@@ -75,6 +81,8 @@
 
         <!-- List attributes -->
         <item name="listPreferredItemHeight">64dip</item>
+        <!-- @hide -->
+        <item name="searchResultListItemHeight">58dip</item>
         <item name="listDivider">@drawable/divider_horizontal_dark</item>
         <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator</item>   
         
@@ -355,7 +363,6 @@
     <!-- Theme for the search input bar. -->
     <style name="Theme.SearchBar" parent="Theme.Panel">
         <item name="android:backgroundDimEnabled">true</item>
-        <item name="android:windowAnimationStyle">@android:style/Animation.SearchBar</item>
         <item name="windowContentOverlay">@null</item>        
     </style>
 
diff --git a/include/ui/Camera.h b/include/ui/Camera.h
index e593fea..12fa20f 100644
--- a/include/ui/Camera.h
+++ b/include/ui/Camera.h
@@ -78,8 +78,8 @@
 {
 public:
             // construct a camera client from an existing remote
-            Camera(const sp<ICamera>& camera);
-
+            Camera(const sp<ICamera>& camera);  // to be removed
+    static  sp<Camera>  create(const sp<ICamera>& camera);
     static  sp<Camera>  connect();
                         ~Camera();
             void        init();
diff --git a/libs/ui/Camera.cpp b/libs/ui/Camera.cpp
index b3cbda1..0fba82c 100644
--- a/libs/ui/Camera.cpp
+++ b/libs/ui/Camera.cpp
@@ -75,6 +75,19 @@
     }
 }
 
+
+sp<Camera> Camera::create(const sp<ICamera>& camera)
+{
+    sp<Camera> c = new Camera();
+    // connect this client to existing camera remote
+    if (camera->connect(c) == NO_ERROR) {
+        c->mStatus = NO_ERROR;
+        c->mCamera = camera;
+        camera->asBinder()->linkToDeath(c);
+    }
+    return c;
+}
+
 void Camera::init()
 {
     mStatus = UNKNOWN_ERROR;
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 7d35814..096622a 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -45,7 +45,10 @@
 
     boolean addGpsStatusListener(IGpsStatusListener listener);
     void removeGpsStatusListener(IGpsStatusListener listener);
-    
+
+    // for reporting callback completion
+    void locationCallbackFinished(ILocationListener listener);
+
     boolean sendExtraCommand(String provider, String command, inout Bundle extras);
 
     void addProximityAlert(double latitude, double longitude, float distance,
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 0c7254e..f587f96 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -194,6 +194,11 @@
                     mListener.onProviderDisabled((String) msg.obj);
                     break;
             }
+            try {
+                mService.locationCallbackFinished(this);
+            } catch (RemoteException e) {
+                Log.e(TAG, "locationCallbackFinished: RemoteException", e);
+            }
         }
     }
     /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index 07b43bb..281828f 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -464,6 +464,7 @@
   public static final String RECORDED_SQVGA_H263 = "/sdcard/SQVGA_H263.3gp";
   public static final String RECORDED_CIF_H263 = "/sdcard/CIF_H263.3gp";
   public static final String RECORDED_QCIF_H263 = "/sdcard/QCIF_H263.3gp";
+  public static final String RECORDED_PORTRAIT_H263 = "/sdcard/QCIF_mp4.3gp";
   
   public static final String RECORDED_HVGA_MP4 = "/sdcard/HVGA_mp4.mp4";
   public static final String RECORDED_QVGA_MP4 = "/sdcard/QVGA_mp4.mp4";
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
index 65451c5..261b4f4 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
@@ -22,6 +22,7 @@
 import java.io.*;
 
 import android.content.Context;
+import android.hardware.Camera;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
 import android.test.ActivityInstrumentationTestCase;
@@ -46,6 +47,7 @@
     private SurfaceHolder mSurfaceHolder = null;
     private MediaRecorder mRecorder;
     Context mContext;
+    Camera mCamera;
   
     public MediaRecorderTest() {
         super("com.android.mediaframeworktest", MediaFrameworkTest.class);
@@ -234,12 +236,32 @@
         assertTrue("QCIFH263 Video Only", videoRecordedResult);
     }
     
-    @Suppress
-    public void testPortraitH263() throws Exception {       
+    @LargeTest
+    /*
+     * This test case set the camera in portrait mode.
+     * Verification: validate the video dimension and the duration.
+     */
+    public void testPortraitH263() throws Exception {
         boolean videoRecordedResult = false;
-        recordVideo(15, 144, 176, MediaRecorder.VideoEncoder.H263, 
-               MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true);      
-        videoRecordedResult = validateVideo(MediaNames.RECORDED_VIDEO_3GP, 144, 176);
+        try {
+            mCamera = Camera.open();
+            Camera.Parameters parameters = mCamera.getParameters();
+            parameters.setPreviewSize(352, 288);
+            parameters.set("orientation", "portrait");
+            mCamera.setParameters(parameters);
+            mCamera.unlock();
+            mRecorder.setCamera(mCamera);
+            Thread.sleep(1000);
+            recordVideo(15, 352, 288, MediaRecorder.VideoEncoder.H263,
+                    MediaRecorder.OutputFormat.THREE_GPP, 
+                    MediaNames.RECORDED_PORTRAIT_H263, true);
+            videoRecordedResult = 
+                validateVideo(MediaNames.RECORDED_PORTRAIT_H263, 352, 288);
+            mCamera.lock();
+            mCamera.release();
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+        }
         assertTrue("PortraitH263", videoRecordedResult);
     }
     
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 5e079d4..2def877 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -93,10 +93,6 @@
     // Max time to hold wake lock for, in milliseconds.
     private static final long MAX_TIME_FOR_WAKE_LOCK = 60 * 1000L;
 
-    // Time to wait after releasing a wake lock for clients to process location update,
-    // in milliseconds.
-    private static final long TIME_AFTER_WAKE_LOCK = 2 * 1000L;
-
     // The last time a location was written, by provider name.
     private HashMap<String,Long> mLastWriteTime = new HashMap<String,Long>();
 
@@ -130,7 +126,6 @@
 
     // Handler messages
     private static final int MESSAGE_LOCATION_CHANGED = 1;
-    private static final int MESSAGE_RELEASE_WAKE_LOCK = 2;
 
     // Alarm manager and wakelock variables
     private final static String ALARM_INTENT = "com.android.location.ALARM_INTENT";
@@ -138,6 +133,7 @@
     private AlarmManager mAlarmManager;
     private long mAlarmInterval = 0;
     private PowerManager.WakeLock mWakeLock = null;
+    private int mPendingBroadcasts;
     private long mWakeLockAcquireTime = 0;
     private boolean mWakeLockGpsReceived = true;
     private boolean mWakeLockNetworkReceived = true;
@@ -159,7 +155,8 @@
         new HashMap<String,ArrayList<UpdateRecord>>();
 
     // Proximity listeners
-    private Receiver mProximityListener = null;
+    private Receiver mProximityReceiver = null;
+    private ILocationListener mProximityListener = null;
     private HashMap<PendingIntent,ProximityAlert> mProximityAlerts =
         new HashMap<PendingIntent,ProximityAlert>();
     private HashSet<ProximityAlert> mProximitiesEntered =
@@ -181,11 +178,12 @@
      * A wrapper class holding either an ILocationListener or a PendingIntent to receive
      * location updates.
      */
-    private final class Receiver implements IBinder.DeathRecipient {
+    private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished {
         final ILocationListener mListener;
         final PendingIntent mPendingIntent;
         final Object mKey;
         final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
+        int mPendingBroadcasts;
 
         Receiver(ILocationListener listener) {
             mListener = listener;
@@ -252,7 +250,16 @@
         public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
             if (mListener != null) {
                 try {
-                    mListener.onStatusChanged(provider, status, extras);
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mListener.onStatusChanged(provider, status, extras);
+                        if (mListener != mProximityListener) {
+                            // call this after broadcasting so we do not increment
+                            // if we throw an exeption.
+                            incrementPendingBroadcastsLocked();
+                        }
+                    }
                 } catch (RemoteException e) {
                     return false;
                 }
@@ -261,7 +268,14 @@
                 statusChanged.putExtras(extras);
                 statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
                 try {
-                    mPendingIntent.send(mContext, 0, statusChanged, null, null);
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler);
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
                 } catch (PendingIntent.CanceledException e) {
                     return false;
                 }
@@ -272,7 +286,16 @@
         public boolean callLocationChangedLocked(Location location) {
             if (mListener != null) {
                 try {
-                    mListener.onLocationChanged(location);
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mListener.onLocationChanged(location);
+                        if (mListener != mProximityListener) {
+                            // call this after broadcasting so we do not increment
+                            // if we throw an exeption.
+                            incrementPendingBroadcastsLocked();
+                        }
+                    }
                 } catch (RemoteException e) {
                     return false;
                 }
@@ -280,7 +303,53 @@
                 Intent locationChanged = new Intent();
                 locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED, location);
                 try {
-                    mPendingIntent.send(mContext, 0, locationChanged, null, null);
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler);
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
+                } catch (PendingIntent.CanceledException e) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public boolean callProviderEnabledLocked(String provider, boolean enabled) {
+            if (mListener != null) {
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        if (enabled) {
+                            mListener.onProviderEnabled(provider);
+                        } else {
+                            mListener.onProviderDisabled(provider);
+                        }
+                        if (mListener != mProximityListener) {
+                            // call this after broadcasting so we do not increment
+                            // if we throw an exeption.
+                            incrementPendingBroadcastsLocked();
+                        }
+                    }
+                } catch (RemoteException e) {
+                    return false;
+                }
+            } else {
+                Intent providerIntent = new Intent();
+                providerIntent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled);
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler);
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
                 } catch (PendingIntent.CanceledException e) {
                     return false;
                 }
@@ -295,6 +364,42 @@
             synchronized (mLock) {
                 removeUpdatesLocked(this);
             }
+            synchronized (this) {
+                if (mPendingBroadcasts > 0) {
+                    LocationManagerService.this.decrementPendingBroadcasts();
+                    mPendingBroadcasts = 0;
+                }
+            }
+        }
+
+        public void onSendFinished(PendingIntent pendingIntent, Intent intent,
+                int resultCode, String resultData, Bundle resultExtras) {
+            decrementPendingBroadcasts();
+        }
+
+        // this must be called while synchronized by callerin a synchronized block
+        // containing the sending of the broadcaset
+        private void incrementPendingBroadcastsLocked() {
+            if (mPendingBroadcasts++ == 0) {
+                synchronized (mLock) {
+                    LocationManagerService.this.incrementPendingBroadcastsLocked();
+                }
+            }
+        }
+
+        private void decrementPendingBroadcasts() {
+            synchronized (this) {
+                if (--mPendingBroadcasts == 0) {
+                    LocationManagerService.this.decrementPendingBroadcasts();
+                }
+            }
+        }
+    }
+
+    public void locationCallbackFinished(ILocationListener listener) {
+        Receiver receiver = getReceiver(listener);
+        if (receiver != null) {
+            receiver.decrementPendingBroadcasts();
         }
     }
 
@@ -722,29 +827,11 @@
             for (int i=0; i<N; i++) {
                 UpdateRecord record = records.get(i);
                 // Sends a notification message to the receiver
-                try {
-                    Receiver receiver = record.mReceiver;
-                    if (receiver.isListener()) {
-                        if (enabled) {
-                            receiver.getListener().onProviderEnabled(provider);
-                        } else {
-                            receiver.getListener().onProviderDisabled(provider);
-                        }
-                    } else {
-                        Intent providerIntent = new Intent();
-                        providerIntent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled);
-                        try {
-                            receiver.getPendingIntent().send(mContext, 0,
-                                 providerIntent, null, null);
-                        } catch (PendingIntent.CanceledException e) {
-                            if (deadReceivers == null) {
-                                deadReceivers = new ArrayList<Receiver>();
-                                deadReceivers.add(receiver);
-                            }
-                        }
+                if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
+                    if (deadReceivers == null) {
+                        deadReceivers = new ArrayList<Receiver>();
+                        deadReceivers.add(record.mReceiver);
                     }
-                } catch (RemoteException e) {
-                    // The death link will clean this up.
                 }
                 listeners++;
             }
@@ -958,15 +1045,8 @@
                 impl.enableLocationTracking(true);
                 updateWakelockStatusLocked();
             } else {
-                try {
-                    // Notify the listener that updates are currently disabled
-                    if (receiver.isListener()) {
-                        receiver.getListener().onProviderDisabled(provider);
-                    }
-                } catch(RemoteException e) {
-                    Log.w(TAG, "RemoteException calling onProviderDisabled on " +
-                            receiver.getListener());
-                }
+                // Notify the listener that updates are currently disabled
+                receiver.callProviderEnabledLocked(provider, false);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -1161,7 +1241,7 @@
     }
 
     // Listener for receiving locations to trigger proximity alerts
-    class ProximityListener extends ILocationListener.Stub {
+    class ProximityListener extends ILocationListener.Stub implements PendingIntent.OnFinished {
 
         boolean isGpsAvailable = false;
 
@@ -1198,7 +1278,14 @@
                         Intent enteredIntent = new Intent();
                         enteredIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
                         try {
-                            intent.send(mContext, 0, enteredIntent, null, null);
+                            synchronized (mLock) {
+                                // synchronize to ensure incrementPendingBroadcastsLocked()
+                                // is called before decrementPendingBroadcasts()
+                                intent.send(mContext, 0, enteredIntent, this, mLocationHandler);
+                                // call this after broadcasting so we do not increment
+                                // if we throw an exeption.
+                                incrementPendingBroadcastsLocked();
+                            }
                         } catch (PendingIntent.CanceledException e) {
                             if (LOCAL_LOGV) {
                                 Log.v(TAG, "Canceled proximity alert: " + alert, e);
@@ -1216,7 +1303,14 @@
                         Intent exitedIntent = new Intent();
                         exitedIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
                         try {
-                            intent.send(mContext, 0, exitedIntent, null, null);
+                            synchronized (mLock) {
+                                // synchronize to ensure incrementPendingBroadcastsLocked()
+                                // is called before decrementPendingBroadcasts()
+                                intent.send(mContext, 0, exitedIntent, this, mLocationHandler);
+                                // call this after broadcasting so we do not increment
+                                // if we throw an exeption.
+                                incrementPendingBroadcastsLocked();
+                            }
                         } catch (PendingIntent.CanceledException e) {
                             if (LOCAL_LOGV) {
                                 Log.v(TAG, "Canceled proximity alert: " + alert, e);
@@ -1269,6 +1363,11 @@
                 isGpsAvailable = false;
             }
         }
+
+        public void onSendFinished(PendingIntent pendingIntent, Intent intent,
+                int resultCode, String resultData, Bundle resultExtras) {
+            decrementPendingBroadcasts();
+        }
     }
 
     public void addProximityAlert(double latitude, double longitude,
@@ -1306,19 +1405,20 @@
                 latitude, longitude, radius, expiration, intent);
         mProximityAlerts.put(intent, alert);
 
-        if (mProximityListener == null) {
-            mProximityListener = new Receiver(new ProximityListener());
+        if (mProximityReceiver == null) {
+            mProximityListener = new ProximityListener();
+            mProximityReceiver = new Receiver(mProximityListener);
 
             LocationProvider provider = LocationProviderImpl.getProvider(
                 LocationManager.GPS_PROVIDER);
             if (provider != null) {
-                requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityListener);
+                requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityReceiver);
             }
 
             provider =
                 LocationProviderImpl.getProvider(LocationManager.NETWORK_PROVIDER);
             if (provider != null) {
-                requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityListener);
+                requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityReceiver);
             }
         }
     }
@@ -1342,7 +1442,8 @@
 
         mProximityAlerts.remove(intent);
         if (mProximityAlerts.size() == 0) {
-            removeUpdatesLocked(mProximityListener);
+            removeUpdatesLocked(mProximityReceiver);
+            mProximityReceiver = null;
             mProximityListener = null;
         }
      }
@@ -1585,35 +1686,7 @@
                         }
 
                         handleLocationChangedLocked(location);
-
-                        if ((mWakeLockAcquireTime != 0) &&
-                            (SystemClock.elapsedRealtime() - mWakeLockAcquireTime
-                                > MAX_TIME_FOR_WAKE_LOCK)) {
-    
-                            removeMessages(MESSAGE_RELEASE_WAKE_LOCK);
-    
-                            log("LocationWorkerHandler: Exceeded max time for wake lock");
-                            Message m = Message.obtain(this, MESSAGE_RELEASE_WAKE_LOCK);
-                            sendMessageAtFrontOfQueue(m);
-    
-                        } else if (mWakeLockAcquireTime != 0 &&
-                            mWakeLockGpsReceived && mWakeLockNetworkReceived) {
-    
-                            removeMessages(MESSAGE_RELEASE_WAKE_LOCK);
-    
-                            log("LocationWorkerHandler: Locations received.");
-                            mWakeLockAcquireTime = 0;
-                            Message m = Message.obtain(this, MESSAGE_RELEASE_WAKE_LOCK);
-                            sendMessageDelayed(m, TIME_AFTER_WAKE_LOCK);
-                        }
-                    }
-                } else if (msg.what == MESSAGE_RELEASE_WAKE_LOCK) {
-                    log("LocationWorkerHandler: Release");
-
-                    // Update wakelock status so the next alarm is set before releasing wakelock
-                    synchronized (mLock) {
                         updateWakelockStatusLocked();
-                        releaseWakeLockLocked();
                     }
                 }
             } catch (Exception e) {
@@ -1727,7 +1800,7 @@
 
         long callerId = Binder.clearCallingIdentity();
         
-        boolean needsLock = false;
+        boolean needsLock = (mPendingBroadcasts > 0);
         long minTime = Integer.MAX_VALUE;
 
         if (mNetworkLocationProvider != null && mNetworkLocationProvider.isLocationTracking()) {
@@ -1757,8 +1830,6 @@
             log("No need for alarm");
             mAlarmInterval = -1;
 
-            // Clear out existing wakelocks
-            mLocationHandler.removeMessages(MESSAGE_RELEASE_WAKE_LOCK);
             releaseWakeLockLocked();
         }
         Binder.restoreCallingIdentity(callerId);
@@ -1836,6 +1907,20 @@
         }
     }
 
+    private void incrementPendingBroadcastsLocked() {
+        if (mPendingBroadcasts++ == 0) {
+            updateWakelockStatusLocked();
+        }
+    }
+
+    private void decrementPendingBroadcasts() {
+        synchronized (mLock) {
+            if (--mPendingBroadcasts == 0) {
+                updateWakelockStatusLocked();
+            }
+        }
+    }
+
     // Geocoder
 
     public String getFromLocation(double latitude, double longitude, int maxResults,
@@ -2061,6 +2146,7 @@
                     i.dump(pw, "      ");
                 }
             }
+            pw.println("  mProximityReceiver=" + mProximityReceiver);
             pw.println("  mProximityListener=" + mProximityListener);
             if (mEnabledProviders.size() > 0) {
                 pw.println("  Enabled Providers:");
diff --git a/telephony/java/com/android/internal/telephony/IccConstants.java b/telephony/java/com/android/internal/telephony/IccConstants.java
index 59ce5bb..014fbb6 100644
--- a/telephony/java/com/android/internal/telephony/IccConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccConstants.java
@@ -21,39 +21,44 @@
  */
 public interface IccConstants {
     // GSM SIM file ids from TS 51.011
-    public static final int EF_ADN = 0x6F3A;
-    public static final int EF_FDN = 0x6F3B;
-    public static final int EF_SDN = 0x6F49;
-    public static final int EF_EXT1 = 0x6F4A;
-    public static final int EF_EXT2 = 0x6F4B;
-    public static final int EF_EXT3 = 0x6F4C;
-    public static final int EF_EXT6 = 0x6fc8;   // Ext record for EF[MBDN]
-    public static final int EF_MWIS = 0x6FCA;
-    public static final int EF_MBDN = 0x6fc7;
-    public static final int EF_PNN = 0x6fc5;
-    public static final int EF_SPN = 0x6F46;
-    public static final int EF_SMS = 0x6F3C;
-    public static final int EF_ICCID = 0x2fe2;
-    public static final int EF_AD = 0x6FAD;
-    public static final int EF_MBI = 0x6fc9;
-    public static final int EF_MSISDN = 0x6f40;
-    public static final int EF_SPDI = 0x6fcd;
-    public static final int EF_SST = 0x6f38;
-    public static final int EF_CFIS = 0x6FCB;
-    public static final int EF_IMG = 0x4f20;
+    static final int EF_ADN = 0x6F3A;
+    static final int EF_FDN = 0x6F3B;
+    static final int EF_SDN = 0x6F49;
+    static final int EF_EXT1 = 0x6F4A;
+    static final int EF_EXT2 = 0x6F4B;
+    static final int EF_EXT3 = 0x6F4C;
+    static final int EF_EXT6 = 0x6fc8;   // Ext record for EF[MBDN]
+    static final int EF_MWIS = 0x6FCA;
+    static final int EF_MBDN = 0x6fc7;
+    static final int EF_PNN = 0x6fc5;
+    static final int EF_SPN = 0x6F46;
+    static final int EF_SMS = 0x6F3C;
+    static final int EF_ICCID = 0x2fe2;
+    static final int EF_AD = 0x6FAD;
+    static final int EF_MBI = 0x6fc9;
+    static final int EF_MSISDN = 0x6f40;
+    static final int EF_SPDI = 0x6fcd;
+    static final int EF_SST = 0x6f38;
+    static final int EF_CFIS = 0x6FCB;
+    static final int EF_IMG = 0x4f20;
 
     // GSM SIM file ids from CPHS (phase 2, version 4.2) CPHS4_2.WW6
-    public static final int EF_MAILBOX_CPHS = 0x6F17;
-    public static final int EF_VOICE_MAIL_INDICATOR_CPHS = 0x6F11;
-    public static final int EF_CFF_CPHS = 0x6F13;
-    public static final int EF_SPN_CPHS = 0x6f14;
-    public static final int EF_SPN_SHORT_CPHS = 0x6f18;
-    public static final int EF_INFO_CPHS = 0x6f16;
+    static final int EF_MAILBOX_CPHS = 0x6F17;
+    static final int EF_VOICE_MAIL_INDICATOR_CPHS = 0x6F11;
+    static final int EF_CFF_CPHS = 0x6F13;
+    static final int EF_SPN_CPHS = 0x6f14;
+    static final int EF_SPN_SHORT_CPHS = 0x6f18;
+    static final int EF_INFO_CPHS = 0x6f16;
 
     // CDMA RUIM file ids from 3GPP2 C.S0023-0
-    public static final int EF_CST = 0x6f32;
-    public static final int EF_RUIM_SPN =0x6F41;
+    static final int EF_CST = 0x6f32;
+    static final int EF_RUIM_SPN =0x6F41;
 
     // SMS record length from TS 51.011 10.5.3
     static public final int SMS_RECORD_LENGTH = 176;
+
+    static final String MF_SIM = "3F00";
+    static final String DF_TELECOM = "7F10";
+    static final String DF_GRAPHICS = "5F50";
+    static final String DF_GSM = "7F20";
 }
diff --git a/telephony/java/com/android/internal/telephony/IccFileHandler.java b/telephony/java/com/android/internal/telephony/IccFileHandler.java
index e751c5e..92ddd2c 100644
--- a/telephony/java/com/android/internal/telephony/IccFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/IccFileHandler.java
@@ -23,7 +23,7 @@
 /**
  * {@hide}
  */
-public abstract class IccFileHandler extends Handler {
+public abstract class IccFileHandler extends Handler implements IccConstants {
 
     //from TS 11.11 9.1 or elsewhere
     static protected final int COMMAND_READ_BINARY = 0xb0;
@@ -145,7 +145,7 @@
             = obtainMessage(EVENT_GET_RECORD_SIZE_DONE,
                         new LoadLinearFixedContext(fileid, recordNum, onLoaded));
 
-        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, null,
+        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
                         0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
     }
 
@@ -163,6 +163,7 @@
                 new LoadLinearFixedContext(IccConstants.EF_IMG, recordNum,
                         onLoaded));
 
+        // TODO(): Verify when path changes are done.
         phone.mCM.iccIO(COMMAND_GET_RESPONSE, IccConstants.EF_IMG, "img",
                 recordNum, READ_RECORD_MODE_ABSOLUTE,
                 GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null, response);
@@ -181,7 +182,7 @@
         Message response
                 = obtainMessage(EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE,
                         new LoadLinearFixedContext(fileid, onLoaded));
-        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, null,
+        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
                     0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
     }
 
@@ -198,7 +199,7 @@
         Message response = obtainMessage(EVENT_GET_RECORD_SIZE_DONE,
                         new LoadLinearFixedContext(fileid,onLoaded));
 
-        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, null,
+        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
                         0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
     }
 
@@ -216,7 +217,7 @@
         Message response = obtainMessage(EVENT_GET_BINARY_SIZE_DONE,
                         fileid, 0, onLoaded);
 
-        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, null,
+        phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
                         0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
     }
 
@@ -250,7 +251,7 @@
      */
     public void updateEFLinearFixed(int fileid, int recordNum, byte[] data,
             String pin2, Message onComplete) {
-        phone.mCM.iccIO(COMMAND_UPDATE_RECORD, fileid, null,
+        phone.mCM.iccIO(COMMAND_UPDATE_RECORD, fileid, getEFPath(fileid),
                         recordNum, READ_RECORD_MODE_ABSOLUTE, data.length,
                         IccUtils.bytesToHexString(data), pin2, onComplete);
     }
@@ -261,7 +262,7 @@
      * @param data must be exactly as long as the EF
      */
     public void updateEFTransparent(int fileid, byte[] data, Message onComplete) {
-        phone.mCM.iccIO(COMMAND_UPDATE_BINARY, fileid, null,
+        phone.mCM.iccIO(COMMAND_UPDATE_BINARY, fileid, getEFPath(fileid),
                         0, 0, data.length,
                         IccUtils.bytesToHexString(data), null, onComplete);
     }
@@ -394,7 +395,7 @@
                      lc.results = new ArrayList<byte[]>(lc.countRecords);
                  }
 
-                 phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, null,
+                 phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid),
                          lc.recordNum,
                          READ_RECORD_MODE_ABSOLUTE,
                          lc.recordSize, null, null,
@@ -432,7 +433,7 @@
                 size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
                        + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
 
-                phone.mCM.iccIO(COMMAND_READ_BINARY, fileid, null,
+                phone.mCM.iccIO(COMMAND_READ_BINARY, fileid, getEFPath(fileid),
                                 0, 0, size, null, null,
                                 obtainMessage(EVENT_READ_BINARY_DONE,
                                               fileid, 0, response));
@@ -467,7 +468,7 @@
                     if (lc.recordNum > lc.countRecords) {
                         sendResult(response, lc.results, null);
                     } else {
-                        phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, null,
+                        phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid),
                                     lc.recordNum,
                                     READ_RECORD_MODE_ABSOLUTE,
                                     lc.recordSize, null, null,
@@ -506,6 +507,36 @@
         }
     }
 
+    /**
+     * Returns the root path of the EF file.
+     * i.e returns MasterFile + DFfile as a string.
+     * Ex: For EF_ADN on a SIM, it will return "3F007F10"
+     * This function handles only EFids that are common to
+     * RUIM, SIM, USIM and other types of Icc cards.
+     *
+     * @param efId
+     * @return root path of the file.
+     */
+    protected String getCommonIccEFPath(int efid) {
+        switch(efid) {
+        case EF_ADN:
+        case EF_FDN:
+        case EF_MSISDN:
+        case EF_SDN:
+        case EF_EXT1:
+        case EF_EXT2:
+        case EF_EXT3:
+            return MF_SIM + DF_TELECOM;
+
+        case EF_ICCID:
+            return MF_SIM;
+        case EF_IMG:
+            return MF_SIM + DF_TELECOM + DF_GRAPHICS;
+        }
+        return null;
+    }
+
+    protected abstract String getEFPath(int efid);
     protected abstract void logd(String s);
 
     protected abstract void loge(String s);
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index e0a84ab..651c5051 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -64,7 +64,7 @@
     private static final String LOG_TAG = "CDMA";
     private static final boolean DBG = true;
 
-    //***** Instance Variables
+    private CDMAPhone mCdmaPhone;
 
     // Indicates baseband will not auto-attach
     private boolean noAutoAttach = false;
@@ -83,20 +83,19 @@
     private CdmaDataConnection mActiveDataConnection;
 
     /** Defined cdma connection profiles */
-    private static int EXTERNAL_NETWORK_DEFAULT_ID = 0;
-    private static int EXTERNAL_NETWORK_NUM_TYPES  = 1;
+    private static final int EXTERNAL_NETWORK_DEFAULT_ID = 0;
+    private static final int EXTERNAL_NETWORK_NUM_TYPES  = 1;
 
     private boolean[] dataEnabled = new boolean[EXTERNAL_NETWORK_NUM_TYPES];
 
-    //***** Constants
-
     /**
      * Pool size of CdmaDataConnection objects.
      */
     private static final int DATA_CONNECTION_POOL_SIZE = 1;
 
     private static final int POLL_CONNECTION_MILLIS = 5 * 1000;
-    private static final String INTENT_RECONNECT_ALARM = "com.android.internal.telephony.cdma-reconnect";
+    private static final String INTENT_RECONNECT_ALARM =
+            "com.android.internal.telephony.cdma-reconnect";
     private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = "reason";
 
     // Possibly promoate to base class, the only difference is
@@ -146,6 +145,7 @@
 
     CdmaDataConnectionTracker(CDMAPhone p) {
         super(p);
+        mCdmaPhone = p;
 
         p.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null);
         p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
@@ -187,15 +187,15 @@
         //Unregister from all events
         phone.mCM.unregisterForAvailable(this);
         phone.mCM.unregisterForOffOrNotAvailable(this);
-        ((CDMAPhone) phone).mRuimRecords.unregisterForRecordsLoaded(this);
+        mCdmaPhone.mRuimRecords.unregisterForRecordsLoaded(this);
         phone.mCM.unregisterForNVReady(this);
         phone.mCM.unregisterForDataStateChanged(this);
-        ((CDMAPhone) phone).mCT.unregisterForVoiceCallEnded(this);
-        ((CDMAPhone) phone).mCT.unregisterForVoiceCallStarted(this);
-        ((CDMAPhone) phone).mSST.unregisterForCdmaDataConnectionAttached(this);
-        ((CDMAPhone) phone).mSST.unregisterForCdmaDataConnectionDetached(this);
-        ((CDMAPhone) phone).mSST.unregisterForRoamingOn(this);
-        ((CDMAPhone) phone).mSST.unregisterForRoamingOff(this);
+        mCdmaPhone.mCT.unregisterForVoiceCallEnded(this);
+        mCdmaPhone.mCT.unregisterForVoiceCallStarted(this);
+        mCdmaPhone.mSST.unregisterForCdmaDataConnectionAttached(this);
+        mCdmaPhone.mSST.unregisterForCdmaDataConnectionDetached(this);
+        mCdmaPhone.mSST.unregisterForRoamingOn(this);
+        mCdmaPhone.mSST.unregisterForRoamingOff(this);
 
         phone.getContext().unregisterReceiver(this.mIntentReceiver);
         destroyAllDataConnectionList();
@@ -277,10 +277,10 @@
     public boolean isDataConnectionAsDesired() {
         boolean roaming = phone.getServiceState().getRoaming();
 
-        if ( ((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) || 
-		        ((CDMAPhone) phone).mRuimRecords.getRecordsLoaded()) &&
-                ((CDMAPhone) phone).mSST.getCurrentCdmaDataConnectionState() == 
-				ServiceState.STATE_IN_SERVICE &&
+        if (((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
+                 mCdmaPhone.mRuimRecords.getRecordsLoaded()) &&
+                (mCdmaPhone.mSST.getCurrentCdmaDataConnectionState() ==
+                 ServiceState.STATE_IN_SERVICE) &&
                 (!roaming || getDataOnRoamingEnabled()) &&
                 !mIsWifiConnected ) {
             return (state == State.CONNECTED);
@@ -353,7 +353,7 @@
             return true;
         }
 
-        int psState = ((CDMAPhone) phone).mSST.getCurrentCdmaDataConnectionState();
+        int psState = mCdmaPhone.mSST.getCurrentCdmaDataConnectionState();
         boolean roaming = phone.getServiceState().getRoaming();
 
         if ((state == State.IDLE || state == State.SCANNING)
@@ -361,9 +361,9 @@
                     psState == ServiceState.RADIO_TECHNOLOGY_EVDO_0 ||
                     psState == ServiceState.RADIO_TECHNOLOGY_EVDO_A)
                 && ((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
-                        ((CDMAPhone) phone).mRuimRecords.getRecordsLoaded())
-                && (((CDMAPhone) phone).mSST.isConcurrentVoiceAndData() ||
-                     phone.getState() == Phone.State.IDLE )
+                        mCdmaPhone.mRuimRecords.getRecordsLoaded())
+                && (mCdmaPhone.mSST.isConcurrentVoiceAndData() ||
+                        phone.getState() == Phone.State.IDLE )
                 && isDataAllowed()) {
 
             return setupData(reason);
@@ -374,8 +374,8 @@
                     " dataState=" + state +
                     " PS state=" + psState +
                     " radio state=" + phone.mCM.getRadioState() +
-                    " ruim=" + ((CDMAPhone) phone).mRuimRecords.getRecordsLoaded() +
-                    " concurrentVoice&Data=" + ((CDMAPhone) phone).mSST.isConcurrentVoiceAndData() +
+                    " ruim=" + mCdmaPhone.mRuimRecords.getRecordsLoaded() +
+                    " concurrentVoice&Data=" + mCdmaPhone.mSST.isConcurrentVoiceAndData() +
                     " phoneState=" + phone.getState() +
                     " dataEnabled=" + getAnyDataEnabled() +
                     " roaming=" + roaming +
@@ -781,7 +781,7 @@
      * @override com.android.internal.telephony.DataConnectionTracker
      */
     protected void onVoiceCallStarted() {
-        if (state == State.CONNECTED && !((CDMAPhone) phone).mSST.isConcurrentVoiceAndData()) {
+        if (state == State.CONNECTED && !mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
             stopNetStatPoll();
             phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
         }
@@ -792,7 +792,7 @@
      */
     protected void onVoiceCallEnded() {
         if (state == State.CONNECTED) {
-            if (!((CDMAPhone) phone).mSST.isConcurrentVoiceAndData()) {
+            if (!mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
                 startNetStatPoll();
                 phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
             } else {
@@ -818,7 +818,7 @@
         CdmaDataConnection dataConn;
 
        for (int i = 0; i < DATA_CONNECTION_POOL_SIZE; i++) {
-            dataConn = new CdmaDataConnection(((CDMAPhone) phone));
+            dataConn = new CdmaDataConnection(mCdmaPhone);
             dataConnectionList.add(dataConn);
        }
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
index 7d392f0..9de6c42 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
@@ -17,7 +17,6 @@
 package com.android.internal.telephony.cdma;
 
 import android.os.*;
-import android.os.AsyncResult;
 import android.util.Log;
 
 import com.android.internal.telephony.IccConstants;
@@ -68,6 +67,11 @@
         super.handleMessage(msg);
     }
 
+    protected String getEFPath(int efid) {
+        // TODO(): Implement for CDMA EFs.
+        return getCommonIccEFPath(efid);
+    }
+
     protected void logd(String msg) {
         Log.d(LOG_TAG, "[RuimFileHandler] " + msg);
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 71ffe74..9e6ebc4 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -836,57 +836,38 @@
             return;
         }
 
+        if (state == State.CONNECTED) {
+            // The way things are supposed to work, the PDP list
+            // should not contain the CID after it disconnects.
+            // However, the way things really work, sometimes the PDP
+            // context is still listed with active = false, which
+            // makes it hard to distinguish an activating context from
+            // an activated-and-then deactivated one.
+            if (!pdpStatesHasCID(pdpStates, cidActive)) {
+                // It looks like the PDP context has deactivated.
+                // Tear everything down and try to reconnect.
 
-        // This is how things are supposed to work:
-        // The PDP list is supposed to be empty of the CID
-        // when it disconnects
+                Log.i(LOG_TAG, "PDP connection has dropped. Reconnecting");
 
-        if (state == State.CONNECTED
-                && !pdpStatesHasCID(pdpStates, cidActive)) {
+                // Add an event log when the network drops PDP
+                int cid = -1;
+                GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
+                if (loc != null) cid = loc.getCid();
+                EventLog.List val = new EventLog.List(cid,
+                        TelephonyManager.getDefault().getNetworkType());
+                EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_PDP_NETWORK_DROP, val);
 
-            // It looks like the PDP context has deactivated
-            // Tear everything down and try to reconnect
-
-            Log.i(LOG_TAG, "PDP connection has dropped. Reconnecting");
-
-            // Add an event log when the network drops PDP
-            int cid = -1;
-            GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
-            if (loc != null) cid = loc.getCid();
-
-            EventLog.List val = new EventLog.List(cid,
-                    TelephonyManager.getDefault().getNetworkType());
-
-            EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_PDP_NETWORK_DROP, val);
-
-            cleanUpConnection(true, null);
-
-            return;
-        }
-
-        if (true) {
-            //
-            // Workaround for issue #655426
-            //
-
-            // --------------------------
-
-            // This is how some things work now: the PDP context is still
-            // listed with active = false, which makes it hard to
-            // distinguish an activating context from an activated-and-then
-            // deactivated one.
-            //
-            // Here, we only consider this authoritative if we asked for the
-            // PDP list. If it was an unsolicited response, we poll again
-            // to make sure everyone agrees on the initial state
-
-            if (state == State.CONNECTED
-                    && !pdpStatesHasActiveCID(pdpStates, cidActive)) {
+                cleanUpConnection(true, null);
+                return;
+            } else if (!pdpStatesHasActiveCID(pdpStates, cidActive)) {
+                // Here, we only consider this authoritative if we asked for the
+                // PDP list. If it was an unsolicited response, we poll again
+                // to make sure everyone agrees on the initial state.
 
                 if (!explicitPoll) {
                     // We think it disconnected but aren't sure...poll from our side
                     phone.mCM.getPDPContextList(
-                        this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
+                            this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
                 } else {
                     Log.i(LOG_TAG, "PDP connection has dropped (active=false case). "
                                     + " Reconnecting");
@@ -895,10 +876,8 @@
                     int cid = -1;
                     GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
                     if (loc != null) cid = loc.getCid();
-
                     EventLog.List val = new EventLog.List(cid,
                             TelephonyManager.getDefault().getNetworkType());
-
                     EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_PDP_NETWORK_DROP, val);
 
                     cleanUpConnection(true, null);
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
index ead1327..a08cdde 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
@@ -16,26 +16,19 @@
 
 package com.android.internal.telephony.gsm;
 
-import android.os.*;
-import android.os.AsyncResult;
+import android.os.Message;
 import android.util.Log;
 
 import com.android.internal.telephony.IccConstants;
-import com.android.internal.telephony.IccException;
 import com.android.internal.telephony.IccFileHandler;
-import com.android.internal.telephony.IccFileTypeMismatch;
-import com.android.internal.telephony.IccIoResult;
-import com.android.internal.telephony.IccUtils;
-import com.android.internal.telephony.PhoneProxy;
-
-import java.util.ArrayList;
 
 /**
  * {@hide}
  */
-public final class SIMFileHandler extends IccFileHandler {
+public final class SIMFileHandler extends IccFileHandler implements IccConstants {
     static final String LOG_TAG = "GSM";
 
+
     //***** Instance Variables
 
     //***** Constructor
@@ -59,6 +52,37 @@
         super.handleMessage(msg);
     }
 
+    protected String getEFPath(int efid) {
+        // TODO(): Make changes when USIM is supported
+        // TODO(): DF_GSM can be 7F20 or 7F21 to handle backward compatibility.
+        // Implement this after discussion with OEMs.
+        switch(efid) {
+        case EF_SMS:
+            return MF_SIM + DF_TELECOM;
+
+        case EF_EXT6:
+        case EF_MWIS:
+        case EF_MBI:
+        case EF_SPN:
+        case EF_AD:
+        case EF_MBDN:
+        case EF_PNN:
+        case EF_SPDI:
+        case EF_SST:
+        case EF_CFIS:
+            return MF_SIM + DF_GSM;
+
+        case EF_MAILBOX_CPHS:
+        case EF_VOICE_MAIL_INDICATOR_CPHS:
+        case EF_CFF_CPHS:
+        case EF_SPN_CPHS:
+        case EF_SPN_SHORT_CPHS:
+        case EF_INFO_CPHS:
+            return MF_SIM + DF_GSM;
+        }
+        return getCommonIccEFPath(efid);
+    }
+
     protected void logd(String msg) {
         Log.d(LOG_TAG, "[SIMFileHandler] " + msg);
     }
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
index 09e3b02..f3c1542 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
@@ -23,27 +23,11 @@
 import android.app.SearchManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
 import android.os.ServiceManager;
-import android.server.search.SearchableInfo;
-import android.server.search.SearchableInfo.ActionKeyInfo;
-import android.test.ActivityInstrumentationTestCase;
-import android.test.MoreAsserts;
-import android.test.mock.MockContext;
-import android.test.mock.MockPackageManager;
+import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.AndroidRuntimeException;
-import android.view.KeyEvent;
-
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * To launch this test from the command line:
@@ -52,7 +36,7 @@
  *   -e class com.android.unit_tests.SearchManagerTest \
  *   com.android.unit_tests/android.test.InstrumentationTestRunner
  */
-public class SearchManagerTest extends ActivityInstrumentationTestCase<LocalActivity> {
+public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalActivity> {
     
     // If non-zero, enable a set of tests that start and stop the search manager.
     // This is currently disabled because it's causing an unwanted jump from the unit test
@@ -71,18 +55,6 @@
      * testSearchManagerInvocations()
      *  FIX - make it work again
      *  stress test with a very long string
-     *  
-     * SearchableInfo tests
-     *  Mock the context so I can provide very specific input data
-     *  Confirm OK with "zero" searchables
-     *  Confirm "good" metadata read properly
-     *  Confirm "bad" metadata skipped properly
-     *  Confirm ordering of searchables
-     *  Confirm "good" actionkeys
-     *  confirm "bad" actionkeys are rejected
-     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
-     *  findActionKey works
-     *  getIcon works
      * 
      * SearchManager tests
      *  confirm proper identification of "default" activity based on policy, not hardcoded contacts
@@ -195,348 +167,6 @@
             searchManager.stopSearch();
         }
      }
-    
-    /**
-     * The goal of this test is to confirm proper operation of the 
-     * SearchableInfo helper class.
-     * 
-     * TODO:  The metadata source needs to be mocked out because adding
-     * searchability metadata via this test is causing it to leak into the
-     * real system.  So for now I'm just going to test for existence of the
-     * GoogleSearch app (which is searchable).
-     */
-    @LargeTest
-    public void testSearchableGoogleSearch() {
-        // test basic array & hashmap
-        SearchableInfo.buildSearchableList(mContext);
 
-        // test linkage from another activity
-        // TODO inject this via mocking into the package manager.
-        // TODO for now, just check for searchable GoogleSearch app (this isn't really a unit test)
-        ComponentName thisActivity = new ComponentName(
-                "com.android.googlesearch", 
-                "com.android.googlesearch.GoogleSearch");
-
-        SearchableInfo si = SearchableInfo.getSearchableInfo(mContext, thisActivity);
-        assertNotNull(si);
-        assertTrue(si.mSearchable);
-        assertEquals(thisActivity, si.mSearchActivity);
-        
-        Context appContext = si.getActivityContext(mContext);
-        assertNotNull(appContext);
-        MoreAsserts.assertNotEqual(appContext, mContext);
-        assertEquals("Google Search", appContext.getString(si.getHintId()));
-        assertEquals("Google", appContext.getString(si.getLabelId()));
-    }
-    
-    /**
-     * Test that non-searchable activities return no searchable info (this would typically
-     * trigger the use of the default searchable e.g. contacts)
-     */
-    @LargeTest
-    public void testNonSearchable() {
-        // test basic array & hashmap
-        SearchableInfo.buildSearchableList(mContext);
-
-        // confirm that we return null for non-searchy activities
-        ComponentName nonActivity = new ComponentName(
-                            "com.android.unit_tests",
-                            "com.android.unit_tests.NO_SEARCH_ACTIVITY");
-        SearchableInfo si = SearchableInfo.getSearchableInfo(mContext, nonActivity);
-        assertNull(si);
-    }
-    
-    /**
-     * This is an attempt to run the searchable info list with a mocked context.  Here are some
-     * things I'd like to test.
-     *
-     *  Confirm OK with "zero" searchables
-     *  Confirm "good" metadata read properly
-     *  Confirm "bad" metadata skipped properly
-     *  Confirm ordering of searchables
-     *  Confirm "good" actionkeys
-     *  confirm "bad" actionkeys are rejected
-     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
-     *  findActionKey works
-     *  getIcon works
-
-     */
-    @LargeTest
-    public void testSearchableMocked() {
-        MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
-        MyMockContext mockContext = new MyMockContext(mContext, mockPM);
-        ArrayList<SearchableInfo> searchables;
-        int count;
-
-        // build item list with real-world source data
-        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
-        SearchableInfo.buildSearchableList(mockContext);
-        // tests with "real" searchables (deprecate, this should be a unit test)
-        searchables = SearchableInfo.getSearchablesList();
-        count = searchables.size();
-        assertTrue(count >= 1);         // this isn't really a unit test
-        checkSearchables(searchables);
-
-        // build item list with mocked search data
-        // this round of tests confirms good operations with "zero" searchables found
-        // This should return either a null pointer or an empty list
-        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
-        SearchableInfo.buildSearchableList(mockContext);
-        searchables = SearchableInfo.getSearchablesList();
-        if (searchables != null) {
-            count = searchables.size();
-            assertTrue(count == 0);
-        }
-    }
-    
-    /**
-     * Generic health checker for an array of searchables.
-     * 
-     * This is designed to pass for any semi-legal searchable, without knowing much about
-     * the format of the underlying data.  It's fairly easy for a non-compliant application
-     * to provide meta-data that will pass here (e.g. a non-existent suggestions authority).
-     * 
-     * @param searchables The list of searchables to examine.
-     */
-    private void checkSearchables(ArrayList<SearchableInfo> searchablesList) {
-        assertNotNull(searchablesList);
-        int count = searchablesList.size();
-        for (int ii = 0; ii < count; ii++) {
-            SearchableInfo si = searchablesList.get(ii);
-            assertNotNull(si);
-            assertTrue(si.mSearchable);
-            assertTrue(si.getLabelId() != 0);        // This must be a useable string
-            assertNotEmpty(si.mSearchActivity.getClassName());
-            assertNotEmpty(si.mSearchActivity.getPackageName());
-            if (si.getSuggestAuthority() != null) {
-                // The suggestion fields are largely optional, so we'll just confirm basic health
-                assertNotEmpty(si.getSuggestAuthority());
-                assertNullOrNotEmpty(si.getSuggestPath());
-                assertNullOrNotEmpty(si.getSuggestSelection());
-                assertNullOrNotEmpty(si.getSuggestIntentAction());
-                assertNullOrNotEmpty(si.getSuggestIntentData());
-            }
-            /* Add a way to get the entire action key list, then explicitly test its elements */
-            /* For now, test the most common action key (CALL) */
-            ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
-            if (ai != null) {
-                assertEquals(ai.mKeyCode, KeyEvent.KEYCODE_CALL);
-                // one of these three fields must be non-null & non-empty
-                boolean m1 = (ai.mQueryActionMsg != null) && (ai.mQueryActionMsg.length() > 0);
-                boolean m2 = (ai.mSuggestActionMsg != null) && (ai.mSuggestActionMsg.length() > 0);
-                boolean m3 = (ai.mSuggestActionMsgColumn != null) && 
-                                (ai.mSuggestActionMsgColumn.length() > 0);
-                assertTrue(m1 || m2 || m3);
-            }
-            
-            /* 
-             * Find ways to test these:
-             * 
-             * private int mSearchMode
-             * private Drawable mIcon
-             */
-            
-            /*
-             * Explicitly not tested here:
-             * 
-             * Can be null, so not much to see:
-             * public String mSearchHint
-             * private String mZeroQueryBanner
-             * 
-             * To be deprecated/removed, so don't bother:
-             * public boolean mFilterMode
-             * public boolean mQuickStart
-             * private boolean mIconResized
-             * private int mIconResizeWidth
-             * private int mIconResizeHeight
-             * 
-             * All of these are "internal" working variables, not part of any contract
-             * private ActivityInfo mActivityInfo
-             * private Rect mTempRect
-             * private String mSuggestProviderPackage
-             * private String mCacheActivityContext
-             */
-        }
-    }
-    
-    /**
-     * Combo assert for "string not null and not empty"
-     */
-    private void assertNotEmpty(final String s) {
-        assertNotNull(s);
-        MoreAsserts.assertNotEqual(s, "");
-    }
-    
-    /**
-     * Combo assert for "string null or (not null and not empty)"
-     */
-    private void assertNullOrNotEmpty(final String s) {
-        if (s != null) {
-            MoreAsserts.assertNotEqual(s, "");
-        }
-    }    
-    
-    /**
-     * This is a mock for context.  Used to perform a true unit test on SearchableInfo.
-     * 
-     */
-    private class MyMockContext extends MockContext {
-        
-        protected Context mRealContext;
-        protected PackageManager mPackageManager;
-        
-        /**
-         * Constructor.
-         * 
-         * @param realContext Please pass in a real context for some pass-throughs to function.
-         */
-        MyMockContext(Context realContext, PackageManager packageManager) {
-            mRealContext = realContext;
-            mPackageManager = packageManager;
-        }
-        
-        /**
-         * Resources.  Pass through for now.
-         */
-        @Override
-        public Resources getResources() {
-            return mRealContext.getResources();
-        }
-
-        /**
-         * Package manager.  Pass through for now.
-         */
-        @Override
-        public PackageManager getPackageManager() {
-            return mPackageManager;
-        }
-
-        /**
-         * Package manager.  Pass through for now.
-         */
-        @Override
-        public Context createPackageContext(String packageName, int flags)
-                throws PackageManager.NameNotFoundException {
-            return mRealContext.createPackageContext(packageName, flags);
-        }
-    }
-
-/**
- * This is a mock for package manager.  Used to perform a true unit test on SearchableInfo.
- * 
- */
-    private class MyMockPackageManager extends MockPackageManager {
-        
-        public final static int SEARCHABLES_PASSTHROUGH = 0;
-        public final static int SEARCHABLES_MOCK_ZERO = 1;
-        public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
-        public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
-        
-        protected PackageManager mRealPackageManager;
-        protected int mSearchablesMode;
-
-        public MyMockPackageManager(PackageManager realPM) {
-            mRealPackageManager = realPM;
-            mSearchablesMode = SEARCHABLES_PASSTHROUGH;
-        }
-
-        /**
-         * Set the mode for various tests.
-         */
-        public void setSearchablesMode(int newMode) {
-            switch (newMode) {
-            case SEARCHABLES_PASSTHROUGH:
-            case SEARCHABLES_MOCK_ZERO:
-                mSearchablesMode = newMode;
-                break;
-                
-            default:
-                throw new UnsupportedOperationException();       
-            }
-        }
-        
-        /**
-         * Find activities that support a given intent.
-         * 
-         * Retrieve all activities that can be performed for the given intent.
-         * 
-         * @param intent The desired intent as per resolveActivity().
-         * @param flags Additional option flags.  The most important is
-         *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
-         *                    those activities that support the CATEGORY_DEFAULT.
-         * 
-         * @return A List<ResolveInfo> containing one entry for each matching
-         *         Activity. These are ordered from best to worst match -- that
-         *         is, the first item in the list is what is returned by
-         *         resolveActivity().  If there are no matching activities, an empty
-         *         list is returned.
-         */
-        @Override 
-        public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
-            assertNotNull(intent);
-            assertEquals(intent.getAction(), Intent.ACTION_SEARCH);
-            switch (mSearchablesMode) {
-            case SEARCHABLES_PASSTHROUGH:
-                return mRealPackageManager.queryIntentActivities(intent, flags);
-            case SEARCHABLES_MOCK_ZERO:
-                return null;
-            default:
-                throw new UnsupportedOperationException();
-            }
-        }
-        
-        /**
-         * Retrieve an XML file from a package.  This is a low-level API used to
-         * retrieve XML meta data.
-         * 
-         * @param packageName The name of the package that this xml is coming from.
-         * Can not be null.
-         * @param resid The resource identifier of the desired xml.  Can not be 0.
-         * @param appInfo Overall information about <var>packageName</var>.  This
-         * may be null, in which case the application information will be retrieved
-         * for you if needed; if you already have this information around, it can
-         * be much more efficient to supply it here.
-         * 
-         * @return Returns an XmlPullParser allowing you to parse out the XML
-         * data.  Returns null if the xml resource could not be found for any
-         * reason.
-         */
-        @Override 
-        public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
-            assertNotNull(packageName);
-            MoreAsserts.assertNotEqual(packageName, "");
-            MoreAsserts.assertNotEqual(resid, 0);
-            switch (mSearchablesMode) {
-            case SEARCHABLES_PASSTHROUGH:
-                return mRealPackageManager.getXml(packageName, resid, appInfo);
-            case SEARCHABLES_MOCK_ZERO:
-            default:
-                throw new UnsupportedOperationException();
-            }
-        }
-        
-        /**
-         * Find a single content provider by its base path name.
-         * 
-         * @param name The name of the provider to find.
-         * @param flags Additional option flags.  Currently should always be 0.
-         * 
-         * @return ContentProviderInfo Information about the provider, if found,
-         *         else null.
-         */
-        @Override 
-        public ProviderInfo resolveContentProvider(String name, int flags) {
-            assertNotNull(name);
-            MoreAsserts.assertNotEqual(name, "");
-            assertEquals(flags, 0);
-            switch (mSearchablesMode) {
-            case SEARCHABLES_PASSTHROUGH:
-                return mRealPackageManager.resolveContentProvider(name, flags);
-            case SEARCHABLES_MOCK_ZERO:
-            default:
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
 }
 
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java
new file mode 100644
index 0000000..c299b10
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.unit_tests;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.server.search.SearchableInfo;
+import android.server.search.Searchables;
+import android.server.search.SearchableInfo.ActionKeyInfo;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.mock.MockContext;
+import android.test.mock.MockPackageManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * To launch this test from the command line:
+ * 
+ * adb shell am instrument -w \
+ *   -e class com.android.unit_tests.SearchablesTest \
+ *   com.android.unit_tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class SearchablesTest extends AndroidTestCase {
+    
+    /*
+     * SearchableInfo tests
+     *  Mock the context so I can provide very specific input data
+     *  Confirm OK with "zero" searchables
+     *  Confirm "good" metadata read properly
+     *  Confirm "bad" metadata skipped properly
+     *  Confirm ordering of searchables
+     *  Confirm "good" actionkeys
+     *  confirm "bad" actionkeys are rejected
+     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
+     *  findActionKey works
+     *  getIcon works
+     */
+    
+    /**
+     * The goal of this test is to confirm proper operation of the 
+     * SearchableInfo helper class.
+     * 
+     * TODO:  The metadata source needs to be mocked out because adding
+     * searchability metadata via this test is causing it to leak into the
+     * real system.  So for now I'm just going to test for existence of the
+     * GoogleSearch app (which is searchable).
+     */
+    public void testSearchableGoogleSearch() {
+        // test basic array & hashmap
+        Searchables searchables = new Searchables(mContext);
+        searchables.buildSearchableList();
+
+        // test linkage from another activity
+        // TODO inject this via mocking into the package manager.
+        // TODO for now, just check for searchable GoogleSearch app (this isn't really a unit test)
+        ComponentName thisActivity = new ComponentName(
+                "com.android.googlesearch", 
+                "com.android.googlesearch.GoogleSearch");
+
+        SearchableInfo si = searchables.getSearchableInfo(thisActivity);
+        assertNotNull(si);
+        assertTrue(si.mSearchable);
+        assertEquals(thisActivity, si.mSearchActivity);
+        
+        Context appContext = si.getActivityContext(mContext);
+        assertNotNull(appContext);
+        MoreAsserts.assertNotEqual(appContext, mContext);
+        assertEquals("Google Search", appContext.getString(si.getHintId()));
+        assertEquals("Google", appContext.getString(si.getLabelId()));
+    }
+    
+    /**
+     * Test that non-searchable activities return no searchable info (this would typically
+     * trigger the use of the default searchable e.g. contacts)
+     */
+    public void testNonSearchable() {
+        // test basic array & hashmap
+        Searchables searchables = new Searchables(mContext);
+        searchables.buildSearchableList();
+
+        // confirm that we return null for non-searchy activities
+        ComponentName nonActivity = new ComponentName(
+                            "com.android.unit_tests",
+                            "com.android.unit_tests.NO_SEARCH_ACTIVITY");
+        SearchableInfo si = searchables.getSearchableInfo(nonActivity);
+        assertNull(si);
+    }
+    
+    /**
+     * This is an attempt to run the searchable info list with a mocked context.  Here are some
+     * things I'd like to test.
+     *
+     *  Confirm OK with "zero" searchables
+     *  Confirm "good" metadata read properly
+     *  Confirm "bad" metadata skipped properly
+     *  Confirm ordering of searchables
+     *  Confirm "good" actionkeys
+     *  confirm "bad" actionkeys are rejected
+     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
+     *  findActionKey works
+     *  getIcon works
+
+     */
+    public void testSearchableMocked() {
+        MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
+        MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+        Searchables searchables;
+        ArrayList<SearchableInfo> searchablesList;
+        int count;
+
+            
+        // build item list with real-world source data
+        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
+        searchables = new Searchables(mockContext);
+        searchables.buildSearchableList();
+        // tests with "real" searchables (deprecate, this should be a unit test)
+        searchablesList = searchables.getSearchablesList();
+        count = searchablesList.size();
+        assertTrue(count >= 1);         // this isn't really a unit test
+        checkSearchables(searchablesList);
+
+        // build item list with mocked search data
+        // this round of tests confirms good operations with "zero" searchables found
+        // This should return either a null pointer or an empty list
+        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
+        searchables = new Searchables(mockContext);
+        searchables.buildSearchableList();
+        searchablesList = searchables.getSearchablesList();
+        if (searchablesList != null) {
+            count = searchablesList.size();
+            assertTrue(count == 0);
+        }
+    }
+    
+    /**
+     * Generic health checker for an array of searchables.
+     * 
+     * This is designed to pass for any semi-legal searchable, without knowing much about
+     * the format of the underlying data.  It's fairly easy for a non-compliant application
+     * to provide meta-data that will pass here (e.g. a non-existent suggestions authority).
+     * 
+     * @param searchables The list of searchables to examine.
+     */
+    private void checkSearchables(ArrayList<SearchableInfo> searchablesList) {
+        assertNotNull(searchablesList);
+        int count = searchablesList.size();
+        for (int ii = 0; ii < count; ii++) {
+            SearchableInfo si = searchablesList.get(ii);
+            assertNotNull(si);
+            assertTrue(si.mSearchable);
+            assertTrue(si.getLabelId() != 0);        // This must be a useable string
+            assertNotEmpty(si.mSearchActivity.getClassName());
+            assertNotEmpty(si.mSearchActivity.getPackageName());
+            if (si.getSuggestAuthority() != null) {
+                // The suggestion fields are largely optional, so we'll just confirm basic health
+                assertNotEmpty(si.getSuggestAuthority());
+                assertNullOrNotEmpty(si.getSuggestPath());
+                assertNullOrNotEmpty(si.getSuggestSelection());
+                assertNullOrNotEmpty(si.getSuggestIntentAction());
+                assertNullOrNotEmpty(si.getSuggestIntentData());
+            }
+            /* Add a way to get the entire action key list, then explicitly test its elements */
+            /* For now, test the most common action key (CALL) */
+            ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
+            if (ai != null) {
+                assertEquals(ai.mKeyCode, KeyEvent.KEYCODE_CALL);
+                // one of these three fields must be non-null & non-empty
+                boolean m1 = (ai.mQueryActionMsg != null) && (ai.mQueryActionMsg.length() > 0);
+                boolean m2 = (ai.mSuggestActionMsg != null) && (ai.mSuggestActionMsg.length() > 0);
+                boolean m3 = (ai.mSuggestActionMsgColumn != null) && 
+                                (ai.mSuggestActionMsgColumn.length() > 0);
+                assertTrue(m1 || m2 || m3);
+            }
+            
+            /* 
+             * Find ways to test these:
+             * 
+             * private int mSearchMode
+             * private Drawable mIcon
+             */
+            
+            /*
+             * Explicitly not tested here:
+             * 
+             * Can be null, so not much to see:
+             * public String mSearchHint
+             * private String mZeroQueryBanner
+             * 
+             * To be deprecated/removed, so don't bother:
+             * public boolean mFilterMode
+             * public boolean mQuickStart
+             * private boolean mIconResized
+             * private int mIconResizeWidth
+             * private int mIconResizeHeight
+             * 
+             * All of these are "internal" working variables, not part of any contract
+             * private ActivityInfo mActivityInfo
+             * private Rect mTempRect
+             * private String mSuggestProviderPackage
+             * private String mCacheActivityContext
+             */
+        }
+    }
+    
+    /**
+     * Combo assert for "string not null and not empty"
+     */
+    private void assertNotEmpty(final String s) {
+        assertNotNull(s);
+        MoreAsserts.assertNotEqual(s, "");
+    }
+    
+    /**
+     * Combo assert for "string null or (not null and not empty)"
+     */
+    private void assertNullOrNotEmpty(final String s) {
+        if (s != null) {
+            MoreAsserts.assertNotEqual(s, "");
+        }
+    }    
+    
+    /**
+     * This is a mock for context.  Used to perform a true unit test on SearchableInfo.
+     * 
+     */
+    private class MyMockContext extends MockContext {
+        
+        protected Context mRealContext;
+        protected PackageManager mPackageManager;
+        
+        /**
+         * Constructor.
+         * 
+         * @param realContext Please pass in a real context for some pass-throughs to function.
+         */
+        MyMockContext(Context realContext, PackageManager packageManager) {
+            mRealContext = realContext;
+            mPackageManager = packageManager;
+        }
+        
+        /**
+         * Resources.  Pass through for now.
+         */
+        @Override
+        public Resources getResources() {
+            return mRealContext.getResources();
+        }
+
+        /**
+         * Package manager.  Pass through for now.
+         */
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        /**
+         * Package manager.  Pass through for now.
+         */
+        @Override
+        public Context createPackageContext(String packageName, int flags)
+                throws PackageManager.NameNotFoundException {
+            return mRealContext.createPackageContext(packageName, flags);
+        }
+    }
+
+/**
+ * This is a mock for package manager.  Used to perform a true unit test on SearchableInfo.
+ * 
+ */
+    private class MyMockPackageManager extends MockPackageManager {
+        
+        public final static int SEARCHABLES_PASSTHROUGH = 0;
+        public final static int SEARCHABLES_MOCK_ZERO = 1;
+        public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
+        public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
+        
+        protected PackageManager mRealPackageManager;
+        protected int mSearchablesMode;
+
+        public MyMockPackageManager(PackageManager realPM) {
+            mRealPackageManager = realPM;
+            mSearchablesMode = SEARCHABLES_PASSTHROUGH;
+        }
+
+        /**
+         * Set the mode for various tests.
+         */
+        public void setSearchablesMode(int newMode) {
+            switch (newMode) {
+            case SEARCHABLES_PASSTHROUGH:
+            case SEARCHABLES_MOCK_ZERO:
+                mSearchablesMode = newMode;
+                break;
+                
+            default:
+                throw new UnsupportedOperationException();       
+            }
+        }
+        
+        /**
+         * Find activities that support a given intent.
+         * 
+         * Retrieve all activities that can be performed for the given intent.
+         * 
+         * @param intent The desired intent as per resolveActivity().
+         * @param flags Additional option flags.  The most important is
+         *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
+         *                    those activities that support the CATEGORY_DEFAULT.
+         * 
+         * @return A List<ResolveInfo> containing one entry for each matching
+         *         Activity. These are ordered from best to worst match -- that
+         *         is, the first item in the list is what is returned by
+         *         resolveActivity().  If there are no matching activities, an empty
+         *         list is returned.
+         */
+        @Override 
+        public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+            assertNotNull(intent);
+            assertEquals(intent.getAction(), Intent.ACTION_SEARCH);
+            switch (mSearchablesMode) {
+            case SEARCHABLES_PASSTHROUGH:
+                return mRealPackageManager.queryIntentActivities(intent, flags);
+            case SEARCHABLES_MOCK_ZERO:
+                return null;
+            default:
+                throw new UnsupportedOperationException();
+            }
+        }
+        
+        /**
+         * Retrieve an XML file from a package.  This is a low-level API used to
+         * retrieve XML meta data.
+         * 
+         * @param packageName The name of the package that this xml is coming from.
+         * Can not be null.
+         * @param resid The resource identifier of the desired xml.  Can not be 0.
+         * @param appInfo Overall information about <var>packageName</var>.  This
+         * may be null, in which case the application information will be retrieved
+         * for you if needed; if you already have this information around, it can
+         * be much more efficient to supply it here.
+         * 
+         * @return Returns an XmlPullParser allowing you to parse out the XML
+         * data.  Returns null if the xml resource could not be found for any
+         * reason.
+         */
+        @Override 
+        public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
+            assertNotNull(packageName);
+            MoreAsserts.assertNotEqual(packageName, "");
+            MoreAsserts.assertNotEqual(resid, 0);
+            switch (mSearchablesMode) {
+            case SEARCHABLES_PASSTHROUGH:
+                return mRealPackageManager.getXml(packageName, resid, appInfo);
+            case SEARCHABLES_MOCK_ZERO:
+            default:
+                throw new UnsupportedOperationException();
+            }
+        }
+        
+        /**
+         * Find a single content provider by its base path name.
+         * 
+         * @param name The name of the provider to find.
+         * @param flags Additional option flags.  Currently should always be 0.
+         * 
+         * @return ContentProviderInfo Information about the provider, if found,
+         *         else null.
+         */
+        @Override 
+        public ProviderInfo resolveContentProvider(String name, int flags) {
+            assertNotNull(name);
+            MoreAsserts.assertNotEqual(name, "");
+            assertEquals(flags, 0);
+            switch (mSearchablesMode) {
+            case SEARCHABLES_PASSTHROUGH:
+                return mRealPackageManager.resolveContentProvider(name, flags);
+            case SEARCHABLES_MOCK_ZERO:
+            default:
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+}
+
diff --git a/tests/FrameworkTest/tests/src/android/widget/AutoCompleteTextViewPopup.java b/tests/FrameworkTest/tests/src/android/widget/AutoCompleteTextViewPopup.java
index 663b7a4..6f89fce 100644
--- a/tests/FrameworkTest/tests/src/android/widget/AutoCompleteTextViewPopup.java
+++ b/tests/FrameworkTest/tests/src/android/widget/AutoCompleteTextViewPopup.java
@@ -147,4 +147,56 @@
         // now try moving "down" - nothing should happen since there's no longer an adapter
         sendKeys("DPAD_DOWN");
     }
+    
+    /** Test the show/hide behavior of the drop-down. */
+    @MediumTest
+    public void testPopupShow() throws Throwable {
+        AutoCompleteTextViewSimple theActivity = getActivity();
+        final AutoCompleteTextView textView = theActivity.getTextView();
+        final Instrumentation instrumentation = getInstrumentation();
+        
+        // Drop-down should not be showing when no text has been entered
+        assertFalse("isPopupShowing() on start", textView.isPopupShowing());
+        
+        // focus and type
+        textView.requestFocus();
+        instrumentation.waitForIdleSync();
+        sendKeys("A");
+        
+        // Drop-down should now be visible
+        assertTrue("isPopupShowing() after typing", textView.isPopupShowing());
+        
+        // Clear the text
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                textView.setText("");
+            }
+        });
+        instrumentation.waitForIdleSync();
+        
+        // Drop-down should be hidden when text is cleared
+        assertFalse("isPopupShowing() after text cleared", textView.isPopupShowing());
+        
+        // Set the text, without filtering
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                textView.setText("a", false);
+            }
+        });
+        instrumentation.waitForIdleSync();
+        
+        // Drop-down should still be hidden
+        assertFalse("isPopupShowing() after setText(\"a\", false)", textView.isPopupShowing());
+        
+        // Set the text, now with filtering
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                textView.setText("a");
+            }
+        });
+        instrumentation.waitForIdleSync();
+        
+        // Drop-down should show up after setText() with filtering 
+        assertTrue("isPopupShowing() after text set", textView.isPopupShowing());
+    }
 }