Merge change 4184 into donut

* changes:
  Add permission test structure for private framework permissions.
diff --git a/Android.mk b/Android.mk
index bffd04c..6e292a8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -76,6 +76,7 @@
 	core/java/android/app/IIntentSender.aidl \
 	core/java/android/app/INotificationManager.aidl \
 	core/java/android/app/ISearchManager.aidl \
+	core/java/android/app/ISearchManagerCallback.aidl \
 	core/java/android/app/IServiceConnection.aidl \
 	core/java/android/app/IStatusBar.aidl \
 	core/java/android/app/IThumbnailReceiver.aidl \
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f9b3d05..7fb3449 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -628,6 +628,8 @@
     boolean mStartedActivity;
     /*package*/ int mConfigChangeFlags;
     /*package*/ Configuration mCurrentConfig;
+    private SearchManager mSearchManager;
+    private Bundle mSearchDialogState = null;
 
     private Window mWindow;
 
@@ -788,6 +790,9 @@
     protected void onCreate(Bundle savedInstanceState) {
         mVisibleFromClient = mWindow.getWindowStyle().getBoolean(
                 com.android.internal.R.styleable.Window_windowNoDisplay, true);
+        // uses super.getSystemService() since this.getSystemService() looks at the
+        // mSearchManager field.
+        mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE);
         mCalled = true;
     }
 
@@ -805,9 +810,10 @@
         
         // Also restore the state of a search dialog (if any)
         // TODO more generic than just this manager
-        SearchManager searchManager = 
-            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
-        searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY);
+        Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY);
+        if (searchState != null) {
+            mSearchManager.restoreSearchDialog(searchState);
+        }
     }
 
     /**
@@ -1013,9 +1019,11 @@
 
         // Also save the state of a search dialog (if any)
         // TODO more generic than just this manager
-        SearchManager searchManager = 
-            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
-        searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY);
+        // onPause() should always be called before this method, so mSearchManagerState
+        // should be up to date.
+        if (mSearchDialogState != null) {
+            outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState);
+        }
     }
 
     /**
@@ -1286,12 +1294,6 @@
                 }
             }
         }
-        
-        // also dismiss search dialog if showing
-        // TODO more generic than just this manager
-        SearchManager searchManager = 
-            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
-        searchManager.stopSearch();
 
         // close any cursors we are managing.
         int numCursors = mManagedCursors.size();
@@ -1301,6 +1303,10 @@
                 c.mCursor.close();
             }
         }
+
+        // Clear any search state saved in performPause(). If the state may be needed in the
+        // future, it will have been saved by performSaveInstanceState()
+        mSearchDialogState = null;
     }
 
     /**
@@ -1324,9 +1330,7 @@
         
         // also update search dialog if showing
         // TODO more generic than just this manager
-        SearchManager searchManager = 
-            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
-        searchManager.onConfigurationChanged(newConfig);
+        mSearchManager.onConfigurationChanged(newConfig);
         
         if (mWindow != null) {
             // Pass the configuration changed event to the window
@@ -2543,10 +2547,7 @@
      */
     public void startSearch(String initialQuery, boolean selectInitialQuery, 
             Bundle appSearchData, boolean globalSearch) {
-        // activate the search manager and start it up!
-        SearchManager searchManager = (SearchManager)
-                        getSystemService(Context.SEARCH_SERVICE);
-        searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+        mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
                         appSearchData, globalSearch); 
     }
 
@@ -3265,6 +3266,8 @@
 
         if (WINDOW_SERVICE.equals(name)) {
             return mWindowManager;
+        } else if (SEARCH_SERVICE.equals(name)) {
+            return mSearchManager;
         }
         return super.getSystemService(name);
     }
@@ -3563,10 +3566,21 @@
                 "Activity " + mComponent.toShortString() +
                 " did not call through to super.onPostResume()");
         }
+
+        // restore search dialog, if any
+        if (mSearchDialogState != null) {
+            mSearchManager.restoreSearchDialog(mSearchDialogState);
+        }
+        mSearchDialogState = null;
     }
 
     final void performPause() {
         onPause();
+
+        // save search dialog state if the search dialog is open,
+        // and then dismiss the search dialog
+        mSearchDialogState = mSearchManager.saveSearchDialog();
+        mSearchManager.stopSearch();
     }
     
     final void performUserLeaving() {
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 374423e..e8bd60a 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -16,7 +16,10 @@
 
 package android.app;
 
+import android.app.ISearchManagerCallback;
 import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Bundle;
 import android.server.search.SearchableInfo;
 
 /** @hide */
@@ -26,4 +29,15 @@
    List<SearchableInfo> getSearchablesForWebSearch();
    SearchableInfo getDefaultSearchableForWebSearch();
    void setDefaultWebSearch(in ComponentName component);
+   void startSearch(in String initialQuery,
+            boolean selectInitialQuery,
+            in ComponentName launchActivity,
+            in Bundle appSearchData,
+            boolean globalSearch,
+            ISearchManagerCallback searchManagerCallback);
+    void stopSearch();
+    boolean isVisible();
+    Bundle onSaveInstanceState();
+    void onRestoreInstanceState(in Bundle savedInstanceState);
+    void onConfigurationChanged(in Configuration newConfig);
 }
diff --git a/core/java/android/app/ISearchManagerCallback.aidl b/core/java/android/app/ISearchManagerCallback.aidl
new file mode 100644
index 0000000..bdfb2ba
--- /dev/null
+++ b/core/java/android/app/ISearchManagerCallback.aidl
@@ -0,0 +1,23 @@
+/**
+ * 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;
+
+/** @hide */
+oneway interface ISearchManagerCallback {
+    void onDismiss();
+    void onCancel();
+}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 7de6572..9141c4c 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -76,8 +76,8 @@
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
- * In-application-process implementation of Search Bar.  This is still controlled by the 
- * SearchManager, but it runs in the current activity's process to keep things lighter weight.
+ * System search dialog. This is controlled by the 
+ * SearchManagerService and runs in the system process.
  * 
  * @hide
  */
@@ -179,17 +179,17 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        Window theWindow = getWindow();
-        theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
-
         setContentView(com.android.internal.R.layout.search_bar);
 
-        theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
-                // taking up the whole window (even when transparent) is less than ideal,
-                // but necessary to show the popup window until the window manager supports
-                // having windows anchored by their parent but not clipped by them.
-                ViewGroup.LayoutParams.FILL_PARENT);
+        Window theWindow = getWindow();
         WindowManager.LayoutParams lp = theWindow.getAttributes();
