Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a3b95a99 authored by Amith Yamasani's avatar Amith Yamasani Committed by Android (Google) Code Review
Browse files

Merge "Move Search dialog out of system process into current activity."

parents 37105cc7 e9ce3f01
Loading
Loading
Loading
Loading
+2 −11
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -3449,17 +3450,7 @@ public class Activity extends ContextThemeWrapper
            return;
        }
        
        // uses super.getSystemService() since this.getSystemService() looks at the
        // mSearchManager field.
        mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE);
        int ident = mIdent;
        if (ident == 0) {
            if (mParent != null) ident = mParent.mIdent;
            if (ident == 0) {
                throw new IllegalArgumentException("no ident");
            }
        }
        mSearchManager.setIdent(ident, getComponentName());
        mSearchManager = new SearchManager(this, null);
    }
    
    @Override
+0 −19
Original line number Diff line number Diff line
@@ -29,23 +29,4 @@ 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,
            int ident);

    void triggerSearch(in String query,
            in ComponentName launchActivity,
            in Bundle appSearchData,
            ISearchManagerCallback searchManagerCallback,
            int ident);

    void stopSearch();


    boolean isVisible();

}
+10 −8
Original line number Diff line number Diff line
@@ -73,8 +73,8 @@ import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * System search dialog. This is controlled by the 
 * SearchManagerService and runs in the system process.
 * Search dialog. This is controlled by the 
 * SearchManager and runs in the current foreground process.
 * 
 * @hide
 */
@@ -118,6 +118,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
    private Bundle mAppSearchData;
    private boolean mGlobalSearchMode;
    private Context mActivityContext;
    private SearchManager mSearchManager;
    
    // Values we store to allow user to toggle between in-app search and global search.
    private ComponentName mStoredComponentName;
