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