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.StringRes; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.ActivityInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.ProviderInfo; 27 import android.content.res.TypedArray; 28 import android.content.res.XmlResourceParser; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.os.UserHandle; 32 import android.text.InputType; 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.util.Xml; 36 import android.view.inputmethod.EditorInfo; 37 38 import org.xmlpull.v1.XmlPullParser; 39 import org.xmlpull.v1.XmlPullParserException; 40 41 import java.io.IOException; 42 import java.util.HashMap; 43 44 /** 45 * Searchability meta-data for an activity. Only applications that search other applications 46 * should need to use this class. 47 * See <a href="{@docRoot}guide/topics/search/searchable-config.html">Searchable Configuration</a> 48 * for more information about declaring searchability meta-data for your application. 49 * 50 * @see SearchManager#getSearchableInfo(ComponentName) 51 * @see SearchManager#getSearchablesInGlobalSearch() 52 */ 53 public final class SearchableInfo implements Parcelable { 54 55 // general debugging support 56 private static final boolean DBG = false; 57 private static final String LOG_TAG = "SearchableInfo"; 58 59 // static strings used for XML lookups. 60 // TODO how should these be documented for the developer, in a more structured way than 61 // the current long wordy javadoc in SearchManager.java ? 62 private static final String MD_LABEL_SEARCHABLE = "android.app.searchable"; 63 private static final String MD_XML_ELEMENT_SEARCHABLE = "searchable"; 64 private static final String MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY = "actionkey"; 65 66 // flags in the searchMode attribute 67 private static final int SEARCH_MODE_BADGE_LABEL = 0x04; 68 private static final int SEARCH_MODE_BADGE_ICON = 0x08; 69 private static final int SEARCH_MODE_QUERY_REWRITE_FROM_DATA = 0x10; 70 private static final int SEARCH_MODE_QUERY_REWRITE_FROM_TEXT = 0x20; 71 72 // true member variables - what we know about the searchability 73 private final int mLabelId; 74 private final ComponentName mSearchActivity; 75 private final int mHintId; 76 private final int mSearchMode; 77 private final int mIconId; 78 private final int mSearchButtonText; 79 private final int mSearchInputType; 80 private final int mSearchImeOptions; 81 private final boolean mIncludeInGlobalSearch; 82 private final boolean mQueryAfterZeroResults; 83 private final boolean mAutoUrlDetect; 84 private final int mSettingsDescriptionId; 85 private final String mSuggestAuthority; 86 private final String mSuggestPath; 87 private final String mSuggestSelection; 88 private final String mSuggestIntentAction; 89 private final String mSuggestIntentData; 90 private final int mSuggestThreshold; 91 // Maps key codes to action key information. auto-boxing is not so bad here, 92 // since keycodes for the hard keys are < 127. For such values, Integer.valueOf() 93 // uses shared Integer objects. 94 // This is not final, to allow lazy initialization. 95 private HashMap<Integer,ActionKeyInfo> mActionKeys = null; 96 private final String mSuggestProviderPackage; 97 98 // Flag values for Searchable_voiceSearchMode 99 private static final int VOICE_SEARCH_SHOW_BUTTON = 1; 100 private static final int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2; 101 private static final int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4; 102 private final int mVoiceSearchMode; 103 private final int mVoiceLanguageModeId; // voiceLanguageModel 104 private final int mVoicePromptTextId; // voicePromptText 105 private final int mVoiceLanguageId; // voiceLanguage 106 private final int mVoiceMaxResults; // voiceMaxResults 107 108 /** 109 * Gets the search suggestion content provider authority. 110 * 111 * @return The search suggestions authority, or {@code null} if not set. 112 * @see android.R.styleable#Searchable_searchSuggestAuthority 113 */ getSuggestAuthority()114 public String getSuggestAuthority() { 115 return mSuggestAuthority; 116 } 117 118 /** 119 * Gets the name of the package where the suggestion provider lives, 120 * or {@code null}. 121 */ getSuggestPackage()122 public String getSuggestPackage() { 123 return mSuggestProviderPackage; 124 } 125 126 /** 127 * Gets the component name of the searchable activity. 128 * 129 * @return A component name, never {@code null}. 130 */ getSearchActivity()131 public ComponentName getSearchActivity() { 132 return mSearchActivity; 133 } 134 135 /** 136 * Checks whether the badge should be a text label. 137 * 138 * @see android.R.styleable#Searchable_searchMode 139 * 140 * @hide This feature is deprecated, no need to add it to the API. 141 */ useBadgeLabel()142 public boolean useBadgeLabel() { 143 return 0 != (mSearchMode & SEARCH_MODE_BADGE_LABEL); 144 } 145 146 /** 147 * Checks whether the badge should be an icon. 148 * 149 * @see android.R.styleable#Searchable_searchMode 150 * 151 * @hide This feature is deprecated, no need to add it to the API. 152 */ useBadgeIcon()153 public boolean useBadgeIcon() { 154 return (0 != (mSearchMode & SEARCH_MODE_BADGE_ICON)) && (mIconId != 0); 155 } 156 157 /** 158 * Checks whether the text in the query field should come from the suggestion intent data. 159 * 160 * @see android.R.styleable#Searchable_searchMode 161 */ shouldRewriteQueryFromData()162 public boolean shouldRewriteQueryFromData() { 163 return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_DATA); 164 } 165 166 /** 167 * Checks whether the text in the query field should come from the suggestion title. 168 * 169 * @see android.R.styleable#Searchable_searchMode 170 */ shouldRewriteQueryFromText()171 public boolean shouldRewriteQueryFromText() { 172 return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_TEXT); 173 } 174 175 /** 176 * Gets the resource id of the description string to use for this source in system search 177 * settings, or {@code 0} if none has been specified. 178 * 179 * @see android.R.styleable#Searchable_searchSettingsDescription 180 */ getSettingsDescriptionId()181 public int getSettingsDescriptionId() { 182 return mSettingsDescriptionId; 183 } 184 185 /** 186 * Gets the content provider path for obtaining search suggestions. 187 * 188 * @return The suggestion path, or {@code null} if not set. 189 * @see android.R.styleable#Searchable_searchSuggestPath 190 */ getSuggestPath()191 public String getSuggestPath() { 192 return mSuggestPath; 193 } 194 195 /** 196 * Gets the selection for obtaining search suggestions. 197 * 198 * @see android.R.styleable#Searchable_searchSuggestSelection 199 */ getSuggestSelection()200 public String getSuggestSelection() { 201 return mSuggestSelection; 202 } 203 204 /** 205 * Gets the optional intent action for use with these suggestions. This is 206 * useful if all intents will have the same action 207 * (e.g. {@link android.content.Intent#ACTION_VIEW}) 208 * 209 * This can be overriden in any given suggestion using the column 210 * {@link SearchManager#SUGGEST_COLUMN_INTENT_ACTION}. 211 * 212 * @return The default intent action, or {@code null} if not set. 213 * @see android.R.styleable#Searchable_searchSuggestIntentAction 214 */ getSuggestIntentAction()215 public String getSuggestIntentAction() { 216 return mSuggestIntentAction; 217 } 218 219 /** 220 * Gets the optional intent data for use with these suggestions. This is 221 * useful if all intents will have similar data URIs, 222 * but you'll likely need to provide a specific ID as well via the column 223 * {@link SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID}, which will be appended to the 224 * intent data URI. 225 * 226 * This can be overriden in any given suggestion using the column 227 * {@link SearchManager#SUGGEST_COLUMN_INTENT_DATA}. 228 * 229 * @return The default intent data, or {@code null} if not set. 230 * @see android.R.styleable#Searchable_searchSuggestIntentData 231 */ getSuggestIntentData()232 public String getSuggestIntentData() { 233 return mSuggestIntentData; 234 } 235 236 /** 237 * Gets the suggestion threshold. 238 * 239 * @return The suggestion threshold, or {@code 0} if not set. 240 * @see android.R.styleable#Searchable_searchSuggestThreshold 241 */ getSuggestThreshold()242 public int getSuggestThreshold() { 243 return mSuggestThreshold; 244 } 245 246 /** 247 * Get the context for the searchable activity. 248 * 249 * @param context You need to supply a context to start with 250 * @return Returns a context related to the searchable activity 251 * @hide 252 */ 253 @UnsupportedAppUsage getActivityContext(Context context)254 public Context getActivityContext(Context context) { 255 return createActivityContext(context, mSearchActivity); 256 } 257 258 /** 259 * Creates a context for another activity. 260 */ createActivityContext(Context context, ComponentName activity)261 private static Context createActivityContext(Context context, ComponentName activity) { 262 Context theirContext = null; 263 try { 264 theirContext = context.createPackageContext(activity.getPackageName(), 0); 265 } catch (PackageManager.NameNotFoundException e) { 266 Log.e(LOG_TAG, "Package not found " + activity.getPackageName()); 267 } catch (java.lang.SecurityException e) { 268 Log.e(LOG_TAG, "Can't make context for " + activity.getPackageName(), e); 269 } 270 271 return theirContext; 272 } 273 274 /** 275 * Get the context for the suggestions provider. 276 * 277 * @param context You need to supply a context to start with 278 * @param activityContext If we can determine that the provider and the activity are the 279 * same, we'll just return this one. 280 * @return Returns a context related to the suggestion provider 281 * @hide 282 */ 283 @UnsupportedAppUsage getProviderContext(Context context, Context activityContext)284 public Context getProviderContext(Context context, Context activityContext) { 285 Context theirContext = null; 286 if (mSearchActivity.getPackageName().equals(mSuggestProviderPackage)) { 287 return activityContext; 288 } 289 if (mSuggestProviderPackage != null) { 290 try { 291 theirContext = context.createPackageContext(mSuggestProviderPackage, 0); 292 } catch (PackageManager.NameNotFoundException e) { 293 // unexpected, but we deal with this by null-checking theirContext 294 } catch (java.lang.SecurityException e) { 295 // unexpected, but we deal with this by null-checking theirContext 296 } 297 } 298 return theirContext; 299 } 300 301 /** 302 * Constructor 303 * 304 * Given a ComponentName, get the searchability info 305 * and build a local copy of it. Use the factory, not this. 306 * 307 * @param activityContext runtime context for the activity that the searchable info is about. 308 * @param attr The attribute set we found in the XML file, contains the values that are used to 309 * construct the object. 310 * @param cName The component name of the searchable activity 311 * @throws IllegalArgumentException if the searchability info is invalid or insufficient 312 */ 313 @UnsupportedAppUsage SearchableInfo(Context activityContext, AttributeSet attr, final ComponentName cName)314 private SearchableInfo(Context activityContext, AttributeSet attr, final ComponentName cName) { 315 mSearchActivity = cName; 316 317 TypedArray a = activityContext.obtainStyledAttributes(attr, 318 com.android.internal.R.styleable.Searchable); 319 mSearchMode = a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0); 320 mLabelId = a.getResourceId(com.android.internal.R.styleable.Searchable_label, 0); 321 mHintId = a.getResourceId(com.android.internal.R.styleable.Searchable_hint, 0); 322 mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0); 323 mSearchButtonText = a.getResourceId( 324 com.android.internal.R.styleable.Searchable_searchButtonText, 0); 325 mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType, 326 InputType.TYPE_CLASS_TEXT | 327 InputType.TYPE_TEXT_VARIATION_NORMAL); 328 mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions, 329 EditorInfo.IME_ACTION_GO); 330 mIncludeInGlobalSearch = a.getBoolean( 331 com.android.internal.R.styleable.Searchable_includeInGlobalSearch, false); 332 mQueryAfterZeroResults = a.getBoolean( 333 com.android.internal.R.styleable.Searchable_queryAfterZeroResults, false); 334 mAutoUrlDetect = a.getBoolean( 335 com.android.internal.R.styleable.Searchable_autoUrlDetect, false); 336 337 mSettingsDescriptionId = a.getResourceId( 338 com.android.internal.R.styleable.Searchable_searchSettingsDescription, 0); 339 mSuggestAuthority = a.getString( 340 com.android.internal.R.styleable.Searchable_searchSuggestAuthority); 341 mSuggestPath = a.getString( 342 com.android.internal.R.styleable.Searchable_searchSuggestPath); 343 mSuggestSelection = a.getString( 344 com.android.internal.R.styleable.Searchable_searchSuggestSelection); 345 mSuggestIntentAction = a.getString( 346 com.android.internal.R.styleable.Searchable_searchSuggestIntentAction); 347 mSuggestIntentData = a.getString( 348 com.android.internal.R.styleable.Searchable_searchSuggestIntentData); 349 mSuggestThreshold = a.getInt( 350 com.android.internal.R.styleable.Searchable_searchSuggestThreshold, 0); 351 352 mVoiceSearchMode = 353 a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0); 354 // TODO this didn't work - came back zero from YouTube 355 mVoiceLanguageModeId = 356 a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0); 357 mVoicePromptTextId = 358 a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0); 359 mVoiceLanguageId = 360 a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0); 361 mVoiceMaxResults = 362 a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0); 363 364 a.recycle(); 365 366 // get package info for suggestions provider (if any) 367 String suggestProviderPackage = null; 368 if (mSuggestAuthority != null) { 369 PackageManager pm = activityContext.getPackageManager(); 370 ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority, 371 PackageManager.MATCH_DEBUG_TRIAGED_MISSING); 372 if (pi != null) { 373 suggestProviderPackage = pi.packageName; 374 } 375 } 376 mSuggestProviderPackage = suggestProviderPackage; 377 378 // for now, implement some form of rules - minimal data 379 if (mLabelId == 0) { 380 throw new IllegalArgumentException("Search label must be a resource reference."); 381 } 382 } 383 384 /** 385 * Information about an action key in searchability meta-data. 386 * 387 * @see SearchableInfo#findActionKey(int) 388 * 389 * @hide This feature is used very little, and on many devices there are no reasonable 390 * keys to use for actions. 391 */ 392 public static class ActionKeyInfo implements Parcelable { 393 394 private final int mKeyCode; 395 private final String mQueryActionMsg; 396 private final String mSuggestActionMsg; 397 private final String mSuggestActionMsgColumn; 398 399 public static final Parcelable.Creator<ActionKeyInfo> CREATOR = 400 new Parcelable.Creator<ActionKeyInfo>() { 401 public ActionKeyInfo createFromParcel(Parcel in) { 402 return new ActionKeyInfo(in); 403 } 404 405 public ActionKeyInfo[] newArray(int size) { 406 return new ActionKeyInfo[size]; 407 } 408 }; 409 410 /** 411 * Create one object using attributeset as input data. 412 * @param activityContext runtime context of the activity that the action key information 413 * is about. 414 * @param attr The attribute set we found in the XML file, contains the values that are used to 415 * construct the object. 416 * @throws IllegalArgumentException if the action key configuration is invalid 417 */ ActionKeyInfo(Context activityContext, AttributeSet attr)418 ActionKeyInfo(Context activityContext, AttributeSet attr) { 419 TypedArray a = activityContext.obtainStyledAttributes(attr, 420 com.android.internal.R.styleable.SearchableActionKey); 421 422 mKeyCode = a.getInt( 423 com.android.internal.R.styleable.SearchableActionKey_keycode, 0); 424 mQueryActionMsg = a.getString( 425 com.android.internal.R.styleable.SearchableActionKey_queryActionMsg); 426 mSuggestActionMsg = a.getString( 427 com.android.internal.R.styleable.SearchableActionKey_suggestActionMsg); 428 mSuggestActionMsgColumn = a.getString( 429 com.android.internal.R.styleable.SearchableActionKey_suggestActionMsgColumn); 430 a.recycle(); 431 432 // validity check. 433 if (mKeyCode == 0) { 434 throw new IllegalArgumentException("No keycode."); 435 } else if ((mQueryActionMsg == null) && 436 (mSuggestActionMsg == null) && 437 (mSuggestActionMsgColumn == null)) { 438 throw new IllegalArgumentException("No message information."); 439 } 440 } 441 442 /** 443 * Instantiate a new ActionKeyInfo from the data in a Parcel that was 444 * previously written with {@link #writeToParcel(Parcel, int)}. 445 * 446 * @param in The Parcel containing the previously written ActionKeyInfo, 447 * positioned at the location in the buffer where it was written. 448 */ ActionKeyInfo(Parcel in)449 private ActionKeyInfo(Parcel in) { 450 mKeyCode = in.readInt(); 451 mQueryActionMsg = in.readString(); 452 mSuggestActionMsg = in.readString(); 453 mSuggestActionMsgColumn = in.readString(); 454 } 455 456 /** 457 * Gets the key code that this action key info is for. 458 * @see android.R.styleable#SearchableActionKey_keycode 459 */ getKeyCode()460 public int getKeyCode() { 461 return mKeyCode; 462 } 463 464 /** 465 * Gets the action message to use for queries. 466 * @see android.R.styleable#SearchableActionKey_queryActionMsg 467 */ 468 @UnsupportedAppUsage getQueryActionMsg()469 public String getQueryActionMsg() { 470 return mQueryActionMsg; 471 } 472 473 /** 474 * Gets the action message to use for suggestions. 475 * @see android.R.styleable#SearchableActionKey_suggestActionMsg 476 */ 477 @UnsupportedAppUsage getSuggestActionMsg()478 public String getSuggestActionMsg() { 479 return mSuggestActionMsg; 480 } 481 482 /** 483 * Gets the name of the column to get the suggestion action message from. 484 * @see android.R.styleable#SearchableActionKey_suggestActionMsgColumn 485 */ 486 @UnsupportedAppUsage getSuggestActionMsgColumn()487 public String getSuggestActionMsgColumn() { 488 return mSuggestActionMsgColumn; 489 } 490 describeContents()491 public int describeContents() { 492 return 0; 493 } 494 writeToParcel(Parcel dest, int flags)495 public void writeToParcel(Parcel dest, int flags) { 496 dest.writeInt(mKeyCode); 497 dest.writeString(mQueryActionMsg); 498 dest.writeString(mSuggestActionMsg); 499 dest.writeString(mSuggestActionMsgColumn); 500 } 501 } 502 503 /** 504 * If any action keys were defined for this searchable activity, look up and return. 505 * 506 * @param keyCode The key that was pressed 507 * @return Returns the action key info, or {@code null} if none defined. 508 * 509 * @hide ActionKeyInfo is hidden 510 */ 511 @UnsupportedAppUsage findActionKey(int keyCode)512 public ActionKeyInfo findActionKey(int keyCode) { 513 if (mActionKeys == null) { 514 return null; 515 } 516 return mActionKeys.get(keyCode); 517 } 518 addActionKey(ActionKeyInfo keyInfo)519 private void addActionKey(ActionKeyInfo keyInfo) { 520 if (mActionKeys == null) { 521 mActionKeys = new HashMap<Integer,ActionKeyInfo>(); 522 } 523 mActionKeys.put(keyInfo.getKeyCode(), keyInfo); 524 } 525 526 /** 527 * Gets search information for the given activity. 528 * 529 * @param context Context to use for reading activity resources. 530 * @param activityInfo Activity to get search information from. 531 * @return Search information about the given activity, or {@code null} if 532 * the activity has no or invalid searchability meta-data. 533 * 534 * @hide For use by SearchManagerService. 535 */ getActivityMetaData(Context context, ActivityInfo activityInfo, int userId)536 public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo, 537 int userId) { 538 Context userContext; 539 try { 540 userContext = context.createPackageContextAsUser("system", 0, 541 new UserHandle(userId)); 542 } catch (NameNotFoundException nnfe) { 543 Log.e(LOG_TAG, "Couldn't create package context for user " + userId); 544 return null; 545 } 546 // for each component, try to find metadata 547 XmlResourceParser xml = 548 activityInfo.loadXmlMetaData(userContext.getPackageManager(), MD_LABEL_SEARCHABLE); 549 if (xml == null) { 550 return null; 551 } 552 ComponentName cName = new ComponentName(activityInfo.packageName, activityInfo.name); 553 554 SearchableInfo searchable = getActivityMetaData(userContext, xml, cName); 555 xml.close(); 556 557 if (DBG) { 558 if (searchable != null) { 559 Log.d(LOG_TAG, "Checked " + activityInfo.name 560 + ",label=" + searchable.getLabelId() 561 + ",icon=" + searchable.getIconId() 562 + ",suggestAuthority=" + searchable.getSuggestAuthority() 563 + ",target=" + searchable.getSearchActivity().getClassName() 564 + ",global=" + searchable.shouldIncludeInGlobalSearch() 565 + ",settingsDescription=" + searchable.getSettingsDescriptionId() 566 + ",threshold=" + searchable.getSuggestThreshold()); 567 } else { 568 Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data"); 569 } 570 } 571 return searchable; 572 } 573 574 /** 575 * Get the metadata for a given activity 576 * 577 * @param context runtime context 578 * @param xml XML parser for reading attributes 579 * @param cName The component name of the searchable activity 580 * 581 * @result A completely constructed SearchableInfo, or null if insufficient XML data for it 582 */ getActivityMetaData(Context context, XmlPullParser xml, final ComponentName cName)583 private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml, 584 final ComponentName cName) { 585 SearchableInfo result = null; 586 Context activityContext = createActivityContext(context, cName); 587 if (activityContext == null) return null; 588 589 // in order to use the attributes mechanism, we have to walk the parser 590 // forward through the file until it's reading the tag of interest. 591 try { 592 int tagType = xml.next(); 593 while (tagType != XmlPullParser.END_DOCUMENT) { 594 if (tagType == XmlPullParser.START_TAG) { 595 if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) { 596 AttributeSet attr = Xml.asAttributeSet(xml); 597 if (attr != null) { 598 try { 599 result = new SearchableInfo(activityContext, attr, cName); 600 } catch (IllegalArgumentException ex) { 601 Log.w(LOG_TAG, "Invalid searchable metadata for " + 602 cName.flattenToShortString() + ": " + ex.getMessage()); 603 return null; 604 } 605 } 606 } else if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY)) { 607 if (result == null) { 608 // Can't process an embedded element if we haven't seen the enclosing 609 return null; 610 } 611 AttributeSet attr = Xml.asAttributeSet(xml); 612 if (attr != null) { 613 try { 614 result.addActionKey(new ActionKeyInfo(activityContext, attr)); 615 } catch (IllegalArgumentException ex) { 616 Log.w(LOG_TAG, "Invalid action key for " + 617 cName.flattenToShortString() + ": " + ex.getMessage()); 618 return null; 619 } 620 } 621 } 622 } 623 tagType = xml.next(); 624 } 625 } catch (XmlPullParserException e) { 626 Log.w(LOG_TAG, "Reading searchable metadata for " + cName.flattenToShortString(), e); 627 return null; 628 } catch (IOException e) { 629 Log.w(LOG_TAG, "Reading searchable metadata for " + cName.flattenToShortString(), e); 630 return null; 631 } 632 633 return result; 634 } 635 636 /** 637 * Gets the "label" (user-visible name) of this searchable context. This must be 638 * read using the searchable Activity's resources. 639 * 640 * @return A resource id, or {@code 0} if no label was specified. 641 * @see android.R.styleable#Searchable_label 642 * 643 * @hide deprecated functionality 644 */ 645 @UnsupportedAppUsage getLabelId()646 public int getLabelId() { 647 return mLabelId; 648 } 649 650 /** 651 * Gets the resource id of the hint text. This must be 652 * read using the searchable Activity's resources. 653 * 654 * @return A resource id, or {@code 0} if no hint was specified. 655 * @see android.R.styleable#Searchable_hint 656 */ getHintId()657 public int getHintId() { 658 return mHintId; 659 } 660 661 /** 662 * Gets the icon id specified by the Searchable_icon meta-data entry. This must be 663 * read using the searchable Activity's resources. 664 * 665 * @return A resource id, or {@code 0} if no icon was specified. 666 * @see android.R.styleable#Searchable_icon 667 * 668 * @hide deprecated functionality 669 */ 670 @UnsupportedAppUsage getIconId()671 public int getIconId() { 672 return mIconId; 673 } 674 675 /** 676 * Checks if the searchable activity wants the voice search button to be shown. 677 * 678 * @see android.R.styleable#Searchable_voiceSearchMode 679 */ getVoiceSearchEnabled()680 public boolean getVoiceSearchEnabled() { 681 return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON); 682 } 683 684 /** 685 * Checks if voice search should start web search. 686 * 687 * @see android.R.styleable#Searchable_voiceSearchMode 688 */ getVoiceSearchLaunchWebSearch()689 public boolean getVoiceSearchLaunchWebSearch() { 690 return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH); 691 } 692 693 /** 694 * Checks if voice search should start in-app search. 695 * 696 * @see android.R.styleable#Searchable_voiceSearchMode 697 */ getVoiceSearchLaunchRecognizer()698 public boolean getVoiceSearchLaunchRecognizer() { 699 return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER); 700 } 701 702 /** 703 * Gets the resource id of the voice search language model string. 704 * 705 * @return A resource id, or {@code 0} if no language model was specified. 706 * @see android.R.styleable#Searchable_voiceLanguageModel 707 */ 708 @StringRes getVoiceLanguageModeId()709 public int getVoiceLanguageModeId() { 710 return mVoiceLanguageModeId; 711 } 712 713 /** 714 * Gets the resource id of the voice prompt text string. 715 * 716 * @return A resource id, or {@code 0} if no voice prompt text was specified. 717 * @see android.R.styleable#Searchable_voicePromptText 718 */ 719 @StringRes getVoicePromptTextId()720 public int getVoicePromptTextId() { 721 return mVoicePromptTextId; 722 } 723 724 /** 725 * Gets the resource id of the spoken language to recognize in voice search. 726 * 727 * @return A resource id, or {@code 0} if no language was specified. 728 * @see android.R.styleable#Searchable_voiceLanguage 729 */ 730 @StringRes getVoiceLanguageId()731 public int getVoiceLanguageId() { 732 return mVoiceLanguageId; 733 } 734 735 /** 736 * The maximum number of voice recognition results to return. 737 * 738 * @return the max results count, if specified in the searchable 739 * activity's metadata, or {@code 0} if not specified. 740 * @see android.R.styleable#Searchable_voiceMaxResults 741 */ getVoiceMaxResults()742 public int getVoiceMaxResults() { 743 return mVoiceMaxResults; 744 } 745 746 /** 747 * Gets the resource id of replacement text for the "Search" button. 748 * 749 * @return A resource id, or {@code 0} if no replacement text was specified. 750 * @see android.R.styleable#Searchable_searchButtonText 751 * @hide This feature is deprecated, no need to add it to the API. 752 */ getSearchButtonText()753 public int getSearchButtonText() { 754 return mSearchButtonText; 755 } 756 757 /** 758 * Gets the input type as specified in the searchable attributes. This will default to 759 * {@link InputType#TYPE_CLASS_TEXT} if not specified (which is appropriate 760 * for free text input). 761 * 762 * @return the input type 763 * @see android.R.styleable#Searchable_inputType 764 */ getInputType()765 public int getInputType() { 766 return mSearchInputType; 767 } 768 769 /** 770 * Gets the input method options specified in the searchable attributes. 771 * This will default to {@link EditorInfo#IME_ACTION_GO} if not specified (which is 772 * appropriate for a search box). 773 * 774 * @return the input type 775 * @see android.R.styleable#Searchable_imeOptions 776 */ getImeOptions()777 public int getImeOptions() { 778 return mSearchImeOptions; 779 } 780 781 /** 782 * Checks whether the searchable should be included in global search. 783 * 784 * @return The value of the {@link android.R.styleable#Searchable_includeInGlobalSearch} 785 * attribute, or {@code false} if the attribute is not set. 786 * @see android.R.styleable#Searchable_includeInGlobalSearch 787 */ shouldIncludeInGlobalSearch()788 public boolean shouldIncludeInGlobalSearch() { 789 return mIncludeInGlobalSearch; 790 } 791 792 /** 793 * Checks whether this searchable activity should be queried for suggestions if a prefix 794 * of the query has returned no results. 795 * 796 * @see android.R.styleable#Searchable_queryAfterZeroResults 797 */ queryAfterZeroResults()798 public boolean queryAfterZeroResults() { 799 return mQueryAfterZeroResults; 800 } 801 802 /** 803 * Checks whether this searchable activity has auto URL detection turned on. 804 * 805 * @see android.R.styleable#Searchable_autoUrlDetect 806 */ autoUrlDetect()807 public boolean autoUrlDetect() { 808 return mAutoUrlDetect; 809 } 810 811 /** 812 * Support for parcelable and aidl operations. 813 */ 814 public static final @android.annotation.NonNull Parcelable.Creator<SearchableInfo> CREATOR 815 = new Parcelable.Creator<SearchableInfo>() { 816 public SearchableInfo createFromParcel(Parcel in) { 817 return new SearchableInfo(in); 818 } 819 820 public SearchableInfo[] newArray(int size) { 821 return new SearchableInfo[size]; 822 } 823 }; 824 825 /** 826 * Instantiates a new SearchableInfo from the data in a Parcel that was 827 * previously written with {@link #writeToParcel(Parcel, int)}. 828 * 829 * @param in The Parcel containing the previously written SearchableInfo, 830 * positioned at the location in the buffer where it was written. 831 */ SearchableInfo(Parcel in)832 SearchableInfo(Parcel in) { 833 mLabelId = in.readInt(); 834 mSearchActivity = ComponentName.readFromParcel(in); 835 mHintId = in.readInt(); 836 mSearchMode = in.readInt(); 837 mIconId = in.readInt(); 838 mSearchButtonText = in.readInt(); 839 mSearchInputType = in.readInt(); 840 mSearchImeOptions = in.readInt(); 841 mIncludeInGlobalSearch = in.readInt() != 0; 842 mQueryAfterZeroResults = in.readInt() != 0; 843 mAutoUrlDetect = in.readInt() != 0; 844 845 mSettingsDescriptionId = in.readInt(); 846 mSuggestAuthority = in.readString(); 847 mSuggestPath = in.readString(); 848 mSuggestSelection = in.readString(); 849 mSuggestIntentAction = in.readString(); 850 mSuggestIntentData = in.readString(); 851 mSuggestThreshold = in.readInt(); 852 853 for (int count = in.readInt(); count > 0; count--) { 854 addActionKey(new ActionKeyInfo(in)); 855 } 856 857 mSuggestProviderPackage = in.readString(); 858 859 mVoiceSearchMode = in.readInt(); 860 mVoiceLanguageModeId = in.readInt(); 861 mVoicePromptTextId = in.readInt(); 862 mVoiceLanguageId = in.readInt(); 863 mVoiceMaxResults = in.readInt(); 864 } 865 describeContents()866 public int describeContents() { 867 return 0; 868 } 869 writeToParcel(Parcel dest, int flags)870 public void writeToParcel(Parcel dest, int flags) { 871 dest.writeInt(mLabelId); 872 mSearchActivity.writeToParcel(dest, flags); 873 dest.writeInt(mHintId); 874 dest.writeInt(mSearchMode); 875 dest.writeInt(mIconId); 876 dest.writeInt(mSearchButtonText); 877 dest.writeInt(mSearchInputType); 878 dest.writeInt(mSearchImeOptions); 879 dest.writeInt(mIncludeInGlobalSearch ? 1 : 0); 880 dest.writeInt(mQueryAfterZeroResults ? 1 : 0); 881 dest.writeInt(mAutoUrlDetect ? 1 : 0); 882 883 dest.writeInt(mSettingsDescriptionId); 884 dest.writeString(mSuggestAuthority); 885 dest.writeString(mSuggestPath); 886 dest.writeString(mSuggestSelection); 887 dest.writeString(mSuggestIntentAction); 888 dest.writeString(mSuggestIntentData); 889 dest.writeInt(mSuggestThreshold); 890 891 if (mActionKeys == null) { 892 dest.writeInt(0); 893 } else { 894 dest.writeInt(mActionKeys.size()); 895 for (ActionKeyInfo actionKey : mActionKeys.values()) { 896 actionKey.writeToParcel(dest, flags); 897 } 898 } 899 900 dest.writeString(mSuggestProviderPackage); 901 902 dest.writeInt(mVoiceSearchMode); 903 dest.writeInt(mVoiceLanguageModeId); 904 dest.writeInt(mVoicePromptTextId); 905 dest.writeInt(mVoiceLanguageId); 906 dest.writeInt(mVoiceMaxResults); 907 } 908 } 909