|  | /* | 
|  | * 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.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 | 
|  | */ | 
|  | 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 | 
|  | */ | 
|  | 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 | 
|  | */ | 
|  | 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 | 
|  | */ | 
|  | public String getQueryActionMsg() { | 
|  | return mQueryActionMsg; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the action message to use for suggestions. | 
|  | * @see android.R.styleable#SearchableActionKey_suggestActionMsg | 
|  | */ | 
|  | public String getSuggestActionMsg() { | 
|  | return mSuggestActionMsg; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the name of the column to get the suggestion action message from. | 
|  | * @see android.R.styleable#SearchableActionKey_suggestActionMsgColumn | 
|  | */ | 
|  | 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 | 
|  | */ | 
|  | 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 | 
|  | */ | 
|  | 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 | 
|  | */ | 
|  | 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 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); | 
|  | } | 
|  | } |