+        lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+        lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+        // taking up the whole window (even when transparent) is less than ideal,
+        // but necessary to show the popup window until the window manager supports
+        // having windows anchored by their parent but not clipped by them.
+        lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
         theWindow.setAttributes(lp);
 
@@ -234,10 +234,12 @@
         
         // Save voice intent for later queries/launching
         mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+        mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                 RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
         
         mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         
         mLocationManager =
                 (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
@@ -278,12 +280,7 @@
      */
     public boolean show(String initialQuery, boolean selectInitialQuery,
             ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
-        if (isShowing()) {
-            // race condition - already showing but not handling events yet.
-            // in this case, just discard the "show" request
-            return true;
-        }
-        
+
         // Reset any stored values from last time dialog was shown.
         mStoredComponentName = null;
         mStoredAppSearchData = null;
@@ -442,11 +439,6 @@
         
         stopLocationUpdates();
         
-        // TODO: Removing the listeners means that they never get called, since 
-        // Dialog.dismissDialog() calls onStop() before sendDismissMessage().
-        setOnCancelListener(null);
-        setOnDismissListener(null);
-        
         // stop receiving broadcasts (throws exception if none registered)
         try {
             getContext().unregisterReceiver(mBroadcastReceiver);
@@ -654,15 +646,15 @@
         
         mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
         mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold());
+        // we dismiss the entire dialog instead
+        mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
 
         if (mGlobalSearchMode) {
             mSearchAutoComplete.setDropDownAlwaysVisible(true);  // fill space until results come in
-            mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
             mSearchAutoComplete.setDropDownBackgroundResource(
                     com.android.internal.R.drawable.search_dropdown_background);
         } else {
             mSearchAutoComplete.setDropDownAlwaysVisible(false);
-            mSearchAutoComplete.setDropDownDismissedOnCompletion(true);
             mSearchAutoComplete.setDropDownBackgroundResource(
                     com.android.internal.R.drawable.search_dropdown_background_apps);
         }
@@ -1317,7 +1309,8 @@
     }
 
     /**
-     * Launches an intent. Also dismisses the search dialog if not in global search mode.
+     * Launches an intent and dismisses the search dialog (unless the intent
+     * is one of the special intents that modifies the state of the search dialog).
      */
     private void launchIntent(Intent intent) {
         if (intent == null) {
@@ -1326,9 +1319,7 @@
         if (handleSpecialIntent(intent)){
             return;
         }
-        if (!mGlobalSearchMode) {
-            dismiss();
-        }
+        dismiss();
         getContext().startActivity(intent);
     }
     
@@ -1511,6 +1502,7 @@
             int actionKey, String actionMsg) {
         // Now build the Intent
         Intent intent = new Intent(action);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         if (data != null) {
             intent.setData(data);
         }
@@ -1595,14 +1587,7 @@
         private boolean isEmpty() {
             return TextUtils.getTrimmedLength(getText()) == 0;
         }
-        
-        /**
-         * Clears the entered text.
-         */
-        private void clear() {
-            setText("");
-        }
-        
+
         /**
          * We override this method to avoid replacing the query box text
          * when a suggestion is clicked.
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 820f192..1ddd20a 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -28,6 +28,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.server.search.SearchableInfo;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import java.util.List;
@@ -1108,6 +1109,10 @@
 public class SearchManager 
         implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
 {
+
+    private static final boolean DBG = false;
+    private static final String TAG = "SearchManager";
+
     /**
      * This is a shortcut definition for the default menu key to use for invoking search.
      * 
@@ -1494,12 +1499,14 @@
     private static ISearchManager sService = getSearchManagerService();
 
     private final Context mContext;
-    private final Handler mHandler;
-    
-    private SearchDialog mSearchDialog;
-    
-    private OnDismissListener mDismissListener = null;
-    private OnCancelListener mCancelListener = null;
+
+    // package private since they are used by the inner class SearchManagerCallback
+    /* package */ boolean mIsShowing = false;
+    /* package */ final Handler mHandler;
+    /* package */ OnDismissListener mDismissListener = null;
+    /* package */ OnCancelListener mCancelListener = null;
+
+    private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback();
 
     /*package*/ SearchManager(Context context, Handler handler)  {
         mContext = context;
@@ -1551,17 +1558,16 @@
                             ComponentName launchActivity,
                             Bundle appSearchData,
                             boolean globalSearch) {
-        
-        if (mSearchDialog == null) {
-            mSearchDialog = new SearchDialog(mContext);
+        if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing);
+        if (mIsShowing) return;
+        try {
+            mIsShowing = true;
+            // activate the search manager and start it up!
+            sService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+                    globalSearch, mSearchManagerCallback);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "startSearch() failed: " + ex);
         }
-
-        // activate the search manager and start it up!
-        mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, 
-                globalSearch);
-        
-        mSearchDialog.setOnCancelListener(this);
-        mSearchDialog.setOnDismissListener(this);
     }
 
     /**
@@ -1575,9 +1581,16 @@
      *
      * @see #startSearch
      */
