diff options
| -rw-r--r-- | core/java/android/app/Activity.java | 37 | ||||
| -rw-r--r-- | core/java/android/app/ISearchManager.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/app/SearchDialog.java | 81 | ||||
| -rw-r--r-- | core/java/android/app/SearchManager.java | 54 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchDialogWrapper.java | 320 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchManagerService.java | 353 | ||||
| -rw-r--r-- | core/java/android/server/search/Searchables.java | 25 | ||||
| -rw-r--r-- | tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java | 20 | 
8 files changed, 442 insertions, 452 deletions
| diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ca9632a..df50f77 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -606,7 +606,6 @@ public class Activity extends ContextThemeWrapper      private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";      private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";      private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; -    private static final String SAVED_SEARCH_DIALOG_KEY = "android:search_dialog";      private SparseArray<Dialog> mManagedDialogs; @@ -630,7 +629,6 @@ public class Activity extends ContextThemeWrapper      /*package*/ int mConfigChangeFlags;      /*package*/ Configuration mCurrentConfig;      private SearchManager mSearchManager; -    private Bundle mSearchDialogState = null;      private Window mWindow; @@ -808,13 +806,6 @@ public class Activity extends ContextThemeWrapper      final void performRestoreInstanceState(Bundle savedInstanceState) {          onRestoreInstanceState(savedInstanceState);          restoreManagedDialogs(savedInstanceState); -         -        // Also restore the state of a search dialog (if any) -        // TODO more generic than just this manager -        Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY); -        if (searchState != null) { -            mSearchManager.restoreSearchDialog(searchState); -        }      }      /** @@ -1030,14 +1021,6 @@ public class Activity extends ContextThemeWrapper      final void performSaveInstanceState(Bundle outState) {          onSaveInstanceState(outState);          saveManagedDialogs(outState); - -        // Also save the state of a search dialog (if any) -        // TODO more generic than just this manager -        // 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); -        }      }      /** @@ -1317,10 +1300,6 @@ public class Activity extends ContextThemeWrapper                  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;      }      /** @@ -1341,11 +1320,7 @@ public class Activity extends ContextThemeWrapper       */      public void onConfigurationChanged(Configuration newConfig) {          mCalled = true; -         -        // also update search dialog if showing -        // TODO more generic than just this manager -        mSearchManager.onConfigurationChanged(newConfig); -         +          if (mWindow != null) {              // Pass the configuration changed event to the window              mWindow.onConfigurationChanged(newConfig); @@ -3575,20 +3550,12 @@ public class Activity extends ContextThemeWrapper                  "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(); +        // dismiss the search dialog if it is open          mSearchManager.stopSearch();      } diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index e8bd60a..5b62192 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -36,8 +36,4 @@ interface ISearchManager {              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/SearchDialog.java b/core/java/android/app/SearchDialog.java index 2bcc9c3..022a9d9 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -19,13 +19,11 @@ 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.ContentResolver;  import android.content.ContentValues; +import android.content.Context; +import android.content.Intent;  import android.content.pm.ActivityInfo;  import android.content.pm.PackageManager;  import android.content.pm.ResolveInfo; @@ -33,8 +31,8 @@ import android.content.pm.PackageManager.NameNotFoundException;  import android.content.res.Configuration;  import android.content.res.Resources;  import android.database.Cursor; -import android.graphics.drawable.Drawable;  import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable;  import android.net.Uri;  import android.os.Bundle;  import android.os.SystemClock; @@ -95,11 +93,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS      private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12;      private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7; -     -    // interaction with runtime -    private IntentFilter mCloseDialogsFilter; -    private IntentFilter mPackageFilter; -     +      // views & widgets      private TextView mBadgeLabel;      private ImageView mAppIcon; @@ -210,15 +204,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS          // Touching outside of the search dialog will dismiss it           setCanceledOnTouchOutside(true); -         -        // Set up broadcast filters -        mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); -        mPackageFilter = new IntentFilter(); -        mPackageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); -        mPackageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); -        mPackageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); -        mPackageFilter.addDataScheme("package"); -         +          // Save voice intent for later queries/launching          mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);          mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -382,15 +368,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS          return true;      } -     -    @Override -    protected void onStart() { -        super.onStart(); -         -        // receive broadcasts -        getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter); -        getContext().registerReceiver(mBroadcastReceiver, mPackageFilter); -    }      /**       * The search dialog is being dismissed, so handle all of the local shutdown operations. @@ -401,14 +378,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS      @Override      public void onStop() {          super.onStop(); -         -        // stop receiving broadcasts (throws exception if none registered) -        try { -            getContext().unregisterReceiver(mBroadcastReceiver); -        } catch (RuntimeException e) { -            // This is OK - it just means we didn't have any registered -        } -         +          closeSuggestionsAdapter();          // dump extra memory we're hanging on to @@ -455,12 +425,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS      /**       * Save the minimal set of data necessary to recreate the search       *  -     * @return A bundle with the state of the dialog. +     * @return A bundle with the state of the dialog, or {@code null} if the search +     *         dialog is not showing.       */      @Override      public Bundle onSaveInstanceState() { +        if (!isShowing()) return null; +          Bundle bundle = new Bundle(); -         +          // setup info so I can recreate this particular search                 bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);          bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData); @@ -483,6 +456,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS       */      @Override      public void onRestoreInstanceState(Bundle savedInstanceState) { +        if (savedInstanceState == null) return; +          ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);          Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);          boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH); @@ -509,7 +484,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS      /**       * Called after resources have changed, e.g. after screen rotation or locale change.       */ -    public void onConfigurationChanged(Configuration newConfig) { +    public void onConfigurationChanged() {          if (isShowing()) {              // Redraw (resources may have changed)              updateSearchButton(); @@ -1014,35 +989,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS              return false;          }      }; -         -    /** -     * 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 -        public void onReceive(Context context, Intent intent) { -            String action = intent.getAction(); -            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { -                cancel(); -            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action) -                    || Intent.ACTION_PACKAGE_REMOVED.equals(action) -                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { -                cancel(); -            } -        } -    };      @Override      public void cancel() { +        if (!isShowing()) return; +          // We made sure the IME was displayed, so also make sure it is closed          // when we go away.          InputMethodManager imm = (InputMethodManager)getContext() diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index e5ba6a4..5d25f10 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -20,7 +20,6 @@ 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; @@ -1730,59 +1729,6 @@ public class SearchManager      }      /** -     * 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 mService.onSaveInstanceState(); -        } catch (RemoteException ex) { -            Log.e(TAG, "onSaveInstanceState() failed: " + ex); -            return null; -        } -    } - -    /** -     * Restores the state of the search dialog. -     * -     * @param searchDialogState Bundle to read the state from. -     * -     * @hide -     */ -    public void restoreSearchDialog(Bundle searchDialogState) { -        if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")"); -        if (searchDialogState == null) return; -        try { -            mService.onRestoreInstanceState(searchDialogState); -        } catch (RemoteException ex) { -            Log.e(TAG, "onRestoreInstanceState() failed: " + ex); -        } -    } - -    /** -     * Update the search dialog after a configuration change. -     * -     * @param newConfig The new configuration. -     * -     * @hide -     */ -    public void onConfigurationChanged(Configuration newConfig) { -        if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing); -        if (!mIsShowing) return; -        try { -            mService.onConfigurationChanged(newConfig); -        } catch (RemoteException ex) { -            Log.e(TAG, "onConfigurationChanged() failed:" + ex); -        } -    } - -    /**       * Gets information about a searchable activity. This method is static so that it can       * be used from non-Activity contexts.       * diff --git a/core/java/android/server/search/SearchDialogWrapper.java b/core/java/android/server/search/SearchDialogWrapper.java new file mode 100644 index 0000000..dbc1e7f --- /dev/null +++ b/core/java/android/server/search/SearchDialogWrapper.java @@ -0,0 +1,320 @@ +/* + * 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.server.search; + +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.os.Bundle; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.Log; + +/** + * Runs an instance of {@link SearchDialog} on its own thread. + */ +class SearchDialogWrapper +implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { + +    private static final String TAG = "SearchManagerService"; +    private static final boolean DBG = false; + +    private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog"; + +    private static final String SEARCH_UI_THREAD_NAME = "SearchDialog"; +    private static final int SEARCH_UI_THREAD_PRIORITY = +        android.os.Process.THREAD_PRIORITY_FOREGROUND; + +    // Takes no arguments +    private static final int MSG_INIT = 0; +    // Takes these arguments: +    // arg1: selectInitialQuery, 0 = false, 1 = true +    // arg2: globalSearch, 0 = false, 1 = true +    // obj: searchManagerCallback +    // data[KEY_INITIAL_QUERY]: initial query +    // data[KEY_LAUNCH_ACTIVITY]: launch activity +    // data[KEY_APP_SEARCH_DATA]: app search data +    private static final int MSG_START_SEARCH = 1; +    // Takes no arguments +    private static final int MSG_STOP_SEARCH = 2; +    // Takes no arguments +    private static final int MSG_ON_CONFIGURATION_CHANGED = 3; + +    private static final String KEY_INITIAL_QUERY = "q"; +    private static final String KEY_LAUNCH_ACTIVITY = "a"; +    private static final String KEY_APP_SEARCH_DATA = "d"; + +    // Context used for getting search UI resources +    private final Context mContext; + +    // Handles messages on the search UI thread. +    private final SearchDialogHandler mSearchUiThread; + +    // The search UI +    SearchDialog mSearchDialog; + +    // If the search UI is visible, this is the callback for the client that showed it. +    ISearchManagerCallback mCallback = null; + +    // Allows disabling of search dialog for stress testing runs +    private final boolean mDisabledOnBoot; + +    /** +     * Creates a new search dialog wrapper and a search UI thread. The search dialog itself will +     * be created some asynchronously on the search UI thread. +     * +     * @param context Context used for getting search UI resources. +     */ +    public SearchDialogWrapper(Context context) { +        mContext = context; + +        mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY)); + +        // Create the search UI thread +        HandlerThread t = new HandlerThread(SEARCH_UI_THREAD_NAME, SEARCH_UI_THREAD_PRIORITY); +        t.start(); +        mSearchUiThread = new SearchDialogHandler(t.getLooper()); + +        // Create search UI on the search UI thread +        mSearchUiThread.sendEmptyMessage(MSG_INIT); +    } + +    /** +     * Initializes the search UI. +     * Must be called from the search UI thread. +     */ +    private void init() { +        mSearchDialog = new SearchDialog(mContext); +        mSearchDialog.setOnCancelListener(this); +        mSearchDialog.setOnDismissListener(this); +    } + +    private void registerBroadcastReceiver() { +        IntentFilter closeDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); +        mContext.registerReceiver(mBroadcastReceiver, closeDialogsFilter); +        IntentFilter configurationChangedFilter = +                new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); +        mContext.registerReceiver(mBroadcastReceiver, configurationChangedFilter); +    } + +    private void unregisterBroadcastReceiver() { +        mContext.unregisterReceiver(mBroadcastReceiver); +    } + +    /** +     * Closes the search dialog when requested by the system (e.g. when a phone call comes in). +     */ +    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { +        @Override +        public void onReceive(Context context, Intent intent) { +            String action = intent.getAction(); +            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { +                if (DBG) debug(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); +                stopSearch(); +            } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { +                if (DBG) debug(Intent.ACTION_CONFIGURATION_CHANGED); +                onConfigurationChanged(); +            } +        } +    }; + +    // +    // External API +    // + +    /** +     * Launches the search UI. +     * Can be called from any thread. +     * +     * @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()"); +        Message msg = Message.obtain(); +        msg.what = MSG_START_SEARCH; +        msg.arg1 = selectInitialQuery ? 1 : 0; +        msg.arg2 = globalSearch ? 1 : 0; +        msg.obj = searchManagerCallback; +        Bundle msgData = msg.getData(); +        msgData.putString(KEY_INITIAL_QUERY, initialQuery); +        msgData.putParcelable(KEY_LAUNCH_ACTIVITY, launchActivity); +        msgData.putBundle(KEY_APP_SEARCH_DATA, appSearchData); +        mSearchUiThread.sendMessage(msg); +    } + +    /** +     * Cancels the search dialog. +     * Can be called from any thread. +     */ +    public void stopSearch() { +        if (DBG) debug("stopSearch()"); +        mSearchUiThread.sendEmptyMessage(MSG_STOP_SEARCH); +    } + +    /** +     * Updates the search UI in response to a configuration change. +     * Can be called from any thread. +     */ +    void onConfigurationChanged() { +        if (DBG) debug("onConfigurationChanged()"); +        mSearchUiThread.sendEmptyMessage(MSG_ON_CONFIGURATION_CHANGED); +    } + +    // +    // Implementation methods that run on the search UI thread +    // + +    private class SearchDialogHandler extends Handler { + +        public SearchDialogHandler(Looper looper) { +            super(looper); +        } + +        @Override +        public void handleMessage(Message msg) { +            switch (msg.what) { +                case MSG_INIT: +                    init(); +                    break; +                case MSG_START_SEARCH: +                    handleStartSearchMessage(msg); +                    break; +                case MSG_STOP_SEARCH: +                    performStopSearch(); +                    break; +                case MSG_ON_CONFIGURATION_CHANGED: +                    performOnConfigurationChanged(); +                    break; +            } +        } + +        private void handleStartSearchMessage(Message msg) { +            Bundle msgData = msg.getData(); +            String initialQuery = msgData.getString(KEY_INITIAL_QUERY); +            boolean selectInitialQuery = msg.arg1 != 0; +            ComponentName launchActivity = +                    (ComponentName) msgData.getParcelable(KEY_LAUNCH_ACTIVITY); +            Bundle appSearchData = msgData.getBundle(KEY_APP_SEARCH_DATA); +            boolean globalSearch = msg.arg2 != 0; +            ISearchManagerCallback searchManagerCallback = (ISearchManagerCallback) msg.obj; +            performStartSearch(initialQuery, selectInitialQuery, launchActivity, +                    appSearchData, globalSearch, searchManagerCallback); +        } + +    } + +    /** +     * Actually launches the search UI. +     * This must be called on the search UI thread. +     */ +    void performStartSearch(String initialQuery, +            boolean selectInitialQuery, +            ComponentName launchActivity, +            Bundle appSearchData, +            boolean globalSearch, +            ISearchManagerCallback searchManagerCallback) { +        if (DBG) debug("performStartSearch()"); + +        if (mDisabledOnBoot) { +            Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY +                    + " system property is set."); +            return; +        } + +        registerBroadcastReceiver(); +        mCallback = searchManagerCallback; +        mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, +                globalSearch); +    } + +    /** +     * Actually cancels the search UI. +     * This must be called on the search UI thread. +     */ +    void performStopSearch() { +        if (DBG) debug("performStopSearch()"); +        mSearchDialog.cancel(); +    } + +    /** +     * Must be called from the search UI thread. +     */ +    void performOnConfigurationChanged() { +        if (DBG) debug("performOnConfigurationChanged()"); +        mSearchDialog.onConfigurationChanged(); +    } + +    /** +     * Called by {@link SearchDialog} when it goes away. +     */ +    public void onDismiss(DialogInterface dialog) { +        if (DBG) debug("onDismiss()"); +        if (mCallback != null) { +            try { +                // should be safe to do on the search UI thread, since it's a oneway interface +                mCallback.onDismiss(); +            } catch (DeadObjectException ex) { +                // The process that hosted the callback has died, do nothing +            } catch (RemoteException ex) { +                Log.e(TAG, "onDismiss() failed: " + ex); +            } +            // we don't need the callback anymore, release it +            mCallback = null; +        } +        unregisterBroadcastReceiver(); +    } + +    /** +     * Called by {@link SearchDialog} when the user or activity cancels search. +     * Whenever this method is called, {@link #onDismiss} is always called afterwards. +     */ +    public void onCancel(DialogInterface dialog) { +        if (DBG) debug("onCancel()"); +        if (mCallback != null) { +            try { +                // should be safe to do on the search UI thread, since it's a oneway interface +                mCallback.onCancel(); +            } catch (DeadObjectException ex) { +                // The process that hosted the callback has died, do nothing +            } catch (RemoteException ex) { +                Log.e(TAG, "onCancel() failed: " + ex); +            } +        } +    } + +    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 373e61f..87adfb3 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -18,52 +18,38 @@ 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.os.SystemProperties; -import android.text.TextUtils;  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 - * presentation (UI).  Its function is to maintain the map & list of "searchable" - * items, which provides a mapping from individual activities (where a user might have - * invoked search) to specific searchable activities (where the search will be dispatched). + * The search manager service handles the search UI, and maintains a registry of searchable + * activities.   */ -public class SearchManagerService extends ISearchManager.Stub -        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener -{ -        // general debugging support +public class SearchManagerService extends ISearchManager.Stub { + +    // general debugging support      private static final String TAG = "SearchManagerService";      private static final boolean DBG = false; -        // class maintenance and general shared data +    // Context that the service is running in.      private final Context mContext; -    private final Handler mHandler; -    private boolean mSearchablesDirty; -    private final Searchables mSearchables; - -    final SearchDialog mSearchDialog; -    ISearchManagerCallback mCallback = null; -    private final boolean mDisabledOnBoot; +    // This field is initialized in initialize(), and then never modified. +    // It is volatile since it can be accessed by multiple threads. +    private volatile Searchables mSearchables; -    private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog"; +    // This field is initialized in initialize(), and then never modified. +    // It is volatile since it can be accessed by multiple threads. +    private volatile SearchDialogWrapper mSearchDialog;      /**       * Initializes the Search Manager service in the provided system context. @@ -73,82 +59,71 @@ public class SearchManagerService extends ISearchManager.Stub       */      public SearchManagerService(Context context)  {          mContext = context; -        mHandler = new Handler(); -        mSearchablesDirty = true; -        mSearchables = new Searchables(context); -        mSearchDialog = new SearchDialog(context); -        mSearchDialog.setOnCancelListener(this); -        mSearchDialog.setOnDismissListener(this); +        // call initialize() after all pending actions on the main system thread have finished +        new Handler().post(new Runnable() { +            public void run() { +                initialize(); +            } +        }); +    } -        // Setup the infrastructure for updating and maintaining the list -        // of searchable activities. -        IntentFilter filter = new IntentFilter(); -        filter.addAction(Intent.ACTION_PACKAGE_ADDED); -        filter.addAction(Intent.ACTION_PACKAGE_REMOVED); -        filter.addAction(Intent.ACTION_PACKAGE_CHANGED); -        filter.addDataScheme("package"); -        mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); +    /** +     * Initializes the search UI and the list of searchable activities. +     */ +    void initialize() { +        mSearchables = createSearchables(); +        mSearchDialog = new SearchDialogWrapper(mContext); +    } + +    private Searchables createSearchables() { +        Searchables searchables = new Searchables(mContext); +        searchables.buildSearchableList(); -        // After startup settles down, preload the searchables list, -        // which will reduce the delay when the search UI is invoked. -        mHandler.post(mRunUpdateSearchable); +        IntentFilter packageFilter = new IntentFilter(); +        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); +        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); +        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); +        packageFilter.addDataScheme("package"); +        mContext.registerReceiver(mPackageChangedReceiver, packageFilter); -        // allows disabling of search dialog for stress testing runs -        mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY)); +        return searchables;      }      /** -     * Listens for intent broadcasts. -     * -     * The primary purpose here is to refresh the "searchables" list -     * if packages are added/removed. +     * Refreshes the "searchables" list when packages are added/removed.       */ -    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { +    private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {          @Override          public void onReceive(Context context, Intent intent) {              String action = intent.getAction(); -            // First, test for intents that matter at any time -            if (action.equals(Intent.ACTION_PACKAGE_ADDED) || -                action.equals(Intent.ACTION_PACKAGE_REMOVED) || -                action.equals(Intent.ACTION_PACKAGE_CHANGED)) { -                mSearchablesDirty = true; -                mHandler.post(mRunUpdateSearchable); -                return; +            if (Intent.ACTION_PACKAGE_ADDED.equals(action) || +                    Intent.ACTION_PACKAGE_REMOVED.equals(action) || +                    Intent.ACTION_PACKAGE_CHANGED.equals(action)) { +                if (DBG) Log.d(TAG, "Got " + action); +                // Dismiss search dialog, since the search context may no longer be valid +                mSearchDialog.stopSearch(); +                // Update list of searchable activities +                mSearchables.buildSearchableList(); +                broadcastSearchablesChanged();              }          }      };      /** -     * This runnable (for the main handler / UI thread) will update the searchables list. -     */ -    private Runnable mRunUpdateSearchable = new Runnable() { -        public void run() { -            updateSearchablesIfDirty(); -        } -    }; - -    /** -     * Updates the list of searchables, either at startup or in response to -     * a package add/remove broadcast message. +     * Informs all listeners that the list of searchables has been updated.       */ -    private void updateSearchables() { -        if (DBG) debug("updateSearchables()"); -        mSearchables.buildSearchableList(); -        mSearchablesDirty = false; +    void broadcastSearchablesChanged() { +        mContext.sendBroadcast( +                new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));      } -    /** -     * Updates the list of searchables if needed. -     */ -    private void updateSearchablesIfDirty() { -        if (mSearchablesDirty) { -            updateSearchables(); -        } -    } +    // +    // Searchable activities API +    //      /** -     * Returns the SearchableInfo for a given activity +     * Returns the SearchableInfo for a given activity.       *       * @param launchActivity The activity from which we're launching this search.       * @param globalSearch If false, this will only launch the search that has been specifically @@ -158,226 +133,84 @@ public class SearchManagerService extends ISearchManager.Stub       * @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) { -        updateSearchablesIfDirty(); -        SearchableInfo si = null; +    public SearchableInfo getSearchableInfo(final ComponentName launchActivity, +            final boolean globalSearch) { +        if (mSearchables == null) return null;          if (globalSearch) { -            si = mSearchables.getDefaultSearchable(); +            return mSearchables.getDefaultSearchable();          } else {              if (launchActivity == null) {                  Log.e(TAG, "getSearchableInfo(), activity == null");                  return null;              } -            si = mSearchables.getSearchableInfo(launchActivity); +            return mSearchables.getSearchableInfo(launchActivity);          } - -        return si;      }      /**       * Returns a list of the searchable activities that can be included in global search.       */      public List<SearchableInfo> getSearchablesInGlobalSearch() { -        updateSearchablesIfDirty(); +        if (mSearchables == null) return null;          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()"); - -        if (mDisabledOnBoot) { -            Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY -                    + " system property is set."); -            return; -        } - -        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. +     * Can be called from any thread.       */      public List<SearchableInfo> getSearchablesForWebSearch() { -        updateSearchablesIfDirty(); +        if (mSearchables == null) return null;          return mSearchables.getSearchablesForWebSearchList();      }      /**       * Returns the default searchable activity for web searches. +     * Can be called from any thread.       */      public SearchableInfo getDefaultSearchableForWebSearch() { -        updateSearchablesIfDirty(); +        if (mSearchables == null) return null;          return mSearchables.getDefaultSearchableForWebSearch();      }      /**       * Sets the default searchable activity for web searches. +     * Can be called from any thread.       */ -    public void setDefaultWebSearch(ComponentName component) { +    public void setDefaultWebSearch(final ComponentName component) { +        if (mSearchables == null) return;          mSearchables.setDefaultWebSearch(component); +        broadcastSearchablesChanged();      } +    // Search UI API +      /** -     * Runs an operation on the handler for the service, blocks until it returns, -     * and returns the value returned by the operation. +     * Launches the search UI. Can be called from any thread.       * -     * @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. +     * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)       */ -    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; -        } +    public void startSearch(String initialQuery, +            boolean selectInitialQuery, +            ComponentName launchActivity, +            Bundle appSearchData, +            boolean globalSearch, +            ISearchManagerCallback searchManagerCallback) { +        if (mSearchDialog == null) return; +        mSearchDialog.startSearch(initialQuery, +                selectInitialQuery, +                launchActivity, +                appSearchData, +                globalSearch, +                searchManagerCallback);      } -    private static void debug(String msg) { -        Thread thread = Thread.currentThread(); -        Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); +    /** +     * Cancels the search dialog. Can be called from any thread. +     */ +    public void stopSearch() { +        if (mSearchDialog == null) return; +        mSearchDialog.stopSearch();      }  } diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java index c7cc8ed..b959907 100644 --- a/core/java/android/server/search/Searchables.java +++ b/core/java/android/server/search/Searchables.java @@ -17,7 +17,6 @@  package android.server.search;  import com.android.internal.app.ResolverActivity; -import com.android.internal.R;  import android.app.SearchManager;  import android.content.ComponentName; @@ -27,7 +26,6 @@ import android.content.IntentFilter;  import android.content.pm.ActivityInfo;  import android.content.pm.PackageManager;  import android.content.pm.ResolveInfo; -import android.content.res.Resources;  import android.os.Bundle;  import android.util.Log; @@ -264,7 +262,7 @@ public class Searchables {          }          // Find the default web search provider. -        ComponentName webSearchActivity = getPreferredWebSearchActivity(); +        ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext);          SearchableInfo newDefaultSearchableForWebSearch = null;          if (webSearchActivity != null) {              newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity); @@ -283,9 +281,6 @@ public class Searchables {              mDefaultSearchable = newDefaultSearchable;              mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;          } - -        // Inform all listeners that the list of searchables has been updated. -        mContext.sendBroadcast(new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));      }      /** @@ -295,9 +290,10 @@ public class Searchables {       * @param action Intent action for which this activity is to be set as preferred.       * @return true if component was detected and set as preferred activity, false if not.       */ -    private boolean setPreferredActivity(ComponentName component, String action) { +    private static boolean setPreferredActivity(Context context, +            ComponentName component, String action) {          Log.d(LOG_TAG, "Checking component " + component); -        PackageManager pm = mContext.getPackageManager(); +        PackageManager pm = context.getPackageManager();          ActivityInfo ai;          try {              ai = pm.getActivityInfo(component, 0); @@ -326,10 +322,10 @@ public class Searchables {          return true;      } -    public ComponentName getPreferredWebSearchActivity() { +    private static ComponentName getPreferredWebSearchActivity(Context context) {          // Check if we have a preferred web search activity.          Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); -        PackageManager pm = mContext.getPackageManager(); +        PackageManager pm = context.getPackageManager();          ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);          if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) { @@ -338,11 +334,11 @@ public class Searchables {              // The components in the providers array are checked in the order of declaration so the              // first one has the highest priority. If the component exists in the system it is set              // as the preferred activity to handle intent action web search. -            String[] preferredActivities = mContext.getResources().getStringArray( +            String[] preferredActivities = context.getResources().getStringArray(                      com.android.internal.R.array.default_web_search_providers);              for (String componentName : preferredActivities) {                  ComponentName component = ComponentName.unflattenFromString(componentName); -                if (setPreferredActivity(component, Intent.ACTION_WEB_SEARCH)) { +                if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {                      return component;                  }              } @@ -354,7 +350,8 @@ public class Searchables {              if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {                  ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(                          ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME); -                if (setPreferredActivity(enhancedGoogleSearch, Intent.ACTION_WEB_SEARCH)) { +                if (setPreferredActivity(context, enhancedGoogleSearch, +                        Intent.ACTION_WEB_SEARCH)) {                      return enhancedGoogleSearch;                  }              } @@ -397,7 +394,7 @@ public class Searchables {       * Sets the default searchable activity for web searches.       */      public synchronized void setDefaultWebSearch(ComponentName component) { -        setPreferredActivity(component, Intent.ACTION_WEB_SEARCH); +        setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);          buildSearchableList();      }  } diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java index c4f1ab6..4c5fefc 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java @@ -107,8 +107,6 @@ public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalAct      }      // 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); @@ -245,22 +243,4 @@ public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalAct          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(); -    } -  } | 
