summaryrefslogtreecommitdiffstats
path: root/core/java/android/server
diff options
context:
space:
mode:
authorBjorn Bringert <bringert@android.com>2009-06-05 13:22:28 +0100
committerBjorn Bringert <bringert@android.com>2009-06-15 09:05:26 +0100
commit8d17f3f24bbda9a9cd7ea08c5925508dc2c011be (patch)
treecf7e5ebb721354bfffc41d2ab4932cb70ea3c033 /core/java/android/server
parent3d59ee7aa66a5b1e80f5042f8d872dd9819b5f40 (diff)
downloadframeworks_base-8d17f3f24bbda9a9cd7ea08c5925508dc2c011be.zip
frameworks_base-8d17f3f24bbda9a9cd7ea08c5925508dc2c011be.tar.gz
frameworks_base-8d17f3f24bbda9a9cd7ea08c5925508dc2c011be.tar.bz2
Run search dialog in the system process.
Fixes http://b/issue?id=1905863 This is needed to address two security issues with global search: http://b/issue?id=1871088 (Apps can read content providers through GlobalSearch) http://b/issue?id=1819627 (Apps can use GlobalSearch to launch arbirtrary intents) This also fixes http://b/issue?id=1693153 (SearchManager.OnDismissListener never gets called) To fix the security issues, GlobalSearch also needs to require a non-app permission to access its content provider and launch intents.
Diffstat (limited to 'core/java/android/server')
-rw-r--r--core/java/android/server/search/SearchManagerService.java195
-rw-r--r--core/java/android/server/search/SearchableInfo.java20
2 files changed, 205 insertions, 10 deletions
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 060bcea..db812d1 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -17,15 +17,25 @@
package android.server.search;
import android.app.ISearchManager;
+import android.app.ISearchManagerCallback;
+import android.app.SearchDialog;
+import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.util.Log;
import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
/**
* This is a simplified version of the Search Manager service. It no longer handles
@@ -34,16 +44,20 @@ import java.util.List;
* invoked search) to specific searchable activities (where the search will be dispatched).
*/
public class SearchManagerService extends ISearchManager.Stub
+ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
{
// general debugging support
private static final String TAG = "SearchManagerService";
- private static final boolean DEBUG = false;
+ private static final boolean DBG = false;
// class maintenance and general shared data
private final Context mContext;
private final Handler mHandler;
private boolean mSearchablesDirty;
- private Searchables mSearchables;
+ private final Searchables mSearchables;
+
+ final SearchDialog mSearchDialog;
+ ISearchManagerCallback mCallback = null;
/**
* Initializes the Search Manager service in the provided system context.
@@ -56,6 +70,9 @@ public class SearchManagerService extends ISearchManager.Stub
mHandler = new Handler();
mSearchablesDirty = true;
mSearchables = new Searchables(context);
+ mSearchDialog = new SearchDialog(context);
+ mSearchDialog.setOnCancelListener(this);
+ mSearchDialog.setOnDismissListener(this);
// Setup the infrastructure for updating and maintaining the list
// of searchable activities.
@@ -107,6 +124,7 @@ public class SearchManagerService extends ISearchManager.Stub
* a package add/remove broadcast message.
*/
private void updateSearchables() {
+ if (DBG) debug("updateSearchables()");
mSearchables.buildSearchableList();
mSearchablesDirty = false;
}
@@ -137,6 +155,10 @@ public class SearchManagerService extends ISearchManager.Stub
if (globalSearch) {
si = mSearchables.getDefaultSearchable();
} else {
+ if (launchActivity == null) {
+ Log.e(TAG, "getSearchableInfo(), activity == null");
+ return null;
+ }
si = mSearchables.getSearchableInfo(launchActivity);
}
@@ -150,6 +172,145 @@ public class SearchManagerService extends ISearchManager.Stub
updateSearchablesIfDirty();
return mSearchables.getSearchablesInGlobalSearchList();
}
+ /**
+ * Launches the search UI on the main thread of the service.
+ *
+ * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
+ */
+ public void startSearch(final String initialQuery,
+ final boolean selectInitialQuery,
+ final ComponentName launchActivity,
+ final Bundle appSearchData,
+ final boolean globalSearch,
+ final ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("startSearch()");
+ Runnable task = new Runnable() {
+ public void run() {
+ performStartSearch(initialQuery,
+ selectInitialQuery,
+ launchActivity,
+ appSearchData,
+ globalSearch,
+ searchManagerCallback);
+ }
+ };
+ mHandler.post(task);
+ }
+
+ /**
+ * Actually launches the search. This must be called on the service UI thread.
+ */
+ /*package*/ void performStartSearch(String initialQuery,
+ boolean selectInitialQuery,
+ ComponentName launchActivity,
+ Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("performStartSearch()");
+ mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch);
+ if (searchManagerCallback != null) {
+ mCallback = searchManagerCallback;
+ }
+ }
+
+ /**
+ * Cancels the search dialog. Can be called from any thread.
+ */
+ public void stopSearch() {
+ if (DBG) debug("stopSearch()");
+ mHandler.post(new Runnable() {
+ public void run() {
+ performStopSearch();
+ }
+ });
+ }
+
+ /**
+ * Cancels the search dialog. Must be called from the service UI thread.
+ */
+ /*package*/ void performStopSearch() {
+ if (DBG) debug("performStopSearch()");
+ mSearchDialog.cancel();
+ }
+
+ /**
+ * Determines if the Search UI is currently displayed.
+ *
+ * @see SearchManager#isVisible()
+ */
+ public boolean isVisible() {
+ return postAndWait(mIsShowing, false, "isShowing()");
+ }
+
+ private final Callable<Boolean> mIsShowing = new Callable<Boolean>() {
+ public Boolean call() {
+ return mSearchDialog.isShowing();
+ }
+ };
+
+ public Bundle onSaveInstanceState() {
+ return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()");
+ }
+
+ private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() {
+ public Bundle call() {
+ if (mSearchDialog.isShowing()) {
+ return mSearchDialog.onSaveInstanceState();
+ } else {
+ return null;
+ }
+ }
+ };
+
+ public void onRestoreInstanceState(final Bundle searchDialogState) {
+ if (searchDialogState != null) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mSearchDialog.onRestoreInstanceState(searchDialogState);
+ }
+ });
+ }
+ }
+
+ public void onConfigurationChanged(final Configuration newConfig) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mSearchDialog.isShowing()) {
+ mSearchDialog.onConfigurationChanged(newConfig);
+ }
+ }
+ });
+ }
+
+ /**
+ * Called by {@link SearchDialog} when it goes away.
+ */
+ public void onDismiss(DialogInterface dialog) {
+ if (DBG) debug("onDismiss()");
+ if (mCallback != null) {
+ try {
+ mCallback.onDismiss();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onDismiss() failed: " + ex);
+ }
+ }
+ }
+
+ /**
+ * Called by {@link SearchDialog} when the user or activity cancels search.
+ * When this is called, {@link #onDismiss} is called too.
+ */
+ public void onCancel(DialogInterface dialog) {
+ if (DBG) debug("onCancel()");
+ if (mCallback != null) {
+ try {
+ mCallback.onCancel();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onCancel() failed: " + ex);
+ }
+ }
+ }
/**
* Returns a list of the searchable activities that handle web searches.
@@ -173,4 +334,34 @@ public class SearchManagerService extends ISearchManager.Stub
public void setDefaultWebSearch(ComponentName component) {
mSearchables.setDefaultWebSearch(component);
}
+
+ /**
+ * Runs an operation on the handler for the service, blocks until it returns,
+ * and returns the value returned by the operation.
+ *
+ * @param <V> Return value type.
+ * @param callable Operation to run.
+ * @param errorResult Value to return if the operations throws an exception.
+ * @param name Operation name to include in error log messages.
+ * @return The value returned by the operation.
+ */
+ private <V> V postAndWait(Callable<V> callable, V errorResult, String name) {
+ FutureTask<V> task = new FutureTask<V>(callable);
+ mHandler.post(task);
+ try {
+ return task.get();
+ } catch (InterruptedException ex) {
+ Log.e(TAG, "Error calling " + name + ": " + ex);
+ return errorResult;
+ } catch (ExecutionException ex) {
+ Log.e(TAG, "Error calling " + name + ": " + ex);
+ return errorResult;
+ }
+ }
+
+ private static void debug(String msg) {
+ Thread thread = Thread.currentThread();
+ Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ }
+
}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 4df7368..90dfa0b 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -320,7 +320,7 @@ public final class SearchableInfo implements Parcelable {
// for now, implement some form of rules - minimal data
if (mLabelId == 0) {
- throw new IllegalArgumentException("No label.");
+ throw new IllegalArgumentException("Search label must be a resource reference.");
}
}
@@ -441,13 +441,17 @@ public final class SearchableInfo implements Parcelable {
xml.close();
if (DBG) {
- Log.d(LOG_TAG, "Checked " + activityInfo.name
- + ",label=" + searchable.getLabelId()
- + ",icon=" + searchable.getIconId()
- + ",suggestAuthority=" + searchable.getSuggestAuthority()
- + ",target=" + searchable.getSearchActivity().getClassName()
- + ",global=" + searchable.shouldIncludeInGlobalSearch()
- + ",threshold=" + searchable.getSuggestThreshold());
+ if (searchable != null) {
+ Log.d(LOG_TAG, "Checked " + activityInfo.name
+ + ",label=" + searchable.getLabelId()
+ + ",icon=" + searchable.getIconId()
+ + ",suggestAuthority=" + searchable.getSuggestAuthority()
+ + ",target=" + searchable.getSearchActivity().getClassName()
+ + ",global=" + searchable.shouldIncludeInGlobalSearch()
+ + ",threshold=" + searchable.getSuggestThreshold());
+ } else {
+ Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data");
+ }
}
return searchable;
}