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