SearchManagerService.java revision 8d17f3f24bbda9a9cd7ea08c5925508dc2c011be
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.server.search;
18
19import android.app.ISearchManager;
20import android.app.ISearchManagerCallback;
21import android.app.SearchDialog;
22import android.app.SearchManager;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.res.Configuration;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.RemoteException;
33import android.util.Log;
34
35import java.util.List;
36import java.util.concurrent.Callable;
37import java.util.concurrent.ExecutionException;
38import java.util.concurrent.FutureTask;
39
40/**
41 * This is a simplified version of the Search Manager service.  It no longer handles
42 * presentation (UI).  Its function is to maintain the map & list of "searchable"
43 * items, which provides a mapping from individual activities (where a user might have
44 * invoked search) to specific searchable activities (where the search will be dispatched).
45 */
46public class SearchManagerService extends ISearchManager.Stub
47        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
48{
49        // general debugging support
50    private static final String TAG = "SearchManagerService";
51    private static final boolean DBG = false;
52
53        // class maintenance and general shared data
54    private final Context mContext;
55    private final Handler mHandler;
56    private boolean mSearchablesDirty;
57    private final Searchables mSearchables;
58
59    final SearchDialog mSearchDialog;
60    ISearchManagerCallback mCallback = null;
61
62    /**
63     * Initializes the Search Manager service in the provided system context.
64     * Only one instance of this object should be created!
65     *
66     * @param context to use for accessing DB, window manager, etc.
67     */
68    public SearchManagerService(Context context)  {
69        mContext = context;
70        mHandler = new Handler();
71        mSearchablesDirty = true;
72        mSearchables = new Searchables(context);
73        mSearchDialog = new SearchDialog(context);
74        mSearchDialog.setOnCancelListener(this);
75        mSearchDialog.setOnDismissListener(this);
76
77        // Setup the infrastructure for updating and maintaining the list
78        // of searchable activities.
79        IntentFilter filter = new IntentFilter();
80        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
81        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
82        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
83        filter.addDataScheme("package");
84        mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
85
86        // After startup settles down, preload the searchables list,
87        // which will reduce the delay when the search UI is invoked.
88        mHandler.post(mRunUpdateSearchable);
89    }
90
91    /**
92     * Listens for intent broadcasts.
93     *
94     * The primary purpose here is to refresh the "searchables" list
95     * if packages are added/removed.
96     */
97    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
98        @Override
99        public void onReceive(Context context, Intent intent) {
100            String action = intent.getAction();
101
102            // First, test for intents that matter at any time
103            if (action.equals(Intent.ACTION_PACKAGE_ADDED) ||
104                action.equals(Intent.ACTION_PACKAGE_REMOVED) ||
105                action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
106                mSearchablesDirty = true;
107                mHandler.post(mRunUpdateSearchable);
108                return;
109            }
110        }
111    };
112
113    /**
114     * This runnable (for the main handler / UI thread) will update the searchables list.
115     */
116    private Runnable mRunUpdateSearchable = new Runnable() {
117        public void run() {
118            updateSearchablesIfDirty();
119        }
120    };
121
122    /**
123     * Updates the list of searchables, either at startup or in response to
124     * a package add/remove broadcast message.
125     */
126    private void updateSearchables() {
127        if (DBG) debug("updateSearchables()");
128        mSearchables.buildSearchableList();
129        mSearchablesDirty = false;
130    }
131
132    /**
133     * Updates the list of searchables if needed.
134     */
135    private void updateSearchablesIfDirty() {
136        if (mSearchablesDirty) {
137            updateSearchables();
138        }
139    }
140
141    /**
142     * Returns the SearchableInfo for a given activity
143     *
144     * @param launchActivity The activity from which we're launching this search.
145     * @param globalSearch If false, this will only launch the search that has been specifically
146     * defined by the application (which is usually defined as a local search).  If no default
147     * search is defined in the current application or activity, no search will be launched.
148     * If true, this will always launch a platform-global (e.g. web-based) search instead.
149     * @return Returns a SearchableInfo record describing the parameters of the search,
150     * or null if no searchable metadata was available.
151     */
152    public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) {
153        updateSearchablesIfDirty();
154        SearchableInfo si = null;
155        if (globalSearch) {
156            si = mSearchables.getDefaultSearchable();
157        } else {
158            if (launchActivity == null) {
159                Log.e(TAG, "getSearchableInfo(), activity == null");
160                return null;
161            }
162            si = mSearchables.getSearchableInfo(launchActivity);
163        }
164
165        return si;
166    }
167
168    /**
169     * Returns a list of the searchable activities that can be included in global search.
170     */
171    public List<SearchableInfo> getSearchablesInGlobalSearch() {
172        updateSearchablesIfDirty();
173        return mSearchables.getSearchablesInGlobalSearchList();
174    }
175    /**
176     * Launches the search UI on the main thread of the service.
177     *
178     * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
179     */
180    public void startSearch(final String initialQuery,
181            final boolean selectInitialQuery,
182            final ComponentName launchActivity,
183            final Bundle appSearchData,
184            final boolean globalSearch,
185            final ISearchManagerCallback searchManagerCallback) {
186        if (DBG) debug("startSearch()");
187        Runnable task = new Runnable() {
188            public void run() {
189                performStartSearch(initialQuery,
190                        selectInitialQuery,
191                        launchActivity,
192                        appSearchData,
193                        globalSearch,
194                        searchManagerCallback);
195            }
196        };
197        mHandler.post(task);
198    }
199
200    /**
201     * Actually launches the search. This must be called on the service UI thread.
202     */
203    /*package*/ void performStartSearch(String initialQuery,
204            boolean selectInitialQuery,
205            ComponentName launchActivity,
206            Bundle appSearchData,
207            boolean globalSearch,
208            ISearchManagerCallback searchManagerCallback) {
209        if (DBG) debug("performStartSearch()");
210        mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
211                globalSearch);
212        if (searchManagerCallback != null) {
213            mCallback = searchManagerCallback;
214        }
215    }
216
217    /**
218     * Cancels the search dialog. Can be called from any thread.
219     */
220    public void stopSearch() {
221        if (DBG) debug("stopSearch()");
222        mHandler.post(new Runnable() {
223            public void run() {
224                performStopSearch();
225            }
226        });
227    }
228
229    /**
230     * Cancels the search dialog. Must be called from the service UI thread.
231     */
232    /*package*/ void performStopSearch() {
233        if (DBG) debug("performStopSearch()");
234        mSearchDialog.cancel();
235    }
236
237    /**
238     * Determines if the Search UI is currently displayed.
239     *
240     * @see SearchManager#isVisible()
241     */
242    public boolean isVisible() {
243        return postAndWait(mIsShowing, false, "isShowing()");
244    }
245
246    private final Callable<Boolean> mIsShowing = new Callable<Boolean>() {
247        public Boolean call() {
248            return mSearchDialog.isShowing();
249        }
250    };
251
252    public Bundle onSaveInstanceState() {
253        return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()");
254    }
255
256    private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() {
257        public Bundle call() {
258            if (mSearchDialog.isShowing()) {
259                return mSearchDialog.onSaveInstanceState();
260            } else {
261                return null;
262            }
263        }
264    };
265
266    public void onRestoreInstanceState(final Bundle searchDialogState) {
267        if (searchDialogState != null) {
268            mHandler.post(new Runnable() {
269                public void run() {
270                    mSearchDialog.onRestoreInstanceState(searchDialogState);
271                }
272            });
273        }
274    }
275
276    public void onConfigurationChanged(final Configuration newConfig) {
277        mHandler.post(new Runnable() {
278            public void run() {
279                if (mSearchDialog.isShowing()) {
280                    mSearchDialog.onConfigurationChanged(newConfig);
281                }
282            }
283        });
284    }
285
286    /**
287     * Called by {@link SearchDialog} when it goes away.
288     */
289    public void onDismiss(DialogInterface dialog) {
290        if (DBG) debug("onDismiss()");
291        if (mCallback != null) {
292            try {
293                mCallback.onDismiss();
294            } catch (RemoteException ex) {
295                Log.e(TAG, "onDismiss() failed: " + ex);
296            }
297        }
298    }
299
300    /**
301     * Called by {@link SearchDialog} when the user or activity cancels search.
302     * When this is called, {@link #onDismiss} is called too.
303     */
304    public void onCancel(DialogInterface dialog) {
305        if (DBG) debug("onCancel()");
306        if (mCallback != null) {
307            try {
308                mCallback.onCancel();
309            } catch (RemoteException ex) {
310                Log.e(TAG, "onCancel() failed: " + ex);
311            }
312        }
313    }
314
315    /**
316     * Returns a list of the searchable activities that handle web searches.
317     */
318    public List<SearchableInfo> getSearchablesForWebSearch() {
319        updateSearchablesIfDirty();
320        return mSearchables.getSearchablesForWebSearchList();
321    }
322
323    /**
324     * Returns the default searchable activity for web searches.
325     */
326    public SearchableInfo getDefaultSearchableForWebSearch() {
327        updateSearchablesIfDirty();
328        return mSearchables.getDefaultSearchableForWebSearch();
329    }
330
331    /**
332     * Sets the default searchable activity for web searches.
333     */
334    public void setDefaultWebSearch(ComponentName component) {
335        mSearchables.setDefaultWebSearch(component);
336    }
337
338    /**
339     * Runs an operation on the handler for the service, blocks until it returns,
340     * and returns the value returned by the operation.
341     *
342     * @param <V> Return value type.
343     * @param callable Operation to run.
344     * @param errorResult Value to return if the operations throws an exception.
345     * @param name Operation name to include in error log messages.
346     * @return The value returned by the operation.
347     */
348    private <V> V postAndWait(Callable<V> callable, V errorResult, String name) {
349        FutureTask<V> task = new FutureTask<V>(callable);
350        mHandler.post(task);
351        try {
352            return task.get();
353        } catch (InterruptedException ex) {
354            Log.e(TAG, "Error calling " + name + ": " + ex);
355            return errorResult;
356        } catch (ExecutionException ex) {
357            Log.e(TAG, "Error calling " + name + ": " + ex);
358            return errorResult;
359        }
360    }
361
362    private static void debug(String msg) {
363        Thread thread = Thread.currentThread();
364        Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
365    }
366
367}
368