summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorAndroid (Google) Code Review <android-gerrit@google.com>2009-07-08 13:28:29 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2009-07-08 13:28:29 -0700
commit91b0a7013e4b6d170087cb4c9a3c35b787f92a6b (patch)
tree714979f975a2130ef1ff613b4fef8c206dfcae86 /core
parentf9c228a51fba54e80826fb6aa5d53f43a2e86042 (diff)
parent444c727e0eecf83e9d0b9c4e7af5cbf5fc4135f8 (diff)
downloadframeworks_base-91b0a7013e4b6d170087cb4c9a3c35b787f92a6b.zip
frameworks_base-91b0a7013e4b6d170087cb4c9a3c35b787f92a6b.tar.gz
frameworks_base-91b0a7013e4b6d170087cb4c9a3c35b787f92a6b.tar.bz2
Merge change 6284 into donut
* changes: Run search UI on its own thread.
Diffstat (limited to 'core')
-rw-r--r--core/java/android/app/Activity.java37
-rw-r--r--core/java/android/app/ISearchManager.aidl4
-rw-r--r--core/java/android/app/SearchDialog.java81
-rw-r--r--core/java/android/app/SearchManager.java54
-rw-r--r--core/java/android/server/search/SearchDialogWrapper.java320
-rw-r--r--core/java/android/server/search/SearchManagerService.java353
-rw-r--r--core/java/android/server/search/Searchables.java25
7 files changed, 442 insertions, 432 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();
}
}