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 org.xmlpull.v1.XmlPullParser; 20 import org.xmlpull.v1.XmlPullParserException; 21 22 import android.annotation.StringRes; 23 import android.annotation.UnsupportedAppUsage; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.pm.ActivityInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ProviderInfo; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.TypedArray; 31 import android.content.res.XmlResourceParser; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.os.UserHandle; 35 import android.text.InputType; 36 import android.util.AttributeSet; 37 import android.util.Log; 38 import android.util.Xml; 39 import android.view.inputmethod.EditorInfo; 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 /** 400 * Create one object using attributeset as input data. 401 * @param activityContext runtime context of the activity that the action key information 402 * is about. 403 * @param attr The attribute set we found in the XML file, contains the values that are used to 404 * construct the object. 405 * @throws IllegalArgumentException if the action key configuration is invalid 406 */ ActionKeyInfo(Context activityContext, AttributeSet attr)407 ActionKeyInfo(Context activityContext, AttributeSet attr) { 408 TypedArray a = activityContext.obtainStyledAttributes(attr, 409 com.android.internal.R.styleable.SearchableActionKey); 410 411 mKeyCode = a.getInt( 412 com.android.internal.R.styleable.SearchableActionKey_keycode, 0); 413 mQueryActionMsg = a.getString( 414 com.android.internal.R.styleable.SearchableActionKey_queryActionMsg); 415 mSuggestActionMsg = a.getString( 416 com.android.internal.R.styleable.SearchableActionKey_suggestActionMsg); 417 mSuggestActionMsgColumn = a.getString( 418 com.android.internal.R.styleable.SearchableActionKey_suggestActionMsgColumn); 419 a.recycle(); 420 421 // sanity check. 422 if (mKeyCode == 0) { 423 throw new IllegalArgumentException("No keycode."); 424 } else if ((mQueryActionMsg == null) && 425 (mSuggestActionMsg == null) && 426 (mSuggestActionMsgColumn == null)) { 427 throw new IllegalArgumentException("No message information."); 428 } 429 } 430 431 /** 432 * Instantiate a new ActionKeyInfo from the data in a Parcel that was 433 * previously written with {@link #writeToParcel(Parcel, int)}. 434 * 435 * @param in The Parcel containing the previously written ActionKeyInfo, 436 * positioned at the location in the buffer where it was written. 437 */ ActionKeyInfo(Parcel in)438 private ActionKeyInfo(Parcel in) { 439 mKeyCode = in.readInt(); 440 mQueryActionMsg = in.readString(); 441 mSuggestActionMsg = in.readString(); 442 mSuggestActionMsgColumn = in.readString(); 443 } 444 445 /** 446 * Gets the key code that this action key info is for. 447 * @see android.R.styleable#SearchableActionKey_keycode 448 */ getKeyCode()449 public int getKeyCode() { 450 return mKeyCode; 451 } 452 453 /** 454 * Gets the action message to use for queries. 455 * @see android.R.styleable#SearchableActionKey_queryActionMsg 456 */ 457 @UnsupportedAppUsage getQueryActionMsg()458 public String getQueryActionMsg() { 459 return mQueryActionMsg; 460 } 461 462 /** 463 * Gets the action message to use for suggestions. 464 * @see android.R.styleable#SearchableActionKey_suggestActionMsg 465 */ 466 @UnsupportedAppUsage getSuggestActionMsg()467 public String getSuggestActionMsg() { 468 return mSuggestActionMsg; 469 } 470 471 /** 472 * Gets the name of the column to get the suggestion action message from. 473 * @see android.R.styleable#SearchableActionKey_suggestActionMsgColumn 474 */ 475 @UnsupportedAppUsage getSuggestActionMsgColumn()476 public String getSuggestActionMsgColumn() { 477 return mSuggestActionMsgColumn; 478 } 479 describeContents()480 public int describeContents() { 481 return 0; 482 } 483 writeToParcel(Parcel dest, int flags)484 public void writeToParcel(Parcel dest, int flags) { 485 dest.writeInt(mKeyCode); 486 dest.writeString(mQueryActionMsg); 487 dest.writeString(mSuggestActionMsg); 488 dest.writeString(mSuggestActionMsgColumn); 489 } 490 } 491 492 /** 493 * If any action keys were defined for this searchable activity, look up and return. 494 * 495 * @param keyCode The key that was pressed 496 * @return Returns the action key info, or {@code null} if none defined. 497 * 498 * @hide ActionKeyInfo is hidden 499 */ 500 @UnsupportedAppUsage findActionKey(int keyCode)501 public ActionKeyInfo findActionKey(int keyCode) { 502 if (mActionKeys == null) { 503 return null; 504 } 505 return mActionKeys.get(keyCode); 506 } 507 addActionKey(ActionKeyInfo keyInfo)508 private void addActionKey(ActionKeyInfo keyInfo) { 509 if (mActionKeys == null) { 510 mActionKeys = new HashMap<Integer,ActionKeyInfo>(); 511 } 512 mActionKeys.put(keyInfo.getKeyCode(), keyInfo); 513 } 514 515 /** 516 * Gets search information for the given activity. 517 * 518 * @param context Context to use for reading activity resources. 519 * @param activityInfo Activity to get search information from. 520 * @return Search information about the given activity, or {@code null} if 521 * the activity has no or invalid searchability meta-data. 522 * 523 * @hide For use by SearchManagerService. 524 */ getActivityMetaData(Context context, ActivityInfo activityInfo, int userId)525 public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo, 526 int userId) { 527 Context userContext = null; 528 try { 529 userContext = context.createPackageContextAsUser("system", 0, 530 new UserHandle(userId)); 531 } catch (NameNotFoundException nnfe) { 532 Log.e(LOG_TAG, "Couldn't create package context for user " + userId); 533 return null; 534 } 535 // for each component, try to find metadata 536 XmlResourceParser xml = 537 activityInfo.loadXmlMetaData(userContext.getPackageManager(), MD_LABEL_SEARCHABLE); 538 if (xml == null) { 539 return null; 540 } 541 ComponentName cName = new ComponentName(activityInfo.packageName, activityInfo.name); 542 543 SearchableInfo searchable = getActivityMetaData(userContext, xml, cName); 544 xml.close(); 545 546 if (DBG) { 547 if (searchable != null) { 548 Log.d(LOG_TAG, "Checked " + activityInfo.name 549 + ",label=" + searchable.getLabelId() 550 + ",icon=" + searchable.getIconId() 551 + ",suggestAuthority=" + searchable.getSuggestAuthority() 552 + ",target=" + searchable.getSearchActivity().getClassName() 553 + ",global=" + searchable.shouldIncludeInGlobalSearch() 554 + ",settingsDescription=" + searchable.getSettingsDescriptionId() 555 + ",threshold=" + searchable.getSuggestThreshold()); 556 } else { 557 Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data"); 558 } 559 } 560 return searchable; 561 } 562 563 /** 564 * Get the metadata for a given activity 565 * 566 * @param context runtime context 567 * @param xml XML parser for reading attributes 568 * @param cName The component name of the searchable activity 569 * 570 * @result A completely constructed SearchableInfo, or null if insufficient XML data for it 571 */ getActivityMetaData(Context context, XmlPullParser xml, final ComponentName cName)572 private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml, 573 final ComponentName cName) { 574 SearchableInfo result = null; 575 Context activityContext = createActivityContext(context, cName); 576 if (activityContext == null) return null; 577 578 // in order to use the attributes mechanism, we have to walk the parser 579 // forward through the file until it's reading the tag of interest. 580 try { 581 int tagType = xml.next(); 582 while (tagType != XmlPullParser.END_DOCUMENT) { 583 if (tagType == XmlPullParser.START_TAG) { 584 if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) { 585 AttributeSet attr = Xml.asAttributeSet(xml); 586 if (attr != null) { 587 try { 588 result = new SearchableInfo(activityContext, attr, cName); 589 } catch (IllegalArgumentException ex) { 590 Log.w(LOG_TAG, "Invalid searchable metadata for " + 591 cName.flattenToShortString() + ": " + ex.getMessage()); 592 return null; 593 } 594 } 595 } else if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY)) { 596 if (result == null) { 597 // Can't process an embedded element if we haven't seen the enclosing 598 return null; 599 } 600 AttributeSet attr = Xml.asAttributeSet(xml); 601 if (attr != null) { 602 try { 603 result.addActionKey(new ActionKeyInfo(activityContext, attr)); 604 } catch (IllegalArgumentException ex) { 605 Log.w(LOG_TAG, "Invalid action key for " + 606 cName.flattenToShortString() + ": " + ex.getMessage()); 607 return null; 608 } 609 } 610 } 611 } 612 tagType = xml.next(); 613 } 614 } catch (XmlPullParserException e) { 615 Log.w(LOG_TAG, "Reading searchable metadata for " + cName.flattenToShortString(), e); 616 return null; 617 } catch (IOException e) { 618 Log.w(LOG_TAG, "Reading searchable metadata for " + cName.flattenToShortString(), e); 619 return null; 620 } 621 622 return result; 623 } 624 625 /** 626 * Gets the "label" (user-visible name) of this searchable context. This must be 627 * read using the searchable Activity's resources. 628 * 629 * @return A resource id, or {@code 0} if no label was specified. 630 * @see android.R.styleable#Searchable_label 631 * 632 * @hide deprecated functionality 633 */ 634 @UnsupportedAppUsage getLabelId()635 public int getLabelId() { 636 return mLabelId; 637 } 638 639 /** 640 * Gets the resource id of the hint text. This must be 641 * read using the searchable Activity's resources. 642 * 643 * @return A resource id, or {@code 0} if no hint was specified. 644 * @see android.R.styleable#Searchable_hint 645 */ getHintId()646 public int getHintId() { 647 return mHintId; 648 } 649 650 /** 651 * Gets the icon id specified by the Searchable_icon meta-data entry. This must be 652 * read using the searchable Activity's resources. 653 * 654 * @return A resource id, or {@code 0} if no icon was specified. 655 * @see android.R.styleable#Searchable_icon 656 * 657 * @hide deprecated functionality 658 */ 659 @UnsupportedAppUsage getIconId()660 public int getIconId() { 661 return mIconId; 662 } 663 664 /** 665 * Checks if the searchable activity wants the voice search button to be shown. 666 * 667 * @see android.R.styleable#Searchable_voiceSearchMode 668 */ getVoiceSearchEnabled()669 public boolean getVoiceSearchEnabled() { 670 return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON); 671 } 672 673 /** 674 * Checks if voice search should start web search. 675 * 676 * @see android.R.styleable#Searchable_voiceSearchMode 677 */ getVoiceSearchLaunchWebSearch()678 public boolean getVoiceSearchLaunchWebSearch() { 679 return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH); 680 } 681 682 /** 683 * Checks if voice search should start in-app search. 684 * 685 * @see android.R.styleable#Searchable_voiceSearchMode 686 */ getVoiceSearchLaunchRecognizer()687 public boolean getVoiceSearchLaunchRecognizer() { 688 return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER); 689 } 690 691 /** 692 * Gets the resource id of the voice search language model string. 693 * 694 * @return A resource id, or {@code 0} if no language model was specified. 695 * @see android.R.styleable#Searchable_voiceLanguageModel 696 */ 697 @StringRes getVoiceLanguageModeId()698 public int getVoiceLanguageModeId() { 699 return mVoiceLanguageModeId; 700 } 701 702 /** 703 * Gets the resource id of the voice prompt text string. 704 * 705 * @return A resource id, or {@code 0} if no voice prompt text was specified. 706 * @see android.R.styleable#Searchable_voicePromptText 707 */ 708 @StringRes getVoicePromptTextId()709 public int getVoicePromptTextId() { 710 return mVoicePromptTextId; 711 } 712 713 /** 714 * Gets the resource id of the spoken language to recognize in voice search. 715 * 716 * @return A resource id, or {@code 0} if no language was specified. 717 * @see android.R.styleable#Searchable_voiceLanguage 718 */ 719 @StringRes getVoiceLanguageId()720 public int getVoiceLanguageId() { 721 return mVoiceLanguageId; 722 } 723 724 /** 725 * The maximum number of voice recognition results to return. 726 * 727 * @return the max results count, if specified in the searchable 728 * activity's metadata, or {@code 0} if not specified. 729 * @see android.R.styleable#Searchable_voiceMaxResults 730 */ getVoiceMaxResults()731 public int getVoiceMaxResults() { 732 return mVoiceMaxResults; 733 } 734 735 /** 736 * Gets the resource id of replacement text for the "Search" button. 737 * 738 * @return A resource id, or {@code 0} if no replacement text was specified. 739 * @see android.R.styleable#Searchable_searchButtonText 740 * @hide This feature is deprecated, no need to add it to the API. 741 */ getSearchButtonText()742 public int getSearchButtonText() { 743 return mSearchButtonText; 744 } 745 746 /** 747 * Gets the input type as specified in the searchable attributes. This will default to 748 * {@link InputType#TYPE_CLASS_TEXT} if not specified (which is appropriate 749 * for free text input). 750 * 751 * @return the input type 752 * @see android.R.styleable#Searchable_inputType 753 */ getInputType()754 public int getInputType() { 755 return mSearchInputType; 756 } 757 758 /** 759 * Gets the input method options specified in the searchable attributes. 760 * This will default to {@link EditorInfo#IME_ACTION_GO} if not specified (which is 761 * appropriate for a search box). 762 * 763 * @return the input type 764 * @see android.R.styleable#Searchable_imeOptions 765 */ getImeOptions()766 public int getImeOptions() { 767 return mSearchImeOptions; 768 } 769 770 /** 771 * Checks whether the searchable should be included in global search. 772 * 773 * @return The value of the {@link android.R.styleable#Searchable_includeInGlobalSearch} 774 * attribute, or {@code false} if the attribute is not set. 775 * @see android.R.styleable#Searchable_includeInGlobalSearch 776 */ shouldIncludeInGlobalSearch()777 public boolean shouldIncludeInGlobalSearch() { 778 return mIncludeInGlobalSearch; 779 } 780 781 /** 782 * Checks whether this searchable activity should be queried for suggestions if a prefix 783 * of the query has returned no results. 784 * 785 * @see android.R.styleable#Searchable_queryAfterZeroResults 786 */ queryAfterZeroResults()787 public boolean queryAfterZeroResults() { 788 return mQueryAfterZeroResults; 789 } 790 791 /** 792 * Checks whether this searchable activity has auto URL detection turned on. 793 * 794 * @see android.R.styleable#Searchable_autoUrlDetect 795 */ autoUrlDetect()796 public boolean autoUrlDetect() { 797 return mAutoUrlDetect; 798 } 799 800 /** 801 * Support for parcelable and aidl operations. 802 */ 803 public static final @android.annotation.NonNull Parcelable.Creator<SearchableInfo> CREATOR 804 = new Parcelable.Creator<SearchableInfo>() { 805 public SearchableInfo createFromParcel(Parcel in) { 806 return new SearchableInfo(in); 807 } 808 809 public SearchableInfo[] newArray(int size) { 810 return new SearchableInfo[size]; 811 } 812 }; 813 814 /** 815 * Instantiates a new SearchableInfo from the data in a Parcel that was 816 * previously written with {@link #writeToParcel(Parcel, int)}. 817 * 818 * @param in The Parcel containing the previously written SearchableInfo, 819 * positioned at the location in the buffer where it was written. 820 */ SearchableInfo(Parcel in)821 SearchableInfo(Parcel in) { 822 mLabelId = in.readInt(); 823 mSearchActivity = ComponentName.readFromParcel(in); 824 mHintId = in.readInt(); 825 mSearchMode = in.readInt(); 826 mIconId = in.readInt(); 827 mSearchButtonText = in.readInt(); 828 mSearchInputType = in.readInt(); 829 mSearchImeOptions = in.readInt(); 830 mIncludeInGlobalSearch = in.readInt() != 0; 831 mQueryAfterZeroResults = in.readInt() != 0; 832 mAutoUrlDetect = in.readInt() != 0; 833 834 mSettingsDescriptionId = in.readInt(); 835 mSuggestAuthority = in.readString(); 836 mSuggestPath = in.readString(); 837 mSuggestSelection = in.readString(); 838 mSuggestIntentAction = in.readString(); 839 mSuggestIntentData = in.readString(); 840 mSuggestThreshold = in.readInt(); 841 842 for (int count = in.readInt(); count > 0; count--) { 843 addActionKey(new ActionKeyInfo(in)); 844 } 845 846 mSuggestProviderPackage = in.readString(); 847 848 mVoiceSearchMode = in.readInt(); 849 mVoiceLanguageModeId = in.readInt(); 850 mVoicePromptTextId = in.readInt(); 851 mVoiceLanguageId = in.readInt(); 852 mVoiceMaxResults = in.readInt(); 853 } 854 describeContents()855 public int describeContents() { 856 return 0; 857 } 858 writeToParcel(Parcel dest, int flags)859 public void writeToParcel(Parcel dest, int flags) { 860 dest.writeInt(mLabelId); 861 mSearchActivity.writeToParcel(dest, flags); 862 dest.writeInt(mHintId); 863 dest.writeInt(mSearchMode); 864 dest.writeInt(mIconId); 865 dest.writeInt(mSearchButtonText); 866 dest.writeInt(mSearchInputType); 867 dest.writeInt(mSearchImeOptions); 868 dest.writeInt(mIncludeInGlobalSearch ? 1 : 0); 869 dest.writeInt(mQueryAfterZeroResults ? 1 : 0); 870 dest.writeInt(mAutoUrlDetect ? 1 : 0); 871 872 dest.writeInt(mSettingsDescriptionId); 873 dest.writeString(mSuggestAuthority); 874 dest.writeString(mSuggestPath); 875 dest.writeString(mSuggestSelection); 876 dest.writeString(mSuggestIntentAction); 877 dest.writeString(mSuggestIntentData); 878 dest.writeInt(mSuggestThreshold); 879 880 if (mActionKeys == null) { 881 dest.writeInt(0); 882 } else { 883 dest.writeInt(mActionKeys.size()); 884 for (ActionKeyInfo actionKey : mActionKeys.values()) { 885 actionKey.writeToParcel(dest, flags); 886 } 887 } 888 889 dest.writeString(mSuggestProviderPackage); 890 891 dest.writeInt(mVoiceSearchMode); 892 dest.writeInt(mVoiceLanguageModeId); 893 dest.writeInt(mVoicePromptTextId); 894 dest.writeInt(mVoiceLanguageId); 895 dest.writeInt(mVoiceMaxResults); 896 } 897 } 898