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 17 package android.app; 18 19 import android.content.ActivityNotFoundException; 20 import android.content.ComponentName; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.database.Cursor; 28 import android.graphics.Rect; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.os.UserHandle; 35 import android.text.TextUtils; 36 import android.util.Log; 37 import android.util.Slog; 38 import android.view.KeyEvent; 39 40 import java.util.List; 41 42 /** 43 * This class provides access to the system search services. 44 * 45 * <p>In practice, you won't interact with this class directly, as search 46 * services are provided through methods in {@link android.app.Activity Activity} 47 * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} 48 * {@link android.content.Intent Intent}. 49 * If you do require direct access to the SearchManager, do not instantiate 50 * this class directly. Instead, retrieve it through 51 * {@link android.content.Context#getSystemService 52 * context.getSystemService(Context.SEARCH_SERVICE)}. 53 * 54 * <div class="special reference"> 55 * <h3>Developer Guides</h3> 56 * <p>For more information about using the search dialog and adding search 57 * suggestions in your application, read the 58 * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p> 59 * </div> 60 */ 61 public class SearchManager 62 implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener 63 { 64 65 private static final boolean DBG = false; 66 private static final String TAG = "SearchManager"; 67 68 /** 69 * This is a shortcut definition for the default menu key to use for invoking search. 70 * 71 * See Menu.Item.setAlphabeticShortcut() for more information. 72 */ 73 public final static char MENU_KEY = 's'; 74 75 /** 76 * This is a shortcut definition for the default menu key to use for invoking search. 77 * 78 * See Menu.Item.setAlphabeticShortcut() for more information. 79 */ 80 public final static int MENU_KEYCODE = KeyEvent.KEYCODE_S; 81 82 /** 83 * Intent extra data key: Use this key with 84 * {@link android.content.Intent#getStringExtra 85 * content.Intent.getStringExtra()} 86 * to obtain the query string from Intent.ACTION_SEARCH. 87 */ 88 public final static String QUERY = "query"; 89 90 /** 91 * Intent extra data key: Use this key with 92 * {@link android.content.Intent#getStringExtra 93 * content.Intent.getStringExtra()} 94 * to obtain the query string typed in by the user. 95 * This may be different from the value of {@link #QUERY} 96 * if the intent is the result of selecting a suggestion. 97 * In that case, {@link #QUERY} will contain the value of 98 * {@link #SUGGEST_COLUMN_QUERY} for the suggestion, and 99 * {@link #USER_QUERY} will contain the string typed by the 100 * user. 101 */ 102 public final static String USER_QUERY = "user_query"; 103 104 /** 105 * Intent extra data key: Use this key with Intent.ACTION_SEARCH and 106 * {@link android.content.Intent#getBundleExtra 107 * content.Intent.getBundleExtra()} 108 * to obtain any additional app-specific data that was inserted by the 109 * activity that launched the search. 110 */ 111 public final static String APP_DATA = "app_data"; 112 113 /** 114 * Intent extra data key: Use {@link android.content.Intent#getBundleExtra 115 * content.Intent.getBundleExtra(SEARCH_MODE)} to get the search mode used 116 * to launch the intent. 117 * The only current value for this is {@link #MODE_GLOBAL_SEARCH_SUGGESTION}. 118 * 119 * @hide 120 */ 121 public final static String SEARCH_MODE = "search_mode"; 122 123 /** 124 * Intent extra data key: Use this key with Intent.ACTION_SEARCH and 125 * {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()} 126 * to obtain the keycode that the user used to trigger this query. It will be zero if the 127 * user simply pressed the "GO" button on the search UI. This is primarily used in conjunction 128 * with the keycode attribute in the actionkey element of your searchable.xml configuration 129 * file. 130 */ 131 public final static String ACTION_KEY = "action_key"; 132 133 /** 134 * Intent extra data key: This key will be used for the extra populated by the 135 * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column. 136 */ 137 public final static String EXTRA_DATA_KEY = "intent_extra_data_key"; 138 139 /** 140 * Boolean extra data key for {@link #INTENT_ACTION_GLOBAL_SEARCH} intents. If {@code true}, 141 * the initial query should be selected when the global search activity is started, so 142 * that the user can easily replace it with another query. 143 */ 144 public final static String EXTRA_SELECT_QUERY = "select_query"; 145 146 /** 147 * Boolean extra data key for {@link Intent#ACTION_WEB_SEARCH} intents. If {@code true}, 148 * this search should open a new browser window, rather than using an existing one. 149 */ 150 public final static String EXTRA_NEW_SEARCH = "new_search"; 151 152 /** 153 * Extra data key for {@link Intent#ACTION_WEB_SEARCH}. If set, the value must be a 154 * {@link PendingIntent}. The search activity handling the {@link Intent#ACTION_WEB_SEARCH} 155 * intent will fill in and launch the pending intent. The data URI will be filled in with an 156 * http or https URI, and {@link android.provider.Browser#EXTRA_HEADERS} may be filled in. 157 */ 158 public static final String EXTRA_WEB_SEARCH_PENDINGINTENT = "web_search_pendingintent"; 159 160 /** 161 * Boolean extra data key for a suggestion provider to return in {@link Cursor#getExtras} to 162 * indicate that the search is not complete yet. This can be used by the search UI 163 * to indicate that a search is in progress. The suggestion provider can return partial results 164 * this way and send a change notification on the cursor when more results are available. 165 */ 166 public final static String CURSOR_EXTRA_KEY_IN_PROGRESS = "in_progress"; 167 168 /** 169 * Intent extra data key: Use this key with Intent.ACTION_SEARCH and 170 * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()} 171 * to obtain the action message that was defined for a particular search action key and/or 172 * suggestion. It will be null if the search was launched by typing "enter", touched the the 173 * "GO" button, or other means not involving any action key. 174 */ 175 public final static String ACTION_MSG = "action_msg"; 176 177 /** 178 * Flag to specify that the entry can be used for query refinement, i.e., the query text 179 * in the search field can be replaced with the text in this entry, when a query refinement 180 * icon is clicked. The suggestion list should show such a clickable icon beside the entry. 181 * <p>Use this flag as a bit-field for {@link #SUGGEST_COLUMN_FLAGS}. 182 */ 183 public final static int FLAG_QUERY_REFINEMENT = 1 << 0; 184 185 /** 186 * Uri path for queried suggestions data. This is the path that the search manager 187 * will use when querying your content provider for suggestions data based on user input 188 * (e.g. looking for partial matches). 189 * Typically you'll use this with a URI matcher. 190 */ 191 public final static String SUGGEST_URI_PATH_QUERY = "search_suggest_query"; 192 193 /** 194 * MIME type for suggestions data. You'll use this in your suggestions content provider 195 * in the getType() function. 196 */ 197 public final static String SUGGEST_MIME_TYPE = 198 "vnd.android.cursor.dir/vnd.android.search.suggest"; 199 200 /** 201 * Uri path for shortcut validation. This is the path that the search manager will use when 202 * querying your content provider to refresh a shortcutted suggestion result and to check if it 203 * is still valid. When asked, a source may return an up to date result, or no result. No 204 * result indicates the shortcut refers to a no longer valid sugggestion. 205 * 206 * @see #SUGGEST_COLUMN_SHORTCUT_ID 207 */ 208 public final static String SUGGEST_URI_PATH_SHORTCUT = "search_suggest_shortcut"; 209 210 /** 211 * MIME type for shortcut validation. You'll use this in your suggestions content provider 212 * in the getType() function. 213 */ 214 public final static String SHORTCUT_MIME_TYPE = 215 "vnd.android.cursor.item/vnd.android.search.suggest"; 216 217 /** 218 * Column name for suggestions cursor. <i>Unused - can be null or column can be omitted.</i> 219 */ 220 public final static String SUGGEST_COLUMN_FORMAT = "suggest_format"; 221 /** 222 * Column name for suggestions cursor. <i>Required.</i> This is the primary line of text that 223 * will be presented to the user as the suggestion. 224 */ 225 public final static String SUGGEST_COLUMN_TEXT_1 = "suggest_text_1"; 226 /** 227 * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, 228 * then all suggestions will be provided in a two-line format. The second line of text is in 229 * a much smaller appearance. 230 */ 231 public final static String SUGGEST_COLUMN_TEXT_2 = "suggest_text_2"; 232 233 /** 234 * Column name for suggestions cursor. <i>Optional.</i> This is a URL that will be shown 235 * as the second line of text instead of {@link #SUGGEST_COLUMN_TEXT_2}. This is a separate 236 * column so that the search UI knows to display the text as a URL, e.g. by using a different 237 * color. If this column is absent, or has the value {@code null}, 238 * {@link #SUGGEST_COLUMN_TEXT_2} will be used instead. 239 */ 240 public final static String SUGGEST_COLUMN_TEXT_2_URL = "suggest_text_2_url"; 241 242 /** 243 * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, 244 * then all suggestions will be provided in a format that includes space for two small icons, 245 * one at the left and one at the right of each suggestion. The data in the column must 246 * be a resource ID of a drawable, or a URI in one of the following formats: 247 * 248 * <ul> 249 * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> 250 * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li> 251 * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> 252 * </ul> 253 * 254 * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 255 * for more information on these schemes. 256 */ 257 public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1"; 258 /** 259 * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, 260 * then all suggestions will be provided in a format that includes space for two small icons, 261 * one at the left and one at the right of each suggestion. The data in the column must 262 * be a resource ID of a drawable, or a URI in one of the following formats: 263 * 264 * <ul> 265 * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> 266 * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li> 267 * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> 268 * </ul> 269 * 270 * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 271 * for more information on these schemes. 272 */ 273 public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2"; 274 /** 275 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 276 * this element exists at the given row, this is the action that will be used when 277 * forming the suggestion's intent. If the element is not provided, the action will be taken 278 * from the android:searchSuggestIntentAction field in your XML metadata. <i>At least one of 279 * these must be present for the suggestion to generate an intent.</i> Note: If your action is 280 * the same for all suggestions, it is more efficient to specify it using XML metadata and omit 281 * it from the cursor. 282 */ 283 public final static String SUGGEST_COLUMN_INTENT_ACTION = "suggest_intent_action"; 284 /** 285 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 286 * this element exists at the given row, this is the data that will be used when 287 * forming the suggestion's intent. If the element is not provided, the data will be taken 288 * from the android:searchSuggestIntentData field in your XML metadata. If neither source 289 * is provided, the Intent's data field will be null. Note: If your data is 290 * the same for all suggestions, or can be described using a constant part and a specific ID, 291 * it is more efficient to specify it using XML metadata and omit it from the cursor. 292 */ 293 public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data"; 294 /** 295 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 296 * this element exists at the given row, this is the data that will be used when 297 * forming the suggestion's intent. If not provided, the Intent's extra data field will be null. 298 * This column allows suggestions to provide additional arbitrary data which will be included as 299 * an extra under the key {@link #EXTRA_DATA_KEY}. 300 */ 301 public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; 302 /** 303 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 304 * this element exists at the given row, then "/" and this value will be appended to the data 305 * field in the Intent. This should only be used if the data field has already been set to an 306 * appropriate base string. 307 */ 308 public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id"; 309 /** 310 * Column name for suggestions cursor. <i>Required if action is 311 * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i> If this 312 * column exists <i>and</i> this element exists at the given row, this is the data that will be 313 * used when forming the suggestion's query. 314 */ 315 public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query"; 316 317 /** 318 * Column name for suggestions cursor. <i>Optional.</i> This column is used to indicate whether 319 * a search suggestion should be stored as a shortcut, and whether it should be refreshed. If 320 * missing, the result will be stored as a shortcut and never validated. If set to 321 * {@link #SUGGEST_NEVER_MAKE_SHORTCUT}, the result will not be stored as a shortcut. 322 * Otherwise, the shortcut id will be used to check back for an up to date suggestion using 323 * {@link #SUGGEST_URI_PATH_SHORTCUT}. 324 */ 325 public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id"; 326 327 /** 328 * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify 329 * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion 330 * is being refreshed. 331 */ 332 public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING = 333 "suggest_spinner_while_refreshing"; 334 335 /** 336 * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify 337 * additional flags per item. Multiple flags can be specified. 338 * <p> 339 * Must be one of {@link #FLAG_QUERY_REFINEMENT} or 0 to indicate no flags. 340 * </p> 341 */ 342 public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags"; 343 344 /** 345 * Column name for suggestions cursor. <i>Optional.</i> This column may be 346 * used to specify the time in {@link System#currentTimeMillis 347 * System.currentTImeMillis()} (wall time in UTC) when an item was last 348 * accessed within the results-providing application. If set, this may be 349 * used to show more-recently-used items first. 350 */ 351 public final static String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint"; 352 353 /** 354 * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion 355 * should not be stored as a shortcut in global search. 356 */ 357 public final static String SUGGEST_NEVER_MAKE_SHORTCUT = "_-1"; 358 359 /** 360 * Query parameter added to suggestion queries to limit the number of suggestions returned. 361 * This limit is only advisory and suggestion providers may chose to ignore it. 362 */ 363 public final static String SUGGEST_PARAMETER_LIMIT = "limit"; 364 365 /** 366 * Intent action for starting the global search activity. 367 * The global search provider should handle this intent. 368 * 369 * Supported extra data keys: {@link #QUERY}, 370 * {@link #EXTRA_SELECT_QUERY}, 371 * {@link #APP_DATA}. 372 */ 373 public final static String INTENT_ACTION_GLOBAL_SEARCH 374 = "android.search.action.GLOBAL_SEARCH"; 375 376 /** 377 * Intent action for starting the global search settings activity. 378 * The global search provider should handle this intent. 379 */ 380 public final static String INTENT_ACTION_SEARCH_SETTINGS 381 = "android.search.action.SEARCH_SETTINGS"; 382 383 /** 384 * Intent action for starting a web search provider's settings activity. 385 * Web search providers should handle this intent if they have provider-specific 386 * settings to implement. 387 */ 388 public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS 389 = "android.search.action.WEB_SEARCH_SETTINGS"; 390 391 /** 392 * Intent action broadcasted to inform that the searchables list or default have changed. 393 * Components should handle this intent if they cache any searchable data and wish to stay 394 * up to date on changes. 395 */ 396 public final static String INTENT_ACTION_SEARCHABLES_CHANGED 397 = "android.search.action.SEARCHABLES_CHANGED"; 398 399 /** 400 * Intent action to be broadcast to inform that the global search provider 401 * has changed. 402 */ 403 public final static String INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED 404 = "android.search.action.GLOBAL_SEARCH_ACTIVITY_CHANGED"; 405 406 /** 407 * Intent action broadcasted to inform that the search settings have changed in some way. 408 * Either searchables have been enabled or disabled, or a different web search provider 409 * has been chosen. 410 */ 411 public final static String INTENT_ACTION_SEARCH_SETTINGS_CHANGED 412 = "android.search.action.SETTINGS_CHANGED"; 413 414 /** 415 * This means that context is voice, and therefore the SearchDialog should 416 * continue showing the microphone until the user indicates that he/she does 417 * not want to re-speak (e.g. by typing). 418 * 419 * @hide 420 */ 421 public final static String CONTEXT_IS_VOICE = "android.search.CONTEXT_IS_VOICE"; 422 423 /** 424 * This means that the voice icon should not be shown at all, because the 425 * current search engine does not support voice search. 426 * @hide 427 */ 428 public final static String DISABLE_VOICE_SEARCH 429 = "android.search.DISABLE_VOICE_SEARCH"; 430 431 /** 432 * Reference to the shared system search service. 433 */ 434 private static ISearchManager mService; 435 436 private final Context mContext; 437 438 /** 439 * The package associated with this seach manager. 440 */ 441 private String mAssociatedPackage; 442 443 // package private since they are used by the inner class SearchManagerCallback 444 /* package */ final Handler mHandler; 445 /* package */ OnDismissListener mDismissListener = null; 446 /* package */ OnCancelListener mCancelListener = null; 447 448 private SearchDialog mSearchDialog; 449 SearchManager(Context context, Handler handler)450 /*package*/ SearchManager(Context context, Handler handler) { 451 mContext = context; 452 mHandler = handler; 453 mService = ISearchManager.Stub.asInterface( 454 ServiceManager.getService(Context.SEARCH_SERVICE)); 455 } 456 457 /** 458 * Launch search UI. 459 * 460 * <p>The search manager will open a search widget in an overlapping 461 * window, and the underlying activity may be obscured. The search 462 * entry state will remain in effect until one of the following events: 463 * <ul> 464 * <li>The user completes the search. In most cases this will launch 465 * a search intent.</li> 466 * <li>The user uses the back, home, or other keys to exit the search.</li> 467 * <li>The application calls the {@link #stopSearch} 468 * method, which will hide the search window and return focus to the 469 * activity from which it was launched.</li> 470 * 471 * <p>Most applications will <i>not</i> use this interface to invoke search. 472 * The primary method for invoking search is to call 473 * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or 474 * {@link android.app.Activity#startSearch Activity.startSearch()}. 475 * 476 * @param initialQuery A search string can be pre-entered here, but this 477 * is typically null or empty. 478 * @param selectInitialQuery If true, the intial query will be preselected, which means that 479 * any further typing will replace it. This is useful for cases where an entire pre-formed 480 * query is being inserted. If false, the selection point will be placed at the end of the 481 * inserted query. This is useful when the inserted query is text that the user entered, 482 * and the user would expect to be able to keep typing. <i>This parameter is only meaningful 483 * if initialQuery is a non-empty string.</i> 484 * @param launchActivity The ComponentName of the activity that has launched this search. 485 * @param appSearchData An application can insert application-specific 486 * context here, in order to improve quality or specificity of its own 487 * searches. This data will be returned with SEARCH intent(s). Null if 488 * no extra data is required. 489 * @param globalSearch If false, this will only launch the search that has been specifically 490 * defined by the application (which is usually defined as a local search). If no default 491 * search is defined in the current application or activity, global search will be launched. 492 * If true, this will always launch a platform-global (e.g. web-based) search instead. 493 * 494 * @see android.app.Activity#onSearchRequested 495 * @see #stopSearch 496 */ startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch)497 public void startSearch(String initialQuery, 498 boolean selectInitialQuery, 499 ComponentName launchActivity, 500 Bundle appSearchData, 501 boolean globalSearch) { 502 startSearch(initialQuery, selectInitialQuery, launchActivity, 503 appSearchData, globalSearch, null); 504 } 505 506 /** 507 * As {@link #startSearch(String, boolean, ComponentName, Bundle, boolean)} but including 508 * source bounds for the global search intent. 509 * 510 * @hide 511 */ startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch, Rect sourceBounds)512 public void startSearch(String initialQuery, 513 boolean selectInitialQuery, 514 ComponentName launchActivity, 515 Bundle appSearchData, 516 boolean globalSearch, 517 Rect sourceBounds) { 518 if (globalSearch) { 519 startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds); 520 return; 521 } 522 523 ensureSearchDialog(); 524 525 mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData); 526 } 527 ensureSearchDialog()528 private void ensureSearchDialog() { 529 if (mSearchDialog == null) { 530 mSearchDialog = new SearchDialog(mContext, this); 531 mSearchDialog.setOnCancelListener(this); 532 mSearchDialog.setOnDismissListener(this); 533 } 534 } 535 536 /** 537 * Starts the global search activity. 538 */ startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds)539 /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery, 540 Bundle appSearchData, Rect sourceBounds) { 541 ComponentName globalSearchActivity = getGlobalSearchActivity(); 542 if (globalSearchActivity == null) { 543 Log.w(TAG, "No global search activity found."); 544 return; 545 } 546 Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH); 547 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 548 intent.setComponent(globalSearchActivity); 549 // Make sure that we have a Bundle to put source in 550 if (appSearchData == null) { 551 appSearchData = new Bundle(); 552 } else { 553 appSearchData = new Bundle(appSearchData); 554 } 555 // Set source to package name of app that starts global search, if not set already. 556 if (!appSearchData.containsKey("source")) { 557 appSearchData.putString("source", mContext.getPackageName()); 558 } 559 intent.putExtra(APP_DATA, appSearchData); 560 if (!TextUtils.isEmpty(initialQuery)) { 561 intent.putExtra(QUERY, initialQuery); 562 } 563 if (selectInitialQuery) { 564 intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery); 565 } 566 intent.setSourceBounds(sourceBounds); 567 try { 568 if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0)); 569 mContext.startActivity(intent); 570 } catch (ActivityNotFoundException ex) { 571 Log.e(TAG, "Global search activity not found: " + globalSearchActivity); 572 } 573 } 574 575 /** 576 * Returns a list of installed apps that handle the global search 577 * intent. 578 * 579 * @hide 580 */ getGlobalSearchActivities()581 public List<ResolveInfo> getGlobalSearchActivities() { 582 try { 583 return mService.getGlobalSearchActivities(); 584 } catch (RemoteException ex) { 585 Log.e(TAG, "getGlobalSearchActivities() failed: " + ex); 586 return null; 587 } 588 } 589 590 /** 591 * Gets the name of the global search activity. 592 */ getGlobalSearchActivity()593 public ComponentName getGlobalSearchActivity() { 594 try { 595 return mService.getGlobalSearchActivity(); 596 } catch (RemoteException ex) { 597 Log.e(TAG, "getGlobalSearchActivity() failed: " + ex); 598 return null; 599 } 600 } 601 602 /** 603 * Gets the name of the web search activity. 604 * 605 * @return The name of the default activity for web searches. This activity 606 * can be used to get web search suggestions. Returns {@code null} if 607 * there is no default web search activity. 608 * 609 * @hide 610 */ getWebSearchActivity()611 public ComponentName getWebSearchActivity() { 612 try { 613 return mService.getWebSearchActivity(); 614 } catch (RemoteException ex) { 615 Log.e(TAG, "getWebSearchActivity() failed: " + ex); 616 return null; 617 } 618 } 619 620 /** 621 * Similar to {@link #startSearch} but actually fires off the search query after invoking 622 * the search dialog. Made available for testing purposes. 623 * 624 * @param query The query to trigger. If empty, request will be ignored. 625 * @param launchActivity The ComponentName of the activity that has launched this search. 626 * @param appSearchData An application can insert application-specific 627 * context here, in order to improve quality or specificity of its own 628 * searches. This data will be returned with SEARCH intent(s). Null if 629 * no extra data is required. 630 * 631 * @see #startSearch 632 */ triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData)633 public void triggerSearch(String query, 634 ComponentName launchActivity, 635 Bundle appSearchData) { 636 if (!mAssociatedPackage.equals(launchActivity.getPackageName())) { 637 throw new IllegalArgumentException("invoking app search on a different package " + 638 "not associated with this search manager"); 639 } 640 if (query == null || TextUtils.getTrimmedLength(query) == 0) { 641 Log.w(TAG, "triggerSearch called with empty query, ignoring."); 642 return; 643 } 644 startSearch(query, false, launchActivity, appSearchData, false); 645 mSearchDialog.launchQuerySearch(); 646 } 647 648 /** 649 * Terminate search UI. 650 * 651 * <p>Typically the user will terminate the search UI by launching a 652 * search or by canceling. This function allows the underlying application 653 * or activity to cancel the search prematurely (for any reason). 654 * 655 * <p>This function can be safely called at any time (even if no search is active.) 656 * 657 * @see #startSearch 658 */ stopSearch()659 public void stopSearch() { 660 if (mSearchDialog != null) { 661 mSearchDialog.cancel(); 662 } 663 } 664 665 /** 666 * Determine if the Search UI is currently displayed. 667 * 668 * This is provided primarily for application test purposes. 669 * 670 * @return Returns true if the search UI is currently displayed. 671 * 672 * @hide 673 */ isVisible()674 public boolean isVisible() { 675 return mSearchDialog == null? false : mSearchDialog.isShowing(); 676 } 677 678 /** 679 * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor 680 * search UI state. 681 */ 682 public interface OnDismissListener { 683 /** 684 * This method will be called when the search UI is dismissed. To make use of it, you must 685 * implement this method in your activity, and call 686 * {@link SearchManager#setOnDismissListener} to register it. 687 */ onDismiss()688 public void onDismiss(); 689 } 690 691 /** 692 * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor 693 * search UI state. 694 */ 695 public interface OnCancelListener { 696 /** 697 * This method will be called when the search UI is canceled. To make use if it, you must 698 * implement this method in your activity, and call 699 * {@link SearchManager#setOnCancelListener} to register it. 700 */ onCancel()701 public void onCancel(); 702 } 703 704 /** 705 * Set or clear the callback that will be invoked whenever the search UI is dismissed. 706 * 707 * @param listener The {@link OnDismissListener} to use, or null. 708 */ setOnDismissListener(final OnDismissListener listener)709 public void setOnDismissListener(final OnDismissListener listener) { 710 mDismissListener = listener; 711 } 712 713 /** 714 * Set or clear the callback that will be invoked whenever the search UI is canceled. 715 * 716 * @param listener The {@link OnCancelListener} to use, or null. 717 */ setOnCancelListener(OnCancelListener listener)718 public void setOnCancelListener(OnCancelListener listener) { 719 mCancelListener = listener; 720 } 721 722 /** 723 * @deprecated This method is an obsolete internal implementation detail. Do not use. 724 */ 725 @Deprecated onCancel(DialogInterface dialog)726 public void onCancel(DialogInterface dialog) { 727 if (mCancelListener != null) { 728 mCancelListener.onCancel(); 729 } 730 } 731 732 /** 733 * @deprecated This method is an obsolete internal implementation detail. Do not use. 734 */ 735 @Deprecated onDismiss(DialogInterface dialog)736 public void onDismiss(DialogInterface dialog) { 737 if (mDismissListener != null) { 738 mDismissListener.onDismiss(); 739 } 740 } 741 742 /** 743 * Gets information about a searchable activity. 744 * 745 * @param componentName The activity to get searchable information for. 746 * @return Searchable information, or <code>null</code> if the activity does not 747 * exist, or is not searchable. 748 */ getSearchableInfo(ComponentName componentName)749 public SearchableInfo getSearchableInfo(ComponentName componentName) { 750 try { 751 return mService.getSearchableInfo(componentName); 752 } catch (RemoteException ex) { 753 Log.e(TAG, "getSearchableInfo() failed: " + ex); 754 return null; 755 } 756 } 757 758 /** 759 * Gets a cursor with search suggestions. 760 * 761 * @param searchable Information about how to get the suggestions. 762 * @param query The search text entered (so far). 763 * @return a cursor with suggestions, or <code>null</null> the suggestion query failed. 764 * 765 * @hide because SearchableInfo is not part of the API. 766 */ getSuggestions(SearchableInfo searchable, String query)767 public Cursor getSuggestions(SearchableInfo searchable, String query) { 768 return getSuggestions(searchable, query, -1); 769 } 770 771 /** 772 * Gets a cursor with search suggestions. 773 * 774 * @param searchable Information about how to get the suggestions. 775 * @param query The search text entered (so far). 776 * @param limit The query limit to pass to the suggestion provider. This is advisory, 777 * the returned cursor may contain more rows. Pass {@code -1} for no limit. 778 * @return a cursor with suggestions, or <code>null</null> the suggestion query failed. 779 * 780 * @hide because SearchableInfo is not part of the API. 781 */ getSuggestions(SearchableInfo searchable, String query, int limit)782 public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) { 783 if (searchable == null) { 784 return null; 785 } 786 787 String authority = searchable.getSuggestAuthority(); 788 if (authority == null) { 789 return null; 790 } 791 792 Uri.Builder uriBuilder = new Uri.Builder() 793 .scheme(ContentResolver.SCHEME_CONTENT) 794 .authority(authority) 795 .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() 796 .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel() 797 798 // if content path provided, insert it now 799 final String contentPath = searchable.getSuggestPath(); 800 if (contentPath != null) { 801 uriBuilder.appendEncodedPath(contentPath); 802 } 803 804 // append standard suggestion query path 805 uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY); 806 807 // get the query selection, may be null 808 String selection = searchable.getSuggestSelection(); 809 // inject query, either as selection args or inline 810 String[] selArgs = null; 811 if (selection != null) { // use selection if provided 812 selArgs = new String[] { query }; 813 } else { // no selection, use REST pattern 814 uriBuilder.appendPath(query); 815 } 816 817 if (limit > 0) { 818 uriBuilder.appendQueryParameter(SUGGEST_PARAMETER_LIMIT, String.valueOf(limit)); 819 } 820 821 Uri uri = uriBuilder.build(); 822 823 // finally, make the query 824 return mContext.getContentResolver().query(uri, null, selection, selArgs, null); 825 } 826 827 /** 828 * Returns a list of the searchable activities that can be included in global search. 829 * 830 * @return a list containing searchable information for all searchable activities 831 * that have the <code>android:includeInGlobalSearch</code> attribute set 832 * in their searchable meta-data. 833 */ getSearchablesInGlobalSearch()834 public List<SearchableInfo> getSearchablesInGlobalSearch() { 835 try { 836 return mService.getSearchablesInGlobalSearch(); 837 } catch (RemoteException e) { 838 Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e); 839 return null; 840 } 841 } 842 843 /** 844 * Gets an intent for launching installed assistant activity, or null if not available. 845 * @return The assist intent. 846 * 847 * @hide 848 */ getAssistIntent(Context context, boolean inclContext)849 public Intent getAssistIntent(Context context, boolean inclContext) { 850 return getAssistIntent(context, inclContext, UserHandle.myUserId()); 851 } 852 853 /** 854 * Gets an intent for launching installed assistant activity, or null if not available. 855 * @return The assist intent. 856 * 857 * @hide 858 */ getAssistIntent(Context context, boolean inclContext, int userHandle)859 public Intent getAssistIntent(Context context, boolean inclContext, int userHandle) { 860 try { 861 if (mService == null) { 862 return null; 863 } 864 ComponentName comp = mService.getAssistIntent(userHandle); 865 if (comp == null) { 866 return null; 867 } 868 Intent intent = new Intent(Intent.ACTION_ASSIST); 869 intent.setComponent(comp); 870 if (inclContext) { 871 IActivityManager am = ActivityManagerNative.getDefault(); 872 Bundle extras = am.getAssistContextExtras(0); 873 if (extras != null) { 874 intent.replaceExtras(extras); 875 } 876 } 877 return intent; 878 } catch (RemoteException re) { 879 Log.e(TAG, "getAssistIntent() failed: " + re); 880 return null; 881 } 882 } 883 } 884