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