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