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