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