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