FileDocCategorySizeDatePackage
SearchableInfo.javaAPI DocAndroid 1.5 API34681Wed May 06 22:41:56 BST 2009android.server.search

SearchableInfo

public final class SearchableInfo extends Object implements android.os.Parcelable

Fields Summary
static final String
LOG_TAG
static final int
DBG_INHIBIT_SUGGESTIONS
private static final String
MD_LABEL_DEFAULT_SEARCHABLE
private static final String
MD_LABEL_SEARCHABLE
private static final String
MD_SEARCHABLE_SYSTEM_SEARCH
private static final String
MD_XML_ELEMENT_SEARCHABLE
private static final String
MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY
private static HashMap
sSearchablesMap
private static ArrayList
sSearchablesList
private static SearchableInfo
sDefaultSearchable
public boolean
mSearchable
private int
mLabelId
public android.content.ComponentName
mSearchActivity
private int
mHintId
private int
mSearchMode
public boolean
mBadgeLabel
public boolean
mBadgeIcon
public boolean
mQueryRewriteFromData
public boolean
mQueryRewriteFromText
private int
mIconId
private int
mSearchButtonText
private int
mSearchInputType
private int
mSearchImeOptions
private String
mSuggestAuthority
private String
mSuggestPath
private String
mSuggestSelection
private String
mSuggestIntentAction
private String
mSuggestIntentData
private ActionKeyInfo
mActionKeyList
private String
mSuggestProviderPackage
private android.content.Context
mCacheActivityContext
private static int
VOICE_SEARCH_SHOW_BUTTON
private static int
VOICE_SEARCH_LAUNCH_WEB_SEARCH
private static int
VOICE_SEARCH_LAUNCH_RECOGNIZER
private int
mVoiceSearchMode
private int
mVoiceLanguageModeId
private int
mVoicePromptTextId
private int
mVoiceLanguageId
private int
mVoiceMaxResults
public static final Parcelable.Creator
CREATOR
Support for parcelable and aidl operations.
Constructors Summary
private SearchableInfo(android.content.Context context, android.util.AttributeSet attr, android.content.ComponentName cName)
Constructor Given a ComponentName, get the searchability info and build a local copy of it. Use the factory, not this.

param
context runtime context
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

        // initialize as an "unsearchable" object
        mSearchable = false;
        mSearchActivity = cName;
        
        // to access another activity's resources, I need its context.
        // BE SURE to release the cache sometime after construction - it's a large object to hold
        mCacheActivityContext = getActivityContext(context);
        if (mCacheActivityContext != null) {
            TypedArray a = mCacheActivityContext.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_SEARCH);

            setSearchModeFlags();
            if (DBG_INHIBIT_SUGGESTIONS == 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);
            }
            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)
            if (mSuggestAuthority != null) {
                ProviderInfo pi =
                    context.getPackageManager().resolveContentProvider(mSuggestAuthority,
                            0);
                if (pi != null) {
                    mSuggestProviderPackage = pi.packageName;
                }
            }
        }

        // for now, implement some form of rules - minimal data
        if (mLabelId != 0) {
            mSearchable = true;
        } else {
            // Provide some help for developers instead of just silently discarding
            Log.w(LOG_TAG, "Insufficient metadata to configure searchability for " + 
                    cName.flattenToShortString());
        }
    
public SearchableInfo(android.os.Parcel in)
Instantiate 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.


                                               
       
        mSearchable = in.readInt() != 0;
        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();
        setSearchModeFlags();

        mSuggestAuthority = in.readString();
        mSuggestPath = in.readString();
        mSuggestSelection = in.readString();
        mSuggestIntentAction = in.readString();
        mSuggestIntentData = in.readString();

        mActionKeyList = null;
        int count = in.readInt();
        while (count-- > 0) {
            mActionKeyList = new ActionKeyInfo(in, mActionKeyList);
        }
        
        mSuggestProviderPackage = in.readString();
        
        mVoiceSearchMode = in.readInt();
        mVoiceLanguageModeId = in.readInt();
        mVoicePromptTextId = in.readInt();
        mVoiceLanguageId = in.readInt();
        mVoiceMaxResults = in.readInt();
    
