/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import android.Manifest; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; 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.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import java.util.List; /** * This class provides access to the system search services. * *
In practice, you won't interact with this class directly, as search * services are provided through methods in {@link android.app.Activity Activity} * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} * {@link android.content.Intent Intent}. * If you do require direct access to the SearchManager, do not instantiate * this class directly. Instead, retrieve it through * {@link android.content.Context#getSystemService * context.getSystemService(Context.SEARCH_SERVICE)}. * *
For a guide to using the search dialog and adding search * suggestions in your application, see the Dev Guide topic about Search.
*The search manager will open a search widget in an overlapping * window, and the underlying activity may be obscured. The search * entry state will remain in effect until one of the following events: *
Most applications will not use this interface to invoke search. * The primary method for invoking search is to call * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or * {@link android.app.Activity#startSearch Activity.startSearch()}. * * @param initialQuery A search string can be pre-entered here, but this * is typically null or empty. * @param selectInitialQuery If true, the intial query will be preselected, which means that * any further typing will replace it. This is useful for cases where an entire pre-formed * query is being inserted. If false, the selection point will be placed at the end of the * inserted query. This is useful when the inserted query is text that the user entered, * and the user would expect to be able to keep typing. This parameter is only meaningful * if initialQuery is a non-empty string. * @param launchActivity The ComponentName of the activity that has launched this search. * @param appSearchData An application can insert application-specific * context here, in order to improve quality or specificity of its own * searches. This data will be returned with SEARCH intent(s). Null if * no extra data is required. * @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, global search will be launched. * If true, this will always launch a platform-global (e.g. web-based) search instead. * * @see android.app.Activity#onSearchRequested * @see #stopSearch */ public void startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch) { if (globalSearch) { startGlobalSearch(initialQuery, selectInitialQuery, appSearchData); return; } ensureSearchDialog(); mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData); } private void ensureSearchDialog() { if (mSearchDialog == null) { mSearchDialog = new SearchDialog(mContext, this); mSearchDialog.setOnCancelListener(this); mSearchDialog.setOnDismissListener(this); } } /** * Starts the global search activity. */ /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData) { ComponentName globalSearchActivity = getGlobalSearchActivity(); if (globalSearchActivity == null) { Log.w(TAG, "No global search activity found."); return; } Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(globalSearchActivity); // Make sure that we have a Bundle to put source in if (appSearchData == null) { appSearchData = new Bundle(); } else { appSearchData = new Bundle(appSearchData); } // Set source to package name of app that starts global search, if not set already. if (!appSearchData.containsKey("source")) { appSearchData.putString("source", mContext.getPackageName()); } intent.putExtra(APP_DATA, appSearchData); if (!TextUtils.isEmpty(initialQuery)) { intent.putExtra(QUERY, initialQuery); } if (selectInitialQuery) { intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery); } try { if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0)); mContext.startActivity(intent); } catch (ActivityNotFoundException ex) { Log.e(TAG, "Global search activity not found: " + globalSearchActivity); } } /** * Gets the name of the global search activity. * * @hide */ public ComponentName getGlobalSearchActivity() { try { return mService.getGlobalSearchActivity(); } catch (RemoteException ex) { Log.e(TAG, "getGlobalSearchActivity() failed: " + ex); return null; } } /** * Gets the name of the web search activity. * * @return The name of the default activity for web searches. This activity * can be used to get web search suggestions. Returns {@code null} if * there is no default web search activity. * * @hide */ public ComponentName getWebSearchActivity() { try { return mService.getWebSearchActivity(); } catch (RemoteException ex) { Log.e(TAG, "getWebSearchActivity() failed: " + ex); return null; } } /** * Similar to {@link #startSearch} but actually fires off the search query after invoking * the search dialog. Made available for testing purposes. * * @param query The query to trigger. If empty, request will be ignored. * @param launchActivity The ComponentName of the activity that has launched this search. * @param appSearchData An application can insert application-specific * context here, in order to improve quality or specificity of its own * searches. This data will be returned with SEARCH intent(s). Null if * no extra data is required. * * @see #startSearch */ public void triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData) { if (!mAssociatedPackage.equals(launchActivity.getPackageName())) { throw new IllegalArgumentException("invoking app search on a different package " + "not associated with this search manager"); } if (query == null || TextUtils.getTrimmedLength(query) == 0) { Log.w(TAG, "triggerSearch called with empty query, ignoring."); return; } startSearch(query, false, launchActivity, appSearchData, false); mSearchDialog.launchQuerySearch(); } /** * Terminate search UI. * *
Typically the user will terminate the search UI by launching a * search or by canceling. This function allows the underlying application * or activity to cancel the search prematurely (for any reason). * *
This function can be safely called at any time (even if no search is active.)
*
* @see #startSearch
*/
public void stopSearch() {
if (mSearchDialog != null) {
mSearchDialog.cancel();
}
}
/**
* Determine if the Search UI is currently displayed.
*
* This is provided primarily for application test purposes.
*
* @return Returns true if the search UI is currently displayed.
*
* @hide
*/
public boolean isVisible() {
return mSearchDialog == null? false : mSearchDialog.isShowing();
}
/**
* See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
* search UI state.
*/
public interface OnDismissListener {
/**
* This method will be called when the search UI is dismissed. To make use of it, you must
* implement this method in your activity, and call
* {@link SearchManager#setOnDismissListener} to register it.
*/
public void onDismiss();
}
/**
* See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
* search UI state.
*/
public interface OnCancelListener {
/**
* This method will be called when the search UI is canceled. To make use if it, you must
* implement this method in your activity, and call
* {@link SearchManager#setOnCancelListener} to register it.
*/
public void onCancel();
}
/**
* Set or clear the callback that will be invoked whenever the search UI is dismissed.
*
* @param listener The {@link OnDismissListener} to use, or null.
*/
public void setOnDismissListener(final OnDismissListener listener) {
mDismissListener = listener;
}
/**
* 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(OnCancelListener listener) {
mCancelListener = listener;
}
/**
* @deprecated This method is an obsolete internal implementation detail. Do not use.
*/
@Deprecated
public void onCancel(DialogInterface dialog) {
if (mCancelListener != null) {
mCancelListener.onCancel();
}
}
/**
* @deprecated This method is an obsolete internal implementation detail. Do not use.
*/
@Deprecated
public void onDismiss(DialogInterface dialog) {
if (mDismissListener != null) {
mDismissListener.onDismiss();
}
}
/**
* Gets information about a searchable activity.
*
* @param componentName The activity to get searchable information for.
* @return Searchable information, or null
if the activity does not
* exist, or is not searchable.
*/
public SearchableInfo getSearchableInfo(ComponentName componentName) {
try {
return mService.getSearchableInfo(componentName);
} catch (RemoteException ex) {
Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
}
}
/**
* Gets a cursor with search suggestions.
*
* @param searchable Information about how to get the suggestions.
* @param query The search text entered (so far).
* @return a cursor with suggestions, or null the suggestion query failed.
*
* @hide because SearchableInfo is not part of the API.
*/
public Cursor getSuggestions(SearchableInfo searchable, String query) {
return getSuggestions(searchable, query, -1);
}
/**
* Gets a cursor with search suggestions.
*
* @param searchable Information about how to get the suggestions.
* @param query The search text entered (so far).
* @param limit The query limit to pass to the suggestion provider. This is advisory,
* the returned cursor may contain more rows. Pass {@code -1} for no limit.
* @return a cursor with suggestions, or
null the suggestion query failed.
*
* @hide because SearchableInfo is not part of the API.
*/
public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) {
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)
.query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
.fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel()
// 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);
}
if (limit > 0) {
uriBuilder.appendQueryParameter(SUGGEST_PARAMETER_LIMIT, String.valueOf(limit));
}
Uri uri = uriBuilder.build();
// finally, make the query
return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
}
/**
* Returns a list of the searchable activities that can be included in global search.
*
* @return a list containing searchable information for all searchable activities
* that have the
android:includeInGlobalSearch
attribute set
* in their searchable meta-data.
*/
public List