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.compat.annotation.UnsupportedAppUsage; 21 import android.content.ActivityNotFoundException; 22 import android.content.ComponentName; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.pm.ResolveInfo; 28 import android.content.res.Configuration; 29 import android.database.Cursor; 30 import android.graphics.Rect; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.RemoteException; 35 import android.os.ServiceManager; 36 import android.os.ServiceManager.ServiceNotFoundException; 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 * <p> 52 * {@link Configuration#UI_MODE_TYPE_WATCH} does not support this system service. 53 * 54 * <div class="special reference"> 55 * <h3>Developer Guides</h3> 56 * <p>For more information about using the search dialog and adding search 57 * suggestions in your application, read the 58 * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p> 59 * </div> 60 */ 61 @SystemService(Context.SEARCH_SERVICE) 62 public class SearchManager 63 implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener { 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", touching 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 /** 260 * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, 261 * then all suggestions will be provided in a format that includes space for two small icons, 262 * one at the left and one at the right of each suggestion. The data in the column must 263 * be a resource ID of a drawable, or a URI in one of the following formats: 264 * 265 * <ul> 266 * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> 267 * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li> 268 * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> 269 * </ul> 270 * 271 * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 272 * for more information on these schemes. 273 */ 274 public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2"; 275 276 /** 277 * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, 278 * then the image will be displayed when forming the suggestion. The suggested dimension for 279 * the image is 270x400 px for portrait mode and 400x225 px for landscape mode. The data in the 280 * column must be a resource ID of a drawable, or a URI in one of the following formats: 281 * 282 * <ul> 283 * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> 284 * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li> 285 * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> 286 * </ul> 287 * 288 * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 289 * for more information on these schemes. 290 */ 291 public final static String SUGGEST_COLUMN_RESULT_CARD_IMAGE = "suggest_result_card_image"; 292 293 /** 294 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 295 * this element exists at the given row, this is the action that will be used when 296 * forming the suggestion's intent. If the element is not provided, the action will be taken 297 * from the android:searchSuggestIntentAction field in your XML metadata. <i>At least one of 298 * these must be present for the suggestion to generate an intent.</i> Note: If your action is 299 * the same for all suggestions, it is more efficient to specify it using XML metadata and omit 300 * it from the cursor. 301 */ 302 public final static String SUGGEST_COLUMN_INTENT_ACTION = "suggest_intent_action"; 303 304 /** 305 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 306 * this element exists at the given row, this is the data that will be used when 307 * forming the suggestion's intent. If the element is not provided, the data will be taken 308 * from the android:searchSuggestIntentData field in your XML metadata. If neither source 309 * is provided, the Intent's data field will be null. Note: If your data is 310 * the same for all suggestions, or can be described using a constant part and a specific ID, 311 * it is more efficient to specify it using XML metadata and omit it from the cursor. 312 */ 313 public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data"; 314 315 /** 316 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 317 * this element exists at the given row, this is the data that will be used when 318 * forming the suggestion's intent. If not provided, the Intent's extra data field will be null. 319 * This column allows suggestions to provide additional arbitrary data which will be included as 320 * an extra under the key {@link #EXTRA_DATA_KEY}. 321 */ 322 public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; 323 324 /** 325 * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> 326 * this element exists at the given row, then "/" and this value will be appended to the data 327 * field in the Intent. This should only be used if the data field has already been set to an 328 * appropriate base string. 329 */ 330 public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id"; 331 332 /** 333 * Column name for suggestions cursor. <i>Required if action is 334 * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i> If this 335 * column exists <i>and</i> this element exists at the given row, this is the data that will be 336 * used when forming the suggestion's query. 337 */ 338 public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query"; 339 340 /** 341 * Column name for suggestions cursor. <i>Optional.</i> This column is used to indicate whether 342 * a search suggestion should be stored as a shortcut, and whether it should be refreshed. If 343 * missing, the result will be stored as a shortcut and never validated. If set to 344 * {@link #SUGGEST_NEVER_MAKE_SHORTCUT}, the result will not be stored as a shortcut. 345 * Otherwise, the shortcut id will be used to check back for an up to date suggestion using 346 * {@link #SUGGEST_URI_PATH_SHORTCUT}. 347 */ 348 public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id"; 349 350 /** 351 * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify 352 * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion 353 * is being refreshed. 354 */ 355 public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING = 356 "suggest_spinner_while_refreshing"; 357 358 /** 359 * Column name for suggestions cursor. <i>Optional.</i> If your content is media type, you 360 * should provide this column so search app could understand more about your content. The data 361 * in the column must specify the MIME type of the content. 362 */ 363 public final static String SUGGEST_COLUMN_CONTENT_TYPE = "suggest_content_type"; 364 365 /** 366 * Column name for suggestions cursor. <i>Optional.</i> If your content is media type, you 367 * should provide this column to specify whether your content is live media such as live video 368 * or live audio. The value in the column is of integer type with value of either 0 indicating 369 * non-live content or 1 indicating live content. 370 */ 371 public final static String SUGGEST_COLUMN_IS_LIVE = "suggest_is_live"; 372 373 /** 374 * Column name for suggestions cursor. <i>Optional.</i> If your content is video, you should 375 * provide this column to specify the number of vertical lines. The data in the column is of 376 * integer type. 377 */ 378 public final static String SUGGEST_COLUMN_VIDEO_WIDTH = "suggest_video_width"; 379 380 /** 381 * Column name for suggestions cursor. <i>Optional.</i> If your content is video, you should 382 * provide this column to specify the number of horizontal lines. The data in the column is of 383 * integer type. 384 */ 385 public final static String SUGGEST_COLUMN_VIDEO_HEIGHT = "suggest_video_height"; 386 387 /** 388 * Column name for suggestions cursor. <i>Optional.</i> If your content contains audio, you 389 * should provide this column to specify the audio channel configuration. The data in the 390 * column is string with format like "channels.subchannels" such as "1.0" or "5.1". 391 */ 392 public final static String SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG = "suggest_audio_channel_config"; 393 394 /** 395 * Column name for suggestions cursor. <i>Optional.</i> If your content is purchasable, you 396 * should provide this column to specify the displayable string representation of the purchase 397 * price of your content including the currency and the amount. If it's free, you should 398 * provide localized string to specify that it's free. This column can be omitted if the content 399 * is not applicable to purchase. 400 */ 401 public final static String SUGGEST_COLUMN_PURCHASE_PRICE = "suggest_purchase_price"; 402 403 /** 404 * Column name for suggestions cursor. <i>Optional.</i> If your content is rentable, you 405 * should provide this column to specify the displayable string representation of the rental 406 * price of your content including the currency and the amount. If it's free, you should 407 * provide localized string to specify that it's free. This column can be omitted if the 408 * content is not applicable to rent. 409 */ 410 public final static String SUGGEST_COLUMN_RENTAL_PRICE = "suggest_rental_price"; 411 412 /** 413 * Column name for suggestions cursor. <i>Optional.</i> If your content has a rating, you 414 * should provide this column to specify the rating style of your content. The data in the 415 * column must be one of the constant values specified in {@link android.media.Rating} 416 */ 417 public final static String SUGGEST_COLUMN_RATING_STYLE = "suggest_rating_style"; 418 419 /** 420 * Column name for suggestions cursor. <i>Optional.</i> If your content has a rating, you 421 * should provide this column to specify the rating score of your content. The data in the 422 * column is of float type. See {@link android.media.Rating} about valid rating scores for each 423 * rating style. 424 */ 425 public final static String SUGGEST_COLUMN_RATING_SCORE = "suggest_rating_score"; 426 427 /** 428 * Column name for suggestions cursor. <i>Optional.</i> If your content is video or audio and 429 * has a known production year, you should provide this column to specify the production year 430 * of your content. The data in the column is of integer type. 431 */ 432 public final static String SUGGEST_COLUMN_PRODUCTION_YEAR = "suggest_production_year"; 433 434 /** 435 * Column name for suggestions cursor. <i>Optional.</i> If your content is video or audio, you 436 * should provide this column to specify the duration of your content in milliseconds. The data 437 * in the column is of long type. 438 */ 439 public final static String SUGGEST_COLUMN_DURATION = "suggest_duration"; 440 441 /** 442 * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify 443 * additional flags per item. Multiple flags can be specified. 444 * <p> 445 * Must be one of {@link #FLAG_QUERY_REFINEMENT} or 0 to indicate no flags. 446 * </p> 447 */ 448 public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags"; 449 450 /** 451 * Column name for suggestions cursor. <i>Optional.</i> This column may be 452 * used to specify the time in {@link System#currentTimeMillis 453 * System.currentTImeMillis()} (wall time in UTC) when an item was last 454 * accessed within the results-providing application. If set, this may be 455 * used to show more-recently-used items first. 456 */ 457 public final static String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint"; 458 459 /** 460 * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion 461 * should not be stored as a shortcut in global search. 462 */ 463 public final static String SUGGEST_NEVER_MAKE_SHORTCUT = "_-1"; 464 465 /** 466 * Query parameter added to suggestion queries to limit the number of suggestions returned. 467 * This limit is only advisory and suggestion providers may chose to ignore it. 468 */ 469 public final static String SUGGEST_PARAMETER_LIMIT = "limit"; 470 471 /** 472 * Intent action for starting the global search activity. 473 * The global search provider should handle this intent. 474 * 475 * Supported extra data keys: {@link #QUERY}, 476 * {@link #EXTRA_SELECT_QUERY}, 477 * {@link #APP_DATA}. 478 */ 479 public final static String INTENT_ACTION_GLOBAL_SEARCH 480 = "android.search.action.GLOBAL_SEARCH"; 481 482 /** 483 * Intent action for starting the global search settings activity. 484 * The global search provider should handle this intent. 485 */ 486 public final static String INTENT_ACTION_SEARCH_SETTINGS 487 = "android.search.action.SEARCH_SETTINGS"; 488 489 /** 490 * Intent action for starting a web search provider's settings activity. 491 * Web search providers should handle this intent if they have provider-specific 492 * settings to implement. 493 */ 494 public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS 495 = "android.search.action.WEB_SEARCH_SETTINGS"; 496 497 /** 498 * Intent action broadcasted to inform that the searchables list or default have changed. 499 * Components should handle this intent if they cache any searchable data and wish to stay 500 * up to date on changes. 501 */ 502 public final static String INTENT_ACTION_SEARCHABLES_CHANGED 503 = "android.search.action.SEARCHABLES_CHANGED"; 504 505 /** 506 * Intent action to be broadcast to inform that the global search provider 507 * has changed. 508 */ 509 public final static String INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED 510 = "android.search.action.GLOBAL_SEARCH_ACTIVITY_CHANGED"; 511 512 /** 513 * Intent action broadcasted to inform that the search settings have changed in some way. 514 * Either searchables have been enabled or disabled, or a different web search provider 515 * has been chosen. 516 */ 517 public final static String INTENT_ACTION_SEARCH_SETTINGS_CHANGED 518 = "android.search.action.SETTINGS_CHANGED"; 519 520 /** 521 * This means that context is voice, and therefore the SearchDialog should 522 * continue showing the microphone until the user indicates that he/she does 523 * not want to re-speak (e.g. by typing). 524 * 525 * @hide 526 */ 527 public final static String CONTEXT_IS_VOICE = "android.search.CONTEXT_IS_VOICE"; 528 529 /** 530 * This means that the voice icon should not be shown at all, because the 531 * current search engine does not support voice search. 532 * @hide 533 */ 534 @UnsupportedAppUsage 535 public final static String DISABLE_VOICE_SEARCH 536 = "android.search.DISABLE_VOICE_SEARCH"; 537 538 /** 539 * Reference to the shared system search service. 540 */ 541 private final ISearchManager mService; 542 543 private final Context mContext; 544 545 // package private since they are used by the inner class SearchManagerCallback 546 /* package */ final Handler mHandler; 547 /* package */ OnDismissListener mDismissListener = null; 548 /* package */ OnCancelListener mCancelListener = null; 549 550 @UnsupportedAppUsage 551 private SearchDialog mSearchDialog; 552 553 @UnsupportedAppUsage SearchManager(Context context, Handler handler)554 /*package*/ SearchManager(Context context, Handler handler) throws ServiceNotFoundException { 555 mContext = context; 556 mHandler = handler; 557 mService = ISearchManager.Stub.asInterface( 558 ServiceManager.getServiceOrThrow(Context.SEARCH_SERVICE)); 559 } 560 561 /** 562 * Launch search UI. 563 * 564 * <p>The search manager will open a search widget in an overlapping 565 * window, and the underlying activity may be obscured. The search 566 * entry state will remain in effect until one of the following events: 567 * <ul> 568 * <li>The user completes the search. In most cases this will launch 569 * a search intent.</li> 570 * <li>The user uses the back, home, or other keys to exit the search.</li> 571 * <li>The application calls the {@link #stopSearch} 572 * method, which will hide the search window and return focus to the 573 * activity from which it was launched.</li> 574 * 575 * <p>Most applications will <i>not</i> use this interface to invoke search. 576 * The primary method for invoking search is to call 577 * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or 578 * {@link android.app.Activity#startSearch Activity.startSearch()}. 579 * 580 * @param initialQuery A search string can be pre-entered here, but this 581 * is typically null or empty. 582 * @param selectInitialQuery If true, the initial query will be preselected, which means that 583 * any further typing will replace it. This is useful for cases where an entire pre-formed 584 * query is being inserted. If false, the selection point will be placed at the end of the 585 * inserted query. This is useful when the inserted query is text that the user entered, 586 * and the user would expect to be able to keep typing. <i>This parameter is only meaningful 587 * if initialQuery is a non-empty string.</i> 588 * @param launchActivity The ComponentName of the activity that has launched this search. 589 * @param appSearchData An application can insert application-specific 590 * context here, in order to improve quality or specificity of its own 591 * searches. This data will be returned with SEARCH intent(s). Null if 592 * no extra data is required. 593 * @param globalSearch If false, this will only launch the search that has been specifically 594 * defined by the application (which is usually defined as a local search). If no default 595 * search is defined in the current application or activity, global search will be launched. 596 * If true, this will always launch a platform-global (e.g. web-based) search instead. 597 * 598 * @see android.app.Activity#onSearchRequested 599 * @see #stopSearch 600 */ startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch)601 public void startSearch(String initialQuery, 602 boolean selectInitialQuery, 603 ComponentName launchActivity, 604 Bundle appSearchData, 605 boolean globalSearch) { 606 startSearch(initialQuery, selectInitialQuery, launchActivity, 607 appSearchData, globalSearch, null); 608 } 609 610 /** 611 * As {@link #startSearch(String, boolean, ComponentName, Bundle, boolean)} but including 612 * source bounds for the global search intent. 613 * 614 * @hide 615 */ 616 @UnsupportedAppUsage startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch, Rect sourceBounds)617 public void startSearch(String initialQuery, 618 boolean selectInitialQuery, 619 ComponentName launchActivity, 620 Bundle appSearchData, 621 boolean globalSearch, 622 Rect sourceBounds) { 623 if (globalSearch) { 624 startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds); 625 return; 626 } 627 628 final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); 629 // Don't show search dialog on televisions. 630 if (uiModeManager.getCurrentModeType() != Configuration.UI_MODE_TYPE_TELEVISION) { 631 ensureSearchDialog(); 632 633 mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData); 634 } 635 } 636 ensureSearchDialog()637 private void ensureSearchDialog() { 638 if (mSearchDialog == null) { 639 mSearchDialog = new SearchDialog(mContext, this); 640 mSearchDialog.setOnCancelListener(this); 641 mSearchDialog.setOnDismissListener(this); 642 } 643 } 644 645 /** 646 * Starts the global search activity. 647 */ startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds)648 /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery, 649 Bundle appSearchData, Rect sourceBounds) { 650 ComponentName globalSearchActivity = getGlobalSearchActivity(); 651 if (globalSearchActivity == null) { 652 Log.w(TAG, "No global search activity found."); 653 return; 654 } 655 Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH); 656 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 657 intent.setComponent(globalSearchActivity); 658 // Make sure that we have a Bundle to put source in 659 if (appSearchData == null) { 660 appSearchData = new Bundle(); 661 } else { 662 appSearchData = new Bundle(appSearchData); 663 } 664 // Set source to package name of app that starts global search, if not set already. 665 if (!appSearchData.containsKey("source")) { 666 appSearchData.putString("source", mContext.getPackageName()); 667 } 668 intent.putExtra(APP_DATA, appSearchData); 669 if (!TextUtils.isEmpty(initialQuery)) { 670 intent.putExtra(QUERY, initialQuery); 671 } 672 if (selectInitialQuery) { 673 intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery); 674 } 675 intent.setSourceBounds(sourceBounds); 676 try { 677 if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0)); 678 mContext.startActivity(intent); 679 } catch (ActivityNotFoundException ex) { 680 Log.e(TAG, "Global search activity not found: " + globalSearchActivity); 681 } 682 } 683 684 /** 685 * Returns a list of installed apps that handle the global search 686 * intent. 687 * 688 * @hide 689 */ getGlobalSearchActivities()690 public List<ResolveInfo> getGlobalSearchActivities() { 691 try { 692 return mService.getGlobalSearchActivities(); 693 } catch (RemoteException ex) { 694 throw ex.rethrowFromSystemServer(); 695 } 696 } 697 698 /** 699 * Gets the name of the global search activity. 700 */ getGlobalSearchActivity()701 public ComponentName getGlobalSearchActivity() { 702 try { 703 return mService.getGlobalSearchActivity(); 704 } catch (RemoteException ex) { 705 throw ex.rethrowFromSystemServer(); 706 } 707 } 708 709 /** 710 * Gets the name of the web search activity. 711 * 712 * @return The name of the default activity for web searches. This activity 713 * can be used to get web search suggestions. Returns {@code null} if 714 * there is no default web search activity. 715 * 716 * @hide 717 */ 718 @UnsupportedAppUsage getWebSearchActivity()719 public ComponentName getWebSearchActivity() { 720 try { 721 return mService.getWebSearchActivity(); 722 } catch (RemoteException ex) { 723 throw ex.rethrowFromSystemServer(); 724 } 725 } 726 727 /** 728 * Similar to {@link #startSearch} but actually fires off the search query after invoking 729 * the search dialog. Made available for testing purposes. 730 * 731 * @param query The query to trigger. If empty, request will be ignored. 732 * @param launchActivity The ComponentName of the activity that has launched this search. 733 * @param appSearchData An application can insert application-specific 734 * context here, in order to improve quality or specificity of its own 735 * searches. This data will be returned with SEARCH intent(s). Null if 736 * no extra data is required. 737 * 738 * @see #startSearch 739 */ triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData)740 public void triggerSearch(String query, 741 ComponentName launchActivity, 742 Bundle appSearchData) { 743 if (query == null || TextUtils.getTrimmedLength(query) == 0) { 744 Log.w(TAG, "triggerSearch called with empty query, ignoring."); 745 return; 746 } 747 startSearch(query, false, launchActivity, appSearchData, false); 748 mSearchDialog.launchQuerySearch(); 749 } 750 751 /** 752 * Terminate search UI. 753 * 754 * <p>Typically the user will terminate the search UI by launching a 755 * search or by canceling. This function allows the underlying application 756 * or activity to cancel the search prematurely (for any reason). 757 * 758 * <p>This function can be safely called at any time (even if no search is active.) 759 * 760 * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method. 761 * 762 * @see #startSearch 763 */ stopSearch()764 public void stopSearch() { 765 if (mSearchDialog != null) { 766 mSearchDialog.cancel(); 767 } 768 } 769 770 /** 771 * Determine if the Search UI is currently displayed. 772 * 773 * This is provided primarily for application test purposes. 774 * 775 * @return Returns true if the search UI is currently displayed. 776 * 777 * @hide 778 */ 779 @UnsupportedAppUsage isVisible()780 public boolean isVisible() { 781 return mSearchDialog == null? false : mSearchDialog.isShowing(); 782 } 783 784 /** 785 * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor 786 * search UI state. 787 */ 788 public interface OnDismissListener { 789 /** 790 * This method will be called when the search UI is dismissed. To make use of it, you must 791 * implement this method in your activity, and call 792 * {@link SearchManager#setOnDismissListener} to register it. 793 */ onDismiss()794 public void onDismiss(); 795 } 796 797 /** 798 * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor 799 * search UI state. 800 */ 801 public interface OnCancelListener { 802 /** 803 * This method will be called when the search UI is canceled. To make use if it, you must 804 * implement this method in your activity, and call 805 * {@link SearchManager#setOnCancelListener} to register it. 806 */ onCancel()807 public void onCancel(); 808 } 809 810 /** 811 * Set or clear the callback that will be invoked whenever the search UI is dismissed. 812 * 813 * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method. 814 * 815 * @param listener The {@link OnDismissListener} to use, or null. 816 */ setOnDismissListener(final OnDismissListener listener)817 public void setOnDismissListener(final OnDismissListener listener) { 818 mDismissListener = listener; 819 } 820 821 /** 822 * Set or clear the callback that will be invoked whenever the search UI is canceled. 823 * 824 * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method. 825 * 826 * @param listener The {@link OnCancelListener} to use, or null. 827 */ setOnCancelListener(OnCancelListener listener)828 public void setOnCancelListener(OnCancelListener listener) { 829 mCancelListener = listener; 830 } 831 832 /** 833 * @deprecated This method is an obsolete internal implementation detail. Do not use. 834 */ 835 @Deprecated onCancel(DialogInterface dialog)836 public void onCancel(DialogInterface dialog) { 837 if (mCancelListener != null) { 838 mCancelListener.onCancel(); 839 } 840 } 841 842 /** 843 * @deprecated This method is an obsolete internal implementation detail. Do not use. 844 */ 845 @Deprecated onDismiss(DialogInterface dialog)846 public void onDismiss(DialogInterface dialog) { 847 if (mDismissListener != null) { 848 mDismissListener.onDismiss(); 849 } 850 } 851 852 /** 853 * Gets information about a searchable activity. 854 * 855 * @param componentName The activity to get searchable information for. 856 * @return Searchable information, or <code>null</code> if the activity does not 857 * exist, or is not searchable. 858 */ getSearchableInfo(ComponentName componentName)859 public SearchableInfo getSearchableInfo(ComponentName componentName) { 860 try { 861 return mService.getSearchableInfo(componentName); 862 } catch (RemoteException ex) { 863 throw ex.rethrowFromSystemServer(); 864 } 865 } 866 867 /** 868 * Gets a cursor with search suggestions. 869 * 870 * @param searchable Information about how to get the suggestions. 871 * @param query The search text entered (so far). 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 */ 876 @UnsupportedAppUsage getSuggestions(SearchableInfo searchable, String query)877 public Cursor getSuggestions(SearchableInfo searchable, String query) { 878 return getSuggestions(searchable, query, -1); 879 } 880 881 /** 882 * Gets a cursor with search suggestions. 883 * 884 * @param searchable Information about how to get the suggestions. 885 * @param query The search text entered (so far). 886 * @param limit The query limit to pass to the suggestion provider. This is advisory, 887 * the returned cursor may contain more rows. Pass {@code -1} for no limit. 888 * @return a cursor with suggestions, or <code>null</null> the suggestion query failed. 889 * 890 * @hide because SearchableInfo is not part of the API. 891 */ 892 @UnsupportedAppUsage getSuggestions(SearchableInfo searchable, String query, int limit)893 public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) { 894 if (searchable == null) { 895 return null; 896 } 897 898 String authority = searchable.getSuggestAuthority(); 899 if (authority == null) { 900 return null; 901 } 902 903 Uri.Builder uriBuilder = new Uri.Builder() 904 .scheme(ContentResolver.SCHEME_CONTENT) 905 .authority(authority) 906 .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() 907 .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel() 908 909 // if content path provided, insert it now 910 final String contentPath = searchable.getSuggestPath(); 911 if (contentPath != null) { 912 uriBuilder.appendEncodedPath(contentPath); 913 } 914 915 // append standard suggestion query path 916 uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY); 917 918 // get the query selection, may be null 919 String selection = searchable.getSuggestSelection(); 920 // inject query, either as selection args or inline 921 String[] selArgs = null; 922 if (selection != null) { // use selection if provided 923 selArgs = new String[] { query }; 924 } else { // no selection, use REST pattern 925 uriBuilder.appendPath(query); 926 } 927 928 if (limit > 0) { 929 uriBuilder.appendQueryParameter(SUGGEST_PARAMETER_LIMIT, String.valueOf(limit)); 930 } 931 932 Uri uri = uriBuilder.build(); 933 934 // finally, make the query 935 return mContext.getContentResolver().query(uri, null, selection, selArgs, null); 936 } 937 938 /** 939 * Returns a list of the searchable activities that can be included in global search. 940 * 941 * @return a list containing searchable information for all searchable activities 942 * that have the <code>android:includeInGlobalSearch</code> attribute set 943 * in their searchable meta-data. 944 */ getSearchablesInGlobalSearch()945 public List<SearchableInfo> getSearchablesInGlobalSearch() { 946 try { 947 return mService.getSearchablesInGlobalSearch(); 948 } catch (RemoteException e) { 949 throw e.rethrowFromSystemServer(); 950 } 951 } 952 953 /** 954 * Gets an intent for launching installed assistant activity, or null if not available. 955 * @return The assist intent. 956 * 957 * @hide 958 */ getAssistIntent(boolean inclContext)959 public Intent getAssistIntent(boolean inclContext) { 960 try { 961 Intent intent = new Intent(Intent.ACTION_ASSIST); 962 if (inclContext) { 963 IActivityTaskManager am = ActivityTaskManager.getService(); 964 Bundle extras = am.getAssistContextExtras(ActivityManager.ASSIST_CONTEXT_BASIC); 965 if (extras != null) { 966 intent.replaceExtras(extras); 967 } 968 } 969 return intent; 970 } catch (RemoteException re) { 971 throw re.rethrowFromSystemServer(); 972 } 973 } 974 975 /** 976 * Starts the assistant. 977 * 978 * @param args the args to pass to the assistant 979 * 980 * @hide 981 */ 982 @UnsupportedAppUsage launchAssist(Bundle args)983 public void launchAssist(Bundle args) { 984 try { 985 if (mService == null) { 986 return; 987 } 988 mService.launchAssist(args); 989 } catch (RemoteException re) { 990 throw re.rethrowFromSystemServer(); 991 } 992 } 993 994 /** 995 * Starts the legacy assistant (i.e. the {@link Intent#ACTION_ASSIST}). 996 * 997 * @param args the args to pass to the assistant 998 * 999 * @hide 1000 */ launchLegacyAssist(String hint, int userHandle, Bundle args)1001 public boolean launchLegacyAssist(String hint, int userHandle, Bundle args) { 1002 try { 1003 if (mService == null) { 1004 return false; 1005 } 1006 return mService.launchLegacyAssist(hint, userHandle, args); 1007 } catch (RemoteException re) { 1008 throw re.rethrowFromSystemServer(); 1009 } 1010 } 1011 } 1012