-    public void stopSearch()  {
-        if (mSearchDialog != null) {
-            mSearchDialog.cancel();
+    public void stopSearch() {
+        if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing);
+        if (!mIsShowing) return;
+        try {
+            sService.stopSearch();
+            // onDismiss will also clear this, but we do it here too since onDismiss() is
+            // called asynchronously.
+            mIsShowing = false;
+        } catch (RemoteException ex) {
+            Log.e(TAG, "stopSearch() failed: " + ex);
         }
     }
 
@@ -1590,13 +1603,11 @@
      * 
      * @hide
      */
-    public boolean isVisible()  {
-        if (mSearchDialog != null) {
-            return mSearchDialog.isShowing();
-        }
-        return false;
+    public boolean isVisible() {
+        if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing);
+        return mIsShowing;
     }
-    
+
     /**
      * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
      * search UI state.
@@ -1631,79 +1642,112 @@
     public void setOnDismissListener(final OnDismissListener listener) {
         mDismissListener = listener;
     }
-    
-    /**
-     * The callback from the search dialog when dismissed
-     * @hide
-     */
-    public void onDismiss(DialogInterface dialog) {
-        if (dialog == mSearchDialog) {
-            if (mDismissListener != null) {
-                mDismissListener.onDismiss();
-            }
-        }
-    }
 
     /**
      * Set or clear the callback that will be invoked whenever the search UI is canceled.
      * 
      * @param listener The {@link OnCancelListener} to use, or null.
      */
-    public void setOnCancelListener(final OnCancelListener listener) {
+    public void setOnCancelListener(OnCancelListener listener) {
         mCancelListener = listener;
     }
-    
-    
-    /**
-     * The callback from the search dialog when canceled
-     * @hide
-     */
+
+    private class SearchManagerCallback extends ISearchManagerCallback.Stub {
+
+        private final Runnable mFireOnDismiss = new Runnable() {
+            public void run() {
+                if (DBG) debug("mFireOnDismiss");
+                mIsShowing = false;
+                if (mDismissListener != null) {
+                    mDismissListener.onDismiss();
+                }
+            }
+        };
+
+        private final Runnable mFireOnCancel = new Runnable() {
+            public void run() {
+                if (DBG) debug("mFireOnCancel");
+                // doesn't need to clear mIsShowing since onDismiss() always gets called too
+                if (mCancelListener != null) {
+                    mCancelListener.onCancel();
+                }
+            }
+        };
+
+        public void onDismiss() {
+            if (DBG) debug("onDismiss()");
+            mHandler.post(mFireOnDismiss);
+        }
+
+        public void onCancel() {
+            if (DBG) debug("onCancel()");
+            mHandler.post(mFireOnCancel);
+        }
+
+    }
+
+    // TODO: remove the DialogInterface interfaces from SearchManager.
+    // This changes the public API, so I'll do it in a separate change.
     public void onCancel(DialogInterface dialog) {
-        if (dialog == mSearchDialog) {
-            if (mCancelListener != null) {
-                mCancelListener.onCancel();
-            }
+        throw new UnsupportedOperationException();
+    }
+    public void onDismiss(DialogInterface dialog) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Saves the state of the search UI.
+     *
+     * @return A Bundle containing the state of the search dialog, or {@code null}
+     *         if the search UI is not visible.
+     *
+     * @hide
+     */
+    public Bundle saveSearchDialog() {
+        if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing);
+        if (!mIsShowing) return null;
+        try {
+            return sService.onSaveInstanceState();
+        } catch (RemoteException ex) {
+            Log.e(TAG, "onSaveInstanceState() failed: " + ex);
+            return null;
         }
     }
 
     /**
-     * Save instance state so we can recreate after a rotation.
-     * 
+     * Restores the state of the search dialog.
+     *
+     * @param searchDialogState Bundle to read the state from.
+     *
      * @hide
      */
-    void saveSearchDialog(Bundle outState, String key) {
-        if (mSearchDialog != null && mSearchDialog.isShowing()) {
-            Bundle searchDialogState = mSearchDialog.onSaveInstanceState();
-            outState.putBundle(key, searchDialogState);
+    public void restoreSearchDialog(Bundle searchDialogState) {
+        if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")");
+        if (searchDialogState == null) return;
+        try {
+            sService.onRestoreInstanceState(searchDialogState);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "onRestoreInstanceState() failed: " + ex);
         }
     }
 
     /**
-     * Restore instance state after a rotation.
-     * 
+     * Update the search dialog after a configuration change.
+     *
+     * @param newConfig The new configuration.
+     *
      * @hide
      */
-    void restoreSearchDialog(Bundle inState, String key) {        
-        Bundle searchDialogState = inState.getBundle(key);
-        if (searchDialogState != null) {
-            if (mSearchDialog == null) {
-                mSearchDialog = new SearchDialog(mContext);
-            }
-            mSearchDialog.onRestoreInstanceState(searchDialogState);
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing);
+        if (!mIsShowing) return;
+        try {
+            sService.onConfigurationChanged(newConfig);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "onConfigurationChanged() failed:" + ex);
         }
     }
-    
-    /**
-     * Hook for updating layout on a rotation
-     * 
-     * @hide
-     */
-    void onConfigurationChanged(Configuration newConfig) {
-        if (mSearchDialog != null && mSearchDialog.isShowing()) {
-            mSearchDialog.onConfigurationChanged(newConfig);
-        }
-    }
-    
+
     private static ISearchManager getSearchManagerService() {
         return ISearchManager.Stub.asInterface(
             ServiceManager.getService(Context.SEARCH_SERVICE));
@@ -1724,7 +1768,8 @@
             boolean globalSearch) {
         try {
             return sService.getSearchableInfo(componentName, globalSearch);
-        } catch (RemoteException e) {
+        } catch (RemoteException ex) {
+            Log.e(TAG, "getSearchableInfo() failed: " + ex);
             return null;
         }
     }
@@ -1805,6 +1850,7 @@
         try {
             return sService.getSearchablesInGlobalSearch();
         } catch (RemoteException e) {
+            Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e);
             return null;
         }
     }
@@ -1812,7 +1858,8 @@
     /**
      * Returns a list of the searchable activities that handle web searches.
      *
-     * @return a a list of all searchable activities that handle {@link SearchManager#ACTION_WEB_SEARCH}.
+     * @return a list of all searchable activities that handle
+     *         {@link android.content.Intent#ACTION_WEB_SEARCH}.
      *
      * @hide because SearchableInfo is not part of the API.
      */
@@ -1820,6 +1867,7 @@
         try {
             return sService.getSearchablesForWebSearch();
         } catch (RemoteException e) {
+            Log.e(TAG, "getSearchablesForWebSearch() failed: " + e);
             return null;
         }
     }
@@ -1835,6 +1883,7 @@
         try {
             return sService.getDefaultSearchableForWebSearch();
         } catch (RemoteException e) {
+            Log.e(TAG, "getDefaultSearchableForWebSearch() failed: " + e);
             return null;
         }
     }
@@ -1850,6 +1899,12 @@
         try {
             sService.setDefaultWebSearch(component);
         } catch (RemoteException e) {
+            Log.e(TAG, "setDefaultWebSearch() failed: " + e);
         }
     }
+
+    private static void debug(String msg) {
+        Thread thread = Thread.currentThread();
+        Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+    }
 }
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 060bcea..db812d1 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -17,15 +17,25 @@
 package android.server.search;
 
 import android.app.ISearchManager;
+import android.app.ISearchManagerCallback;
+import android.app.SearchDialog;
+import android.app.SearchManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.util.Log;
 
 import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
 
 /**
  * This is a simplified version of the Search Manager service.  It no longer handles
@@ -34,16 +44,20 @@
  * invoked search) to specific searchable activities (where the search will be dispatched).
  */
 public class SearchManagerService extends ISearchManager.Stub
