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