am f3ccf8a: Merge branch \'readonly-p4-donut\' into donut

This commit is contained in:
The Android Open Source Project
2009-04-29 13:34:51 -07:00
37 changed files with 2632 additions and 1673 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -17,12 +17,17 @@
package android.app;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.server.search.SearchableInfo;
import android.view.KeyEvent;
/**
@@ -439,20 +444,18 @@ import android.view.KeyEvent;
*
* <tr><th>{@link #SUGGEST_COLUMN_ICON_1}</th>
* <td>If your cursor includes this column, then all suggestions will be provided in an
* icons+text format. This value should be a reference (resource ID) of the icon to
* icons+text format. This value should be a reference to the icon to
* draw on the left side, or it can be null or zero to indicate no icon in this row.
* You must provide both cursor columns, or neither.
* </td>
* <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_2}</td>
* <td align="center">No.</td>
* </tr>
*
* <tr><th>{@link #SUGGEST_COLUMN_ICON_2}</th>
* <td>If your cursor includes this column, then all suggestions will be provided in an
* icons+text format. This value should be a reference (resource ID) of the icon to
* icons+text format. This value should be a reference to the icon to
* draw on the right side, or it can be null or zero to indicate no icon in this row.
* You must provide both cursor columns, or neither.
* </td>
* <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_1}</td>
* <td align="center">No.</td>
* </tr>
*
* <tr><th>{@link #SUGGEST_COLUMN_INTENT_ACTION}</th>
@@ -1154,6 +1157,13 @@ public class SearchManager
*/
public final static String ACTION_KEY = "action_key";
/**
* Intent extra data key: This key will be used for the extra populated by the
* {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
* {@hide}
*/
public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
@@ -1195,20 +1205,58 @@ public class SearchManager
public final static String SUGGEST_COLUMN_TEXT_2 = "suggest_text_2";
/**
* Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
* then all suggestions will be provided in format that includes space for two small icons,
* then all suggestions will be provided in a format that includes space for two small icons,
* one at the left and one at the right of each suggestion. The data in the column must
* be a a resource ID for the icon you wish to have displayed. If you include this column,
* you must also include {@link #SUGGEST_COLUMN_ICON_2}.
* be a resource ID of a drawable, or a URI in one of the following formats:
*
* <ul>
* <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
* <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
* <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
* </ul>
*
* See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
* for more information on these schemes.
*/
public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1";
/**
* Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
* then all suggestions will be provided in format that includes space for two small icons,
* then all suggestions will be provided in a format that includes space for two small icons,
* one at the left and one at the right of each suggestion. The data in the column must
* be a a resource ID for the icon you wish to have displayed. If you include this column,
* you must also include {@link #SUGGEST_COLUMN_ICON_1}.
* be a resource ID of a drawable, or a URI in one of the following formats:
*
* <ul>
* <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
* <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
* <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
* </ul>
*
* See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
* for more information on these schemes.
*/
public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
/**
* Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
* then all suggestions will be provided in a format that includes space for two small icons,
* one at the left and one at the right of each suggestion. The data in the column must
* be a blob that contains a bitmap.
*
* This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_1} column.
*
* @hide
*/
public final static String SUGGEST_COLUMN_ICON_1_BITMAP = "suggest_icon_1_bitmap";
/**
* Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
* then all suggestions will be provided in a format that includes space for two small icons,
* one at the left and one at the right of each suggestion. The data in the column must
* be a blob that contains a bitmap.
*
* This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_2} column.
*
* @hide
*/
public final static String SUGGEST_COLUMN_ICON_2_BITMAP = "suggest_icon_2_bitmap";
/**
* Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i>
* this element exists at the given row, this is the action that will be used when
@@ -1229,6 +1277,14 @@ public class SearchManager
* it is more efficient to specify it using XML metadata and omit it from the cursor.
*/
public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data";
/**
* Column name for suggestions cursor. <i>Optional.</i> This column allows suggestions
* to provide additional arbitrary data which will be included as an extra under the key
* {@link #EXTRA_DATA_KEY}.
*
* @hide pending API council approval
*/
public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
/**
* Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i>
* this element exists at the given row, then "/" and this value will be appended to the data
@@ -1244,6 +1300,54 @@ public class SearchManager
*/
public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query";
/**
* If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
* the search dialog will switch to a different suggestion source when the
* suggestion is clicked.
*
* {@link #SUGGEST_COLUMN_INTENT_DATA} must contain
* the flattened {@link ComponentName} of the activity which is to be searched.
*
* TODO: Should {@link #SUGGEST_COLUMN_INTENT_DATA} instead contain a URI in the format
* used by {@link android.provider.Applications}?
*
* TODO: This intent should be protected by the same permission that we use
* for replacing the global search provider.
*
* The query text field will be set to the value of {@link #SUGGEST_COLUMN_QUERY}.
*
* @hide Pending API council approval.
*/
public final static String INTENT_ACTION_CHANGE_SEARCH_SOURCE
= "android.search.action.CHANGE_SEARCH_SOURCE";
/**
* If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
* the search dialog will call {@link Cursor#respond(Bundle)} when the
* suggestion is clicked.
*
* The {@link Bundle} argument will be constructed
* in the same way as the "extra" bundle included in an Intent constructed
* from the suggestion.
*
* @hide Pending API council approval.
*/
public final static String INTENT_ACTION_CURSOR_RESPOND
= "android.search.action.CURSOR_RESPOND";
/**
* Intent action for starting the global search settings activity.
* The global search provider should handle this intent.
*
* @hide Pending API council approval.
*/
public final static String INTENT_ACTION_SEARCH_SETTINGS
= "android.search.action.SEARCH_SETTINGS";
/**
* Reference to the shared system search service.
*/
private static ISearchManager sService = getSearchManagerService();
private final Context mContext;
private final Handler mHandler;
@@ -1257,12 +1361,6 @@ public class SearchManager
mContext = context;
mHandler = handler;
}
private static ISearchManager mService;
static {
mService = ISearchManager.Stub.asInterface(
ServiceManager.getService(Context.SEARCH_SERVICE));
}
/**
* Launch search UI.
@@ -1459,5 +1557,93 @@ public class SearchManager
mSearchDialog.onConfigurationChanged(newConfig);
}
}
private static ISearchManager getSearchManagerService() {
return ISearchManager.Stub.asInterface(
ServiceManager.getService(Context.SEARCH_SERVICE));
}
/**
* Gets information about a searchable activity. This method is static so that it can
* be used from non-Activity contexts.
*
* @param componentName The activity to get searchable information for.
* @param globalSearch If <code>false</code>, return information about the given activity.
* If <code>true</code>, return information about the global search activity.
* @return Searchable information, or <code>null</code> if the activity is not searchable.
*
* @hide because SearchableInfo is not part of the API.
*/
public static SearchableInfo getSearchableInfo(ComponentName componentName,
boolean globalSearch) {
try {
return sService.getSearchableInfo(componentName, globalSearch);
} catch (RemoteException e) {
return null;
}
}
/**
* Checks whether the given searchable is the default searchable.
*
* @hide because SearchableInfo is not part of the API.
*/
public static boolean isDefaultSearchable(SearchableInfo searchable) {
SearchableInfo defaultSearchable = SearchManager.getSearchableInfo(null, true);
return defaultSearchable != null
&& defaultSearchable.mSearchActivity.equals(searchable.mSearchActivity);
}
/**
* Gets a cursor with search suggestions. This method is static so that it can
* be used from non-Activity context.
*
* @param searchable Information about how to get the suggestions.
* @param query The search text entered (so far).
* @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
*
* @hide because SearchableInfo is not part of the API.
*/
public static Cursor getSuggestions(Context context, SearchableInfo searchable, String query) {
if (searchable == null) {
return null;
}
String authority = searchable.getSuggestAuthority();
if (authority == null) {
return null;
}
Uri.Builder uriBuilder = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority);
// if content path provided, insert it now
final String contentPath = searchable.getSuggestPath();
if (contentPath != null) {
uriBuilder.appendEncodedPath(contentPath);
}
// append standard suggestion query path
uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
// get the query selection, may be null
String selection = searchable.getSuggestSelection();
// inject query, either as selection args or inline
String[] selArgs = null;
if (selection != null) { // use selection if provided
selArgs = new String[] { query };
} else { // no selection, use REST pattern
uriBuilder.appendPath(query);
}
Uri uri = uriBuilder
.query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
.fragment("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
.build();
// finally, make the query
return context.getContentResolver().query(uri, null, selection, selArgs, null);
}
}

