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