Methods Summary
public static voidbuildSearchableList(android.content.Context context)
Super-factory. Builds an entire list (suitable for display) of activities that are searchable, by iterating the entire set of ACTION_SEARCH intents. Also clears the hash of all activities -> searches which will refill as the user clicks "search". This should only be done at startup and again if we know that the list has changed. TODO: every activity that provides a ACTION_SEARCH intent should also provide searchability meta-data. There are a bunch of checks here that, if data is not found, silently skip to the next activity. This won't help a developer trying to figure out why their activity isn't showing up in the list, but an exception here is too rough. I would like to find a better notification mechanism. TODO: sort the list somehow? UI choice.

param
context a context we can use during this work

        
        // create empty hash & list
        HashMap<ComponentName, SearchableInfo> newSearchablesMap 
                                = new HashMap<ComponentName, SearchableInfo>();
        ArrayList<SearchableInfo> newSearchablesList
                                = new ArrayList<SearchableInfo>();

        // use intent resolver to generate list of ACTION_SEARCH receivers
        final PackageManager pm = context.getPackageManager();
        List<ResolveInfo> infoList;
        final Intent intent = new Intent(Intent.ACTION_SEARCH);
        infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
        
        // analyze each one, generate a Searchables record, and record
        if (infoList != null) {
            int count = infoList.size();
            for (int ii = 0; ii < count; ii++) {
                // for each component, try to find metadata
                ResolveInfo info = infoList.get(ii);
                ActivityInfo ai = info.activityInfo;
                XmlResourceParser xml = ai.loadXmlMetaData(context.getPackageManager(), 
                                                       MD_LABEL_SEARCHABLE);
                if (xml == null) {
                    continue;
                }
                ComponentName cName = new ComponentName(
                        info.activityInfo.packageName, 
                        info.activityInfo.name);
                
                SearchableInfo searchable = getActivityMetaData(context, xml, cName);
                xml.close();
                
                if (searchable != null) {
                    // no need to keep the context any longer.  setup time is over.
                    searchable.mCacheActivityContext  = null;
                    
                    newSearchablesList.add(searchable);
                    newSearchablesMap.put(cName, searchable);
                }
            }
        }
        
        // record the final values as a coherent pair
        synchronized (SearchableInfo.class) {
            sSearchablesList = newSearchablesList;
            sSearchablesMap = newSearchablesMap;
        }
    
public intdescribeContents()

        return 0;
    
public android.server.search.SearchableInfo$ActionKeyInfofindActionKey(int keyCode)
If any action keys were defined for this searchable activity, look up and return.

param
keyCode The key that was pressed
return
Returns the ActionKeyInfo record, or null if none defined

        ActionKeyInfo info = mActionKeyList;
        while (info != null) {
            if (info.mKeyCode == keyCode) {
                return info;
            }
            info = info.mNext;
        }
        return null;
    
public android.content.ContextgetActivityContext(android.content.Context context)
Get the context for the searchable activity. This is fairly expensive so do it on the original scan, or when an app is selected, but don't hang on to the result forever.