View File

@@ -0,0 +1,344 @@
/*
* Copyright (C) 2009 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 android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.server.search.SearchableInfo;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
import java.io.FileNotFoundException;
import java.util.WeakHashMap;
/**
* Provides the contents for the suggestion drop-down list.in {@link SearchDialog}.
*
* @hide
*/
class SuggestionsAdapter extends ResourceCursorAdapter {
private static final boolean DBG = false;
private static final String LOG_TAG = "SuggestionsAdapter";
private SearchableInfo mSearchable;
private Context mProviderContext;
private WeakHashMap<String, Drawable> mOutsideDrawablesCache;
// Cached column indexes, updated when the cursor changes.
private int mFormatCol;
private int mText1Col;
private int mText2Col;
private int mIconName1Col;
private int mIconName2Col;
private int mIconBitmap1Col;
private int mIconBitmap2Col;
public SuggestionsAdapter(Context context, SearchableInfo searchable,
WeakHashMap<String, Drawable> outsideDrawablesCache) {
super(context,
com.android.internal.R.layout.search_dropdown_item_icons_2line,
null, // no initial cursor
true); // auto-requery
mSearchable = searchable;
// set up provider resources (gives us icons, etc.)
Context activityContext = mSearchable.getActivityContext(mContext);
mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
mOutsideDrawablesCache = outsideDrawablesCache;
}
/**
* Overridden to always return <code>false</code>, since we cannot be sure that
* suggestion sources return stable IDs.
*/
@Override
public boolean hasStableIds() {
return false;
}
/**
* Use the search suggestions provider to obtain a live cursor. This will be called
* in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
* The results will be processed in the UI thread and changeCursor() will be called.
*/
@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
String query = (constraint == null) ? "" : constraint.toString();
try {
return SearchManager.getSuggestions(mContext, mSearchable, query);
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
return null;
}
}
/**
* Cache columns.
*/
@Override
public void changeCursor(Cursor c) {
if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
super.changeCursor(c);
if (c != null) {
mFormatCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
mIconBitmap1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1_BITMAP);
mIconBitmap2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2_BITMAP);
}
}
/**
* Tags the view with cached child view look-ups.
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = super.newView(context, cursor, parent);
v.setTag(new ChildViewCache(v));
return v;
}
/**
* Cache of the child views of drop-drown list items, to avoid looking up the children
* each time the contents of a list item are changed.
*/
private final static class ChildViewCache {
public final TextView mText1;
public final TextView mText2;
public final ImageView mIcon1;
public final ImageView mIcon2;
public ChildViewCache(View v) {
mText1 = (TextView) v.findViewById(com.android.internal.R.id.text1);
mText2 = (TextView) v.findViewById(com.android.internal.R.id.text2);
mIcon1 = (ImageView) v.findViewById(com.android.internal.R.id.icon1);
mIcon2 = (ImageView) v.findViewById(com.android.internal.R.id.icon2);
}
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ChildViewCache views = (ChildViewCache) view.getTag();
String format = cursor.getString(mFormatCol);
boolean isHtml = "html".equals(format);
setViewText(cursor, views.mText1, mText1Col, isHtml);
setViewText(cursor, views.mText2, mText2Col, isHtml);
setViewIcon(cursor, views.mIcon1, mIconBitmap1Col, mIconName1Col);
setViewIcon(cursor, views.mIcon2, mIconBitmap2Col, mIconName2Col);
}
private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) {
if (v == null) {
return;
}
CharSequence text = null;
if (textCol >= 0) {
String str = cursor.getString(textCol);
text = (str != null && isHtml) ? Html.fromHtml(str) : str;
}
// Set the text even if it's null, since we need to clear any previous text.
v.setText(text);
if (TextUtils.isEmpty(text)) {
v.setVisibility(View.GONE);
} else {
v.setVisibility(View.VISIBLE);
}
}
private void setViewIcon(Cursor cursor, ImageView v, int iconBitmapCol, int iconNameCol) {
if (v == null) {
return;
}
Drawable drawable = null;
// First try the bitmap column
if (iconBitmapCol >= 0) {
byte[] data = cursor.getBlob(iconBitmapCol);
if (data != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
if (bitmap != null) {
drawable = new BitmapDrawable(bitmap);
}
}
}
// If there was no bitmap, try the icon resource column.
if (drawable == null && iconNameCol >= 0) {
String value = cursor.getString(iconNameCol);
drawable = getDrawableFromResourceValue(value);
}
// Set the icon even if the drawable is null, since we need to clear any
// previous icon.
v.setImageDrawable(drawable);
if (drawable == null) {
v.setVisibility(View.GONE);
} else {
v.setVisibility(View.VISIBLE);
}
}
/**
* Gets the text to show in the query field when a suggestion is selected.
*
* @param cursor The Cursor to read the suggestion data from. The Cursor should already
* be moved to the suggestion that is to be read from.
* @return The text to show, or <code>null</code> if the query should not be
* changed when selecting this suggestion.
*/
@Override
public CharSequence convertToString(Cursor cursor) {
if (cursor == null) {
return null;
}
String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
if (query != null) {
return query;
}
if (mSearchable.mQueryRewriteFromData) {
String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
if (data != null) {
return data;
}
}
if (mSearchable.mQueryRewriteFromText) {
String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1);
if (text1 != null) {
return text1;
}
}
return null;
}
/**
* This method is overridden purely to provide a bit of protection against
* flaky content providers.
*
* @see android.widget.ListAdapter#getView(int, View, ViewGroup)
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
try {
return super.getView(position, convertView, parent);
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
// Put exception string in item title
View v = newView(mContext, mCursor, parent);
if (v != null) {
ChildViewCache views = (ChildViewCache) v.getTag();
TextView tv = views.mText1;
tv.setText(e.toString());
}
return v;
}
}
/**
* Gets a drawable given a value provided by a suggestion provider.
*
* This value could be just the string value of a resource id
* (e.g., "2130837524"), in which case we will try to retrieve a drawable from
* the provider's resources. If the value is not an integer, it is
* treated as a Uri and opened with
* {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
*
* All resources and URIs are read using the suggestion provider's context.
*
* If the string is not formatted as expected, or no drawable can be found for
* the provided value, this method returns null.
*
* @param drawableId a string like "2130837524",
* "android.resource://com.android.alarmclock/2130837524",
* or "content://contacts/photos/253".
* @return a Drawable, or null if none found
*/
private Drawable getDrawableFromResourceValue(String drawableId) {
if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
return null;
}
// First, check the cache.
Drawable drawable = mOutsideDrawablesCache.get(drawableId);
if (drawable != null) return drawable;
try {
// Not cached, try using it as a plain resource ID in the provider's context.
int resourceId = Integer.parseInt(drawableId);
drawable = mProviderContext.getResources().getDrawable(resourceId);
} catch (NumberFormatException nfe) {
// The id was not an integer resource id.
// Let the ContentResolver handle content, android.resource and file URIs.
try {
Uri uri = Uri.parse(drawableId);
drawable = Drawable.createFromStream(
mProviderContext.getContentResolver().openInputStream(uri),
null);
} catch (FileNotFoundException fnfe) {
// drawable = null;
}
// If we got a drawable for this resource id, then stick it in the
// map so we don't do this lookup again.
if (drawable != null) {
mOutsideDrawablesCache.put(drawableId, drawable);
}
} catch (NotFoundException nfe) {
// Resource could not be found
// drawable = null;
}
return drawable;
}
/**
* Gets the value of a string column by name.
*
* @param cursor Cursor to read the value from.
* @param columnName The name of the column to read.
* @return The value of the given column, or <code>null</null>
* if the cursor does not contain the given column.
*/
public static String getColumnString(Cursor cursor, String columnName) {
int col = cursor.getColumnIndex(columnName);
if (col == -1) {
return null;
}
return cursor.getString(col);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2009 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.provider;
import android.app.SearchManager;
import android.net.Uri;
import android.widget.SimpleCursorAdapter;
/**
* <p>The Applications provider gives information about installed applications.</p>
*
* <p>This provider provides the following columns:
*
* <table border="2" width="85%" align="center" frame="hsides" rules="rows">
*
* <thead>
* <tr><th>Column Name</th> <th>Description</th> </tr>
* </thead>
*
* <tbody>
* <tr><th>{@link SearchManager#SUGGEST_COLUMN_TEXT_1}</th>
* <td>The application name.</td>
* </tr>
*
* <tr><th>{@link SearchManager#SUGGEST_COLUMN_INTENT_COMPONENT}</th>
* <td>The component to be used when forming the intent.</td>
* </tr>
*
* <tr><th>{@link SearchManager#SUGGEST_COLUMN_ICON_1}</th>
* <td>The application's icon resource id, prepended by its package name and
* separated by a colon, e.g., "com.android.alarmclock:2130837524". The
* package name is required for an activity interpreting this value to
* be able to correctly access the icon drawable, for example, in an override of
* {@link SimpleCursorAdapter#setViewImage(android.widget.ImageView, String)}.</td>
* </tr>
*
* <tr><th>{@link SearchManager#SUGGEST_COLUMN_ICON_2}</th>
* <td><i>Unused - column provided to conform to the {@link SearchManager} stipulation
* that all providers provide either both or neither of
* {@link SearchManager#SUGGEST_COLUMN_ICON_1} and
* {@link SearchManager#SUGGEST_COLUMN_ICON_2}.</td>
* </tr>
*
* @hide pending API council approval - should be unhidden at the same time as
* {@link SearchManager#SUGGEST_COLUMN_INTENT_COMPONENT}
*/
public class Applications {
private static final String TAG = "Applications";
/**
* The content authority for this provider.
*
* @hide
*/
public static final String AUTHORITY = "applications";
/**
* The content:// style URL for this provider
*
* @hide
*/
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
/**
* no public constructor since this is a utility class
*/
private Applications() {}
}

View File

@@ -22,8 +22,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Handler;
import android.util.Config;
/**
* This is a simplified version of the Search Manager service. It no longer handles
@@ -36,7 +36,6 @@ 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;
@@ -45,9 +44,10 @@ public class SearchManagerService extends ISearchManager.Stub
private final Context mContext;
private final Handler mHandler;
private boolean mSearchablesDirty;
private Searchables mSearchables;
/**
* Initialize the Search Manager service in the provided system context.
* Initializes 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.
@@ -55,6 +55,8 @@ public class SearchManagerService extends ISearchManager.Stub
public SearchManagerService(Context context) {
mContext = context;
mHandler = new Handler();
mSearchablesDirty = true;
mSearchables = new Searchables(context);
// Setup the infrastructure for updating and maintaining the list
// of searchable activities.
@@ -64,7 +66,6 @@ public class SearchManagerService extends ISearchManager.Stub
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.
@@ -109,34 +110,41 @@ public class SearchManagerService extends ISearchManager.Stub
};
/**
* Update the list of searchables, either at startup or in response to
* Updates the list of searchables, either at startup or in response to
* a package add/remove broadcast message.
*/
private void updateSearchables() {
SearchableInfo.buildSearchableList(mContext);
mSearchables.buildSearchableList();
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);
// TODO SearchableInfo should be the source of truth about whether a searchable exists.
// As it stands, if the package exists but is misconfigured in some way, then this
// would fail, and needs to be fixed.
ComponentName defaultSearch = new ComponentName(
"com.android.globalsearch",
"com.android.globalsearch.GlobalSearch");
try {
mContext.getPackageManager().getActivityInfo(defaultSearch, 0);
} catch (NameNotFoundException e) {
defaultSearch = new ComponentName(
"com.android.googlesearch",
"com.android.googlesearch.GoogleSearch");
}
mSearchables.setDefaultSearchable(defaultSearch);
}
/**
* Return the searchableinfo for a given activity
* Returns 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.
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) {
// final check. however we should try to avoid this, because
@@ -146,11 +154,12 @@ public class SearchManagerService extends ISearchManager.Stub
}
SearchableInfo si = null;
if (globalSearch) {
si = SearchableInfo.getDefaultSearchable();
si = mSearchables.getDefaultSearchable();
} else {
si = SearchableInfo.getSearchableInfo(mContext, launchActivity);
si = mSearchables.getSearchableInfo(launchActivity);
}
return si;
}
}

View File

@@ -21,14 +21,11 @@ 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.text.InputType;
@@ -38,9 +35,6 @@ import android.util.Xml;
import android.view.inputmethod.EditorInfo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public final class SearchableInfo implements Parcelable {
@@ -50,19 +44,12 @@ public final class SearchableInfo implements Parcelable {
// 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.
// 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_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
@@ -86,7 +73,6 @@ public final class SearchableInfo implements Parcelable {
private String mSuggestIntentData = null;
private ActionKeyInfo mActionKeyList = null;
private String mSuggestProviderPackage = null;
private Context mCacheActivityContext = null; // use during setup only - don't hold memory!
// Flag values for Searchable_voiceSearchMode
private static int VOICE_SEARCH_SHOW_BUTTON = 1;
@@ -97,37 +83,7 @@ public final class SearchableInfo implements Parcelable {
private int mVoicePromptTextId; // voicePromptText
private int mVoiceLanguageId; // voiceLanguage
private int mVoiceMaxResults; // voiceMaxResults
/**
* 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.
@@ -193,9 +149,16 @@ public final class SearchableInfo implements Parcelable {
* @return Returns a context related to the searchable activity
*/
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(mSearchActivity.getPackageName(), 0);
theirContext = context.createPackageContext(activity.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
// unexpected, but we deal with this by null-checking theirContext
} catch (java.lang.SecurityException e) {
@@ -233,243 +196,69 @@ public final class SearchableInfo implements Parcelable {
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 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
*/
private SearchableInfo(Context context, AttributeSet attr, final ComponentName cName) {
private SearchableInfo(Context activityContext, 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);
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);
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_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);
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();
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;
}
// get package info for suggestions provider (if any)
if (mSuggestAuthority != null) {
PackageManager pm = activityContext.getPackageManager();
ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority, 0);
if (pi != null) {
mSuggestProviderPackage = pi.packageName;
}
}
@@ -496,7 +285,7 @@ public final class SearchableInfo implements Parcelable {
/**
* Private class used to hold the "action key" configuration
*/
public class ActionKeyInfo implements Parcelable {
public static class ActionKeyInfo implements Parcelable {
public int mKeyCode = 0;
public String mQueryActionMsg;
@@ -506,14 +295,15 @@ public final class SearchableInfo implements Parcelable {
/**
* Create one object using attributeset as input data.
* @param context runtime context
* @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.
* @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,
public ActionKeyInfo(Context activityContext, AttributeSet attr, ActionKeyInfo next) {
TypedArray a = activityContext.obtainStyledAttributes(attr,
com.android.internal.R.styleable.SearchableActionKey);
mKeyCode = a.getInt(
@@ -584,6 +374,20 @@ public final class SearchableInfo implements Parcelable {
return null;
}
public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) {
// for each component, try to find metadata
XmlResourceParser xml =
activityInfo.loadXmlMetaData(context.getPackageManager(), MD_LABEL_SEARCHABLE);
if (xml == null) {
return null;
}
ComponentName cName = new ComponentName(activityInfo.packageName, activityInfo.name);
SearchableInfo searchable = getActivityMetaData(context, xml, cName);
xml.close();
return searchable;
}
/**
* Get the metadata for a given activity
*
@@ -598,6 +402,7 @@ public final class SearchableInfo implements Parcelable {
private static SearchableInfo getActivityMetaData(Context context, XmlPullParser xml,
final ComponentName cName) {
SearchableInfo result = null;
Context activityContext = createActivityContext(context, cName);
// 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.
@@ -608,7 +413,7 @@ public final class SearchableInfo implements Parcelable {
if (xml.getName().equals(MD_XML_ELEMENT_SEARCHABLE)) {
AttributeSet attr = Xml.asAttributeSet(xml);
if (attr != null) {
result = new SearchableInfo(context, attr, cName);
result = new SearchableInfo(activityContext, attr, cName);
// if the constructor returned a bad object, exit now.
if (! result.mSearchable) {
return null;
@@ -621,7 +426,7 @@ public final class SearchableInfo implements Parcelable {
}
AttributeSet attr = Xml.asAttributeSet(xml);
if (attr != null) {
ActionKeyInfo keyInfo = result.new ActionKeyInfo(context, attr,
ActionKeyInfo keyInfo = new ActionKeyInfo(activityContext, attr,
result.mActionKeyList);
// only add to list if it is was useable
if (keyInfo.mKeyCode != 0) {
@@ -637,6 +442,7 @@ public final class SearchableInfo implements Parcelable {
} catch (IOException e) {
throw new RuntimeException(e);
}
return result;
}
@@ -756,16 +562,6 @@ public final class SearchableInfo implements Parcelable {
return mSearchImeOptions;
}
/**
* 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.
*/

View File

@@ -0,0 +1,243 @@
/*
* Copyright (C) 2009 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.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* This class maintains the information about all searchable activities.
*/
public class Searchables {
// 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_SEARCHABLE_SYSTEM_SEARCH = "*";
private Context mContext;
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private SearchableInfo mDefaultSearchable = null;
/**
*
* @param context Context to use for looking up activities etc.
*/
public Searchables (Context context) {
mContext = context;
}
/**
* 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 SearchableInfo getSearchableInfo(ComponentName activity) {
// Step 1. Is the result already hashed? (case 1)
SearchableInfo result;
synchronized (this) {
result = mSearchablesMap.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;
try {
ai = mContext.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 (this) {
result = mSearchablesMap.get(referredActivity);
if (result != null) {
mSearchablesMap.put(activity, result);
return result;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
// case 3: no metadata
}
// Step 3. None found. Return null.
return null;
}
/**
* Set the default searchable activity (when none is specified).
*/
public synchronized void setDefaultSearchable(ComponentName activity) {
SearchableInfo si = null;
if (activity != null) {
si = getSearchableInfo(activity);
if (si != null) {
// move to front of list
mSearchablesList.remove(si);
mSearchablesList.add(0, si);
}
}
mDefaultSearchable = 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 synchronized SearchableInfo getDefaultSearchable() {
return mDefaultSearchable;
}
public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
return searchable == mDefaultSearchable;
}
/**
* 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.
*/
public void buildSearchableList() {
// 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 = mContext.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;
SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
if (searchable != null) {
newSearchablesList.add(searchable);
newSearchablesMap.put(searchable.mSearchActivity, searchable);
}
}
}
// record the final values as a coherent pair
synchronized (this) {
mSearchablesList = newSearchablesList;
mSearchablesMap = newSearchablesMap;
}
}
/**
* Returns the list of searchable activities.
*/
public synchronized ArrayList<SearchableInfo> getSearchablesList() {
ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
return result;
}
}

View File

@@ -983,18 +983,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mSelectorRect.setEmpty();
invalidate();
}
/**
* The list is empty and we need to change the layout, so *really* clear everything out.
* @hide - for AutoCompleteTextView & SearchDialog only
*/
/* package */ void resetListAndClearViews() {
rememberSyncState();
removeAllViewsInLayout();
mRecycler.clear();
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
requestLayout();
}
@Override
protected int computeVerticalScrollExtent() {

View File

@@ -110,6 +110,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private final DropDownItemClickListener mDropDownItemClickListener =
new DropDownItemClickListener();
private boolean mDropDownAlwaysVisible = false;
private boolean mDropDownDismissedOnCompletion = true;
private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
private boolean mOpenBefore;
@@ -255,6 +259,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownAnchorId = id;
mDropDownAnchorView = null;
}
<<<<<<< HEAD:core/java/android/widget/AutoCompleteTextView.java
/**
* <p>Gets the background of the auto-complete drop-down list.</p>
@@ -325,6 +330,169 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return mDropDownHorizontalOffset;
}
|||||||
=======
/**
* <p>Gets the background of the auto-complete drop-down list.</p>
*
* @return the background drawable
*
* @attr ref android.R.styleable#PopupWindow_popupBackground
*
* @hide Pending API council approval
*/
public Drawable getDropDownBackground() {
return mPopup.getBackground();
}
/**
* <p>Sets the background of the auto-complete drop-down list.</p>
*
* @param d the drawable to set as the background
*
* @attr ref android.R.styleable#PopupWindow_popupBackground
*
* @hide Pending API council approval
*/
public void setDropDownBackgroundDrawable(Drawable d) {
mPopup.setBackgroundDrawable(d);
}
/**
* <p>Sets the background of the auto-complete drop-down list.</p>
*
* @param id the id of the drawable to set as the background
*
* @attr ref android.R.styleable#PopupWindow_popupBackground
*
* @hide Pending API council approval
*/
public void setDropDownBackgroundResource(int id) {
mPopup.setBackgroundDrawable(getResources().getDrawable(id));
}
/**
* <p>Sets the animation style of the auto-complete drop-down list.</p>
*
* <p>If the drop-down is showing, calling this method will take effect only
* the next time the drop-down is shown.</p>
*
* @param animationStyle animation style to use when the drop-down appears
* and disappears. Set to -1 for the default animation, 0 for no
* animation, or a resource identifier for an explicit animation.
*
* @hide Pending API council approval
*/
public void setDropDownAnimationStyle(int animationStyle) {
mPopup.setAnimationStyle(animationStyle);
}
>>>>>>> f3ccf8a5a5a3f6e46781538358bddca992a70e3d:core/java/android/widget/AutoCompleteTextView.java
/**
* <p>Returns the animation style that is used when the drop-down list appears and disappears
* </p>
*
* @return the animation style that is used when the drop-down list appears and disappears
*
* @hide Pending API council approval
*/
public int getDropDownAnimationStyle() {
return mPopup.getAnimationStyle();
}
/**
* <p>Sets the vertical offset used for the auto-complete drop-down list.</p>
*
* @param offset the vertical offset
*
* @hide Pending API council approval
*/
public void setDropDownVerticalOffset(int offset) {
mDropDownVerticalOffset = offset;
}
/**
* <p>Gets the vertical offset used for the auto-complete drop-down list.</p>
*
* @return the vertical offset
*
* @hide Pending API council approval
*/
public int getDropDownVerticalOffset() {
return mDropDownVerticalOffset;
}
/**
* <p>Sets the horizontal offset used for the auto-complete drop-down list.</p>
*
* @param offset the horizontal offset
*
* @hide Pending API council approval
*/
public void setDropDownHorizontalOffset(int offset) {
mDropDownHorizontalOffset = offset;
}
/**
* <p>Gets the horizontal offset used for the auto-complete drop-down list.</p>
*
* @return the horizontal offset
*
* @hide Pending API council approval
*/
public int getDropDownHorizontalOffset() {
return mDropDownHorizontalOffset;
}
/**
* @return Whether the drop-down is visible as long as there is {@link #enoughToFilter()}
*
* @hide Pending API council approval
*/
public boolean isDropDownAlwaysVisible() {
return mDropDownAlwaysVisible;
}
/**
* Sets whether the drop-down should remain visible as long as there is there is
* {@link #enoughToFilter()}. This is useful if an unknown number of results are expected
* to show up in the adapter sometime in the future.
*
* The drop-down will occupy the entire screen below {@link #getDropDownAnchor} regardless
* of the size or content of the list. {@link #getDropDownBackground()} will fill any space
* that is not used by the list.
*
* @param dropDownAlwaysVisible Whether to keep the drop-down visible.
*
* @hide Pending API council approval
*/
public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
mDropDownAlwaysVisible = dropDownAlwaysVisible;
}
/**
* Checks whether the drop-down is dismissed when a suggestion is clicked.
*
* @hide Pending API council approval
*/
public boolean isDropDownDismissedOnCompletion() {
return mDropDownDismissedOnCompletion;
}
/**
* Sets whether the drop-down is dismissed when a suggestion is clicked. This is
* true by default.
*
* @param dropDownDismissedOnCompletion Whether to dismiss the drop-down.
*
* @hide Pending API council approval
*/
public void setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion) {
mDropDownDismissedOnCompletion = dropDownDismissedOnCompletion;
}
/**
* <p>Returns the number of characters the user must type before the drop
* down list is shown.</p>
@@ -705,16 +873,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
return ListView.INVALID_POSITION;
}
/**
* We're changing the adapter and its views so really, really clear everything out
* @hide - for SearchDialog only
*/
public void resetListAndClearViews() {
if (mDropDownList != null) {
mDropDownList.resetListAndClearViews();
}
}
/**
* <p>Starts filtering the content of the drop down list. The filtering
@@ -786,7 +944,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
dismissDropDown();
if (mDropDownDismissedOnCompletion) {
dismissDropDown();
}
}
/**
@@ -797,6 +957,42 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return mBlockCompletion;
}
/**
* Like {@link #setText(CharSequence)}, except that it can disable filtering.
*
* @param filter If <code>false</code>, no filtering will be performed
* as a result of this call.
*
* @hide Pending API council approval.
*/
public void setText(CharSequence text, boolean filter) {
if (filter) {
setText(text);
} else {
mBlockCompletion = true;
setText(text);
mBlockCompletion = false;
}
}
/**
* Like {@link #setTextKeepState(CharSequence)}, except that it can disable filtering.
*
* @param filter If <code>false</code>, no filtering will be performed
* as a result of this call.
*
* @hide Pending API council approval.
*/
public void setTextKeepState(CharSequence text, boolean filter) {
if (filter) {
setTextKeepState(text);
} else {
mBlockCompletion = true;
setTextKeepState(text);
mBlockCompletion = false;
}
}
/**
* <p>Performs the text completion by replacing the current text by the
* selected item. Subclasses should override this method to avoid replacing
@@ -811,6 +1007,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
Selection.setSelection(spannable, spannable.length());
}
/** {@inheritDoc} */
public void onFilterComplete(int count) {
if (mAttachCount <= 0) return;
@@ -821,7 +1018,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* to filter.
*/
if (count > 0 && enoughToFilter()) {
if ((count > 0 || mDropDownAlwaysVisible) && enoughToFilter()) {
if (hasFocus() && hasWindowFocus()) {
showDropDown();
}
@@ -917,10 +1114,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownVerticalOffset, widthSpec, height);
} else {
if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT, 0);
} else {
mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
mPopup.setWindowLayoutMode(0, 0);
if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setWidth(getDropDownAnchorView().getWidth());
} else {
@@ -1027,8 +1223,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
final int maxHeight = mPopup.getMaxAvailableHeight(this, mDropDownVerticalOffset);
//otherHeights += dropDownView.getPaddingTop() + dropDownView.getPaddingBottom();
return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
final int measuredHeight = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
return mDropDownAlwaysVisible ? maxHeight : measuredHeight;
}
private View getHintView(Context context) {