| /* |
| * 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.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.os.Bundle; |
| import android.os.DeadObjectException; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| /** |
| * Runs an instance of {@link SearchDialog} on its own thread. |
| */ |
| class SearchDialogWrapper |
| implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { |
| |
| private static final String TAG = "SearchManagerService"; |
| private static final boolean DBG = false; |
| |
| private static final String SEARCH_UI_THREAD_NAME = "SearchDialog"; |
| private static final int SEARCH_UI_THREAD_PRIORITY = |
| android.os.Process.THREAD_PRIORITY_DEFAULT; |
| |
| // Takes no arguments |
| private static final int MSG_INIT = 0; |
| // Takes these arguments: |
| // arg1: selectInitialQuery, 0 = false, 1 = true |
| // arg2: globalSearch, 0 = false, 1 = true |
| // obj: searchManagerCallback |
| // data[KEY_INITIAL_QUERY]: initial query |
| // data[KEY_LAUNCH_ACTIVITY]: launch activity |
| // data[KEY_APP_SEARCH_DATA]: app search data |
| // data[KEY_TRIGGER]: 0 = false, 1 = true |
| private static final int MSG_START_SEARCH = 1; |
| // Takes no arguments |
| private static final int MSG_STOP_SEARCH = 2; |
| // arg1 is activity id |
| private static final int MSG_ACTIVITY_RESUMING = 3; |
| // obj is the reason |
| private static final int MSG_CLOSING_SYSTEM_DIALOGS = 4; |
| |
| private static final String KEY_INITIAL_QUERY = "q"; |
| private static final String KEY_LAUNCH_ACTIVITY = "a"; |
| private static final String KEY_APP_SEARCH_DATA = "d"; |
| private static final String KEY_IDENT = "i"; |
| private static final String KEY_TRIGGER = "t"; |
| |
| // Context used for getting search UI resources |
| private final Context mContext; |
| |
| // Handles messages on the search UI thread. |
| private final SearchDialogHandler mSearchUiThread; |
| |
| // The search UI |
| SearchDialog mSearchDialog; |
| |
| // If the search UI is visible, this is the callback for the client that showed it. |
| ISearchManagerCallback mCallback = null; |
| |
| // Identity of last activity that started search. |
| private int mStartedIdent = 0; |
| |
| // Identity of currently resumed activity. |
| private int mResumedIdent = 0; |
| |
| // True if we have registered our receivers. |
| private boolean mReceiverRegistered; |
| |
| private volatile boolean mVisible = false; |
| |
| /** |
| * Creates a new search dialog wrapper and a search UI thread. The search dialog itself will |
| * be created some asynchronously on the search UI thread. |
| * |
| * @param context Context used for getting search UI resources. |
| */ |
| public SearchDialogWrapper(Context context) { |
| mContext = context; |
| |
| // Create the search UI thread |
| HandlerThread t = new HandlerThread(SEARCH_UI_THREAD_NAME, SEARCH_UI_THREAD_PRIORITY); |
| t.start(); |
| mSearchUiThread = new SearchDialogHandler(t.getLooper()); |
| |
| // Create search UI on the search UI thread |
| mSearchUiThread.sendEmptyMessage(MSG_INIT); |
| } |
| |
| public boolean isVisible() { |
| return mVisible; |
| } |
| |
| /** |
| * Initializes the search UI. |
| * Must be called from the search UI thread. |
| */ |
| private void init() { |
| mSearchDialog = new SearchDialog(mContext); |
| mSearchDialog.setOnCancelListener(this); |
| mSearchDialog.setOnDismissListener(this); |
| } |
| |
| private void registerBroadcastReceiver() { |
| if (!mReceiverRegistered) { |
| IntentFilter filter = new IntentFilter( |
| Intent.ACTION_CONFIGURATION_CHANGED); |
| mContext.registerReceiver(mBroadcastReceiver, filter, null, |
| mSearchUiThread); |
| mReceiverRegistered = true; |
| } |
| } |
| |
| private void unregisterBroadcastReceiver() { |
| if (mReceiverRegistered) { |
| mContext.unregisterReceiver(mBroadcastReceiver); |
| mReceiverRegistered = false; |
| } |
| } |
| |
| /** |
| * Closes the search dialog when requested by the system (e.g. when a phone call comes in). |
| */ |
| private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { |
| if (DBG) debug(Intent.ACTION_CONFIGURATION_CHANGED); |
| performOnConfigurationChanged(); |
| } |
| } |
| }; |
| |
| // |
| // External API |
| // |
| |
| /** |
| * Launches the search UI. |
| * Can be called from any thread. |
| * |
| * @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, |
| int ident, |
| boolean trigger) { |
| if (DBG) debug("startSearch()"); |
| Message msg = Message.obtain(); |
| msg.what = MSG_START_SEARCH; |
| msg.arg1 = selectInitialQuery ? 1 : 0; |
| msg.arg2 = globalSearch ? 1 : 0; |
| msg.obj = searchManagerCallback; |
| Bundle msgData = msg.getData(); |
| msgData.putString(KEY_INITIAL_QUERY, initialQuery); |
| msgData.putParcelable(KEY_LAUNCH_ACTIVITY, launchActivity); |
| msgData.putBundle(KEY_APP_SEARCH_DATA, appSearchData); |
| msgData.putInt(KEY_IDENT, ident); |
| msgData.putInt(KEY_TRIGGER, trigger ? 1 : 0); |
| mSearchUiThread.sendMessage(msg); |
| // be a little more eager in setting this so isVisible will return the correct value if |
| // called immediately after startSearch |
| mVisible = true; |
| } |
| |
| /** |
| * Cancels the search dialog. |
| * Can be called from any thread. |
| */ |
| public void stopSearch() { |
| if (DBG) debug("stopSearch()"); |
| mSearchUiThread.sendEmptyMessage(MSG_STOP_SEARCH); |
| // be a little more eager in setting this so isVisible will return the correct value if |
| // called immediately after stopSearch |
| mVisible = false; |
| } |
| |
| /** |
| * Updates the currently resumed activity. |
| * Can be called from any thread. |
| */ |
| public void activityResuming(int ident) { |
| if (DBG) debug("activityResuming(ident=" + ident + ")"); |
| Message msg = Message.obtain(); |
| msg.what = MSG_ACTIVITY_RESUMING; |
| msg.arg1 = ident; |
| mSearchUiThread.sendMessage(msg); |
| } |
| |
| /** |
| * Handles closing of system windows/dialogs |
| * Can be called from any thread. |
| */ |
| public void closingSystemDialogs(String reason) { |
| if (DBG) debug("closingSystemDialogs(reason=" + reason + ")"); |
| Message msg = Message.obtain(); |
| msg.what = MSG_CLOSING_SYSTEM_DIALOGS; |
| msg.obj = reason; |
| mSearchUiThread.sendMessage(msg); |
| } |
| |
| // |
| // Implementation methods that run on the search UI thread |
| // |
| |
| private class SearchDialogHandler extends Handler { |
| |
| public SearchDialogHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_INIT: |
| init(); |
| break; |
| case MSG_START_SEARCH: |
| handleStartSearchMessage(msg); |
| break; |
| case MSG_STOP_SEARCH: |
| performStopSearch(); |
| break; |
| case MSG_ACTIVITY_RESUMING: |
| performActivityResuming(msg.arg1); |
| break; |
| case MSG_CLOSING_SYSTEM_DIALOGS: |
| performClosingSystemDialogs((String)msg.obj); |
| break; |
| } |
| } |
| |
| private void handleStartSearchMessage(Message msg) { |
| Bundle msgData = msg.getData(); |
| String initialQuery = msgData.getString(KEY_INITIAL_QUERY); |
| boolean selectInitialQuery = msg.arg1 != 0; |
| ComponentName launchActivity = |
| (ComponentName) msgData.getParcelable(KEY_LAUNCH_ACTIVITY); |
| Bundle appSearchData = msgData.getBundle(KEY_APP_SEARCH_DATA); |
| boolean globalSearch = msg.arg2 != 0; |
| ISearchManagerCallback searchManagerCallback = (ISearchManagerCallback) msg.obj; |
| int ident = msgData.getInt(KEY_IDENT); |
| boolean trigger = msgData.getInt(KEY_TRIGGER) != 0; |
| performStartSearch(initialQuery, selectInitialQuery, launchActivity, |
| appSearchData, globalSearch, searchManagerCallback, ident, trigger); |
| } |
| |
| } |
| |
| /** |
| * Actually launches the search UI. |
| * This must be called on the search UI thread. |
| */ |
| void performStartSearch(String initialQuery, |
| boolean selectInitialQuery, |
| ComponentName launchActivity, |
| Bundle appSearchData, |
| boolean globalSearch, |
| ISearchManagerCallback searchManagerCallback, |
| int ident, |
| boolean trigger) { |
| if (DBG) debug("performStartSearch()"); |
| |
| registerBroadcastReceiver(); |
| mCallback = searchManagerCallback; |
| |
| // clean up any hidden dialog that we were waiting to resume |
| if (mStartedIdent != 0) { |
| mSearchDialog.dismiss(); |
| } |
| |
| mStartedIdent = ident; |
| if (DBG) Log.v(TAG, "******************* DIALOG: start"); |
| |
| mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, |
| globalSearch); |
| mVisible = true; |
| if (trigger) { |
| mSearchDialog.launchQuerySearch(); |
| } |
| } |
| |
| /** |
| * Actually cancels the search UI. |
| * This must be called on the search UI thread. |
| */ |
| void performStopSearch() { |
| if (DBG) debug("performStopSearch()"); |
| if (DBG) Log.v(TAG, "******************* DIALOG: cancel"); |
| mSearchDialog.cancel(); |
| mVisible = false; |
| mStartedIdent = 0; |
| } |
| |
| /** |
| * Updates the resumed activity |
| * This must be called on the search UI thread. |
| */ |
| void performActivityResuming(int ident) { |
| if (DBG) debug("performResumingActivity(): mStartedIdent=" |
| + mStartedIdent + ", resuming: " + ident); |
| this.mResumedIdent = ident; |
| if (mStartedIdent != 0) { |
| if (mStartedIdent == mResumedIdent) { |
| // we are resuming into the activity where we previously hid the dialog, bring it |
| // back |
| if (DBG) Log.v(TAG, "******************* DIALOG: show"); |
| mSearchDialog.show(); |
| mVisible = true; |
| } else { |
| // resuming into some other activity; hide ourselves in case we ever come back |
| // so we can show ourselves quickly again |
| if (DBG) Log.v(TAG, "******************* DIALOG: hide"); |
| mSearchDialog.hide(); |
| mVisible = false; |
| } |
| } |
| } |
| |
| /** |
| * Updates due to system dialogs being closed |
| * This must be called on the search UI thread. |
| */ |
| void performClosingSystemDialogs(String reason) { |
| if (DBG) debug("performClosingSystemDialogs(): mStartedIdent=" |
| + mStartedIdent + ", reason: " + reason); |
| if (!"search".equals(reason)) { |
| if (DBG) debug(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); |
| performStopSearch(); |
| } |
| } |
| |
| /** |
| * Must be called from the search UI thread. |
| */ |
| void performOnConfigurationChanged() { |
| if (DBG) debug("performOnConfigurationChanged()"); |
| mSearchDialog.onConfigurationChanged(); |
| } |
| |
| /** |
| * Called by {@link SearchDialog} when it goes away. |
| */ |
| public void onDismiss(DialogInterface dialog) { |
| if (DBG) debug("onDismiss()"); |
| mStartedIdent = 0; |
| mVisible = false; |
| callOnDismiss(); |
| |
| // we don't need the callback anymore, release it |
| mCallback = null; |
| unregisterBroadcastReceiver(); |
| } |
| |
| |
| /** |
| * Called by {@link SearchDialog} when the user or activity cancels search. |
| * Whenever this method is called, {@link #onDismiss} is always called afterwards. |
| */ |
| public void onCancel(DialogInterface dialog) { |
| if (DBG) debug("onCancel()"); |
| callOnCancel(); |
| } |
| |
| private void callOnDismiss() { |
| if (mCallback == null) return; |
| try { |
| // should be safe to do on the search UI thread, since it's a oneway interface |
| mCallback.onDismiss(); |
| } catch (DeadObjectException ex) { |
| // The process that hosted the callback has died, do nothing |
| } catch (RemoteException ex) { |
| Log.e(TAG, "onDismiss() failed: " + ex); |
| } |
| } |
| |
| private void callOnCancel() { |
| if (mCallback != null) { |
| try { |
| // should be safe to do on the search UI thread, since it's a oneway interface |
| mCallback.onCancel(); |
| } catch (DeadObjectException ex) { |
| // The process that hosted the callback has died, do nothing |
| } catch (RemoteException ex) { |
| Log.e(TAG, "onCancel() failed: " + ex); |
| } |
| } |
| } |
| |
| private static void debug(String msg) { |
| Thread thread = Thread.currentThread(); |
| Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); |
| } |
| } |