+        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
 {
         // general debugging support
     private static final String TAG = "SearchManagerService";
-    private static final boolean DEBUG = false;
+    private static final boolean DBG = false;
 
         // class maintenance and general shared data
     private final Context mContext;
     private final Handler mHandler;
     private boolean mSearchablesDirty;
-    private Searchables mSearchables;
+    private final Searchables mSearchables;
+
+    final SearchDialog mSearchDialog;
+    ISearchManagerCallback mCallback = null;
 
     /**
      * Initializes the Search Manager service in the provided system context.
@@ -56,6 +70,9 @@
         mHandler = new Handler();
         mSearchablesDirty = true;
         mSearchables = new Searchables(context);
+        mSearchDialog = new SearchDialog(context);
+        mSearchDialog.setOnCancelListener(this);
+        mSearchDialog.setOnDismissListener(this);
 
         // Setup the infrastructure for updating and maintaining the list
         // of searchable activities.
@@ -107,6 +124,7 @@
      * a package add/remove broadcast message.
      */
     private void updateSearchables() {
+        if (DBG) debug("updateSearchables()");
         mSearchables.buildSearchableList();
         mSearchablesDirty = false;
     }
@@ -137,6 +155,10 @@
         if (globalSearch) {
             si = mSearchables.getDefaultSearchable();
         } else {
+            if (launchActivity == null) {
+                Log.e(TAG, "getSearchableInfo(), activity == null");
+                return null;
+            }
             si = mSearchables.getSearchableInfo(launchActivity);
         }
 
@@ -150,6 +172,145 @@
         updateSearchablesIfDirty();
         return mSearchables.getSearchablesInGlobalSearchList();
     }
+    /**
+     * Launches the search UI on the main thread of the service.
+     *
+     * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
+     */
+    public void startSearch(final String initialQuery,
+            final boolean selectInitialQuery,
+            final ComponentName launchActivity,
+            final Bundle appSearchData,
+            final boolean globalSearch,
+            final ISearchManagerCallback searchManagerCallback) {
+        if (DBG) debug("startSearch()");
+        Runnable task = new Runnable() {
+            public void run() {
+                performStartSearch(initialQuery,
+                        selectInitialQuery,
+                        launchActivity,
+                        appSearchData,
+                        globalSearch,
+                        searchManagerCallback);
+            }
+        };
+        mHandler.post(task);
+    }
+
+    /**
+     * Actually launches the search. This must be called on the service UI thread.
+     */
+    /*package*/ void performStartSearch(String initialQuery,
+            boolean selectInitialQuery,
+            ComponentName launchActivity,
+            Bundle appSearchData,
+            boolean globalSearch,
+            ISearchManagerCallback searchManagerCallback) {
+        if (DBG) debug("performStartSearch()");
+        mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+                globalSearch);
+        if (searchManagerCallback != null) {
+            mCallback = searchManagerCallback;
+        }
+    }
+
+    /**
+     * Cancels the search dialog. Can be called from any thread.
+     */
+    public void stopSearch() {
+        if (DBG) debug("stopSearch()");
+        mHandler.post(new Runnable() {
+            public void run() {
+                performStopSearch();
+            }
+        });
+    }
+
+    /**
+     * Cancels the search dialog. Must be called from the service UI thread.
+     */
+    /*package*/ void performStopSearch() {
+        if (DBG) debug("performStopSearch()");
+        mSearchDialog.cancel();
+    }
+
+    /**
+     * Determines if the Search UI is currently displayed.
+     *
+     * @see SearchManager#isVisible()
+     */
+    public boolean isVisible() {
+        return postAndWait(mIsShowing, false, "isShowing()");
+    }
+
+    private final Callable<Boolean> mIsShowing = new Callable<Boolean>() {
+        public Boolean call() {
+            return mSearchDialog.isShowing();
+        }
+    };
+
+    public Bundle onSaveInstanceState() {
+        return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()");
+    }
+
+    private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() {
+        public Bundle call() {
+            if (mSearchDialog.isShowing()) {
+                return mSearchDialog.onSaveInstanceState();
+            } else {
+                return null;
+            }
+        }
+    };
+
+    public void onRestoreInstanceState(final Bundle searchDialogState) {
+        if (searchDialogState != null) {
+            mHandler.post(new Runnable() {
+                public void run() {
+                    mSearchDialog.onRestoreInstanceState(searchDialogState);
+                }
+            });
+        }
+    }
+
+    public void onConfigurationChanged(final Configuration newConfig) {
+        mHandler.post(new Runnable() {
+            public void run() {
+                if (mSearchDialog.isShowing()) {
+                    mSearchDialog.onConfigurationChanged(newConfig);
+                }
+            }
+        });
+    }
+
+    /**
+     * Called by {@link SearchDialog} when it goes away.
+     */
+    public void onDismiss(DialogInterface dialog) {
+        if (DBG) debug("onDismiss()");
+        if (mCallback != null) {
+            try {
+                mCallback.onDismiss();
+            } catch (RemoteException ex) {
+                Log.e(TAG, "onDismiss() failed: " + ex);
+            }
+        }
+    }
+
+    /**
+     * Called by {@link SearchDialog} when the user or activity cancels search.
+     * When this is called, {@link #onDismiss} is called too.
+     */
+    public void onCancel(DialogInterface dialog) {
+        if (DBG) debug("onCancel()");
+        if (mCallback != null) {
+            try {
+                mCallback.onCancel();
+            } catch (RemoteException ex) {
+                Log.e(TAG, "onCancel() failed: " + ex);
+            }
+        }
+    }
 
     /**
      * Returns a list of the searchable activities that handle web searches.
@@ -173,4 +334,34 @@
     public void setDefaultWebSearch(ComponentName component) {
         mSearchables.setDefaultWebSearch(component);
     }
+
+    /**
+     * Runs an operation on the handler for the service, blocks until it returns,
+     * and returns the value returned by the operation.
+     *
+     * @param <V> Return value type.
+     * @param callable Operation to run.
+     * @param errorResult Value to return if the operations throws an exception.
+     * @param name Operation name to include in error log messages.
+     * @return The value returned by the operation.
+     */
+    private <V> V postAndWait(Callable<V> callable, V errorResult, String name) {
+        FutureTask<V> task = new FutureTask<V>(callable);
+        mHandler.post(task);
+        try {
+            return task.get();
+        } catch (InterruptedException ex) {
+            Log.e(TAG, "Error calling " + name + ": " + ex);
+            return errorResult;
+        } catch (ExecutionException ex) {
+            Log.e(TAG, "Error calling " + name + ": " + ex);
+            return errorResult;
+        }
+    }
+
+    private static void debug(String msg) {
+        Thread thread = Thread.currentThread();
+        Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+    }
+
 }
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 4df7368..90dfa0b 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -320,7 +320,7 @@
 
         // for now, implement some form of rules - minimal data
         if (mLabelId == 0) {
-            throw new IllegalArgumentException("No label.");
+            throw new IllegalArgumentException("Search label must be a resource reference.");
         }
     }
     
