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