Merge change 6284 into donut

* changes:
  Run search UI on its own thread.
This commit is contained in:
Android (Google) Code Review
2009-07-08 13:28:29 -07:00
8 changed files with 457 additions and 467 deletions

View File

@@ -606,7 +606,6 @@ public class Activity extends ContextThemeWrapper
private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; 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; private SparseArray<Dialog> mManagedDialogs;
@@ -630,7 +629,6 @@ public class Activity extends ContextThemeWrapper
/*package*/ int mConfigChangeFlags; /*package*/ int mConfigChangeFlags;
/*package*/ Configuration mCurrentConfig; /*package*/ Configuration mCurrentConfig;
private SearchManager mSearchManager; private SearchManager mSearchManager;
private Bundle mSearchDialogState = null;
private Window mWindow; private Window mWindow;
@@ -808,13 +806,6 @@ public class Activity extends ContextThemeWrapper
final void performRestoreInstanceState(Bundle savedInstanceState) { final void performRestoreInstanceState(Bundle savedInstanceState) {
onRestoreInstanceState(savedInstanceState); onRestoreInstanceState(savedInstanceState);
restoreManagedDialogs(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) { final void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState); onSaveInstanceState(outState);
saveManagedDialogs(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(); 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) { public void onConfigurationChanged(Configuration newConfig) {
mCalled = true; mCalled = true;
// also update search dialog if showing
// TODO more generic than just this manager
mSearchManager.onConfigurationChanged(newConfig);
if (mWindow != null) { if (mWindow != null) {
// Pass the configuration changed event to the window // Pass the configuration changed event to the window
mWindow.onConfigurationChanged(newConfig); mWindow.onConfigurationChanged(newConfig);
@@ -3575,20 +3550,12 @@ public class Activity extends ContextThemeWrapper
"Activity " + mComponent.toShortString() + "Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()"); " did not call through to super.onPostResume()");
} }
// restore search dialog, if any
if (mSearchDialogState != null) {
mSearchManager.restoreSearchDialog(mSearchDialogState);
}
mSearchDialogState = null;
} }
final void performPause() { final void performPause() {
onPause(); onPause();
// save search dialog state if the search dialog is open, // dismiss the search dialog if it is open
// and then dismiss the search dialog
mSearchDialogState = mSearchManager.saveSearchDialog();
mSearchManager.stopSearch(); mSearchManager.stopSearch();
} }

View File

@@ -36,8 +36,4 @@ interface ISearchManager {
boolean globalSearch, boolean globalSearch,
ISearchManagerCallback searchManagerCallback); ISearchManagerCallback searchManagerCallback);
void stopSearch(); void stopSearch();
boolean isVisible();
Bundle onSaveInstanceState();
void onRestoreInstanceState(in Bundle savedInstanceState);
void onConfigurationChanged(in Configuration newConfig);
} }

View File