@@ -441,13 +441,17 @@
         xml.close();
         
         if (DBG) {
-            Log.d(LOG_TAG, "Checked " + activityInfo.name
-                    + ",label=" + searchable.getLabelId()
-                    + ",icon=" + searchable.getIconId()
-                    + ",suggestAuthority=" + searchable.getSuggestAuthority()
-                    + ",target=" + searchable.getSearchActivity().getClassName()
-                    + ",global=" + searchable.shouldIncludeInGlobalSearch()
-                    + ",threshold=" + searchable.getSuggestThreshold());
+            if (searchable != null) {
+                Log.d(LOG_TAG, "Checked " + activityInfo.name
+                        + ",label=" + searchable.getLabelId()
+                        + ",icon=" + searchable.getIconId()
+                        + ",suggestAuthority=" + searchable.getSuggestAuthority()
+                        + ",target=" + searchable.getSearchActivity().getClassName()
+                        + ",global=" + searchable.shouldIncludeInGlobalSearch()
+                        + ",threshold=" + searchable.getSuggestThreshold());
+            } else {
+                Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data");
+            }
         }
         return searchable;
     }
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 1b30aa0..367b26c 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -59,9 +59,15 @@
             result = result / 1024;
         }
         if (result < 100) {
-            return String.format("%.2f%s", result, context.getText(suffix).toString());
+            String value = String.format("%.2f", result);
+            return context.getResources().
+                getString(com.android.internal.R.string.fileSizeSuffix,
+                          value, context.getString(suffix));
         }
-        return String.format("%.0f%s", result, context.getText(suffix).toString());
+        String value = String.format("%.0f", result);
+        return context.getResources().
+            getString(com.android.internal.R.string.fileSizeSuffix,
+                      value, context.getString(suffix));
     }
     
     /**
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 2a84683..7739300 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -186,6 +186,21 @@
         mHasInstantTimer = false;
     }
 
+    private String[] getKeyStrengthList() {
+        // FIXME: fake the list for now
+        String[] list = new String[2];
+        list[0] = "1024";
+        list[1] = "512";
+        return list;
+    }
+
+    private String getSignedPublicKey(int index, String challenge, String url) {
+        // FIXME: do nothing for now
+        Log.w(LOGTAG, "getSignedPublicKey for " + index + " and challenge="
+                + challenge + " and url=" + url);
+        return "";
+    }
+
     private native void nativeConstructor();
     private native void nativeFinalize();
     private native void sharedTimerFired();
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index e85b14c..0011c45 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -21,6 +21,7 @@
     <string name="gigabyteShort">"Go"</string>
     <string name="terabyteShort">"To"</string>
     <string name="petabyteShort">"Po"</string>
+    <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="KB">%2$s</xliff:g></string>
     <string name="untitled">"&lt;sans titre&gt;"</string>
     <string name="ellipsis">"…"</string>
     <string name="emptyPhoneNumber">"(Aucun numéro de téléphone)"</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 79dc1ba..6f5b29a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -30,6 +30,11 @@
     <string name="terabyteShort">TB</string>
     <!-- Suffix added to a number to signify size in petabytes. -->
     <string name="petabyteShort">PB</string>
+    <!-- Format string used to add a suffix like "KB" or "MB" to a number
+         to display a size in kilobytes, megabytes, or other size units.
+         Some languages (like French) will want to add a space between
+         the placeholders. -->
+    <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g><xliff:g id="unit" example="KB">%2$s</xliff:g></string>
 
     <!-- Used in Contacts for a field that has no label and in Note Pad
          for a note with no name. -->
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 01106b3..fdefe9c 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -20,8 +20,8 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.net.vpn.SingleServerProfile;
 import android.net.vpn.VpnManager;
+import android.net.vpn.VpnProfile;
 import android.net.vpn.VpnState;
 import android.os.FileObserver;
 import android.os.SystemProperties;
@@ -38,7 +38,7 @@
 /**
  * The service base class for managing a type of VPN connection.
  */
