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.app; 18 19import android.content.ActivityNotFoundException; 20import android.content.ComponentName; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.DialogInterface; 24import android.content.Intent; 25import android.content.pm.PackageManager; 26import android.content.pm.ResolveInfo; 27import android.database.Cursor; 28import android.graphics.Rect; 29import android.net.Uri; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.RemoteException; 33import android.os.ServiceManager; 34import android.os.UserHandle; 35import android.text.TextUtils; 36import android.util.Log; 37import android.util.Slog; 38import android.view.KeyEvent; 39 40import 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 */ 61public 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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