@@ -19,13 +19,11 @@ package android.app;
import static android.app.SuggestionsAdapter.getColumnString; import static android.app.SuggestionsAdapter.getColumnString;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
@@ -33,8 +31,8 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemClock; 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_GLOBAL = 12;
private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7; private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
// interaction with runtime
private IntentFilter mCloseDialogsFilter;
private IntentFilter mPackageFilter;
// views & widgets // views & widgets
private TextView mBadgeLabel; private TextView mBadgeLabel;
private ImageView mAppIcon; private ImageView mAppIcon;
@@ -210,15 +204,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Touching outside of the search dialog will dismiss it // Touching outside of the search dialog will dismiss it
setCanceledOnTouchOutside(true); 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 // Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -382,15 +368,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return true; 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. * 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 @Override
public void onStop() { public void onStop() {
super.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(); closeSuggestionsAdapter();
// dump extra memory we're hanging on to // 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 * 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 @Override
public Bundle onSaveInstanceState() { public Bundle onSaveInstanceState() {
if (!isShowing()) return null;
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
// setup info so I can recreate this particular search // setup info so I can recreate this particular search
bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent); bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData); bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
@@ -483,6 +456,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/ */
@Override @Override
public void onRestoreInstanceState(Bundle savedInstanceState) { public void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState == null) return;
ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT); ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA); Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH); 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. * Called after resources have changed, e.g. after screen rotation or locale change.
*/ */
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged() {
if (isShowing()) { if (isShowing()) {
// Redraw (resources may have changed) // Redraw (resources may have changed)
updateSearchButton(); updateSearchButton();
@@ -1014,35 +989,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return false; 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 @Override
public void cancel() { public void cancel() {
if (!isShowing()) return;
// We made sure the IME was displayed, so also make sure it is closed // We made sure the IME was displayed, so also make sure it is closed
// when we go away. // when we go away.
InputMethodManager imm = (InputMethodManager)getContext() InputMethodManager imm = (InputMethodManager)getContext()

View File

@@ -20,7 +20,6 @@ import android.content.ComponentName;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.Configuration;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -1729,59 +1728,6 @@ public class SearchManager
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* Saves the state of the search UI.
*
* @return A Bundle containing the state of the search dialog, or {@code null}
* if the search UI is not visible.
*
* @hide
*/
public Bundle saveSearchDialog() {
if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing);
if (!mIsShowing) return null;
try {
return 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 * Gets information about a searchable activity. This method is static so that it can
* be used from non-Activity contexts. * be used from non-Activity contexts.

View File

@@ -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() + ")");
}
}

View File

@@ -18,52 +18,38 @@ package android.server.search;
import android.app.ISearchManager; import android.app.ISearchManager;
import android.app.ISearchManagerCallback; import android.app.ISearchManagerCallback;
import android.app.SearchDialog;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import java.util.List; 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 * The search manager service handles the search UI, and maintains a registry of searchable
* presentation (UI). Its function is to maintain the map & list of "searchable" * activities.
* 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).
*/ */
public class SearchManagerService extends ISearchManager.Stub public class SearchManagerService extends ISearchManager.Stub {
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
{ // general debugging support
// general debugging support
private static final String TAG = "SearchManagerService"; private static final String TAG = "SearchManagerService";
private static final boolean DBG = false; 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 Context mContext;
private final Handler mHandler;
private boolean mSearchablesDirty;
private final Searchables mSearchables;
final SearchDialog mSearchDialog; // This field is initialized in initialize(), and then never modified.
ISearchManagerCallback mCallback = null; // It is volatile since it can be accessed by multiple threads.
private volatile Searchables mSearchables;
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 static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog"; private volatile SearchDialogWrapper mSearchDialog;
/** /**
* Initializes the Search Manager service in the provided system context. * Initializes the Search Manager service in the provided system context.
@@ -73,82 +59,71 @@ public class SearchManagerService extends ISearchManager.Stub
*/ */
public SearchManagerService(Context context) { public SearchManagerService(Context context) {
mContext = context; mContext = context;
mHandler = new Handler(); // call initialize() after all pending actions on the main system thread have finished
mSearchablesDirty = true; new Handler().post(new Runnable() {
mSearchables = new Searchables(context); public void run() {
mSearchDialog = new SearchDialog(context); initialize();
mSearchDialog.setOnCancelListener(this); }
mSearchDialog.setOnDismissListener(this); });
// 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);
// After startup settles down, preload the searchables list,
// which will reduce the delay when the search UI is invoked.
mHandler.post(mRunUpdateSearchable);
// allows disabling of search dialog for stress testing runs
mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY));
} }
/** /**
* Listens for intent broadcasts. * Initializes the search UI and the list of searchable activities.
*
* The primary purpose here is to refresh the "searchables" list
* if packages are added/removed.
*/ */
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { void initialize() {
mSearchables = createSearchables();
mSearchDialog = new SearchDialogWrapper(mContext);
}
private Searchables createSearchables() {
Searchables searchables = new Searchables(mContext);
searchables.buildSearchableList();
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);
return searchables;
}
/**
* Refreshes the "searchables" list when packages are added/removed.
*/
private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
// First, test for intents that matter at any time if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
if (action.equals(Intent.ACTION_PACKAGE_ADDED) || Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
action.equals(Intent.ACTION_PACKAGE_REMOVED) || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
action.equals(Intent.ACTION_PACKAGE_CHANGED)) { if (DBG) Log.d(TAG, "Got " + action);
mSearchablesDirty = true; // Dismiss search dialog, since the search context may no longer be valid
mHandler.post(mRunUpdateSearchable); mSearchDialog.stopSearch();
return; // Update list of searchable activities
mSearchables.buildSearchableList();
broadcastSearchablesChanged();
} }
} }
}; };
/** /**
* This runnable (for the main handler / UI thread) will update the searchables list. * Informs all listeners that the list of searchables has been updated.
*/ */
private Runnable mRunUpdateSearchable = new Runnable() { void broadcastSearchablesChanged() {
public void run() { mContext.sendBroadcast(
updateSearchablesIfDirty(); new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
}
};
/**
* Updates the list of searchables, either at startup or in response to
* a package add/remove broadcast message.
*/
private void updateSearchables() {
if (DBG) debug("updateSearchables()");
mSearchables.buildSearchableList();
mSearchablesDirty = false;
} }
/** //
* Updates the list of searchables if needed. // Searchable activities API
*/ //
private void updateSearchablesIfDirty() {
if (mSearchablesDirty) {
updateSearchables();
}
}
/** /**
* 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 launchActivity The activity from which we're launching this search.
* @param globalSearch If false, this will only launch the search that has been specifically * @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, * @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available. * or null if no searchable metadata was available.
*/ */
public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) { public SearchableInfo getSearchableInfo(final ComponentName launchActivity,
updateSearchablesIfDirty(); final boolean globalSearch) {
SearchableInfo si = null; if (mSearchables == null) return null;
if (globalSearch) { if (globalSearch) {
si = mSearchables.getDefaultSearchable(); return mSearchables.getDefaultSearchable();
} else { } else {
if (launchActivity == null) { if (launchActivity == null) {
Log.e(TAG, "getSearchableInfo(), activity == null"); Log.e(TAG, "getSearchableInfo(), activity == null");
return 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. * Returns a list of the searchable activities that can be included in global search.
*/ */
public List<SearchableInfo> getSearchablesInGlobalSearch() { public List<SearchableInfo> getSearchablesInGlobalSearch() {
updateSearchablesIfDirty(); if (mSearchables == null) return null;
return mSearchables.getSearchablesInGlobalSearchList(); 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. * Returns a list of the searchable activities that handle web searches.
* Can be called from any thread.
*/ */
/*package*/ void performStartSearch(String initialQuery, public List<SearchableInfo> getSearchablesForWebSearch() {
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() {
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(final ComponentName component) {
if (mSearchables == null) return;
mSearchables.setDefaultWebSearch(component);
broadcastSearchablesChanged();
}
// Search UI API
/**
* Launches the search UI. Can be called from any thread.
*
* @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
*/
public void startSearch(String initialQuery,
boolean selectInitialQuery, boolean selectInitialQuery,
ComponentName launchActivity, ComponentName launchActivity,
Bundle appSearchData, Bundle appSearchData,
boolean globalSearch, boolean globalSearch,
ISearchManagerCallback searchManagerCallback) { ISearchManagerCallback searchManagerCallback) {
if (DBG) debug("performStartSearch()"); if (mSearchDialog == null) return;
mSearchDialog.startSearch(initialQuery,
if (mDisabledOnBoot) { selectInitialQuery,
Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY launchActivity,
+ " system property is set."); appSearchData,
return; globalSearch,
} searchManagerCallback);
mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
globalSearch);
if (searchManagerCallback != null) {
mCallback = searchManagerCallback;
}
} }
/** /**
* Cancels the search dialog. Can be called from any thread. * Cancels the search dialog. Can be called from any thread.
*/ */
public void stopSearch() { public void stopSearch() {
if (DBG) debug("stopSearch()"); if (mSearchDialog == null) return;
mHandler.post(new Runnable() { mSearchDialog.stopSearch();
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.
*/
public List<SearchableInfo> getSearchablesForWebSearch() {
updateSearchablesIfDirty();
return mSearchables.getSearchablesForWebSearchList();
}
/**
* Returns the default searchable activity for web searches.
*/
public SearchableInfo getDefaultSearchableForWebSearch() {
updateSearchablesIfDirty();
return mSearchables.getDefaultSearchableForWebSearch();
}
/**
* Sets the default searchable activity for web searches.
*/
public void setDefaultWebSearch(ComponentName component) {
mSearchables.setDefaultWebSearch(component);
}
/**
* Runs an operation on the handler for the service, blocks until it returns,
* and returns the value returned by the operation.
*
* @param <V> Return value type.
* @param callable Operation to run.
* @param errorResult Value to return if the operations throws an exception.
* @param name Operation name to include in error log messages.
* @return The value returned by the operation.
*/
private <V> V postAndWait(Callable<V> callable, V errorResult, String name) {
FutureTask<V> task = new FutureTask<V>(callable);
mHandler.post(task);
try {
return task.get();
} catch (InterruptedException ex) {
Log.e(TAG, "Error calling " + name + ": " + ex);
return errorResult;
} catch (ExecutionException ex) {
Log.e(TAG, "Error calling " + name + ": " + ex);
return errorResult;
}
}
private static void debug(String msg) {
Thread thread = Thread.currentThread();
Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
} }
} }

View File

@@ -17,7 +17,6 @@
package android.server.search; package android.server.search;
import com.android.internal.app.ResolverActivity; import com.android.internal.app.ResolverActivity;
import com.android.internal.R;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.ComponentName; import android.content.ComponentName;
@@ -27,7 +26,6 @@ import android.content.IntentFilter;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@@ -264,7 +262,7 @@ public class Searchables {
} }
// Find the default web search provider. // Find the default web search provider.
ComponentName webSearchActivity = getPreferredWebSearchActivity(); ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext);
SearchableInfo newDefaultSearchableForWebSearch = null; SearchableInfo newDefaultSearchableForWebSearch = null;
if (webSearchActivity != null) { if (webSearchActivity != null) {
newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity); newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
@@ -283,9 +281,6 @@ public class Searchables {
mDefaultSearchable = newDefaultSearchable; mDefaultSearchable = newDefaultSearchable;
mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch; 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. * @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. * @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); Log.d(LOG_TAG, "Checking component " + component);
PackageManager pm = mContext.getPackageManager(); PackageManager pm = context.getPackageManager();
ActivityInfo ai; ActivityInfo ai;
try { try {
ai = pm.getActivityInfo(component, 0); ai = pm.getActivityInfo(component, 0);
@@ -326,10 +322,10 @@ public class Searchables {
return true; return true;
} }
public ComponentName getPreferredWebSearchActivity() { private static ComponentName getPreferredWebSearchActivity(Context context) {
// Check if we have a preferred web search activity. // Check if we have a preferred web search activity.
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); 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); ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) { 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 // 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 // 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. // 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); com.android.internal.R.array.default_web_search_providers);
for (String componentName : preferredActivities) { for (String componentName : preferredActivities) {
ComponentName component = ComponentName.unflattenFromString(componentName); ComponentName component = ComponentName.unflattenFromString(componentName);
if (setPreferredActivity(component, Intent.ACTION_WEB_SEARCH)) { if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {
return component; return component;
} }
} }
@@ -354,7 +350,8 @@ public class Searchables {
if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) { if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {
ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString( ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(
ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME); ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME);
if (setPreferredActivity(enhancedGoogleSearch, Intent.ACTION_WEB_SEARCH)) { if (setPreferredActivity(context, enhancedGoogleSearch,
Intent.ACTION_WEB_SEARCH)) {
return enhancedGoogleSearch; return enhancedGoogleSearch;
} }
} }
@@ -397,7 +394,7 @@ public class Searchables {
* Sets the default searchable activity for web searches. * Sets the default searchable activity for web searches.
*/ */
public synchronized void setDefaultWebSearch(ComponentName component) { public synchronized void setDefaultWebSearch(ComponentName component) {
setPreferredActivity(component, Intent.ACTION_WEB_SEARCH); setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);
buildSearchableList(); buildSearchableList();
} }
} }

View File

@@ -107,8 +107,6 @@ public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalAct
} }
// Checks that the search UI is not visible. // 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() { private void assertSearchNotVisible() {
SearchManager searchManager = (SearchManager) SearchManager searchManager = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE); mContext.getSystemService(Context.SEARCH_SERVICE);
@@ -245,22 +243,4 @@ public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalAct
assertSearchNotVisible(); 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();
}
} }