diff options
| author | Bjorn Bringert <bringert@android.com> | 2009-06-05 13:22:28 +0100 |
|---|---|---|
| committer | Bjorn Bringert <bringert@android.com> | 2009-06-15 09:05:26 +0100 |
| commit | 8d17f3f24bbda9a9cd7ea08c5925508dc2c011be (patch) | |
| tree | cf7e5ebb721354bfffc41d2ab4932cb70ea3c033 /core | |
| parent | 3d59ee7aa66a5b1e80f5042f8d872dd9819b5f40 (diff) | |
| download | frameworks_base-8d17f3f24bbda9a9cd7ea08c5925508dc2c011be.zip frameworks_base-8d17f3f24bbda9a9cd7ea08c5925508dc2c011be.tar.gz frameworks_base-8d17f3f24bbda9a9cd7ea08c5925508dc2c011be.tar.bz2 | |
Run search dialog in the system process.
Fixes http://b/issue?id=1905863
This is needed to address two security issues with global search:
http://b/issue?id=1871088 (Apps can read content providers through GlobalSearch)
http://b/issue?id=1819627 (Apps can use GlobalSearch to launch arbirtrary intents)
This also fixes http://b/issue?id=1693153 (SearchManager.OnDismissListener
never gets called)
To fix the security issues, GlobalSearch also needs to require
a non-app permission to access its content provider and launch intents.
Diffstat (limited to 'core')
| -rw-r--r-- | core/java/android/app/Activity.java | 52 | ||||
| -rw-r--r-- | core/java/android/app/ISearchManager.aidl | 14 | ||||
| -rw-r--r-- | core/java/android/app/ISearchManagerCallback.aidl | 23 | ||||
| -rw-r--r-- | core/java/android/app/SearchDialog.java | 55 | ||||
| -rw-r--r-- | core/java/android/app/SearchManager.java | 199 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchManagerService.java | 195 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchableInfo.java | 20 |
7 files changed, 422 insertions, 136 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f9b3d05..7fb3449 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -628,6 +628,8 @@ public class Activity extends ContextThemeWrapper boolean mStartedActivity; /*package*/ int mConfigChangeFlags; /*package*/ Configuration mCurrentConfig; + private SearchManager mSearchManager; + private Bundle mSearchDialogState = null; private Window mWindow; @@ -788,6 +790,9 @@ public class Activity extends ContextThemeWrapper protected void onCreate(Bundle savedInstanceState) { mVisibleFromClient = mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, true); + // uses super.getSystemService() since this.getSystemService() looks at the + // mSearchManager field. + mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE); mCalled = true; } @@ -805,9 +810,10 @@ public class Activity extends ContextThemeWrapper // Also restore the state of a search dialog (if any) // TODO more generic than just this manager - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY); + Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY); + if (searchState != null) { + mSearchManager.restoreSearchDialog(searchState); + } } /** @@ -1013,9 +1019,11 @@ public class Activity extends ContextThemeWrapper // Also save the state of a search dialog (if any) // TODO more generic than just this manager - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY); + // onPause() should always be called before this method, so mSearchManagerState + // should be up to date. + if (mSearchDialogState != null) { + outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState); + } } /** @@ -1286,12 +1294,6 @@ public class Activity extends ContextThemeWrapper } } } - - // also dismiss search dialog if showing - // TODO more generic than just this manager - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - searchManager.stopSearch(); // close any cursors we are managing. int numCursors = mManagedCursors.size(); @@ -1301,6 +1303,10 @@ 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; } /** @@ -1324,9 +1330,7 @@ public class Activity extends ContextThemeWrapper // also update search dialog if showing // TODO more generic than just this manager - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - searchManager.onConfigurationChanged(newConfig); + mSearchManager.onConfigurationChanged(newConfig); if (mWindow != null) { // Pass the configuration changed event to the window @@ -2543,10 +2547,7 @@ public class Activity extends ContextThemeWrapper */ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch) { - // activate the search manager and start it up! - SearchManager searchManager = (SearchManager) - getSystemService(Context.SEARCH_SERVICE); - searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), + mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), appSearchData, globalSearch); } @@ -3265,6 +3266,8 @@ public class Activity extends ContextThemeWrapper if (WINDOW_SERVICE.equals(name)) { return mWindowManager; + } else if (SEARCH_SERVICE.equals(name)) { + return mSearchManager; } return super.getSystemService(name); } @@ -3563,10 +3566,21 @@ 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(); + mSearchManager.stopSearch(); } final void performUserLeaving() { diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index 374423e..e8bd60a 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -16,7 +16,10 @@ package android.app; +import android.app.ISearchManagerCallback; import android.content.ComponentName; +import android.content.res.Configuration; +import android.os.Bundle; import android.server.search.SearchableInfo; /** @hide */ @@ -26,4 +29,15 @@ interface ISearchManager { List<SearchableInfo> getSearchablesForWebSearch(); SearchableInfo getDefaultSearchableForWebSearch(); void setDefaultWebSearch(in ComponentName component); + void startSearch(in String initialQuery, + boolean selectInitialQuery, + in ComponentName launchActivity, + in Bundle appSearchData, + boolean globalSearch, + ISearchManagerCallback searchManagerCallback); + void stopSearch(); + boolean isVisible(); + Bundle onSaveInstanceState(); + void onRestoreInstanceState(in Bundle savedInstanceState); + void onConfigurationChanged(in Configuration newConfig); } diff --git a/core/java/android/app/ISearchManagerCallback.aidl b/core/java/android/app/ISearchManagerCallback.aidl new file mode 100644 index 0000000..bdfb2ba --- /dev/null +++ b/core/java/android/app/ISearchManagerCallback.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +/** @hide */ +oneway interface ISearchManagerCallback { + void onDismiss(); + void onCancel(); +} diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 7de6572..9141c4c 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -76,8 +76,8 @@ import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicLong; /** - * In-application-process implementation of Search Bar. This is still controlled by the - * SearchManager, but it runs in the current activity's process to keep things lighter weight. + * System search dialog. This is controlled by the + * SearchManagerService and runs in the system process. * * @hide */ @@ -179,17 +179,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Window theWindow = getWindow(); - theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL); - setContentView(com.android.internal.R.layout.search_bar); - theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT, - // taking up the whole window (even when transparent) is less than ideal, - // but necessary to show the popup window until the window manager supports - // having windows anchored by their parent but not clipped by them. - ViewGroup.LayoutParams.FILL_PARENT); + Window theWindow = getWindow(); WindowManager.LayoutParams lp = theWindow.getAttributes(); + lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR; + lp.width = ViewGroup.LayoutParams.FILL_PARENT; + // taking up the whole window (even when transparent) is less than ideal, + // but necessary to show the popup window until the window manager supports + // having windows anchored by their parent but not clipped by them. + lp.height = ViewGroup.LayoutParams.FILL_PARENT; + lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; theWindow.setAttributes(lp); @@ -234,10 +234,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // Save voice intent for later queries/launching mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mLocationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); @@ -278,12 +280,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ public boolean show(String initialQuery, boolean selectInitialQuery, ComponentName componentName, Bundle appSearchData, boolean globalSearch) { - if (isShowing()) { - // race condition - already showing but not handling events yet. - // in this case, just discard the "show" request - return true; - } - + // Reset any stored values from last time dialog was shown. mStoredComponentName = null; mStoredAppSearchData = null; @@ -442,11 +439,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS stopLocationUpdates(); - // TODO: Removing the listeners means that they never get called, since - // Dialog.dismissDialog() calls onStop() before sendDismissMessage(). - setOnCancelListener(null); - setOnDismissListener(null); - // stop receiving broadcasts (throws exception if none registered) try { getContext().unregisterReceiver(mBroadcastReceiver); @@ -654,15 +646,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold()); + // we dismiss the entire dialog instead + mSearchAutoComplete.setDropDownDismissedOnCompletion(false); if (mGlobalSearchMode) { mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in - mSearchAutoComplete.setDropDownDismissedOnCompletion(false); mSearchAutoComplete.setDropDownBackgroundResource( com.android.internal.R.drawable.search_dropdown_background); } else { mSearchAutoComplete.setDropDownAlwaysVisible(false); - mSearchAutoComplete.setDropDownDismissedOnCompletion(true); mSearchAutoComplete.setDropDownBackgroundResource( com.android.internal.R.drawable.search_dropdown_background_apps); } @@ -1317,7 +1309,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } /** - * Launches an intent. Also dismisses the search dialog if not in global search mode. + * Launches an intent and dismisses the search dialog (unless the intent + * is one of the special intents that modifies the state of the search dialog). */ private void launchIntent(Intent intent) { if (intent == null) { @@ -1326,9 +1319,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (handleSpecialIntent(intent)){ return; } - if (!mGlobalSearchMode) { - dismiss(); - } + dismiss(); getContext().startActivity(intent); } @@ -1511,6 +1502,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS int actionKey, String actionMsg) { // Now build the Intent Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (data != null) { intent.setData(data); } @@ -1595,14 +1587,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS private boolean isEmpty() { return TextUtils.getTrimmedLength(getText()) == 0; } - - /** - * Clears the entered text. - */ - private void clear() { - setText(""); - } - + /** * We override this method to avoid replacing the query box text * when a suggestion is clicked. diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 820f192..1ddd20a 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.server.search.SearchableInfo; +import android.util.Log; import android.view.KeyEvent; import java.util.List; @@ -1108,6 +1109,10 @@ import java.util.List; public class SearchManager implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener { + + private static final boolean DBG = false; + private static final String TAG = "SearchManager"; + /** * This is a shortcut definition for the default menu key to use for invoking search. * @@ -1494,12 +1499,14 @@ public class SearchManager private static ISearchManager sService = getSearchManagerService(); private final Context mContext; - private final Handler mHandler; - - private SearchDialog mSearchDialog; - - private OnDismissListener mDismissListener = null; - private OnCancelListener mCancelListener = null; + + // package private since they are used by the inner class SearchManagerCallback + /* package */ boolean mIsShowing = false; + /* package */ final Handler mHandler; + /* package */ OnDismissListener mDismissListener = null; + /* package */ OnCancelListener mCancelListener = null; + + private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback(); /*package*/ SearchManager(Context context, Handler handler) { mContext = context; @@ -1551,17 +1558,16 @@ public class SearchManager ComponentName launchActivity, Bundle appSearchData, boolean globalSearch) { - - if (mSearchDialog == null) { - mSearchDialog = new SearchDialog(mContext); + if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing); + if (mIsShowing) return; + try { + mIsShowing = true; + // activate the search manager and start it up! + sService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData, + globalSearch, mSearchManagerCallback); + } catch (RemoteException ex) { + Log.e(TAG, "startSearch() failed: " + ex); } - - // activate the search manager and start it up! - mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, - globalSearch); - - mSearchDialog.setOnCancelListener(this); - mSearchDialog.setOnDismissListener(this); } /** @@ -1575,9 +1581,16 @@ public class SearchManager * * @see #startSearch */ - public void stopSearch() { - if (mSearchDialog != null) { - mSearchDialog.cancel(); + public void stopSearch() { + if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing); + if (!mIsShowing) return; + try { + sService.stopSearch(); + // onDismiss will also clear this, but we do it here too since onDismiss() is + // called asynchronously. + mIsShowing = false; + } catch (RemoteException ex) { + Log.e(TAG, "stopSearch() failed: " + ex); } } @@ -1590,13 +1603,11 @@ public class SearchManager * * @hide */ - public boolean isVisible() { - if (mSearchDialog != null) { - return mSearchDialog.isShowing(); - } - return false; + public boolean isVisible() { + if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing); + return mIsShowing; } - + /** * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor * search UI state. @@ -1631,79 +1642,112 @@ public class SearchManager public void setOnDismissListener(final OnDismissListener listener) { mDismissListener = listener; } - - /** - * The callback from the search dialog when dismissed - * @hide - */ - public void onDismiss(DialogInterface dialog) { - if (dialog == mSearchDialog) { - if (mDismissListener != null) { - mDismissListener.onDismiss(); - } - } - } /** * Set or clear the callback that will be invoked whenever the search UI is canceled. * * @param listener The {@link OnCancelListener} to use, or null. */ - public void setOnCancelListener(final OnCancelListener listener) { + public void setOnCancelListener(OnCancelListener listener) { mCancelListener = listener; } - - - /** - * The callback from the search dialog when canceled - * @hide - */ - public void onCancel(DialogInterface dialog) { - if (dialog == mSearchDialog) { - if (mCancelListener != null) { - mCancelListener.onCancel(); + + private class SearchManagerCallback extends ISearchManagerCallback.Stub { + + private final Runnable mFireOnDismiss = new Runnable() { + public void run() { + if (DBG) debug("mFireOnDismiss"); + mIsShowing = false; + if (mDismissListener != null) { + mDismissListener.onDismiss(); + } + } + }; + + private final Runnable mFireOnCancel = new Runnable() { + public void run() { + if (DBG) debug("mFireOnCancel"); + // doesn't need to clear mIsShowing since onDismiss() always gets called too + if (mCancelListener != null) { + mCancelListener.onCancel(); + } } + }; + + public void onDismiss() { + if (DBG) debug("onDismiss()"); + mHandler.post(mFireOnDismiss); + } + + public void onCancel() { + if (DBG) debug("onCancel()"); + mHandler.post(mFireOnCancel); } + + } + + // TODO: remove the DialogInterface interfaces from SearchManager. + // This changes the public API, so I'll do it in a separate change. + public void onCancel(DialogInterface dialog) { + throw new UnsupportedOperationException(); + } + public void onDismiss(DialogInterface dialog) { + throw new UnsupportedOperationException(); } /** - * Save instance state so we can recreate after a rotation. - * + * 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 */ - void saveSearchDialog(Bundle outState, String key) { - if (mSearchDialog != null && mSearchDialog.isShowing()) { - Bundle searchDialogState = mSearchDialog.onSaveInstanceState(); - outState.putBundle(key, searchDialogState); + public Bundle saveSearchDialog() { + if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing); + if (!mIsShowing) return null; + try { + return sService.onSaveInstanceState(); + } catch (RemoteException ex) { + Log.e(TAG, "onSaveInstanceState() failed: " + ex); + return null; } } /** - * Restore instance state after a rotation. - * + * Restores the state of the search dialog. + * + * @param searchDialogState Bundle to read the state from. + * * @hide */ - void restoreSearchDialog(Bundle inState, String key) { - Bundle searchDialogState = inState.getBundle(key); - if (searchDialogState != null) { - if (mSearchDialog == null) { - mSearchDialog = new SearchDialog(mContext); - } - mSearchDialog.onRestoreInstanceState(searchDialogState); + public void restoreSearchDialog(Bundle searchDialogState) { + if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")"); + if (searchDialogState == null) return; + try { + sService.onRestoreInstanceState(searchDialogState); + } catch (RemoteException ex) { + Log.e(TAG, "onRestoreInstanceState() failed: " + ex); } } - + /** - * Hook for updating layout on a rotation - * + * Update the search dialog after a configuration change. + * + * @param newConfig The new configuration. + * * @hide */ - void onConfigurationChanged(Configuration newConfig) { - if (mSearchDialog != null && mSearchDialog.isShowing()) { - mSearchDialog.onConfigurationChanged(newConfig); + public void onConfigurationChanged(Configuration newConfig) { + if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing); + if (!mIsShowing) return; + try { + sService.onConfigurationChanged(newConfig); + } catch (RemoteException ex) { + Log.e(TAG, "onConfigurationChanged() failed:" + ex); } } - + private static ISearchManager getSearchManagerService() { return ISearchManager.Stub.asInterface( ServiceManager.getService(Context.SEARCH_SERVICE)); @@ -1724,7 +1768,8 @@ public class SearchManager boolean globalSearch) { try { return sService.getSearchableInfo(componentName, globalSearch); - } catch (RemoteException e) { + } catch (RemoteException ex) { + Log.e(TAG, "getSearchableInfo() failed: " + ex); return null; } } @@ -1805,6 +1850,7 @@ public class SearchManager try { return sService.getSearchablesInGlobalSearch(); } catch (RemoteException e) { + Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e); return null; } } @@ -1812,7 +1858,8 @@ public class SearchManager /** * Returns a list of the searchable activities that handle web searches. * - * @return a a list of all searchable activities that handle {@link SearchManager#ACTION_WEB_SEARCH}. + * @return a list of all searchable activities that handle + * {@link android.content.Intent#ACTION_WEB_SEARCH}. * * @hide because SearchableInfo is not part of the API. */ @@ -1820,6 +1867,7 @@ public class SearchManager try { return sService.getSearchablesForWebSearch(); } catch (RemoteException e) { + Log.e(TAG, "getSearchablesForWebSearch() failed: " + e); return null; } } @@ -1835,6 +1883,7 @@ public class SearchManager try { return sService.getDefaultSearchableForWebSearch(); } catch (RemoteException e) { + Log.e(TAG, "getDefaultSearchableForWebSearch() failed: " + e); return null; } } @@ -1850,6 +1899,12 @@ public class SearchManager try { sService.setDefaultWebSearch(component); } catch (RemoteException e) { + Log.e(TAG, "setDefaultWebSearch() failed: " + e); } } + + private static void debug(String msg) { + Thread thread = Thread.currentThread(); + Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); + } } diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 060bcea..db812d1 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -17,15 +17,25 @@ package android.server.search; import android.app.ISearchManager; +import android.app.ISearchManagerCallback; +import android.app.SearchDialog; +import android.app.SearchManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; +import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.util.Log; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; /** * This is a simplified version of the Search Manager service. It no longer handles @@ -34,16 +44,20 @@ import java.util.List; * invoked search) to specific searchable activities (where the search will be dispatched). */ public class SearchManagerService extends ISearchManager.Stub + implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { // general debugging support private static final String TAG = "SearchManagerService"; - private static final boolean DEBUG = false; + private static final boolean DBG = false; // class maintenance and general shared data private final Context mContext; private final Handler mHandler; private boolean mSearchablesDirty; - private Searchables mSearchables; + private final Searchables mSearchables; + + final SearchDialog mSearchDialog; + ISearchManagerCallback mCallback = null; /** * Initializes the Search Manager service in the provided system context. @@ -56,6 +70,9 @@ public class SearchManagerService extends ISearchManager.Stub mHandler = new Handler(); mSearchablesDirty = true; mSearchables = new Searchables(context); + mSearchDialog = new SearchDialog(context); + mSearchDialog.setOnCancelListener(this); + mSearchDialog.setOnDismissListener(this); // Setup the infrastructure for updating and maintaining the list // of searchable activities. @@ -107,6 +124,7 @@ public class SearchManagerService extends ISearchManager.Stub * a package add/remove broadcast message. */ private void updateSearchables() { + if (DBG) debug("updateSearchables()"); mSearchables.buildSearchableList(); mSearchablesDirty = false; } @@ -137,6 +155,10 @@ public class SearchManagerService extends ISearchManager.Stub if (globalSearch) { si = mSearchables.getDefaultSearchable(); } else { + if (launchActivity == null) { + Log.e(TAG, "getSearchableInfo(), activity == null"); + return null; + } si = mSearchables.getSearchableInfo(launchActivity); } @@ -150,6 +172,145 @@ public class SearchManagerService extends ISearchManager.Stub updateSearchablesIfDirty(); return mSearchables.getSearchablesInGlobalSearchList(); } + /** + * Launches the search UI on the main thread of the service. + * + * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean) + */ + public void startSearch(final String initialQuery, + final boolean selectInitialQuery, + final ComponentName launchActivity, + final Bundle appSearchData, + final boolean globalSearch, + final ISearchManagerCallback searchManagerCallback) { + if (DBG) debug("startSearch()"); + Runnable task = new Runnable() { + public void run() { + performStartSearch(initialQuery, + selectInitialQuery, + launchActivity, + appSearchData, + globalSearch, + searchManagerCallback); + } + }; + mHandler.post(task); + } + + /** + * Actually launches the search. This must be called on the service UI thread. + */ + /*package*/ void performStartSearch(String initialQuery, + boolean selectInitialQuery, + ComponentName launchActivity, + Bundle appSearchData, + boolean globalSearch, + ISearchManagerCallback searchManagerCallback) { + if (DBG) debug("performStartSearch()"); + mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, + globalSearch); + if (searchManagerCallback != null) { + mCallback = searchManagerCallback; + } + } + + /** + * Cancels the search dialog. Can be called from any thread. + */ + public void stopSearch() { + if (DBG) debug("stopSearch()"); + mHandler.post(new Runnable() { + public void run() { + performStopSearch(); + } + }); + } + + /** + * Cancels the search dialog. Must be called from the service UI thread. + */ + /*package*/ void performStopSearch() { + if (DBG) debug("performStopSearch()"); + mSearchDialog.cancel(); + } + + /** + * Determines if the Search UI is currently displayed. + * + * @see SearchManager#isVisible() + */ + public boolean isVisible() { + return postAndWait(mIsShowing, false, "isShowing()"); + } + + private final Callable<Boolean> mIsShowing = new Callable<Boolean>() { + public Boolean call() { + return mSearchDialog.isShowing(); + } + }; + + public Bundle onSaveInstanceState() { + return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()"); + } + + private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() { + public Bundle call() { + if (mSearchDialog.isShowing()) { + return mSearchDialog.onSaveInstanceState(); + } else { + return null; + } + } + }; + + public void onRestoreInstanceState(final Bundle searchDialogState) { + if (searchDialogState != null) { + mHandler.post(new Runnable() { + public void run() { + mSearchDialog.onRestoreInstanceState(searchDialogState); + } + }); + } + } + + public void onConfigurationChanged(final Configuration newConfig) { + mHandler.post(new Runnable() { + public void run() { + if (mSearchDialog.isShowing()) { + mSearchDialog.onConfigurationChanged(newConfig); + } + } + }); + } + + /** + * Called by {@link SearchDialog} when it goes away. + */ + public void onDismiss(DialogInterface dialog) { + if (DBG) debug("onDismiss()"); + if (mCallback != null) { + try { + mCallback.onDismiss(); + } catch (RemoteException ex) { + Log.e(TAG, "onDismiss() failed: " + ex); + } + } + } + + /** + * Called by {@link SearchDialog} when the user or activity cancels search. + * When this is called, {@link #onDismiss} is called too. + */ + public void onCancel(DialogInterface dialog) { + if (DBG) debug("onCancel()"); + if (mCallback != null) { + try { + mCallback.onCancel(); + } catch (RemoteException ex) { + Log.e(TAG, "onCancel() failed: " + ex); + } + } + } /** * Returns a list of the searchable activities that handle web searches. @@ -173,4 +334,34 @@ public class SearchManagerService extends ISearchManager.Stub public void setDefaultWebSearch(ComponentName component) { mSearchables.setDefaultWebSearch(component); } + + /** + * Runs an operation on the handler for the service, blocks until it returns, + * and returns the value returned by the operation. + * + * @param <V> Return value type. + * @param callable Operation to run. + * @param errorResult Value to return if the operations throws an exception. + * @param name Operation name to include in error log messages. + * @return The value returned by the operation. + */ + private <V> V postAndWait(Callable<V> callable, V errorResult, String name) { + FutureTask<V> task = new FutureTask<V>(callable); + mHandler.post(task); + try { + return task.get(); + } catch (InterruptedException ex) { + Log.e(TAG, "Error calling " + name + ": " + ex); + return errorResult; + } catch (ExecutionException ex) { + Log.e(TAG, "Error calling " + name + ": " + ex); + return errorResult; + } + } + + private static void debug(String msg) { + Thread thread = Thread.currentThread(); + Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); + } + } diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java index 4df7368..90dfa0b 100644 --- a/core/java/android/server/search/SearchableInfo.java +++ b/core/java/android/server/search/SearchableInfo.java @@ -320,7 +320,7 @@ public final class SearchableInfo implements Parcelable { // for now, implement some form of rules - minimal data if (mLabelId == 0) { - throw new IllegalArgumentException("No label."); + throw new IllegalArgumentException("Search label must be a resource reference."); } } @@ -441,13 +441,17 @@ public final class SearchableInfo implements Parcelable { xml.close(); if (DBG) { - Log.d(LOG_TAG, "Checked " + activityInfo.name - + ",label=" + searchable.getLabelId() - + ",icon=" + searchable.getIconId() - + ",suggestAuthority=" + searchable.getSuggestAuthority() - + ",target=" + searchable.getSearchActivity().getClassName() - + ",global=" + searchable.shouldIncludeInGlobalSearch() - + ",threshold=" + searchable.getSuggestThreshold()); + if (searchable != null) { + Log.d(LOG_TAG, "Checked " + activityInfo.name + + ",label=" + searchable.getLabelId() + + ",icon=" + searchable.getIconId() + + ",suggestAuthority=" + searchable.getSuggestAuthority() + + ",target=" + searchable.getSearchActivity().getClassName() + + ",global=" + searchable.shouldIncludeInGlobalSearch() + + ",threshold=" + searchable.getSuggestThreshold()); + } else { + Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data"); + } } return searchable; } |
