/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.server.search; import android.app.ActivityManagerNative; import android.app.IActivityWatcher; import android.app.ISearchManager; import android.app.ISearchManagerCallback; import android.app.SearchManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.util.Log; import java.util.List; /** * The search manager service handles the search UI, and maintains a registry of searchable * activities. */ public class SearchManagerService extends ISearchManager.Stub { // general debugging support private static final String TAG = "SearchManagerService"; private static final boolean DBG = false; // Context that the service is running in. private final Context mContext; // This field is initialized in ensureSearchablesCreated(), and then never modified. // Only accessed by ensureSearchablesCreated() and getSearchables() private Searchables mSearchables; // This field is initialized in ensureSearchDialogCreated(), and then never modified. // Only accessed by ensureSearchDialogCreated() and getSearchDialog() private SearchDialogWrapper mSearchDialog; /** * 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. */ public SearchManagerService(Context context) { mContext = context; // call initialize() after all pending actions on the main system thread have finished new Handler().post(new Runnable() { public void run() { initialize(); } }); } /** * Initializes the list of searchable activities and the search UI. */ void initialize() { try { ActivityManagerNative.getDefault().registerActivityWatcher( mActivityWatcher); } catch (RemoteException e) { } } private synchronized void ensureSearchablesCreated() { if (mSearchables != null) return; // already created mSearchables = new Searchables(mContext); mSearchables.buildSearchableList(); IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addDataScheme("package"); mContext.registerReceiver(mPackageChangedReceiver, packageFilter); } private synchronized void ensureSearchDialogCreated() { if (mSearchDialog != null) return; mSearchDialog = new SearchDialogWrapper(mContext); } private synchronized Searchables getSearchables() { ensureSearchablesCreated(); return mSearchables; } private synchronized SearchDialogWrapper getSearchDialog() { ensureSearchDialogCreated(); return mSearchDialog; } /** * Refreshes the "searchables" list when packages are added/removed. */ private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_PACKAGE_ADDED.equals(action) || Intent.ACTION_PACKAGE_REMOVED.equals(action) || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { if (DBG) Log.d(TAG, "Got " + action); // Dismiss search dialog, since the search context may no longer be valid getSearchDialog().stopSearch(); // Update list of searchable activities getSearchables().buildSearchableList(); broadcastSearchablesChanged(); } } }; private IActivityWatcher.Stub mActivityWatcher = new IActivityWatcher.Stub() { public void activityResuming(int activityId) throws RemoteException { if (DBG) Log.i("foo", "********************** resuming: " + activityId); if (mSearchDialog == null) return; mSearchDialog.activityResuming(activityId); } public void closingSystemDialogs(String reason) { if (DBG) Log.i("foo", "********************** closing dialogs: " + reason); if (mSearchDialog == null) return; mSearchDialog.closingSystemDialogs(reason); } }; /** * Informs all listeners that the list of searchables has been updated. */ void broadcastSearchablesChanged() { mContext.sendBroadcast( new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED)); } // // Searchable activities API // /** * Returns the SearchableInfo for a given activity. * * @param launchActivity The activity from which we're launching this search. * @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(final ComponentName launchActivity, final boolean globalSearch) { if (globalSearch) { return getSearchables().getDefaultSearchable(); } else { if (launchActivity == null) { Log.e(TAG, "getSearchableInfo(), activity == null"); return null; } return getSearchables().getSearchableInfo(launchActivity); } } /** * Returns a list of the searchable activities that can be included in global search. */ public List<SearchableInfo> getSearchablesInGlobalSearch() { return getSearchables().getSearchablesInGlobalSearchList(); } /** * Returns a list of the searchable activities that handle web searches. * Can be called from any thread. */ public List<SearchableInfo> getSearchablesForWebSearch() { return getSearchables().getSearchablesForWebSearchList(); } /** * Returns the default searchable activity for web searches. * Can be called from any thread. */ public SearchableInfo getDefaultSearchableForWebSearch() { return getSearchables().getDefaultSearchableForWebSearch(); } /** * Sets the default searchable activity for web searches. * Can be called from any thread. */ public void setDefaultWebSearch(final ComponentName component) { getSearchables().setDefaultWebSearch(component); broadcastSearchablesChanged(); } // Search UI API /** * Launches the search UI. Can be called from any thread. * * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean) */ public void startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch, ISearchManagerCallback searchManagerCallback, int ident) { getSearchDialog().startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData, globalSearch, searchManagerCallback, ident, false); // don't trigger } /** * Launches the search UI and triggers the search, as if the user had clicked on the * search button within the dialog. * * @see SearchManager#triggerSearch(String, android.content.ComponentName, android.os.Bundle) */ public void triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData, ISearchManagerCallback searchManagerCallback, int ident) { getSearchDialog().startSearch( query, false, launchActivity, appSearchData, false, searchManagerCallback, ident, true); // triger search after launching } /** * Cancels the search dialog. Can be called from any thread. */ public void stopSearch() { getSearchDialog().stopSearch(); } public boolean isVisible() { return mSearchDialog != null && mSearchDialog.isVisible(); } }