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