summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--core/java/android/app/Activity.java52
-rw-r--r--core/java/android/app/ISearchManager.aidl14
-rw-r--r--core/java/android/app/ISearchManagerCallback.aidl23
-rw-r--r--core/java/android/app/SearchDialog.java55
-rw-r--r--core/java/android/app/SearchManager.java199
-rw-r--r--core/java/android/server/search/SearchManagerService.java195
-rw-r--r--core/java/android/server/search/SearchableInfo.java20
-rw-r--r--tests/AndroidTests/AndroidManifest.xml15
-rw-r--r--tests/AndroidTests/res/values/strings.xml3
-rw-r--r--tests/AndroidTests/res/xml/searchable.xml11
-rw-r--r--tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java182
-rw-r--r--tests/AndroidTests/src/com/android/unit_tests/SearchableActivity.java30
-rw-r--r--tests/AndroidTests/src/com/android/unit_tests/SuggestionProvider.java110
14 files changed, 735 insertions, 175 deletions
diff --git a/Android.mk b/Android.mk
index bffd04c..6e292a8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -76,6 +76,7 @@ LOCAL_SRC_FILES += \
core/java/android/app/IIntentSender.aidl \
core/java/android/app/INotificationManager.aidl \
core/java/android/app/ISearchManager.aidl \
+ core/java/android/app/ISearchManagerCallback.aidl \
core/java/android/app/IServiceConnection.aidl \
core/java/android/app/IStatusBar.aidl \
core/java/android/app/IThumbnailReceiver.aidl \
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f9b3d05..7fb3449 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -628,6 +628,8 @@ public class Activity extends ContextThemeWrapper
boolean mStartedActivity;
/*package*/ int mConfigChangeFlags;
/*package*/ Configuration mCurrentConfig;
+ private SearchManager mSearchManager;
+ private Bundle mSearchDialogState = null;
private Window mWindow;
@@ -788,6 +790,9 @@ public class Activity extends ContextThemeWrapper
protected void onCreate(Bundle savedInstanceState) {
mVisibleFromClient = mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, true);
+ // uses super.getSystemService() since this.getSystemService() looks at the
+ // mSearchManager field.
+ mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE);
mCalled = true;
}
@@ -805,9 +810,10 @@ public class Activity extends ContextThemeWrapper
// Also restore the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY);
+ Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY);
+ if (searchState != null) {
+ mSearchManager.restoreSearchDialog(searchState);
+ }
}
/**
@@ -1013,9 +1019,11 @@ public class Activity extends ContextThemeWrapper
// Also save the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY);
+ // onPause() should always be called before this method, so mSearchManagerState
+ // should be up to date.
+ if (mSearchDialogState != null) {
+ outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState);
+ }
}
/**
@@ -1286,12 +1294,6 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
- // also dismiss search dialog if showing
- // TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.stopSearch();
// close any cursors we are managing.
int numCursors = mManagedCursors.size();
@@ -1301,6 +1303,10 @@ public class Activity extends ContextThemeWrapper
c.mCursor.close();
}
}
+
+ // Clear any search state saved in performPause(). If the state may be needed in the
+ // future, it will have been saved by performSaveInstanceState()
+ mSearchDialogState = null;
}
/**
@@ -1324,9 +1330,7 @@ public class Activity extends ContextThemeWrapper
// also update search dialog if showing
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.onConfigurationChanged(newConfig);
+ mSearchManager.onConfigurationChanged(newConfig);
if (mWindow != null) {
// Pass the configuration changed event to the window
@@ -2543,10 +2547,7 @@ public class Activity extends ContextThemeWrapper
*/
public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
- // activate the search manager and start it up!
- SearchManager searchManager = (SearchManager)
- getSystemService(Context.SEARCH_SERVICE);
- searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
appSearchData, globalSearch);
}
@@ -3265,6 +3266,8 @@ public class Activity extends ContextThemeWrapper
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
+ } else if (SEARCH_SERVICE.equals(name)) {
+ return mSearchManager;
}
return super.getSystemService(name);
}
@@ -3563,10 +3566,21 @@ public class Activity extends ContextThemeWrapper
"Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()");
}
+
+ // restore search dialog, if any
+ if (mSearchDialogState != null) {
+ mSearchManager.restoreSearchDialog(mSearchDialogState);
+ }
+ mSearchDialogState = null;
}
final void performPause() {
onPause();
+
+ // save search dialog state if the search dialog is open,
+ // and then dismiss the search dialog
+ mSearchDialogState = mSearchManager.saveSearchDialog();
+ mSearchManager.stopSearch();
}
final void performUserLeaving() {
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 374423e..e8bd60a 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -16,7 +16,10 @@
package android.app;
+import android.app.ISearchManagerCallback;
import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.server.search.SearchableInfo;
/** @hide */
@@ -26,4 +29,15 @@ interface ISearchManager {
List<SearchableInfo> getSearchablesForWebSearch();
SearchableInfo getDefaultSearchableForWebSearch();
void setDefaultWebSearch(in ComponentName component);
+ void startSearch(in String initialQuery,
+ boolean selectInitialQuery,
+ in ComponentName launchActivity,
+ in Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback);
+ void stopSearch();
+ boolean isVisible();
+ Bundle onSaveInstanceState();
+ void onRestoreInstanceState(in Bundle savedInstanceState);
+ void onConfigurationChanged(in Configuration newConfig);
}
diff --git a/core/java/android/app/ISearchManagerCallback.aidl b/core/java/android/app/ISearchManagerCallback.aidl
new file mode 100644
index 0000000..bdfb2ba
--- /dev/null
+++ b/core/java/android/app/ISearchManagerCallback.aidl
@@ -0,0 +1,23 @@
+/**
+ * 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;
+
+/** @hide */
+oneway interface ISearchManagerCallback {
+ void onDismiss();
+ void onCancel();
+}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 7de6572..9141c4c 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -76,8 +76,8 @@ import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
- * In-application-process implementation of Search Bar. This is still controlled by the
- * SearchManager, but it runs in the current activity's process to keep things lighter weight.
+ * System search dialog. This is controlled by the
+ * SearchManagerService and runs in the system process.
*
* @hide
*/
@@ -179,17 +179,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Window theWindow = getWindow();
- theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
-
setContentView(com.android.internal.R.layout.search_bar);
- theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
- // taking up the whole window (even when transparent) is less than ideal,
- // but necessary to show the popup window until the window manager supports
- // having windows anchored by their parent but not clipped by them.
- ViewGroup.LayoutParams.FILL_PARENT);
+ Window theWindow = getWindow();
WindowManager.LayoutParams lp = theWindow.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+ lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+ // taking up the whole window (even when transparent) is less than ideal,
+ // but necessary to show the popup window until the window manager supports
+ // having windows anchored by their parent but not clipped by them.
+ lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
theWindow.setAttributes(lp);
@@ -234,10 +234,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mLocationManager =
(LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
@@ -278,12 +280,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
public boolean show(String initialQuery, boolean selectInitialQuery,
ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
- if (isShowing()) {
- // race condition - already showing but not handling events yet.
- // in this case, just discard the "show" request
- return true;
- }
-
+
// Reset any stored values from last time dialog was shown.
mStoredComponentName = null;
mStoredAppSearchData = null;
@@ -442,11 +439,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
stopLocationUpdates();
- // TODO: Removing the listeners means that they never get called, since
- // Dialog.dismissDialog() calls onStop() before sendDismissMessage().
- setOnCancelListener(null);
- setOnDismissListener(null);
-
// stop receiving broadcasts (throws exception if none registered)
try {
getContext().unregisterReceiver(mBroadcastReceiver);
@@ -654,15 +646,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold());
+ // we dismiss the entire dialog instead
+ mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
if (mGlobalSearchMode) {
mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in
- mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
mSearchAutoComplete.setDropDownBackgroundResource(
com.android.internal.R.drawable.search_dropdown_background);
} else {
mSearchAutoComplete.setDropDownAlwaysVisible(false);
- mSearchAutoComplete.setDropDownDismissedOnCompletion(true);
mSearchAutoComplete.setDropDownBackgroundResource(
com.android.internal.R.drawable.search_dropdown_background_apps);
}
@@ -1317,7 +1309,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Launches an intent. Also dismisses the search dialog if not in global search mode.
+ * Launches an intent and dismisses the search dialog (unless the intent
+ * is one of the special intents that modifies the state of the search dialog).
*/
private void launchIntent(Intent intent) {
if (intent == null) {
@@ -1326,9 +1319,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (handleSpecialIntent(intent)){
return;
}
- if (!mGlobalSearchMode) {
- dismiss();
- }
+ dismiss();
getContext().startActivity(intent);
}
@@ -1511,6 +1502,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
int actionKey, String actionMsg) {
// Now build the Intent
Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (data != null) {
intent.setData(data);
}
@@ -1595,14 +1587,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private boolean isEmpty() {
return TextUtils.getTrimmedLength(getText()) == 0;
}
-
- /**
- * Clears the entered text.
- */
- private void clear() {
- setText("");
- }
-
+
/**
* We override this method to avoid replacing the query box text
* when a suggestion is clicked.
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 820f192..1ddd20a 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.server.search.SearchableInfo;
+import android.util.Log;
import android.view.KeyEvent;
import java.util.List;
@@ -1108,6 +1109,10 @@ import java.util.List;
public class SearchManager
implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
{
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SearchManager";
+
/**
* This is a shortcut definition for the default menu key to use for invoking search.
*
@@ -1494,12 +1499,14 @@ public class SearchManager
private static ISearchManager sService = getSearchManagerService();
private final Context mContext;
- private final Handler mHandler;
-
- private SearchDialog mSearchDialog;
-
- private OnDismissListener mDismissListener = null;
- private OnCancelListener mCancelListener = null;
+
+ // package private since they are used by the inner class SearchManagerCallback
+ /* package */ boolean mIsShowing = false;
+ /* package */ final Handler mHandler;
+ /* package */ OnDismissListener mDismissListener = null;
+ /* package */ OnCancelListener mCancelListener = null;
+
+ private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback();
/*package*/ SearchManager(Context context, Handler handler) {
mContext = context;
@@ -1551,17 +1558,16 @@ public class SearchManager
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch) {
-
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
+ if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing);
+ if (mIsShowing) return;
+ try {
+ mIsShowing = true;
+ // activate the search manager and start it up!
+ sService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch, mSearchManagerCallback);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startSearch() failed: " + ex);
}
-
- // activate the search manager and start it up!
- mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch);
-
- mSearchDialog.setOnCancelListener(this);
- mSearchDialog.setOnDismissListener(this);
}
/**
@@ -1575,9 +1581,16 @@ public class SearchManager
*
* @see #startSearch
*/
- public void stopSearch() {
- if (mSearchDialog != null) {
- mSearchDialog.cancel();
+ public void stopSearch() {
+ if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ sService.stopSearch();
+ // onDismiss will also clear this, but we do it here too since onDismiss() is
+ // called asynchronously.
+ mIsShowing = false;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "stopSearch() failed: " + ex);
}
}
@@ -1590,13 +1603,11 @@ public class SearchManager
*
* @hide
*/
- public boolean isVisible() {
- if (mSearchDialog != null) {
- return mSearchDialog.isShowing();
- }
- return false;
+ public boolean isVisible() {
+ if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing);
+ return mIsShowing;
}
-
+
/**
* See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
* search UI state.
@@ -1631,79 +1642,112 @@ public class SearchManager
public void setOnDismissListener(final OnDismissListener listener) {
mDismissListener = listener;
}
-
- /**
- * The callback from the search dialog when dismissed
- * @hide
- */
- public void onDismiss(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss();
- }
- }
- }
/**
* Set or clear the callback that will be invoked whenever the search UI is canceled.
*
* @param listener The {@link OnCancelListener} to use, or null.
*/
- public void setOnCancelListener(final OnCancelListener listener) {
+ public void setOnCancelListener(OnCancelListener listener) {
mCancelListener = listener;
}
-
-
- /**
- * The callback from the search dialog when canceled
- * @hide
- */
- public void onCancel(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mCancelListener != null) {
- mCancelListener.onCancel();
+
+ private class SearchManagerCallback extends ISearchManagerCallback.Stub {
+
+ private final Runnable mFireOnDismiss = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnDismiss");
+ mIsShowing = false;
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss();
+ }
+ }
+ };
+
+ private final Runnable mFireOnCancel = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnCancel");
+ // doesn't need to clear mIsShowing since onDismiss() always gets called too
+ if (mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
}
+ };
+
+ public void onDismiss() {
+ if (DBG) debug("onDismiss()");
+ mHandler.post(mFireOnDismiss);
+ }
+
+ public void onCancel() {
+ if (DBG) debug("onCancel()");
+ mHandler.post(mFireOnCancel);
}
+
+ }
+
+ // TODO: remove the DialogInterface interfaces from SearchManager.
+ // This changes the public API, so I'll do it in a separate change.
+ public void onCancel(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
+ }
+ public void onDismiss(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
}
/**
- * Save instance state so we can recreate after a rotation.
- *
+ * Saves the state of the search UI.
+ *
+ * @return A Bundle containing the state of the search dialog, or {@code null}
+ * if the search UI is not visible.
+ *
* @hide
*/
- void saveSearchDialog(Bundle outState, String key) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- Bundle searchDialogState = mSearchDialog.onSaveInstanceState();
- outState.putBundle(key, searchDialogState);
+ public Bundle saveSearchDialog() {
+ if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return null;
+ try {
+ return sService.onSaveInstanceState();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onSaveInstanceState() failed: " + ex);
+ return null;
}
}
/**
- * Restore instance state after a rotation.
- *
+ * Restores the state of the search dialog.
+ *
+ * @param searchDialogState Bundle to read the state from.
+ *
* @hide
*/
- void restoreSearchDialog(Bundle inState, String key) {
- Bundle searchDialogState = inState.getBundle(key);
- if (searchDialogState != null) {
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
- }
- mSearchDialog.onRestoreInstanceState(searchDialogState);
+ public void restoreSearchDialog(Bundle searchDialogState) {
+ if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")");
+ if (searchDialogState == null) return;
+ try {
+ sService.onRestoreInstanceState(searchDialogState);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onRestoreInstanceState() failed: " + ex);
}
}
-
+
/**
- * Hook for updating layout on a rotation
- *
+ * Update the search dialog after a configuration change.
+ *
+ * @param newConfig The new configuration.
+ *
* @hide
*/
- void onConfigurationChanged(Configuration newConfig) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- mSearchDialog.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ sService.onConfigurationChanged(newConfig);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onConfigurationChanged() failed:" + ex);
}
}
-
+
private static ISearchManager getSearchManagerService() {
return ISearchManager.Stub.asInterface(
ServiceManager.getService(Context.SEARCH_SERVICE));
@@ -1724,7 +1768,8 @@ public class SearchManager
boolean globalSearch) {
try {
return sService.getSearchableInfo(componentName, globalSearch);
- } catch (RemoteException e) {
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
}
}
@@ -1805,6 +1850,7 @@ public class SearchManager
try {
return sService.getSearchablesInGlobalSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e);
return null;
}
}
@@ -1812,7 +1858,8 @@ public class SearchManager
/**
* Returns a list of the searchable activities that handle web searches.
*
- * @return a a list of all searchable activities that handle {@link SearchManager#ACTION_WEB_SEARCH}.
+ * @return a list of all searchable activities that handle
+ * {@link android.content.Intent#ACTION_WEB_SEARCH}.
*
* @hide because SearchableInfo is not part of the API.
*/
@@ -1820,6 +1867,7 @@ public class SearchManager
try {
return sService.getSearchablesForWebSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesForWebSearch() failed: " + e);
return null;
}
}
@@ -1835,6 +1883,7 @@ public class SearchManager
try {
return sService.getDefaultSearchableForWebSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getDefaultSearchableForWebSearch() failed: " + e);
return null;
}
}
@@ -1850,6 +1899,12 @@ public class SearchManager
try {
sService.setDefaultWebSearch(component);
} catch (RemoteException e) {
+ Log.e(TAG, "setDefaultWebSearch() failed: " + e);
}
}
+
+ 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/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;
}
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index fd6e6d8..55d4d64 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -219,7 +219,20 @@
</service>
<!-- Application components used for search manager tests -->
- <!-- TODO: Removed temporarily - need to be replaced using mocks -->
+
+ <activity android:name=".SearchableActivity"
+ android:label="Searchable Activity">
+ <intent-filter>
+ <action android:name="android.intent.action.SEARCH" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="android.app.searchable"
+ android:resource="@xml/searchable" />
+ </activity>
+
+ <provider android:name=".SuggestionProvider"
+ android:authorities="com.android.unit_tests.SuggestionProvider">
+ </provider>
<!-- Used to test IPC. -->
<service android:name=".binder.BinderTestService"
diff --git a/tests/AndroidTests/res/values/strings.xml b/tests/AndroidTests/res/values/strings.xml
index 21c72cf..49d8ae7 100644
--- a/tests/AndroidTests/res/values/strings.xml
+++ b/tests/AndroidTests/res/values/strings.xml
@@ -50,5 +50,8 @@
<item quantity="other">Some dogs</item>
</plurals>
+ <string name="searchable_label">SearchManager Test</string>
+ <string name="searchable_hint">A search hint</string>
+
<!-- <string name="layout_six_text_text">F</string> -->
</resources>
diff --git a/tests/AndroidTests/res/xml/searchable.xml b/tests/AndroidTests/res/xml/searchable.xml
index a40d53d..9d293b5 100644
--- a/tests/AndroidTests/res/xml/searchable.xml
+++ b/tests/AndroidTests/res/xml/searchable.xml
@@ -15,7 +15,12 @@
-->
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
- android:label="SearchManagerTest"
- android:hint="SearchManagerTest Hint"
-/>
+ android:label="@string/searchable_label"
+ android:hint="@string/searchable_hint"
+ android:searchSuggestAuthority="com.android.unit_tests.SuggestionProvider"
+ >
+ <actionkey android:keycode="KEYCODE_CALL"
+ android:suggestActionMsgColumn="suggest_action_msg_call" />
+
+</searchable> \ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
index f3c1542..f03a779 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
@@ -23,7 +23,10 @@ import android.app.ISearchManager;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
import android.os.ServiceManager;
+import android.server.search.SearchableInfo;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
@@ -37,12 +40,11 @@ import android.util.AndroidRuntimeException;
* com.android.unit_tests/android.test.InstrumentationTestRunner
*/
public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalActivity> {
-
- // If non-zero, enable a set of tests that start and stop the search manager.
- // This is currently disabled because it's causing an unwanted jump from the unit test
- // activity into the contacts activity. We'll put this back after we disable that jump.
- private static final int TEST_SEARCH_START = 0;
-
+
+ private ComponentName SEARCHABLE_ACTIVITY =
+ new ComponentName("com.android.unit_tests",
+ "com.android.unit_tests.SearchableActivity");
+
/*
* Bug list of test ideas.
*
@@ -88,7 +90,30 @@ public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalAct
super.setUp();
Activity testActivity = getActivity();
- mContext = (Context)testActivity;
+ mContext = testActivity;
+ }
+
+ private ISearchManager getSearchManagerService() {
+ return ISearchManager.Stub.asInterface(
+ ServiceManager.getService(Context.SEARCH_SERVICE));
+ }
+
+ // Checks that the search UI is visible.
+ private void assertSearchVisible() {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertTrue("SearchManager thinks search UI isn't visible when it should be",
+ searchManager.isVisible());
+ }
+
+ // Checks that the search UI is not visible.
+ // This checks both the SearchManager and the SearchManagerService,
+ // since SearchManager keeps a local variable for the visibility.
+ private void assertSearchNotVisible() {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertFalse("SearchManager thinks search UI is visible when it shouldn't be",
+ searchManager.isVisible());
}
/**
@@ -97,9 +122,7 @@ public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalAct
*/
@MediumTest
public void testSearchManagerInterfaceAvailable() {
- ISearchManager searchManager1 = ISearchManager.Stub.asInterface(
- ServiceManager.getService(Context.SEARCH_SERVICE));
- assertNotNull(searchManager1);
+ assertNotNull(getSearchManagerService());
}
/**
@@ -135,38 +158,127 @@ public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalAct
SearchManager searchManager2 = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE);
assertNotNull(searchManager2);
- assertSame( searchManager1, searchManager2 );
+ assertSame(searchManager1, searchManager2 );
}
-
+
+ @MediumTest
+ public void testSearchables() {
+ SearchableInfo si;
+
+ si = SearchManager.getSearchableInfo(SEARCHABLE_ACTIVITY, false);
+ assertNotNull(si);
+ assertFalse(SearchManager.isDefaultSearchable(si));
+ si = SearchManager.getSearchableInfo(SEARCHABLE_ACTIVITY, true);
+ assertNotNull(si);
+ assertTrue(SearchManager.isDefaultSearchable(si));
+ si = SearchManager.getSearchableInfo(null, true);
+ assertNotNull(si);
+ assertTrue(SearchManager.isDefaultSearchable(si));
+ }
+
+ /**
+ * Tests that rapid calls to start-stop-start doesn't cause problems.
+ */
+ @MediumTest
+ public void testSearchManagerFastInvocations() throws Exception {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager);
+ assertSearchNotVisible();
+
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ assertSearchVisible();
+ searchManager.stopSearch();
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+ }
+
+ /**
+ * Tests that startSearch() is idempotent.
+ */
+ @MediumTest
+ public void testStartSearchIdempotent() throws Exception {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager);
+ assertSearchNotVisible();
+
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ assertSearchVisible();
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+ }
+
+ /**
+ * Tests that stopSearch() is idempotent and can be called when the search UI is not visible.
+ */
+ @MediumTest
+ public void testStopSearchIdempotent() throws Exception {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager);
+ assertSearchNotVisible();
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ assertSearchVisible();
+ searchManager.stopSearch();
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+ }
+
/**
* The goal of this test is to confirm that we can start and then
* stop a simple search.
*/
-
- @MediumTest
- public void testSearchManagerInvocations() {
+ @MediumTest
+ public void testSearchManagerInvocations() throws Exception {
SearchManager searchManager = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE);
assertNotNull(searchManager);
-
- // TODO: make a real component name, or remove this need
- final ComponentName cn = new ComponentName("", "");
-
- if (TEST_SEARCH_START != 0) {
- // These tests should simply run to completion w/o exceptions
- searchManager.startSearch(null, false, cn, null, false);
- searchManager.stopSearch();
-
- searchManager.startSearch("", false, cn, null, false);
- searchManager.stopSearch();
-
- searchManager.startSearch("test search string", false, cn, null, false);
- searchManager.stopSearch();
-
- searchManager.startSearch("test search string", true, cn, null, false);
- searchManager.stopSearch();
- }
- }
+ assertSearchNotVisible();
-}
+ // These tests should simply run to completion w/o exceptions
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ assertSearchVisible();
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+
+ searchManager.startSearch("", false, SEARCHABLE_ACTIVITY, null, false);
+ assertSearchVisible();
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+
+ searchManager.startSearch("test search string", false, SEARCHABLE_ACTIVITY, null, false);
+ assertSearchVisible();
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+
+ searchManager.startSearch("test search string", true, SEARCHABLE_ACTIVITY, null, false);
+ assertSearchVisible();
+ searchManager.stopSearch();
+ assertSearchNotVisible();
+ }
+ @MediumTest
+ public void testSearchDialogState() throws Exception {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager);
+
+ Bundle searchState;
+
+ // search dialog not visible, so no state should be stored
+ searchState = searchManager.saveSearchDialog();
+ assertNull(searchState);
+
+ searchManager.startSearch("test search string", true, SEARCHABLE_ACTIVITY, null, false);
+ searchState = searchManager.saveSearchDialog();
+ assertNotNull(searchState);
+ searchManager.stopSearch();
+ }
+
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchableActivity.java b/tests/AndroidTests/src/com/android/unit_tests/SearchableActivity.java
new file mode 100644
index 0000000..53f40e9
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchableActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.unit_tests;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SearchableActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ finish();
+ }
+
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SuggestionProvider.java b/tests/AndroidTests/src/com/android/unit_tests/SuggestionProvider.java
new file mode 100644
index 0000000..bc61e27
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/SuggestionProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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 com.android.unit_tests;
+
+import android.app.SearchManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+/** Simple test provider that runs in the local process.
+ *
+ * Used by {@link SearchManagerTest}.
+ */
+public class SuggestionProvider extends ContentProvider {
+ private static final String TAG = "SuggestionProvider";
+
+ private static final int SEARCH_SUGGESTIONS = 1;
+
+ private static final UriMatcher sURLMatcher = new UriMatcher(
+ UriMatcher.NO_MATCH);
+
+ static {
+ sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY,
+ SEARCH_SUGGESTIONS);
+ sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
+ SEARCH_SUGGESTIONS);
+ }
+
+ private static final String[] COLUMNS = new String[] {
+ "_id",
+ SearchManager.SUGGEST_COLUMN_TEXT_1,
+ SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+ SearchManager.SUGGEST_COLUMN_QUERY
+ };
+
+ public SuggestionProvider() {
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn, String selection,
+ String[] selectionArgs, String sort) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case SEARCH_SUGGESTIONS:
+ String query = url.getLastPathSegment();
+ MatrixCursor cursor = new MatrixCursor(COLUMNS);
+ String[] suffixes = { "", "a", " foo", "XXXXXXXXXXXXXXXXX" };
+ for (String suffix : suffixes) {
+ addRow(cursor, query + suffix);
+ }
+ return cursor;
+ default:
+ throw new IllegalArgumentException("Unknown URL: " + url);
+ }
+ }
+
+ private void addRow(MatrixCursor cursor, String string) {
+ long id = cursor.getCount();
+ cursor.newRow().add(id).add(string).add(Intent.ACTION_SEARCH).add(string);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case SEARCH_SUGGESTIONS:
+ return SearchManager.SUGGEST_MIME_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URL: " + url);
+ }
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("update not supported");
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ throw new UnsupportedOperationException("insert not supported");
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("delete not supported");
+ }
+}