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