Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app;
     18 
     19 import org.xmlpull.v1.XmlPullParser;
     20 import org.xmlpull.v1.XmlPullParserException;
     21 
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.pm.ActivityInfo;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.ProviderInfo;
     27 import android.content.pm.PackageManager.NameNotFoundException;
     28 import android.content.res.TypedArray;
     29 import android.content.res.XmlResourceParser;
     30 import android.os.Parcel;
     31 import android.os.Parcelable;
     32 import android.os.UserHandle;
     33 import android.text.InputType;
     34 import android.util.AttributeSet;
     35 import android.util.Log;
     36 import android.util.Xml;
     37 import android.view.inputmethod.EditorInfo;
     38 
     39 import java.io.IOException;
     40 import 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  */
     51 public 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