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