param
context You need to supply a context to start with
return
Returns a context related to the searchable activity

        Context theirContext = null;
        try {
            theirContext = context.createPackageContext(mSearchActivity.getPackageName(), 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;
    
private static android.server.search.SearchableInfogetActivityMetaData(android.content.Context context, org.xmlpull.v1.XmlPullParser xml, android.content.ComponentName cName)
Get the metadata for a given activity TODO: clean up where we return null vs. where we throw exceptions.

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

        SearchableInfo result = 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) {
                            result = new SearchableInfo(context, attr, cName);
                            // if the constructor returned a bad object, exit now.
                            if (! result.mSearchable) {
                                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) {
                            ActionKeyInfo keyInfo = result.new ActionKeyInfo(context, attr, 
                                    result.mActionKeyList);
                            // only add to list if it is was useable
                            if (keyInfo.mKeyCode != 0) {
                                result.mActionKeyList = keyInfo;
                            }
                        }
                    }
                }
                tagType = xml.next();
            }
        } catch (XmlPullParserException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return result;
    
public static android.server.search.SearchableInfogetDefaultSearchable()
Provides the system-default search activity, which you can use whenever getSearchableInfo() returns null;

return
Returns the system-default search activity, null if never defined

        synchronized (SearchableInfo.class) {
            return sDefaultSearchable;
        }
    
public intgetHintId()
Return the resource Id of the hint text. This must be accessed using the target (searchable) Activity's resources, not simply the context of the caller.

return
Returns the resource Id, or 0 if not specified by this package.

        return mHintId;
    
public intgetIconId()
Return the icon Id specified by the Searchable_icon meta-data entry. This must be accessed using the target (searchable) Activity's resources, not simply the context of the caller.

return
Returns the resource id.

        return mIconId;
    
public intgetImeOptions()
Return the input method options specified in the searchable attributes. This will default to EditorInfo.ACTION_SEARCH if not specified (which is appropriate for a search box).

return
the input type

        return mSearchImeOptions;
    
public intgetInputType()
Return the input type as specified in the searchable attributes. This will default to InputType.TYPE_CLASS_TEXT if not specified (which is appropriate for free text input).

return
the input type

        return mSearchInputType;
    
public intgetLabelId()
Return the "label" (user-visible name) of this searchable context. This must be accessed using the target (searchable) Activity's resources, not simply the context of the caller.

return
Returns the resource Id

        return mLabelId;
    
public android.content.ContextgetProviderContext(android.content.Context context, android.content.Context activityContext)
Get the context for the suggestions provider. This is fairly expensive so do it on the original scan, or when an app is selected, but don't hang on to the result forever.

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 context provider

        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;
    
public intgetSearchButtonText()
Return the resource Id of replacement text for the "Search" button.

return
Returns the resource Id, or 0 if not specified by this package.

        return mSearchButtonText;
    
public static android.server.search.SearchableInfogetSearchableInfo(android.content.Context context, android.content.ComponentName activity)
Factory. Look up, or construct, based on the activity. The activities fall into three cases, based on meta-data found in the manifest entry:
  1. The activity itself implements search. This is indicated by the presence of a "android.app.searchable" meta-data attribute. The value is a reference to an XML file containing search information.
  2. A related activity implements search. This is indicated by the presence of a "android.app.default_searchable" meta-data attribute. The value is a string naming the activity implementing search. In this case the factory will "redirect" and return the searchable data.
  3. No searchability data is provided. We return null here and other code will insert the "default" (e.g. contacts) search. TODO: cache the result in the map, and check the map first. TODO: it might make sense to implement the searchable reference as an application meta-data entry. This way we don't have to pepper each and every activity. TODO: can we skip the constructor step if it's a non-searchable? TODO: does it make sense to plug the default into a slot here for automatic return? Probably not, but it's one way to do it.

    param
    activity The name of the current activity, or null if the activity does not define any explicit searchable metadata.

            // Step 1.  Is the result already hashed?  (case 1)
            SearchableInfo result;
            synchronized (SearchableInfo.class) {
                result = sSearchablesMap.get(activity);
                if (result != null) return result;
            }
            
            // Step 2.  See if the current activity references a searchable.
            // Note:  Conceptually, this could be a while(true) loop, but there's
            // no point in implementing reference chaining here and risking a loop.  
            // References must point directly to searchable activities.
           
            ActivityInfo ai = null;
            XmlPullParser xml = null;
            try {
                ai = context.getPackageManager().
                           getActivityInfo(activity, PackageManager.GET_META_DATA );
                String refActivityName = null;
                
                // First look for activity-specific reference
                Bundle md = ai.metaData;
                if (md != null) {
                    refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
                }
                // If not found, try for app-wide reference
                if (refActivityName == null) {
                    md = ai.applicationInfo.metaData;
                    if (md != null) {
                        refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
                    }
                }
                
                // Irrespective of source, if a reference was found, follow it.
                if (refActivityName != null)
                {
                    // An app or activity can declare that we should simply launch 
                    // "system default search" if search is invoked.
                    if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
                        return getDefaultSearchable();
                    }
                    String pkg = activity.getPackageName();
                    ComponentName referredActivity;
                    if (refActivityName.charAt(0) == '.") {
                        referredActivity = new ComponentName(pkg, pkg + refActivityName);
                    } else {
                        referredActivity = new ComponentName(pkg, refActivityName);
                    }
    
                    // Now try the referred activity, and if found, cache
                    // it against the original name so we can skip the check
                    synchronized (SearchableInfo.class) {
                        result = sSearchablesMap.get(referredActivity);
                        if (result != null) {
                            sSearchablesMap.put(activity, result);
                            return result;
                        }
                    }
                }
            } catch (PackageManager.NameNotFoundException e) {
                // case 3: no metadata
            }
     
            // Step 3.  None found. Return null.
            return null;
            
        
public static java.util.ArrayListgetSearchablesList()
Return the list of searchable activities, for use in the drop-down.

        synchronized (SearchableInfo.class) {
            ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(sSearchablesList);
            return result;
        }
    
public java.lang.StringgetSuggestAuthority()
Retrieve the authority for obtaining search suggestions.

