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