content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.app;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import android.annotation.StringRes;
  import android.annotation.UnsupportedAppUsage;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.pm.ActivityInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.ProviderInfo;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.res.TypedArray;
  import android.content.res.XmlResourceParser;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.os.UserHandle;
  import android.text.InputType;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.Xml;
  import android.view.inputmethod.EditorInfo;
  
  import java.io.IOException;
  import java.util.HashMap;
  
  /**
   * Searchability meta-data for an activity. Only applications that search other applications
   * should need to use this class.
   * See <a href="{@docRoot}guide/topics/search/searchable-config.html">Searchable Configuration</a>
   * for more information about declaring searchability meta-data for your application.
   *
   * @see SearchManager#getSearchableInfo(ComponentName)
   * @see SearchManager#getSearchablesInGlobalSearch()
   */
  public final class SearchableInfo implements Parcelable {
  
      // general debugging support
      private static final boolean DBG = false;
      private static final String LOG_TAG = "SearchableInfo";
  
      // static strings used for XML lookups.
      // TODO how should these be documented for the developer, in a more structured way than 
      // the current long wordy javadoc in SearchManager.java ?
      private static final String MD_LABEL_SEARCHABLE = "android.app.searchable";
      private static final String MD_XML_ELEMENT_SEARCHABLE = "searchable";
      private static final String MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY = "actionkey";
  
      // flags in the searchMode attribute
      private static final int SEARCH_MODE_BADGE_LABEL = 0x04;
      private static final int SEARCH_MODE_BADGE_ICON = 0x08;
      private static final int SEARCH_MODE_QUERY_REWRITE_FROM_DATA = 0x10;
      private static final int SEARCH_MODE_QUERY_REWRITE_FROM_TEXT = 0x20;
  
      // true member variables - what we know about the searchability
      private final int mLabelId;
      private final ComponentName mSearchActivity;
      private final int mHintId;
      private final int mSearchMode;
      private final int mIconId;
      private final int mSearchButtonText;
      private final int mSearchInputType;
      private final int mSearchImeOptions;
      private final boolean mIncludeInGlobalSearch;
      private final boolean mQueryAfterZeroResults;
      private final boolean mAutoUrlDetect;
      private final int mSettingsDescriptionId;
      private final String mSuggestAuthority;
      private final String mSuggestPath;
      private final String mSuggestSelection;
      private final String mSuggestIntentAction;
      private final String mSuggestIntentData;
      private final int mSuggestThreshold;
      // Maps key codes to action key information. auto-boxing is not so bad here,
      // since keycodes for the hard keys are < 127. For such values, Integer.valueOf()
      // uses shared Integer objects.
      // This is not final, to allow lazy initialization.
      private HashMap<Integer,ActionKeyInfo> mActionKeys = null;
      private final String mSuggestProviderPackage;
  
      // Flag values for Searchable_voiceSearchMode
      private static final int VOICE_SEARCH_SHOW_BUTTON = 1;
      private static final int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
      private static final int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
      private final int mVoiceSearchMode;
      private final int mVoiceLanguageModeId;       // voiceLanguageModel
      private final int mVoicePromptTextId;         // voicePromptText
      private final int mVoiceLanguageId;           // voiceLanguage
      private final int mVoiceMaxResults;           // voiceMaxResults
  
      /**
       * Gets the search suggestion content provider authority.
       *
       * @return The search suggestions authority, or {@code null} if not set.
       * @see android.R.styleable#Searchable_searchSuggestAuthority
       */
      public String getSuggestAuthority() {
          return mSuggestAuthority;
      }
  
      /**
       * Gets the name of the package where the suggestion provider lives,
       * or {@code null}.
       */
      public String getSuggestPackage() {
          return mSuggestProviderPackage;
      }
  
      /**
       * Gets the component name of the searchable activity.
       *
       * @return A component name, never {@code null}.
       */
      public ComponentName getSearchActivity() {
          return mSearchActivity;
      }
  
      /**
       * Checks whether the badge should be a text label.
       *
       * @see android.R.styleable#Searchable_searchMode
       *
       * @hide This feature is deprecated, no need to add it to the API.
       */
      public boolean useBadgeLabel() {
          return 0 != (mSearchMode & SEARCH_MODE_BADGE_LABEL);
      }
  
      /**
       * Checks whether the badge should be an icon.
       *
       * @see android.R.styleable#Searchable_searchMode
       *
       * @hide This feature is deprecated, no need to add it to the API.
       */
      public boolean useBadgeIcon() {
          return (0 != (mSearchMode & SEARCH_MODE_BADGE_ICON)) && (mIconId != 0);
      }
  
      /**
       * Checks whether the text in the query field should come from the suggestion intent data.
       *
       * @see android.R.styleable#Searchable_searchMode
       */
      public boolean shouldRewriteQueryFromData() {
          return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_DATA);
      }
  
      /**
       * Checks whether the text in the query field should come from the suggestion title.
       *
       * @see android.R.styleable#Searchable_searchMode
       */
      public boolean shouldRewriteQueryFromText() {
          return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_TEXT);
      }
  
      /**
       * Gets the resource id of the description string to use for this source in system search
       * settings, or {@code 0} if none has been specified.
       *
       * @see android.R.styleable#Searchable_searchSettingsDescription
       */
      public int getSettingsDescriptionId() {
          return mSettingsDescriptionId;
      }
  
      /**
       * Gets the content provider path for obtaining search suggestions.
       * 
       * @return The suggestion path, or {@code null} if not set.
       * @see android.R.styleable#Searchable_searchSuggestPath
       */
      public String getSuggestPath() {
          return mSuggestPath;
      }
  
      /**
       * Gets the selection for obtaining search suggestions.
       *
       * @see android.R.styleable#Searchable_searchSuggestSelection
       */
      public String getSuggestSelection() {
          return mSuggestSelection;
      }
  
      /**
       * Gets the optional intent action for use with these suggestions. This is
       * useful if all intents will have the same action
       * (e.g. {@link android.content.Intent#ACTION_VIEW})
       *
       * This can be overriden in any given suggestion using the column
       * {@link SearchManager#SUGGEST_COLUMN_INTENT_ACTION}.
       *
       * @return The default intent action, or {@code null} if not set.
       * @see android.R.styleable#Searchable_searchSuggestIntentAction
       */
      public String getSuggestIntentAction() {
          return mSuggestIntentAction;
      }
  
      /**
       * Gets the optional intent data for use with these suggestions.  This is
       * useful if all intents will have similar data URIs,
       * but you'll likely need to provide a specific ID as well via the column
       * {@link SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID}, which will be appended to the
       * intent data URI.
       * 
       * This can be overriden in any given suggestion using the column
       * {@link SearchManager#SUGGEST_COLUMN_INTENT_DATA}.
       * 
       * @return The default intent data, or {@code null} if not set.
       * @see android.R.styleable#Searchable_searchSuggestIntentData
       */
      public String getSuggestIntentData() {
          return mSuggestIntentData;
      }
  
      /**
       * Gets the suggestion threshold.
       * 
       * @return The suggestion threshold, or {@code 0} if not set.
       * @see android.R.styleable#Searchable_searchSuggestThreshold
       */
      public int getSuggestThreshold() {
          return mSuggestThreshold;
      }
  
      /**
       * Get the context for the searchable activity.
       *
       * @param context You need to supply a context to start with
       * @return Returns a context related to the searchable activity
       * @hide
       */
      @UnsupportedAppUsage
      public Context getActivityContext(Context context) {
          return createActivityContext(context, mSearchActivity);
      }
  
      /**
       * Creates a context for another activity.
       */
      private static Context createActivityContext(Context context, ComponentName activity) {
          Context theirContext = null;
          try {
              theirContext = context.createPackageContext(activity.getPackageName(), 0);
          } catch (PackageManager.NameNotFoundException e) {
              Log.e(LOG_TAG, "Package not found " + activity.getPackageName());
          } catch (java.lang.SecurityException e) {
              Log.e(LOG_TAG, "Can't make context for " + activity.getPackageName(), e);
          }
          
          return theirContext;
      }
  
      /**
       * Get the context for the suggestions provider.
       *
       * @param context You need to supply a context to start with
       * @param activityContext If we can determine that the provider and the activity are the
       *        same, we'll just return this one.
       * @return Returns a context related to the suggestion provider
       * @hide
       */
      @UnsupportedAppUsage
      public Context getProviderContext(Context context, Context activityContext) {
          Context theirContext = null;
          if (mSearchActivity.getPackageName().equals(mSuggestProviderPackage)) {
              return activityContext;
          }
          if (mSuggestProviderPackage != null) {
              try {
                  theirContext = context.createPackageContext(mSuggestProviderPackage, 0);
              } catch (PackageManager.NameNotFoundException e) {
                  // unexpected, but we deal with this by null-checking theirContext
              } catch (java.lang.SecurityException e) {
                  // unexpected, but we deal with this by null-checking theirContext
              }
          }
          return theirContext;
      }
  
      /**
       * Constructor
       * 
       * Given a ComponentName, get the searchability info
       * and build a local copy of it.  Use the factory, not this.
       * 
       * @param activityContext runtime context for the activity that the searchable info is about.
       * @param attr The attribute set we found in the XML file, contains the values that are used to
       * construct the object.
       * @param cName The component name of the searchable activity
       * @throws IllegalArgumentException if the searchability info is invalid or insufficient
       */
      @UnsupportedAppUsage
      private SearchableInfo(Context activityContext, AttributeSet attr, final ComponentName cName) {
          mSearchActivity = cName;
          
          TypedArray a = activityContext.obtainStyledAttributes(attr,
                  com.android.internal.R.styleable.Searchable);
          mSearchMode = a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0);
          mLabelId = a.getResourceId(com.android.internal.R.styleable.Searchable_label, 0);
          mHintId = a.getResourceId(com.android.internal.R.styleable.Searchable_hint, 0);
          mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0);
          mSearchButtonText = a.getResourceId(
                  com.android.internal.R.styleable.Searchable_searchButtonText, 0);
          mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType, 
                  InputType.TYPE_CLASS_TEXT |
                  InputType.TYPE_TEXT_VARIATION_NORMAL);
          mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions, 
                  EditorInfo.IME_ACTION_GO);
          mIncludeInGlobalSearch = a.getBoolean(
                  com.android.internal.R.styleable.Searchable_includeInGlobalSearch, false);
          mQueryAfterZeroResults = a.getBoolean(
                  com.android.internal.R.styleable.Searchable_queryAfterZeroResults, false);
          mAutoUrlDetect = a.getBoolean(
                  com.android.internal.R.styleable.Searchable_autoUrlDetect, false);
  
          mSettingsDescriptionId = a.getResourceId(
                  com.android.internal.R.styleable.Searchable_searchSettingsDescription, 0);
          mSuggestAuthority = a.getString(
                  com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
          mSuggestPath = a.getString(
                  com.android.internal.R.styleable.Searchable_searchSuggestPath);
          mSuggestSelection = a.getString(
                  com.android.internal.R.styleable.Searchable_searchSuggestSelection);
          mSuggestIntentAction = a.getString(
                  com.android.internal.R.styleable.Searchable_searchSuggestIntentAction);
          mSuggestIntentData = a.getString(
                  com.android.internal.R.styleable.Searchable_searchSuggestIntentData);
          mSuggestThreshold = a.getInt(
                  com.android.internal.R.styleable.Searchable_searchSuggestThreshold, 0);
  
          mVoiceSearchMode = 
              a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0);
          // TODO this didn't work - came back zero from YouTube
          mVoiceLanguageModeId = 
              a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0);
          mVoicePromptTextId = 
              a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0);
          mVoiceLanguageId = 
              a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0);
          mVoiceMaxResults = 
              a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0);
  
          a.recycle();
  
          // get package info for suggestions provider (if any)
          String suggestProviderPackage = null;
          if (mSuggestAuthority != null) {
              PackageManager pm = activityContext.getPackageManager();
              ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority,
                      PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
              if (pi != null) {
                  suggestProviderPackage = pi.packageName;
              }
          }
          mSuggestProviderPackage = suggestProviderPackage;
  
          // for now, implement some form of rules - minimal data
          if (mLabelId == 0) {
              throw new IllegalArgumentException("Search label must be a resource reference.");
          }
      }
  
      /**
       * Information about an action key in searchability meta-data.
       *
       * @see SearchableInfo#findActionKey(int)
       *
       * @hide This feature is used very little, and on many devices there are no reasonable
       *       keys to use for actions.
       */
      public static class ActionKeyInfo implements Parcelable {
  
          private final int mKeyCode;
          private final String mQueryActionMsg;
          private final String mSuggestActionMsg;
          private final String mSuggestActionMsgColumn;
  
          /**
           * Create one object using attributeset as input data.
           * @param activityContext runtime context of the activity that the action key information
           *        is about.
           * @param attr The attribute set we found in the XML file, contains the values that are used to
           * construct the object.
           * @throws IllegalArgumentException if the action key configuration is invalid
           */
          ActionKeyInfo(Context activityContext, AttributeSet attr) {
              TypedArray a = activityContext.obtainStyledAttributes(attr,
                      com.android.internal.R.styleable.SearchableActionKey);
  
              mKeyCode = a.getInt(
                      com.android.internal.R.styleable.SearchableActionKey_keycode, 0);
              mQueryActionMsg = a.getString(
                      com.android.internal.R.styleable.SearchableActionKey_queryActionMsg);
              mSuggestActionMsg = a.getString(
                      com.android.internal.R.styleable.SearchableActionKey_suggestActionMsg);
              mSuggestActionMsgColumn = a.getString(
                      com.android.internal.R.styleable.SearchableActionKey_suggestActionMsgColumn);
              a.recycle();
  
              // sanity check.
              if (mKeyCode == 0) {
                  throw new IllegalArgumentException("No keycode.");
              } else if ((mQueryActionMsg == null) && 
                      (mSuggestActionMsg == null) && 
                      (mSuggestActionMsgColumn == null)) {
                  throw new IllegalArgumentException("No message information.");
              }
          }
  
          /**
           * Instantiate a new ActionKeyInfo from the data in a Parcel that was
           * previously written with {@link #writeToParcel(Parcel, int)}.
           *
           * @param in The Parcel containing the previously written ActionKeyInfo,
           * positioned at the location in the buffer where it was written.
           */
          private ActionKeyInfo(Parcel in) {
              mKeyCode = in.readInt();
              mQueryActionMsg = in.readString();
              mSuggestActionMsg = in.readString();
              mSuggestActionMsgColumn = in.readString();
          }
  
          /**
           * Gets the key code that this action key info is for.
           * @see android.R.styleable#SearchableActionKey_keycode
           */
          public int getKeyCode() {
              return mKeyCode;
          }
  
          /**
           * Gets the action message to use for queries.
           * @see android.R.styleable#SearchableActionKey_queryActionMsg
           */
          @UnsupportedAppUsage
          public String getQueryActionMsg() {
              return mQueryActionMsg;
          }
  
          /**
           * Gets the action message to use for suggestions.
           * @see android.R.styleable#SearchableActionKey_suggestActionMsg
           */
          @UnsupportedAppUsage
          public String getSuggestActionMsg() {
              return mSuggestActionMsg;
          }
  
          /**
           * Gets the name of the column to get the suggestion action message from.
           * @see android.R.styleable#SearchableActionKey_suggestActionMsgColumn
           */
          @UnsupportedAppUsage
          public String getSuggestActionMsgColumn() {
              return mSuggestActionMsgColumn;
          }
  
          public int describeContents() {
              return 0;
          }
  
          public void writeToParcel(Parcel dest, int flags) {
              dest.writeInt(mKeyCode);
              dest.writeString(mQueryActionMsg);
              dest.writeString(mSuggestActionMsg);
              dest.writeString(mSuggestActionMsgColumn);
          }
      }
  
      /**
       * If any action keys were defined for this searchable activity, look up and return.
       * 
       * @param keyCode The key that was pressed
       * @return Returns the action key info, or {@code null} if none defined.
       *
       * @hide ActionKeyInfo is hidden
       */
      @UnsupportedAppUsage
      public ActionKeyInfo findActionKey(int keyCode) {
          if (mActionKeys == null) {
              return null;
          }
          return mActionKeys.get(keyCode);
      }
  
      private void addActionKey(ActionKeyInfo keyInfo) {
          if (mActionKeys == null) {
              mActionKeys = new HashMap<Integer,ActionKeyInfo>();
          }
          mActionKeys.put(keyInfo.getKeyCode(), keyInfo);
      }
  
      /**
       * Gets search information for the given activity.
       *
       * @param context Context to use for reading activity resources.
       * @param activityInfo Activity to get search information from.
       * @return Search information about the given activity, or {@code null} if
       *         the activity has no or invalid searchability meta-data.
       *
       * @hide For use by SearchManagerService.
       */
      public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo,
              int userId) {
          Context userContext = null;
          try {
              userContext = context.createPackageContextAsUser("system", 0,
                  new UserHandle(userId));
          } catch (NameNotFoundException nnfe) {
              Log.e(LOG_TAG, "Couldn't create package context for user " + userId);
              return null;
          }
          // for each component, try to find metadata
          XmlResourceParser xml = 
                  activityInfo.loadXmlMetaData(userContext.getPackageManager(), MD_LABEL_SEARCHABLE);
          if (xml == null) {
              return null;
          }
          ComponentName cName = new ComponentName(activityInfo.packageName, activityInfo.name);
          
          SearchableInfo searchable = getActivityMetaData(userContext, xml, cName);
          xml.close();
          
          if (DBG) {
              if (searchable != null) {
                  Log.d(LOG_TAG, "Checked " + activityInfo.name
                          + ",label=" + searchable.getLabelId()
                          + ",icon=" + searchable.getIconId()
                          + ",suggestAuthority=" + searchable.getSuggestAuthority()
                          + ",target=" + searchable.getSearchActivity().getClassName()
                          + ",global=" + searchable.shouldIncludeInGlobalSearch()
                          + ",settingsDescription=" + searchable.getSettingsDescriptionId()
                          + ",threshold=" + searchable.getSuggestThreshold());
              } else {
                  Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data");
              }
          }
          return searchable;
      }
  
      /**
       * Get the metadata for a given activity
       * 
       * @param context runtime context
       * @param xml XML parser for reading attributes
       * @param cName The component name of the searchable activity
       * 
       * @result A completely constructed SearchableInfo, or null if insufficient XML data for it
       */
      private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml,
              final ComponentName cName)  {
          SearchableInfo result = null;
          Context activityContext = createActivityContext(context, cName);
          if (activityContext == null) return null;
  
          // in order to use the attributes mechanism, we have to walk the parser
          // forward through the file until it's reading the tag of interest.
          try {
              int tagType = xml.next();
              while (tagType != XmlPullParser.END_DOCUMENT) {
                  if (tagType == XmlPullParser.START_TAG) {
                      if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) {
                          AttributeSet attr = Xml.asAttributeSet(xml);
                          if (attr != null) {
                              try {
                                  result = new SearchableInfo(activityContext, attr, cName);
                              } catch (IllegalArgumentException ex) {
                                  Log.w(LOG_TAG, "Invalid searchable metadata for " +
                                          cName.flattenToShortString() + ": " + ex.getMessage());
                                  return null;
                              }
                          }
                      } else if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY)) {
                          if (result == null) {
                              // Can't process an embedded element if we haven't seen the enclosing
                              return null;
                          }
                          AttributeSet attr = Xml.asAttributeSet(xml);
                          if (attr != null) {
                              try {
                                  result.addActionKey(new ActionKeyInfo(activityContext, attr));
                              } catch (IllegalArgumentException ex) {
                                  Log.w(LOG_TAG, "Invalid action key for " +
                                          cName.flattenToShortString() + ": " + ex.getMessage());
                                  return null;
                              }
                          }
                      }
                  }
                  tagType = xml.next();
              }
          } catch (XmlPullParserException e) {
              Log.w(LOG_TAG, "Reading searchable metadata for " + cName.flattenToShortString(), e);
              return null;
          } catch (IOException e) {
              Log.w(LOG_TAG, "Reading searchable metadata for " + cName.flattenToShortString(), e);
              return null;
          }
          
          return result;
      }
  
      /**
       * Gets the "label" (user-visible name) of this searchable context. This must be
       * read using the searchable Activity's resources.
       * 
       * @return A resource id, or {@code 0} if no label was specified.
       * @see android.R.styleable#Searchable_label
       *
       * @hide deprecated functionality
       */
      @UnsupportedAppUsage
      public int getLabelId() {
          return mLabelId;
      }
  
      /**
       * Gets the resource id of the hint text. This must be
       * read using the searchable Activity's resources.
       * 
       * @return A resource id, or {@code 0} if no hint was specified.
       * @see android.R.styleable#Searchable_hint
       */
      public int getHintId() {
          return mHintId;
      }
  
      /**
       * Gets the icon id specified by the Searchable_icon meta-data entry. This must be
       * read using the searchable Activity's resources.
       * 
       * @return A resource id, or {@code 0} if no icon was specified.
       * @see android.R.styleable#Searchable_icon
       *
       * @hide deprecated functionality
       */
      @UnsupportedAppUsage
      public int getIconId() {
          return mIconId;
      }
  
      /**
       * Checks if the searchable activity wants the voice search button to be shown.
       *
       * @see android.R.styleable#Searchable_voiceSearchMode
       */
      public boolean getVoiceSearchEnabled() {
          return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON);
      }
  
      /**
       * Checks if voice search should start web search.
       *
       * @see android.R.styleable#Searchable_voiceSearchMode
       */
      public boolean getVoiceSearchLaunchWebSearch() {
          return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH);
      }
  
      /**
       * Checks if voice search should start in-app search.
       *
       * @see android.R.styleable#Searchable_voiceSearchMode
       */
      public boolean getVoiceSearchLaunchRecognizer() {
          return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER);
      }
  
      /**
       * Gets the resource id of the voice search language model string.
       *
       * @return A resource id, or {@code 0} if no language model was specified.
       * @see android.R.styleable#Searchable_voiceLanguageModel
       */
      @StringRes
      public int getVoiceLanguageModeId() {
          return mVoiceLanguageModeId;
      }
  
      /**
       * Gets the resource id of the voice prompt text string.
       *
       * @return A resource id, or {@code 0} if no voice prompt text was specified.
       * @see android.R.styleable#Searchable_voicePromptText
       */
      @StringRes
      public int getVoicePromptTextId() {
          return mVoicePromptTextId;
      }
  
      /**
       * Gets the resource id of the spoken language to recognize in voice search.
       *
       * @return A resource id, or {@code 0} if no language was specified.
       * @see android.R.styleable#Searchable_voiceLanguage
       */
      @StringRes
      public int getVoiceLanguageId() {
          return mVoiceLanguageId;
      }
  
      /**
       * The maximum number of voice recognition results to return.
       *
       * @return the max results count, if specified in the searchable
       *         activity's metadata, or {@code 0} if not specified.
       * @see android.R.styleable#Searchable_voiceMaxResults
       */
      public int getVoiceMaxResults() {
          return mVoiceMaxResults;
      }
  
      /**
       * Gets the resource id of replacement text for the "Search" button.
       *
       * @return A resource id, or {@code 0} if no replacement text was specified.
       * @see android.R.styleable#Searchable_searchButtonText
       * @hide This feature is deprecated, no need to add it to the API.
       */
      public int getSearchButtonText() {
          return mSearchButtonText;
      }
  
      /**
       * Gets the input type as specified in the searchable attributes. This will default to
       * {@link InputType#TYPE_CLASS_TEXT} if not specified (which is appropriate
       * for free text input).
       * 
       * @return the input type
       * @see android.R.styleable#Searchable_inputType
       */
      public int getInputType() {
          return mSearchInputType;
      }
  
      /**
       * Gets the input method options specified in the searchable attributes.
       * This will default to {@link EditorInfo#IME_ACTION_GO} if not specified (which is
       * appropriate for a search box).
       * 
       * @return the input type
       * @see android.R.styleable#Searchable_imeOptions
       */
      public int getImeOptions() {
          return mSearchImeOptions;
      }
  
      /**
       * Checks whether the searchable should be included in global search.
       *
       * @return The value of the {@link android.R.styleable#Searchable_includeInGlobalSearch}
       *         attribute, or {@code false} if the attribute is not set.
       * @see android.R.styleable#Searchable_includeInGlobalSearch
       */
      public boolean shouldIncludeInGlobalSearch() {
          return mIncludeInGlobalSearch;
      }
  
      /**
       * Checks whether this searchable activity should be queried for suggestions if a prefix
       * of the query has returned no results.
       *
       * @see android.R.styleable#Searchable_queryAfterZeroResults
       */
      public boolean queryAfterZeroResults() {
          return mQueryAfterZeroResults;
      }
  
      /**
       * Checks whether this searchable activity has auto URL detection turned on.
       *
       * @see android.R.styleable#Searchable_autoUrlDetect
       */
      public boolean autoUrlDetect() {
          return mAutoUrlDetect;
      }
  
      /**
       * Support for parcelable and aidl operations.
       */
      public static final @android.annotation.NonNull Parcelable.Creator<SearchableInfo> CREATOR
      = new Parcelable.Creator<SearchableInfo>() {
          public SearchableInfo createFromParcel(Parcel in) {
              return new SearchableInfo(in);
          }
  
          public SearchableInfo[] newArray(int size) {
              return new SearchableInfo[size];
          }
      };
  
      /**
       * Instantiates a new SearchableInfo from the data in a Parcel that was
       * previously written with {@link #writeToParcel(Parcel, int)}.
       *
       * @param in The Parcel containing the previously written SearchableInfo,
       * positioned at the location in the buffer where it was written.
       */
      SearchableInfo(Parcel in) {
          mLabelId = in.readInt();
          mSearchActivity = ComponentName.readFromParcel(in);
          mHintId = in.readInt();
          mSearchMode = in.readInt();
          mIconId = in.readInt();
          mSearchButtonText = in.readInt();
          mSearchInputType = in.readInt();
          mSearchImeOptions = in.readInt();
          mIncludeInGlobalSearch = in.readInt() != 0;
          mQueryAfterZeroResults = in.readInt() != 0;
          mAutoUrlDetect = in.readInt() != 0;
          
          mSettingsDescriptionId = in.readInt();
          mSuggestAuthority = in.readString();
          mSuggestPath = in.readString();
          mSuggestSelection = in.readString();
          mSuggestIntentAction = in.readString();
          mSuggestIntentData = in.readString();
          mSuggestThreshold = in.readInt();
  
          for (int count = in.readInt(); count > 0; count--) {
              addActionKey(new ActionKeyInfo(in));
          }
  
          mSuggestProviderPackage = in.readString();
          
          mVoiceSearchMode = in.readInt();
          mVoiceLanguageModeId = in.readInt();
          mVoicePromptTextId = in.readInt();
          mVoiceLanguageId = in.readInt();
          mVoiceMaxResults = in.readInt();
      }
  
      public int describeContents() {
          return 0;
      }
  
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeInt(mLabelId);
          mSearchActivity.writeToParcel(dest, flags);
          dest.writeInt(mHintId);
          dest.writeInt(mSearchMode);
          dest.writeInt(mIconId);
          dest.writeInt(mSearchButtonText);
          dest.writeInt(mSearchInputType);
          dest.writeInt(mSearchImeOptions);
          dest.writeInt(mIncludeInGlobalSearch ? 1 : 0);
          dest.writeInt(mQueryAfterZeroResults ? 1 : 0);
          dest.writeInt(mAutoUrlDetect ? 1 : 0);
          
          dest.writeInt(mSettingsDescriptionId);
          dest.writeString(mSuggestAuthority);
          dest.writeString(mSuggestPath);
          dest.writeString(mSuggestSelection);
          dest.writeString(mSuggestIntentAction);
          dest.writeString(mSuggestIntentData);
          dest.writeInt(mSuggestThreshold);
  
          if (mActionKeys == null) {
              dest.writeInt(0);
          } else {
              dest.writeInt(mActionKeys.size());
              for (ActionKeyInfo actionKey : mActionKeys.values()) {
                  actionKey.writeToParcel(dest, flags);
              }
          }
  
          dest.writeString(mSuggestProviderPackage);
  
          dest.writeInt(mVoiceSearchMode);
          dest.writeInt(mVoiceLanguageModeId);
          dest.writeInt(mVoicePromptTextId);
          dest.writeInt(mVoiceLanguageId);
          dest.writeInt(mVoiceMaxResults);
      }
  }
  