return
Returns a string containing the suggestions authority.

        return mSuggestAuthority;
    
public java.lang.StringgetSuggestIntentAction()
Retrieve the (optional) intent action for use with these suggestions. This is useful if all intents will have the same action (e.g. "android.intent.action.VIEW"). Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_ACTION column.

return
Returns a string containing the default intent action.

        return mSuggestIntentAction;
    
public java.lang.StringgetSuggestIntentData()
Retrieve the (optional) intent data for use with these suggestions. This is useful if all intents will have similar data URIs (e.g. "android.intent.action.VIEW"), but you'll likely need to provide a specific ID as well via the column AUTOSUGGEST_COLUMN_INTENT_DATA_ID, which will be appended to the intent data URI. Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_DATA column.

return
Returns a string containing the default intent data.

        return mSuggestIntentData;
    
public java.lang.StringgetSuggestPath()
Retrieve the path for obtaining search suggestions.

return
Returns a string containing the suggestions path, or null if not provided.

        return mSuggestPath;
    
public java.lang.StringgetSuggestSelection()
Retrieve the selection pattern for obtaining search suggestions. This must include a single ? which will be used for the user-typed characters.

return
Returns a string containing the suggestions authority.

        return mSuggestSelection;
    
public intgetVoiceLanguageId()

return
the resource Id of the spoken langauge, if specified in the searchable activity's metadata, or 0 if not specified.

        return mVoiceLanguageId;
    
public intgetVoiceLanguageModeId()

return
the resource Id of the language model string, if specified in the searchable activity's metadata, or 0 if not specified.

        return mVoiceLanguageModeId;
    
public intgetVoiceMaxResults()

return
the max results count, if specified in the searchable activity's metadata, or 0 if not specified.

        return mVoiceMaxResults;
    
public intgetVoicePromptTextId()

return
the resource Id of the voice prompt text string, if specified in the searchable activity's metadata, or 0 if not specified.

        return mVoicePromptTextId;
    
public booleangetVoiceSearchEnabled()

return
true if android:voiceSearchMode="showVoiceSearchButton"

        return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON);
    
public booleangetVoiceSearchLaunchRecognizer()

return
true if android:voiceSearchMode="launchRecognizer"

        return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER);
    
public booleangetVoiceSearchLaunchWebSearch()

return
true if android:voiceSearchMode="launchWebSearch"

        return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH);
    
public static voidsetDefaultSearchable(android.content.Context context, android.content.ComponentName activity)
Set the default searchable activity (when none is specified).

           // voiceMaxResults
    
                  
         
                                              
        synchronized (SearchableInfo.class) {
            SearchableInfo si = null;
            if (activity != null) {
                si = getSearchableInfo(context, activity);
                if (si != null) {
                    // move to front of list
                    sSearchablesList.remove(si);
                    sSearchablesList.add(0, si);
                }
            }
            sDefaultSearchable = si;
        }
    
private voidsetSearchModeFlags()
Convert searchmode to flags.

        mBadgeLabel = (0 != (mSearchMode & 4));
        mBadgeIcon = (0 != (mSearchMode & 8)) && (mIconId != 0);
        mQueryRewriteFromData = (0 != (mSearchMode & 0x10));
        mQueryRewriteFromText = (0 != (mSearchMode & 0x20));
    
public voidwriteToParcel(android.os.Parcel dest, int flags)

        dest.writeInt(mSearchable ? 1 : 0);
        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.writeString(mSuggestAuthority);
        dest.writeString(mSuggestPath);
        dest.writeString(mSuggestSelection);
        dest.writeString(mSuggestIntentAction);
        dest.writeString(mSuggestIntentData);

        // This is usually a very short linked list so we'll just pre-count it
        ActionKeyInfo nextKeyInfo = mActionKeyList;
        int count = 0;
        while (nextKeyInfo != null) {
            ++count;
            nextKeyInfo = nextKeyInfo.mNext;
        }
        dest.writeInt(count);
        // Now write count of 'em
        nextKeyInfo = mActionKeyList;
        while (count-- > 0) {
            nextKeyInfo.writeToParcel(dest, flags);
        }
        
        dest.writeString(mSuggestProviderPackage);

        dest.writeInt(mVoiceSearchMode);
        dest.writeInt(mVoiceLanguageModeId);
        dest.writeInt(mVoicePromptTextId);
        dest.writeInt(mVoiceLanguageId);
        dest.writeInt(mVoiceMaxResults);