SearchManagerService.java revision 8d17f3f24bbda9a9cd7ea08c5925508dc2c011be
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.server.search; 18 19import android.app.ISearchManager; 20import android.app.ISearchManagerCallback; 21import android.app.SearchDialog; 22import android.app.SearchManager; 23import android.content.BroadcastReceiver; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.res.Configuration; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.RemoteException; 33import android.util.Log; 34 35import java.util.List; 36import java.util.concurrent.Callable; 37import java.util.concurrent.ExecutionException; 38import java.util.concurrent.FutureTask; 39 40/** 41 * This is a simplified version of the Search Manager service. It no longer handles 42 * presentation (UI). Its function is to maintain the map & list of "searchable" 43 * items, which provides a mapping from individual activities (where a user might have 44 * invoked search) to specific searchable activities (where the search will be dispatched). 45 */ 46public class SearchManagerService extends ISearchManager.Stub 47 implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener 48{ 49 // general debugging support 50 private static final String TAG = "SearchManagerService"; 51 private static final boolean DBG = false; 52 53 // class maintenance and general shared data 54 private final Context mContext; 55 private final Handler mHandler; 56 private boolean mSearchablesDirty; 57 private final Searchables mSearchables; 58 59 final SearchDialog mSearchDialog; 60 ISearchManagerCallback mCallback = null; 61 62 /** 63 * Initializes the Search Manager service in the provided system context. 64 * Only one instance of this object should be created! 65 * 66 * @param context to use for accessing DB, window manager, etc. 67 */ 68 public SearchManagerService(Context context) { 69 mContext = context; 70 mHandler = new Handler(); 71 mSearchablesDirty = true; 72 mSearchables = new Searchables(context); 73 mSearchDialog = new SearchDialog(context); 74 mSearchDialog.setOnCancelListener(this); 75 mSearchDialog.setOnDismissListener(this); 76 77 // Setup the infrastructure for updating and maintaining the list 78 // of searchable activities. 79 IntentFilter filter = new IntentFilter(); 80 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 81 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 82 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 83 filter.addDataScheme("package"); 84 mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); 85 86 // After startup settles down, preload the searchables list, 87 // which will reduce the delay when the search UI is invoked. 88 mHandler.post(mRunUpdateSearchable); 89 } 90 91 /** 92 * Listens for intent broadcasts. 93 * 94 * The primary purpose here is to refresh the "searchables" list 95 * if packages are added/removed. 96 */ 97 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 98 @Override 99 public void onReceive(Context context, Intent intent) { 100 String action = intent.getAction(); 101 102 // First, test for intents that matter at any time 103 if (action.equals(Intent.ACTION_PACKAGE_ADDED) || 104 action.equals(Intent.ACTION_PACKAGE_REMOVED) || 105 action.equals(Intent.ACTION_PACKAGE_CHANGED)) { 106 mSearchablesDirty = true; 107 mHandler.post(mRunUpdateSearchable); 108 return; 109 } 110 } 111 }; 112 113 /** 114 * This runnable (for the main handler / UI thread) will update the searchables list. 115 */ 116 private Runnable mRunUpdateSearchable = new Runnable() { 117 public void run() { 118 updateSearchablesIfDirty(); 119 } 120 }; 121 122 /** 123 * Updates the list of searchables, either at startup or in response to 124 * a package add/remove broadcast message. 125 */ 126 private void updateSearchables() { 127 if (DBG) debug("updateSearchables()"); 128 mSearchables.buildSearchableList(); 129 mSearchablesDirty = false; 130 } 131 132 /** 133 * Updates the list of searchables if needed. 134 */ 135 private void updateSearchablesIfDirty() { 136 if (mSearchablesDirty) { 137 updateSearchables(); 138 } 139 } 140 141 /** 142 * Returns the SearchableInfo for a given activity 143 * 144 * @param launchActivity The activity from which we're launching this search. 145 * @param globalSearch If false, this will only launch the search that has been specifically 146 * defined by the application (which is usually defined as a local search). If no default 147 * search is defined in the current application or activity, no search will be launched. 148 * If true, this will always launch a platform-global (e.g. web-based) search instead. 149 * @return Returns a SearchableInfo record describing the parameters of the search, 150 * or null if no searchable metadata was available. 151 */ 152 public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) { 153 updateSearchablesIfDirty(); 154 SearchableInfo si = null; 155 if (globalSearch) { 156 si = mSearchables.getDefaultSearchable(); 157 } else { 158 if (launchActivity == null) { 159 Log.e(TAG, "getSearchableInfo(), activity == null"); 160 return null; 161 } 162 si = mSearchables.getSearchableInfo(launchActivity); 163 } 164 165 return si; 166 } 167 168 /** 169 * Returns a list of the searchable activities that can be included in global search. 170 */ 171 public List<SearchableInfo> getSearchablesInGlobalSearch() { 172 updateSearchablesIfDirty(); 173 return mSearchables.getSearchablesInGlobalSearchList(); 174 } 175 /** 176 * Launches the search UI on the main thread of the service. 177 * 178 * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean) 179 */ 180 public void startSearch(final String initialQuery, 181 final boolean selectInitialQuery, 182 final ComponentName launchActivity, 183 final Bundle appSearchData, 184 final boolean globalSearch, 185 final ISearchManagerCallback searchManagerCallback) { 186 if (DBG) debug("startSearch()"); 187 Runnable task = new Runnable() { 188 public void run() { 189 performStartSearch(initialQuery, 190 selectInitialQuery, 191 launchActivity, 192 appSearchData, 193 globalSearch, 194 searchManagerCallback); 195 } 196 }; 197 mHandler.post(task); 198 } 199 200 /** 201 * Actually launches the search. This must be called on the service UI thread. 202 */ 203 /*package*/ void performStartSearch(String initialQuery, 204 boolean selectInitialQuery, 205 ComponentName launchActivity, 206 Bundle appSearchData, 207 boolean globalSearch, 208 ISearchManagerCallback searchManagerCallback) { 209 if (DBG) debug("performStartSearch()"); 210 mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, 211 globalSearch); 212 if (searchManagerCallback != null) { 213 mCallback = searchManagerCallback; 214 } 215 } 216 217 /** 218 * Cancels the search dialog. Can be called from any thread. 219 */ 220 public void stopSearch() { 221 if (DBG) debug("stopSearch()"); 222 mHandler.post(new Runnable() { 223 public void run() { 224 performStopSearch(); 225 } 226 }); 227 } 228 229 /** 230 * Cancels the search dialog. Must be called from the service UI thread. 231 */ 232 /*package*/ void performStopSearch() { 233 if (DBG) debug("performStopSearch()"); 234 mSearchDialog.cancel(); 235 } 236 237 /** 238 * Determines if the Search UI is currently displayed. 239 * 240 * @see SearchManager#isVisible() 241 */ 242 public boolean isVisible() { 243 return postAndWait(mIsShowing, false, "isShowing()"); 244 } 245 246 private final Callable<Boolean> mIsShowing = new Callable<Boolean>() { 247 public Boolean call() { 248 return mSearchDialog.isShowing(); 249 } 250 }; 251 252 public Bundle onSaveInstanceState() { 253 return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()"); 254 } 255 256 private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() { 257 public Bundle call() { 258 if (mSearchDialog.isShowing()) { 259 return mSearchDialog.onSaveInstanceState(); 260 } else { 261 return null; 262 } 263 } 264 }; 265 266 public void onRestoreInstanceState(final Bundle searchDialogState) { 267 if (searchDialogState != null) { 268 mHandler.post(new Runnable() { 269 public void run() { 270 mSearchDialog.onRestoreInstanceState(searchDialogState); 271 } 272 }); 273 } 274 } 275 276 public void onConfigurationChanged(final Configuration newConfig) { 277 mHandler.post(new Runnable() { 278 public void run() { 279 if (mSearchDialog.isShowing()) { 280 mSearchDialog.onConfigurationChanged(newConfig); 281 } 282 } 283 }); 284 } 285 286 /** 287 * Called by {@link SearchDialog} when it goes away. 288 */ 289 public void onDismiss(DialogInterface dialog) { 290 if (DBG) debug("onDismiss()"); 291 if (mCallback != null) { 292 try { 293 mCallback.onDismiss(); 294 } catch (RemoteException ex) { 295 Log.e(TAG, "onDismiss() failed: " + ex); 296 } 297 } 298 } 299 300 /** 301 * Called by {@link SearchDialog} when the user or activity cancels search. 302 * When this is called, {@link #onDismiss} is called too. 303 */ 304 public void onCancel(DialogInterface dialog) { 305 if (DBG) debug("onCancel()"); 306 if (mCallback != null) { 307 try { 308 mCallback.onCancel(); 309 } catch (RemoteException ex) { 310 Log.e(TAG, "onCancel() failed: " + ex); 311 } 312 } 313 } 314 315 /** 316 * Returns a list of the searchable activities that handle web searches. 317 */ 318 public List<SearchableInfo> getSearchablesForWebSearch() { 319 updateSearchablesIfDirty(); 320 return mSearchables.getSearchablesForWebSearchList(); 321 } 322 323 /** 324 * Returns the default searchable activity for web searches. 325 */ 326 public SearchableInfo getDefaultSearchableForWebSearch() { 327 updateSearchablesIfDirty(); 328 return mSearchables.getDefaultSearchableForWebSearch(); 329 } 330 331 /** 332 * Sets the default searchable activity for web searches. 333 */ 334 public void setDefaultWebSearch(ComponentName component) { 335 mSearchables.setDefaultWebSearch(component); 336 } 337 338 /** 339 * Runs an operation on the handler for the service, blocks until it returns, 340 * and returns the value returned by the operation. 341 * 342 * @param <V> Return value type. 343 * @param callable Operation to run. 344 * @param errorResult Value to return if the operations throws an exception. 345 * @param name Operation name to include in error log messages. 346 * @return The value returned by the operation. 347 */ 348 private <V> V postAndWait(Callable<V> callable, V errorResult, String name) { 349 FutureTask<V> task = new FutureTask<V>(callable); 350 mHandler.post(task); 351 try { 352 return task.get(); 353 } catch (InterruptedException ex) { 354 Log.e(TAG, "Error calling " + name + ": " + ex); 355 return errorResult; 356 } catch (ExecutionException ex) { 357 Log.e(TAG, "Error calling " + name + ": " + ex); 358 return errorResult; 359 } 360 } 361 362 private static void debug(String msg) { 363 Thread thread = Thread.currentThread(); 364 Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); 365 } 366 367} 368