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