diff options
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/Activity.java | 37 | ||||
| -rw-r--r-- | core/java/android/app/ISearchManager.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/app/SearchDialog.java | 81 | ||||
| -rw-r--r-- | core/java/android/app/SearchManager.java | 54 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchDialogWrapper.java | 320 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchManagerService.java | 353 | ||||
| -rw-r--r-- | core/java/android/server/search/Searchables.java | 25 |
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(); } } |