-abstract class VpnService<E extends SingleServerProfile> {
+abstract class VpnService<E extends VpnProfile> {
     private static final int NOTIFICATION_ID = 1;
     private static final String PROFILES_ROOT = VpnManager.PROFILES_PATH + "/";
     public static final String DEFAULT_CONFIG_PATH = "/etc";
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index fd6e6d8..55d4d64 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -219,7 +219,20 @@
         </service>
 
         <!-- Application components used for search manager tests -->
-        <!-- TODO: Removed temporarily - need to be replaced using mocks -->
+
+        <activity android:name=".SearchableActivity"
+                android:label="Searchable Activity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="android.app.searchable"
+                    android:resource="@xml/searchable" />
+        </activity>
+
+        <provider android:name=".SuggestionProvider"
+                android:authorities="com.android.unit_tests.SuggestionProvider">
+        </provider>
 
         <!-- Used to test IPC. -->
         <service android:name=".binder.BinderTestService"
diff --git a/tests/AndroidTests/res/values/strings.xml b/tests/AndroidTests/res/values/strings.xml
index 21c72cf..49d8ae7 100644
--- a/tests/AndroidTests/res/values/strings.xml
+++ b/tests/AndroidTests/res/values/strings.xml
@@ -50,5 +50,8 @@
         <item quantity="other">Some dogs</item>
     </plurals>
     
+    <string name="searchable_label">SearchManager Test</string>
+    <string name="searchable_hint">A search hint</string>
+    
 <!--    <string name="layout_six_text_text">F</string> -->
 </resources>
diff --git a/tests/AndroidTests/res/xml/searchable.xml b/tests/AndroidTests/res/xml/searchable.xml
index a40d53d..9d293b5 100644
--- a/tests/AndroidTests/res/xml/searchable.xml
+++ b/tests/AndroidTests/res/xml/searchable.xml
@@ -15,7 +15,12 @@
 -->
 
 <searchable xmlns:android="http://schemas.android.com/apk/res/android"
-    android:label="SearchManagerTest"
-    android:hint="SearchManagerTest Hint"
-/>
+    android:label="@string/searchable_label"
+    android:hint="@string/searchable_hint"
+    android:searchSuggestAuthority="com.android.unit_tests.SuggestionProvider"
+    >
 
+        <actionkey android:keycode="KEYCODE_CALL"
+            android:suggestActionMsgColumn="suggest_action_msg_call" />
+
+</searchable>
\ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
index f3c1542..f03a779 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
@@ -23,7 +23,10 @@
 import android.app.SearchManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.server.search.SearchableInfo;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -37,12 +40,11 @@
  *   com.android.unit_tests/android.test.InstrumentationTestRunner
  */
 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
-    // activity into the contacts activity.  We'll put this back after we disable that jump.
-    private static final int TEST_SEARCH_START = 0;
-    
+
+    private ComponentName SEARCHABLE_ACTIVITY =
+            new ComponentName("com.android.unit_tests",
+                    "com.android.unit_tests.SearchableActivity");
+
     /*
      * Bug list of test ideas.
      * 
@@ -88,7 +90,30 @@
         super.setUp();
         
         Activity testActivity = getActivity();
-        mContext = (Context)testActivity;
+        mContext = testActivity;
+    }
+
+    private ISearchManager getSearchManagerService() {
+        return ISearchManager.Stub.asInterface(
+                ServiceManager.getService(Context.SEARCH_SERVICE));
+    }
+
+    // Checks that the search UI is visible.
+    private void assertSearchVisible() {
+        SearchManager searchManager = (SearchManager)
+                mContext.getSystemService(Context.SEARCH_SERVICE);
+        assertTrue("SearchManager thinks search UI isn't visible when it should be",
+                searchManager.isVisible());
+    }
+
+    // Checks that the search UI is not visible.
+    // This checks both the SearchManager and the SearchManagerService,
+    // since SearchManager keeps a local variable for the visibility.
+    private void assertSearchNotVisible() {
+        SearchManager searchManager = (SearchManager)
+                mContext.getSystemService(Context.SEARCH_SERVICE);
+        assertFalse("SearchManager thinks search UI is visible when it shouldn't be",
+                searchManager.isVisible());
     }
 
     /**
@@ -97,9 +122,7 @@
      */
     @MediumTest
     public void testSearchManagerInterfaceAvailable() {
-        ISearchManager searchManager1 = ISearchManager.Stub.asInterface(
-                ServiceManager.getService(Context.SEARCH_SERVICE));
-        assertNotNull(searchManager1);
+        assertNotNull(getSearchManagerService());
     }
     
     /**
@@ -135,38 +158,127 @@
         SearchManager searchManager2 = (SearchManager)
                 mContext.getSystemService(Context.SEARCH_SERVICE);
         assertNotNull(searchManager2);
-        assertSame( searchManager1, searchManager2 );
+        assertSame(searchManager1, searchManager2 );
     }
-    
+
+    @MediumTest
+    public void testSearchables() {
+        SearchableInfo si;
+
+        si = SearchManager.getSearchableInfo(SEARCHABLE_ACTIVITY, false);
+        assertNotNull(si);
+        assertFalse(SearchManager.isDefaultSearchable(si));
+        si = SearchManager.getSearchableInfo(SEARCHABLE_ACTIVITY, true);
+        assertNotNull(si);
+        assertTrue(SearchManager.isDefaultSearchable(si));
+        si = SearchManager.getSearchableInfo(null, true);
+        assertNotNull(si);
+        assertTrue(SearchManager.isDefaultSearchable(si));
+    }
+
+    /**
+     * Tests that rapid calls to start-stop-start doesn't cause problems.
+     */
+    @MediumTest
+    public void testSearchManagerFastInvocations() throws Exception {
+         SearchManager searchManager = (SearchManager)
+                 mContext.getSystemService(Context.SEARCH_SERVICE);
+         assertNotNull(searchManager);
+         assertSearchNotVisible();
+
+         searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+         assertSearchVisible();
+         searchManager.stopSearch();
+         searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+         searchManager.stopSearch();
+         assertSearchNotVisible();
+    }
+
+    /**
+     * Tests that startSearch() is idempotent.
+     */
+    @MediumTest
+    public void testStartSearchIdempotent() throws Exception {
+         SearchManager searchManager = (SearchManager)
+                 mContext.getSystemService(Context.SEARCH_SERVICE);
+         assertNotNull(searchManager);
+         assertSearchNotVisible();
+
+         searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+         searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+         assertSearchVisible();
+         searchManager.stopSearch();
+         assertSearchNotVisible();
+    }
+
+    /**
+     * Tests that stopSearch() is idempotent and can be called when the search UI is not visible.
+     */
+    @MediumTest
+    public void testStopSearchIdempotent() throws Exception {
+         SearchManager searchManager = (SearchManager)
+                 mContext.getSystemService(Context.SEARCH_SERVICE);
+         assertNotNull(searchManager);
+         assertSearchNotVisible();
+         searchManager.stopSearch();
+         assertSearchNotVisible();
+
+         searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+         assertSearchVisible();
+         searchManager.stopSearch();
+         searchManager.stopSearch();
+         assertSearchNotVisible();
+    }
+
     /**
      * The goal of this test is to confirm that we can start and then
      * stop a simple search.
      */
-    
-   @MediumTest
-   public void testSearchManagerInvocations() {
+    @MediumTest
+    public void testSearchManagerInvocations() throws Exception {
         SearchManager searchManager = (SearchManager)
                 mContext.getSystemService(Context.SEARCH_SERVICE);
         assertNotNull(searchManager);
-        
-            // TODO: make a real component name, or remove this need
-        final ComponentName cn = new ComponentName("", "");
+        assertSearchNotVisible();
 
-        if (TEST_SEARCH_START != 0) {
-            // These tests should simply run to completion w/o exceptions
-            searchManager.startSearch(null, false, cn, null, false);
-            searchManager.stopSearch();
-            
-            searchManager.startSearch("", false, cn, null, false);
-            searchManager.stopSearch();
-            
-            searchManager.startSearch("test search string", false, cn, null, false);
-            searchManager.stopSearch();
-            
-            searchManager.startSearch("test search string", true, cn, null, false);
-            searchManager.stopSearch();
-        }
-     }
+        // These tests should simply run to completion w/o exceptions
+        searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+        assertSearchVisible();
+        searchManager.stopSearch();
+        assertSearchNotVisible();
+
+        searchManager.startSearch("", false, SEARCHABLE_ACTIVITY, null, false);
+        assertSearchVisible();
+        searchManager.stopSearch();
+        assertSearchNotVisible();
+
+        searchManager.startSearch("test search string", false, SEARCHABLE_ACTIVITY, null, false);
+        assertSearchVisible();
+        searchManager.stopSearch();
+        assertSearchNotVisible();
+
+        searchManager.startSearch("test search string", true, SEARCHABLE_ACTIVITY, null, false);
+        assertSearchVisible();
+        searchManager.stopSearch();
+        assertSearchNotVisible();
+    }
+
+    @MediumTest
+    public void testSearchDialogState() throws Exception {
+        SearchManager searchManager = (SearchManager)
+                mContext.getSystemService(Context.SEARCH_SERVICE);
+        assertNotNull(searchManager);
+
+        Bundle searchState;
+
+        // search dialog not visible, so no state should be stored
+        searchState = searchManager.saveSearchDialog();
+        assertNull(searchState);
+
+        searchManager.startSearch("test search string", true, SEARCHABLE_ACTIVITY, null, false);
+        searchState = searchManager.saveSearchDialog();
+        assertNotNull(searchState);
+        searchManager.stopSearch();
+    }
 
 }
-
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchableActivity.java b/tests/AndroidTests/src/com/android/unit_tests/SearchableActivity.java
new file mode 100644
index 0000000..53f40e9
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchableActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.app.Activity;
+import android.os.Bundle;
+
+public class SearchableActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        finish();
+    }
+
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SuggestionProvider.java b/tests/AndroidTests/src/com/android/unit_tests/SuggestionProvider.java
new file mode 100644
index 0000000..bc61e27
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/SuggestionProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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.app.SearchManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+/** Simple test provider that runs in the local process.
+ *
+ * Used by {@link SearchManagerTest}.
+ */
+public class SuggestionProvider extends ContentProvider {
+    private static final String TAG = "SuggestionProvider";
+
+    private static final int SEARCH_SUGGESTIONS = 1;
+
+    private static final UriMatcher sURLMatcher = new UriMatcher(
+            UriMatcher.NO_MATCH);
+
+    static {
+        sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY,
+                SEARCH_SUGGESTIONS);
+        sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
+                SEARCH_SUGGESTIONS);
+    }
+
+    private static final String[] COLUMNS = new String[] {
+            "_id",
+            SearchManager.SUGGEST_COLUMN_TEXT_1,
+            SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+            SearchManager.SUGGEST_COLUMN_QUERY
+    };
+
+    public SuggestionProvider() {
+    }
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri url, String[] projectionIn, String selection,
+            String[] selectionArgs, String sort) {
+        int match = sURLMatcher.match(url);
+        switch (match) {
+            case SEARCH_SUGGESTIONS:
+                String query = url.getLastPathSegment();
+                MatrixCursor cursor = new MatrixCursor(COLUMNS);
+                String[] suffixes = { "", "a", " foo", "XXXXXXXXXXXXXXXXX" };
+                for (String suffix : suffixes) {
+                    addRow(cursor, query + suffix);
+                }
+                return cursor;
+            default:
+                throw new IllegalArgumentException("Unknown URL: " + url);
+        }
+    }
+
+    private void addRow(MatrixCursor cursor, String string) {
+        long id = cursor.getCount();
+        cursor.newRow().add(id).add(string).add(Intent.ACTION_SEARCH).add(string);
+    }
+
+    @Override
+    public String getType(Uri url) {
+        int match = sURLMatcher.match(url);
+        switch (match) {
+            case SEARCH_SUGGESTIONS:
+                return SearchManager.SUGGEST_MIME_TYPE;
+            default:
+                throw new IllegalArgumentException("Unknown URL: " + url);
+        }
+    }
+
+    @Override
+    public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+        throw new UnsupportedOperationException("update not supported");
+    }
+
+    @Override
+    public Uri insert(Uri url, ContentValues initialValues) {
+        throw new UnsupportedOperationException("insert not supported");
+    }
+
+    @Override
+    public int delete(Uri url, String where, String[] whereArgs) {
+        throw new UnsupportedOperationException("delete not supported");
+    }
+}
diff --git a/tests/DumpRenderTree/assets/run_layout_tests.py b/tests/DumpRenderTree/assets/run_layout_tests.py
index 5409a0c..50ccb24 100755
--- a/tests/DumpRenderTree/assets/run_layout_tests.py
+++ b/tests/DumpRenderTree/assets/run_layout_tests.py
@@ -61,7 +61,8 @@
   adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
   return adb_output.strip() == "#DONE"
 
