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