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