-def DiffResults(marker, new_results, old_results, diff_results, strip_reason):
+def DiffResults(marker, new_results, old_results, diff_results, strip_reason,
+                new_count_first=True):
    """ Given two result files, generate diff and
        write to diff_results file. All arguments are absolute paths
        to files.
@@ -85,21 +86,25 @@
      for i in range(0, len(cdict)):
        cdict[i] = cdict[i].split(' ')[0] + "\n"
 
-   # Find results in new_results missing in old_results
-   new_count=0
-   for line in ndict:
-     if line not in cdict:
-       diff_file.writelines("+ " + line)
-       new_count += 1
+   params = {
+       "new": [0, ndict, cdict, "+"],
+       "miss": [0, cdict, ndict, "-"]
+       }
+   if new_count_first:
+     order = ["new", "miss"]
+   else:
+     order = ["miss", "new"]
 
-   # Find results in old_results missing in new_results
-   missing_count=0
-   for line in cdict:
-     if line not in ndict:
-       diff_file.writelines("- " + line)
-       missing_count += 1
+   for key in order:
+     for line in params[key][1]:
+       if line not in params[key][2]:
+         if line[-1] != "\n":
+           line += "\n";
+         diff_file.writelines(params[key][3] + line)
+         params[key][0] += 1
 
-   logging.info(marker + "  >>> " + str(new_count) + " new, " + str(missing_count) + " misses")
+   logging.info(marker + "  >>> " + str(params["new"][0]) + " new, " +
+                str(params["miss"][0]) + " misses")
 
    diff_file.writelines("\n\n")
 
@@ -121,12 +126,12 @@
   if os.path.exists(diff_result):
     os.remove(diff_result)
 
-  files=["passed", "failed", "nontext", "crashed"]
+  files=["crashed", "failed", "passed", "nontext"]
   for f in files:
     result_file_name = "layout_tests_" + f + ".txt"
     DiffResults(f, os.path.join(results_dir, result_file_name),
                 os.path.join(ref_dir, result_file_name), diff_result,
-                f == "failed")
+                False, files != "passed")
   logging.info("Detailed diffs are in " + diff_result)
 
 def main(options, args):
diff --git a/tests/DumpRenderTree/assets/run_reliability_tests.py b/tests/DumpRenderTree/assets/run_reliability_tests.py
index b038740..23f93df 100755
--- a/tests/DumpRenderTree/assets/run_reliability_tests.py
+++ b/tests/DumpRenderTree/assets/run_reliability_tests.py
@@ -14,6 +14,7 @@
 import subprocess
 import sys
 import time
+from Numeric import *
 
 TEST_LIST_FILE = "/sdcard/android/reliability_tests_list.txt"
 TEST_STATUS_FILE = "/sdcard/android/reliability_running_test.txt"
@@ -75,21 +76,28 @@
       logging.info("Line has more than one '|': " + line)
       continue
     if pair[0] not in load_times:
-      load_times[pair[0]] = [0, 0]
+      load_times[pair[0]] = []
     try:
       pair[1] = int(pair[1])
     except ValueError:
       logging.info("Lins has non-numeric load time: " + line)
       continue
-    load_times[pair[0]][0] += pair[1]
-    load_times[pair[0]][1] += 1
+    load_times[pair[0]].append(pair[1])
 
   log_handle.close()
 
   # rewrite the average time to file
   log_handle = open(raw_log, "w")
   for url, times in load_times.iteritems():
-    log_handle.write("%s|%f\n" % (url, float(times[0]) / times[1]))
+    # calculate std
+    arr = array(times)
+    avg = average(arr)
+    d = arr - avg
+    std = sqrt(sum(d * d) / len(arr))
+    output = ("%-70s%-10d%-10d%-12.2f%-12.2f%s\n" %
+              (url, min(arr), max(arr), avg, std,
+               array2string(arr)))
+    log_handle.write(output)
   log_handle.close()
 
 
@@ -156,6 +164,7 @@
   # clean up previous results
   RemoveDeviceFile(adb_cmd, TEST_STATUS_FILE)
   RemoveDeviceFile(adb_cmd, TEST_TIMEOUT_FILE)
+  RemoveDeviceFile(adb_cmd, TEST_LOAD_TIME_FILE)
 
   logging.info("Running the test ...")
 
diff --git a/vpn/java/android/net/vpn/L2tpIpsecProfile.java b/vpn/java/android/net/vpn/L2tpIpsecProfile.java
index 893afbe..181619d 100644
--- a/vpn/java/android/net/vpn/L2tpIpsecProfile.java
+++ b/vpn/java/android/net/vpn/L2tpIpsecProfile.java
@@ -22,7 +22,7 @@
  * The profile for L2TP-over-IPSec type of VPN.
  * {@hide}
  */
-public class L2tpIpsecProfile extends SingleServerProfile {
+public class L2tpIpsecProfile extends VpnProfile {
     private static final long serialVersionUID = 1L;
 
     private String mUserCertificate;
diff --git a/vpn/java/android/net/vpn/L2tpProfile.java b/vpn/java/android/net/vpn/L2tpProfile.java
index 559590f..59d4981 100644
--- a/vpn/java/android/net/vpn/L2tpProfile.java
+++ b/vpn/java/android/net/vpn/L2tpProfile.java
@@ -20,7 +20,7 @@
  * The profile for L2TP type of VPN.
  * {@hide}
  */
-public class L2tpProfile extends SingleServerProfile {
+public class L2tpProfile extends VpnProfile {
     private static final long serialVersionUID = 1L;
 
     @Override
diff --git a/vpn/java/android/net/vpn/SingleServerProfile.java b/vpn/java/android/net/vpn/SingleServerProfile.java
deleted file mode 100644
index 59b5a7b..0000000
--- a/vpn/java/android/net/vpn/SingleServerProfile.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2007, 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.net.vpn;
-
-import android.os.Parcel;
-
-/**
- * The profile for single-server type of VPN.
- * {@hide}
- */
-public abstract class SingleServerProfile extends VpnProfile {
-    private String mServerName;
-
-    public void setServerName(String name) {
-        mServerName = name;
-    }
-
-    public String getServerName() {
-        return mServerName;
-    }
-
-    @Override
-    protected void readFromParcel(Parcel in) {
-        super.readFromParcel(in);
-        mServerName = in.readString();
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        super.writeToParcel(parcel, flags);
-        parcel.writeString(mServerName);
-    }
-}
diff --git a/vpn/java/android/net/vpn/VpnProfile.java b/vpn/java/android/net/vpn/VpnProfile.java
index 1bfc102..9e24da4 100644
--- a/vpn/java/android/net/vpn/VpnProfile.java
+++ b/vpn/java/android/net/vpn/VpnProfile.java
@@ -31,8 +31,10 @@
     private static final long serialVersionUID = 1L;
     private String mName; // unique display name
     private String mId; // unique identifier
+    private String mServerName; // VPN server name
     private String mDomainSuffices; // space separated list
     private String mRouteList; // space separated list
+    private String mSavedUsername;
     private boolean mIsCustomized;
     private transient VpnState mState = VpnState.IDLE;
 
@@ -58,6 +60,17 @@
     }
 
     /**
+     * Sets the name of the VPN server. Used for DNS lookup.
+     */
+    public void setServerName(String name) {
+        mServerName = name;
+    }
+
+    public String getServerName() {
+        return mServerName;
+    }
+
+    /**
      * Sets the domain suffices for DNS resolution.
      *
      * @param entries a comma-separated list of domain suffices
@@ -84,6 +97,14 @@
         return mRouteList;
     }
 
+    public void setSavedUsername(String name) {
+        mSavedUsername = name;
+    }
+
+    public String getSavedUsername() {
+        return mSavedUsername;
+    }
+
     public void setState(VpnState state) {
         mState = state;
     }
@@ -116,8 +137,10 @@
     protected void readFromParcel(Parcel in) {
         mName = in.readString();
         mId = in.readString();
+        mServerName = in.readString();
         mDomainSuffices = in.readString();
         mRouteList = in.readString();
+        mSavedUsername = in.readString();
     }
 
     public static final Parcelable.Creator<VpnProfile> CREATOR =
@@ -142,8 +165,10 @@
         parcel.writeInt(mIsCustomized ? 1 : 0);
         parcel.writeString(mName);
         parcel.writeString(mId);
+        parcel.writeString(mServerName);
         parcel.writeString(mDomainSuffices);
         parcel.writeString(mRouteList);
+        parcel.writeString(mSavedUsername);
     }
 
     public int describeContents() {