@@ -157,7 +158,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
     * 
     * @param context Application Context we can use for system acess
     */
    public SearchDialog(Context context) {
    public SearchDialog(Context context, SearchManager searchManager) {
        super(context, com.android.internal.R.style.Theme_GlobalSearchBar);

        // Save voice intent for later queries/launching
@@ -168,6 +169,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS

        mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mSearchManager = searchManager;
    }

    /**
@@ -180,7 +182,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS

        Window theWindow = getWindow();
        WindowManager.LayoutParams lp = theWindow.getAttributes();
        lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
        lp.width = ViewGroup.LayoutParams.MATCH_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
@@ -292,7 +293,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
            // TODO: When the browser icon issue is reconciled in Eclair, remove this special case.
            if (isBrowserSearch()) currentSearchText = "";

            return doShow(currentSearchText, false, null, mAppSearchData, true);
            cancel();
            mSearchManager.startGlobalSearch(currentSearchText, false, mStoredAppSearchData);
            return true;
        } else {
            if (mStoredComponentName != null) {
                // This means we should toggle *back* to an in-app search context from
@@ -1314,8 +1317,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
    }

    /**
     * Launches an intent, including any special intent handling.  Doesn't dismiss the dialog
     * since that will be handled in {@link SearchDialogWrapper#performActivityResuming}
     * Launches an intent, including any special intent handling.
     */
    private void launchIntent(Intent intent) {
        if (intent == null) {
+24 −68
Original line number Diff line number Diff line
@@ -1709,7 +1709,7 @@ public class SearchManager
    /* package */ OnDismissListener mDismissListener = null;
    /* package */ OnCancelListener mCancelListener = null;

    private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback();
    private SearchDialog mSearchDialog;

    /*package*/ SearchManager(Context context, Handler handler)  {
        mContext = context;
@@ -1778,31 +1778,29 @@ public class SearchManager
                            ComponentName launchActivity,
                            Bundle appSearchData,
                            boolean globalSearch) {
        if (mIdent == 0) throw new IllegalArgumentException(
                "Called from outside of an Activity context");
        ensureSearchDialog();

        if (globalSearch) {
            startGlobalSearch(initialQuery, selectInitialQuery, appSearchData);
            return;
        }

        if (!mAssociatedPackage.equals(launchActivity.getPackageName())) {
            Log.w(TAG, "invoking app search on a different package " +
                    "not associated with this search manager");
        mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
                globalSearch);
    }
        try {
            // activate the search manager and start it up!
            mService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData,
                    globalSearch, mSearchManagerCallback, mIdent);
        } catch (RemoteException ex) {
            Log.e(TAG, "startSearch() failed.", ex);

    private void ensureSearchDialog() {
        if (mSearchDialog == null) {
            mSearchDialog = new SearchDialog(mContext, this);
            mSearchDialog.setOnCancelListener(this);
            mSearchDialog.setOnDismissListener(this);
        }
    }

    /**
     * Starts the global search activity.
     */
    private void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
    /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
            Bundle appSearchData) {
        ComponentName globalSearchActivity = getGlobalSearchActivity();
        if (globalSearchActivity == null) {
@@ -1876,8 +1874,6 @@ public class SearchManager
    public void triggerSearch(String query,
                              ComponentName launchActivity,
                              Bundle appSearchData) {
        if (mIdent == 0) throw new IllegalArgumentException(
                "Called from outside of an Activity context");
        if (!mAssociatedPackage.equals(launchActivity.getPackageName())) {
            throw new IllegalArgumentException("invoking app search on a different package " +
                    "not associated with this search manager");
@@ -1886,12 +1882,8 @@ public class SearchManager
            Log.w(TAG, "triggerSearch called with empty query, ignoring.");
            return;
        }
        try {
            mService.triggerSearch(query, launchActivity, appSearchData, mSearchManagerCallback,
                    mIdent);
        } catch (RemoteException ex) {
            Log.e(TAG, "triggerSearch() failed.", ex);
        }
        startSearch(query, false, launchActivity, appSearchData, false);
        mSearchDialog.launchQuerySearch();
    }

    /**
@@ -1906,10 +1898,8 @@ public class SearchManager
     * @see #startSearch
     */
    public void stopSearch() {
        if (DBG) debug("stopSearch()");
        try {
            mService.stopSearch();
        } catch (RemoteException ex) {
        if (mSearchDialog != null) {
            mSearchDialog.cancel();
        }
    }

@@ -1923,13 +1913,7 @@ public class SearchManager
     * @hide
     */
    public boolean isVisible() {
        if (DBG) debug("isVisible()");
        try {
            return mService.isVisible();
        } catch (RemoteException e) {
            Log.e(TAG, "isVisible() failed: " + e);
            return false;
        }
        return mSearchDialog == null? false : mSearchDialog.isShowing();
    }

    /**
@@ -1976,44 +1960,14 @@ public class SearchManager
        mCancelListener = listener;
    }

    private class SearchManagerCallback extends ISearchManagerCallback.Stub {

        private final Runnable mFireOnDismiss = new Runnable() {
            public void run() {
                if (DBG) debug("mFireOnDismiss");
                if (mDismissListener != null) {
                    mDismissListener.onDismiss();
                }
            }
        };

        private final Runnable mFireOnCancel = new Runnable() {
            public void run() {
                if (DBG) debug("mFireOnCancel");
                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);
        }

    }

    /**
     * @deprecated This method is an obsolete internal implementation detail. Do not use.
     */
    @Deprecated
    public void onCancel(DialogInterface dialog) {
        throw new UnsupportedOperationException();
        if (mCancelListener != null) {
            mCancelListener.onCancel();
        }
    }

    /**
@@ -2021,7 +1975,9 @@ public class SearchManager
     */
    @Deprecated
    public void onDismiss(DialogInterface dialog) {
        throw new UnsupportedOperationException();
        if (mDismissListener != null) {
            mDismissListener.onDismiss();
        }
    }

    /**
+0 −426
Original line number Diff line number Diff line
/*
 * 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() + ")");
    }
}
Loading