diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /core/java/android/server/search | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'core/java/android/server/search')
-rw-r--r-- | core/java/android/server/search/SearchManagerService.java | 156 | ||||
-rw-r--r-- | core/java/android/server/search/SearchableInfo.aidl | 19 | ||||
-rw-r--r-- | core/java/android/server/search/SearchableInfo.java | 747 | ||||
-rw-r--r-- | core/java/android/server/search/package.html | 5 |
4 files changed, 927 insertions, 0 deletions
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java new file mode 100644 index 0000000..fe15553 --- /dev/null +++ b/core/java/android/server/search/SearchManagerService.java @@ -0,0 +1,156 @@ +/* + * 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.server.search; + +import android.app.ISearchManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.util.Config; + +/** + * This is a simplified version of the Search Manager service. It no longer handles + * presentation (UI). Its function is to maintain the map & list of "searchable" + * items, which provides a mapping from individual activities (where a user might have + * invoked search) to specific searchable activities (where the search will be dispatched). + */ +public class SearchManagerService extends ISearchManager.Stub +{ + // general debugging support + private static final String TAG = "SearchManagerService"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + + // configuration choices + private static final boolean IMMEDIATE_SEARCHABLES_UPDATE = true; + + // class maintenance and general shared data + private final Context mContext; + private final Handler mHandler; + private boolean mSearchablesDirty; + + /** + * Initialize the Search Manager service in the provided system context. + * Only one instance of this object should be created! + * + * @param context to use for accessing DB, window manager, etc. + */ + public SearchManagerService(Context context) { + mContext = context; + mHandler = new Handler(); + + // Setup the infrastructure for updating and maintaining the list + // of searchable activities. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + mSearchablesDirty = true; + + // After startup settles down, preload the searchables list, + // which will reduce the delay when the search UI is invoked. + if (IMMEDIATE_SEARCHABLES_UPDATE) { + mHandler.post(mRunUpdateSearchable); + } + } + + /** + * Listens for intent broadcasts. + * + * The primary purpose here is to refresh the "searchables" list + * if packages are added/removed. + */ + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + // First, test for intents that matter at any time + if (action.equals(Intent.ACTION_PACKAGE_ADDED) || + action.equals(Intent.ACTION_PACKAGE_REMOVED) || + action.equals(Intent.ACTION_PACKAGE_CHANGED)) { + mSearchablesDirty = true; + if (IMMEDIATE_SEARCHABLES_UPDATE) { + mHandler.post(mRunUpdateSearchable); + } + return; + } + } + }; + + /** + * This runnable (for the main handler / UI thread) will update the searchables list. + */ + private Runnable mRunUpdateSearchable = new Runnable() { + public void run() { + if (mSearchablesDirty) { + updateSearchables(); + } + } + }; + + /** + * Update the list of searchables, either at startup or in response to + * a package add/remove broadcast message. + */ + private void updateSearchables() { + SearchableInfo.buildSearchableList(mContext); + mSearchablesDirty = false; + + // TODO This is a hack. This shouldn't be hardcoded here, it's probably + // a policy. +// ComponentName defaultSearch = new ComponentName( +// "com.android.contacts", +// "com.android.contacts.ContactsListActivity" ); + ComponentName defaultSearch = new ComponentName( + "com.android.googlesearch", + "com.android.googlesearch.GoogleSearch" ); + SearchableInfo.setDefaultSearchable(mContext, defaultSearch); + } + + /** + * Return the searchableinfo for a given activity + * + * @param launchActivity The activity from which we're launching this search. + * @return Returns a SearchableInfo record describing the parameters of the search, + * or null if no searchable metadata was available. + * @param globalSearch If false, this will only launch the search that has been specifically + * defined by the application (which is usually defined as a local search). If no default + * search is defined in the current application or activity, no search will be launched. + * If true, this will always launch a platform-global (e.g. web-based) search instead. + */ + public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) { + // final check. however we should try to avoid this, because + // it slows down the entry into the UI. + if (mSearchablesDirty) { + updateSearchables(); + } + SearchableInfo si = null; + if (globalSearch) { + si = SearchableInfo.getDefaultSearchable(); + } else { + si = SearchableInfo.getSearchableInfo(mContext, launchActivity); + } + + return si; + } +} diff --git a/core/java/android/server/search/SearchableInfo.aidl b/core/java/android/server/search/SearchableInfo.aidl new file mode 100644 index 0000000..9576c2b --- /dev/null +++ b/core/java/android/server/search/SearchableInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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.server.search; + +parcelable SearchableInfo; diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java new file mode 100644 index 0000000..5b9942e --- /dev/null +++ b/core/java/android/server/search/SearchableInfo.java @@ -0,0 +1,747 @@ +/* + * 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.server.search; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public final class SearchableInfo implements Parcelable { + + // general debugging support + final static String LOG_TAG = "SearchableInfo"; + + // set this flag to 1 to prevent any apps from providing suggestions + final static int DBG_INHIBIT_SUGGESTIONS = 0; + + // static strings used for XML lookups, etc. + // 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_DEFAULT_SEARCHABLE = "android.app.default_searchable"; + private static final String MD_LABEL_SEARCHABLE = "android.app.searchable"; + private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*"; + private static final String MD_XML_ELEMENT_SEARCHABLE = "searchable"; + private static final String MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY = "actionkey"; + + // class maintenance and general shared data + private static HashMap<ComponentName, SearchableInfo> sSearchablesMap = null; + private static ArrayList<SearchableInfo> sSearchablesList = null; + private static SearchableInfo sDefaultSearchable = null; + + // true member variables - what we know about the searchability + // TO-DO replace public with getters + public boolean mSearchable = false; + private int mLabelId = 0; + public ComponentName mSearchActivity = null; + private int mHintId = 0; + private int mSearchMode = 0; + public boolean mBadgeLabel = false; + public boolean mBadgeIcon = false; + public boolean mQueryRewriteFromData = false; + public boolean mQueryRewriteFromText = false; + private int mIconId = 0; + private int mSearchButtonText = 0; + private String mSuggestAuthority = null; + private String mSuggestPath = null; + private String mSuggestSelection = null; + private String mSuggestIntentAction = null; + private String mSuggestIntentData = null; + private ActionKeyInfo mActionKeyList = null; + private String mSuggestProviderPackage = null; + private Context mCacheActivityContext = null; // use during setup only - don't hold memory! + + /** + * Set the default searchable activity (when none is specified). + */ + public static void setDefaultSearchable(Context context, + ComponentName activity) { + 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; + } + } + + /** + * 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 + */ + public static SearchableInfo getDefaultSearchable() { + synchronized (SearchableInfo.class) { + return sDefaultSearchable; + } + } + + /** + * Retrieve the authority for obtaining search suggestions. + * + * @return Returns a string containing the suggestions authority. + */ + public String getSuggestAuthority() { + return mSuggestAuthority; + } + + /** + * Retrieve the path for obtaining search suggestions. + * + * @return Returns a string containing the suggestions path, or null if not provided. + */ + public String getSuggestPath() { + return mSuggestPath; + } + + /** + * 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. + */ + public String getSuggestSelection() { + return mSuggestSelection; + } + + /** + * 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. + */ + public String getSuggestIntentAction() { + return mSuggestIntentAction; + } + + /** + * 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. + */ + public String getSuggestIntentData() { + return mSuggestIntentData; + } + + /** + * 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 + */ + public Context getActivityContext(Context context) { + 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; + } + + /** + * 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 + */ + 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; + } + + /** + * Factory. Look up, or construct, based on the activity. + * + * The activities fall into three cases, based on meta-data found in + * the manifest entry: + * <ol> + * <li>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.</li> + * <li>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.</li> + * <li>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. + */ + public static SearchableInfo getSearchableInfo(Context context, + ComponentName activity) { + // 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; + + } + + /** + * 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 + */ + public static void buildSearchableList(Context context) { + + // 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; + } + } + + /** + * 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 + */ + private SearchableInfo(Context context, AttributeSet attr, final ComponentName cName) { + // 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); + 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); + } + 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()); + } + } + + /** + * Convert searchmode to flags. + */ + private void setSearchModeFlags() { + // decompose searchMode attribute + // TODO How do I reconcile these hardcoded values with the flag bits defined in + // in attrs.xml? e.g. android.R.id.filterMode = 0x010200a4 instead of just "1" + /* mFilterMode = (0 != (mSearchMode & 1)); */ + /* mQuickStart = (0 != (mSearchMode & 2)); */ + mBadgeLabel = (0 != (mSearchMode & 4)); + mBadgeIcon = (0 != (mSearchMode & 8)) && (mIconId != 0); + mQueryRewriteFromData = (0 != (mSearchMode & 0x10)); + mQueryRewriteFromText = (0 != (mSearchMode & 0x20)); + } + + /** + * Private class used to hold the "action key" configuration + */ + public class ActionKeyInfo implements Parcelable { + + public int mKeyCode = 0; + public String mQueryActionMsg; + public String mSuggestActionMsg; + public String mSuggestActionMsgColumn; + private ActionKeyInfo mNext; + + /** + * Create one object using attributeset as input data. + * @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 next We'll build these up using a simple linked list (since there are usually + * just zero or one). + */ + public ActionKeyInfo(Context context, AttributeSet attr, ActionKeyInfo next) { + TypedArray a = mCacheActivityContext.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); + if (DBG_INHIBIT_SUGGESTIONS == 0) { + mSuggestActionMsg = a.getString( + com.android.internal.R.styleable.SearchableActionKey_suggestActionMsg); + mSuggestActionMsgColumn = a.getString( + com.android.internal.R.styleable.SearchableActionKey_suggestActionMsgColumn); + } + a.recycle(); + + // initialize any other fields + mNext = next; + + // sanity check. must have at least one action message, or invalidate the object. + if ((mQueryActionMsg == null) && + (mSuggestActionMsg == null) && + (mSuggestActionMsgColumn == null)) { + mKeyCode = 0; + } + } + + /** + * 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. + * @param next The value to place in mNext, creating a linked list + */ + public ActionKeyInfo(Parcel in, ActionKeyInfo next) { + mKeyCode = in.readInt(); + mQueryActionMsg = in.readString(); + mSuggestActionMsg = in.readString(); + mSuggestActionMsgColumn = in.readString(); + mNext = next; + } + + 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 ActionKeyInfo record, or null if none defined + */ + public ActionKeyInfo findActionKey(int keyCode) { + ActionKeyInfo info = mActionKeyList; + while (info != null) { + if (info.mKeyCode == keyCode) { + return info; + } + info = info.mNext; + } + return null; + } + + /** + * 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 + */ + private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml, + final ComponentName cName) { + 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; + } + + /** + * 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 + */ + public int getLabelId() { + return mLabelId; + } + + /** + * 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. + */ + public int getHintId() { + return mHintId; + } + + /** + * 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. + */ + public int getIconId() { + return mIconId; + } + + /** + * Return the resource Id of replacement text for the "Search" button. + * + * @return Returns the resource Id, or 0 if not specified by this package. + */ + public int getSearchButtonText() { + return mSearchButtonText; + } + + /** + * Return the list of searchable activities, for use in the drop-down. + */ + public static ArrayList<SearchableInfo> getSearchablesList() { + synchronized (SearchableInfo.class) { + ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(sSearchablesList); + return result; + } + } + + /** + * 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]; + } + }; + + /** + * 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. + */ + public SearchableInfo(Parcel in) { + mLabelId = in.readInt(); + mSearchActivity = ComponentName.readFromParcel(in); + mHintId = in.readInt(); + mSearchMode = in.readInt(); + mIconId = in.readInt(); + mSearchButtonText = 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(); + } + + 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.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); + } +} diff --git a/core/java/android/server/search/package.html b/core/java/android/server/search/package.html new file mode 100644 index 0000000..c9f96a6 --- /dev/null +++ b/core/java/android/server/search/package.html @@ -0,0 +1,5 @@ +<body> + +{@hide} + +</body> |