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

Commit 720959cc authored by Sandeep Siddhartha's avatar Sandeep Siddhartha Committed by Android (Google) Code Review
Browse files

Merge "Start using the Hotword recognition APIs" into klp-dev

parents 52485b0b 42c3e02c
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -38,9 +38,6 @@
    <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />

    <!-- Permission for the Hotword detector service -->
    <uses-permission android:name="com.google.android.googlequicksearchbox.SEARCH_API" />

    <application android:label="@string/app_name"
        android:process="com.android.systemui"
        android:persistent="true" >
+0 −208
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.android.keyguard;

import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;

import com.google.android.search.service.IHotwordService;
import com.google.android.search.service.IHotwordServiceCallback;

/**
 * Utility class with its callbacks to simplify usage of {@link IHotwordService}.
 *
 * The client is meant to be used for a single hotword detection in a session.
 * start() -> stop(); client is asked to stop & disconnect from the service.
 * start() -> onHotwordDetected(); client disconnects from the service automatically.
 */
public class HotwordServiceClient implements Handler.Callback {
    private static final String TAG = "HotwordServiceClient";
    private static final boolean DBG = true;
    private static final String ACTION_HOTWORD =
            "com.google.android.search.service.IHotwordService";

    private static final int MSG_SERVICE_CONNECTED = 0;
    private static final int MSG_SERVICE_DISCONNECTED = 1;
    private static final int MSG_HOTWORD_STARTED = 2;
    private static final int MSG_HOTWORD_STOPPED = 3;
    private static final int MSG_HOTWORD_DETECTED = 4;

    private final Context mContext;
    private final Callback mClientCallback;
    private final Handler mHandler;

    private IHotwordService mService;

    public HotwordServiceClient(Context context, Callback callback) {
        mContext = context;
        mClientCallback = callback;
        mHandler = new Handler(this);
    }

    public interface Callback {
        void onServiceConnected();
        void onServiceDisconnected();
        void onHotwordDetectionStarted();
        void onHotwordDetectionStopped();
        void onHotwordDetected(String action);
    }

    /**
     * Binds to the {@link IHotwordService} and starts hotword detection
     * when the service is connected.
     *
     * @return false if the service can't be bound to.
     */
    public synchronized boolean start() {
        if (mService != null) {
            if (DBG) Log.d(TAG, "Multiple call to start(), service was already bound");
            return true;
        } else {
            // TODO: The hotword service is currently hosted within the search app
            // so the component handling the assist intent should handle hotwording
            // as well.
            final Intent intent =
                    ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
                            .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
            if (intent == null) {
                return false;
            }

            Intent hotwordIntent = new Intent(ACTION_HOTWORD);
            hotwordIntent.fillIn(intent, Intent.FILL_IN_PACKAGE);
            return mContext.bindService(
                    hotwordIntent,
                   mConnection,
                   Context.BIND_AUTO_CREATE);
        }
    }

    /**
     * Unbinds from the the {@link IHotwordService}.
     */
    public synchronized void stop() {
        if (mService != null) {
            mContext.unbindService(mConnection);
            mService = null;
        }
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_SERVICE_CONNECTED:
                handleServiceConnected();
                break;
            case MSG_SERVICE_DISCONNECTED:
                handleServiceDisconnected();
                break;
            case MSG_HOTWORD_STARTED:
                handleHotwordDetectionStarted();
                break;
            case MSG_HOTWORD_STOPPED:
                handleHotwordDetectionStopped();
                break;
            case MSG_HOTWORD_DETECTED:
                handleHotwordDetected((String) msg.obj);
                break;
            default:
                if (DBG) Log.e(TAG, "Unhandled message");
                return false;
        }
        return true;
    }

    private void handleServiceConnected() {
        if (DBG) Log.d(TAG, "handleServiceConnected()");
        if (mClientCallback != null) mClientCallback.onServiceConnected();
        try {
            mService.requestHotwordDetection(mServiceCallback);
        } catch (RemoteException e) {
            Log.e(TAG, "Exception while registering callback", e);
            mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
        }
    }

    private void handleServiceDisconnected() {
        if (DBG) Log.d(TAG, "handleServiceDisconnected()");
        mService = null;
        if (mClientCallback != null) mClientCallback.onServiceDisconnected();
    }

    private void handleHotwordDetectionStarted() {
        if (DBG) Log.d(TAG, "handleHotwordDetectionStarted()");
        if (mClientCallback != null) mClientCallback.onHotwordDetectionStarted();
    }

    private void handleHotwordDetectionStopped() {
        if (DBG) Log.d(TAG, "handleHotwordDetectionStopped()");
        if (mClientCallback != null) mClientCallback.onHotwordDetectionStopped();
    }

    void handleHotwordDetected(final String action) {
        if (DBG) Log.d(TAG, "handleHotwordDetected()");
        if (mClientCallback != null) mClientCallback.onHotwordDetected(action);
        stop();
    }

    /**
     * Implements service connection methods.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        /**
         * Called when the service connects after calling bind().
         */
        public void onServiceConnected(ComponentName className, IBinder iservice) {
            mService = IHotwordService.Stub.asInterface(iservice);
            mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED);
        }

        /**
         * Called if the service unexpectedly disconnects. This indicates an error.
         */
        public void onServiceDisconnected(ComponentName className) {
            mService = null;
            mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
        }
    };

    /**
     * Implements the AIDL IHotwordServiceCallback interface.
     */
    private final IHotwordServiceCallback mServiceCallback = new IHotwordServiceCallback.Stub() {

        public void onHotwordDetectionStarted() {
            mHandler.sendEmptyMessage(MSG_HOTWORD_STARTED);
        }

        public void onHotwordDetectionStopped() {
            mHandler.sendEmptyMessage(MSG_HOTWORD_STOPPED);
        }

        public void onHotwordDetected(String action) {
            mHandler.obtainMessage(MSG_HOTWORD_DETECTED, action).sendToTarget();
        }
    };
}
+69 −50
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.keyguard;

import android.animation.ObjectAnimator;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -23,9 +24,12 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.speech.hotword.HotwordRecognitionListener;
import android.speech.hotword.HotwordRecognizer;
import android.telephony.TelephonyManager;
import android.util.AttributeSet;
import android.util.Log;
@@ -45,7 +49,10 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
    private static final String ASSIST_ICON_METADATA_NAME =
        "com.android.systemui.action_assist_icon";
    // Flag to enable/disable hotword detection on lock screen.
    private static final boolean FLAG_HOTWORD = false;
    private static final boolean FLAG_HOTWORD = true;

    // TODO: Fix this to be non-static.
    private static HotwordRecognizer sHotwordClient;

    private KeyguardSecurityCallback mCallback;
    private GlowPadView mGlowPadView;
@@ -57,15 +64,13 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
    private LockPatternUtils mLockPatternUtils;
    private SecurityMessageDisplay mSecurityMessageDisplay;
    private Drawable mBouncerFrame;
    private HotwordServiceClient mHotwordClient;

    OnTriggerListener mOnTriggerListener = new OnTriggerListener() {

        public void onTrigger(View v, int target) {
            final int resId = mGlowPadView.getResourceIdForTarget(target);
            if (FLAG_HOTWORD) {
            maybeStopHotwordDetector();
            }

            switch (resId) {
                case R.drawable.ic_action_assist_generic:
                    Intent assistIntent =
@@ -128,11 +133,9 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
        @Override
        public void onPhoneStateChanged(int phoneState) {
            if (FLAG_HOTWORD) {
                // We need to stop the hotwording when a phone call comes in
                // TODO(sansid): This is not really needed if onPause triggers
                // when we navigate away from the keyguard
                if (phoneState == TelephonyManager.CALL_STATE_RINGING) {
                    if (DEBUG) Log.d(TAG, "Stopping due to CALL_STATE_RINGING");
                // We need to stop hotword detection when a call state is not idle anymore.
                if (phoneState != TelephonyManager.CALL_STATE_IDLE) {
                    if (DEBUG) Log.d(TAG, "Stopping due to call state not being idle");
                    maybeStopHotwordDetector();
                }
            }
@@ -180,8 +183,8 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
        mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
        View bouncerFrameView = findViewById(R.id.keyguard_selector_view_frame);
        mBouncerFrame = bouncerFrameView.getBackground();
        if (FLAG_HOTWORD) {
            mHotwordClient = new HotwordServiceClient(getContext(), mHotwordCallback);
        if (FLAG_HOTWORD && sHotwordClient == null) {
            sHotwordClient = HotwordRecognizer.createHotwordRecognizer(getContext());
        }
    }

@@ -286,20 +289,19 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
    @Override
    public void onPause() {
        KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mUpdateCallback);
        maybeStopHotwordDetector();
    }

    @Override
    public void onResume(int reason) {
        KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mUpdateCallback);
        // TODO: Figure out if there's a better way to do it.
        // Right now we don't get onPause at all, and onResume gets called
        // multiple times (even when the screen is turned off with VIEW_REVEALED)
        // onResume gets called multiple times, however we are interested in
        // the reason to figure out when to start/stop hotword detection.
        if (reason == SCREEN_ON) {
            if (!KeyguardUpdateMonitor.getInstance(getContext()).isSwitchingUser()) {
                maybeStartHotwordDetector();
            }
        } else {
            maybeStopHotwordDetector();
        }
    }

@@ -324,15 +326,15 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri

    /**
     * Start the hotword detector if:
     * <li> HOTWORDING_ENABLED is true and
     * <li> HotwordUnlock is initialized and
     * <li> FLAG_HOTWORD is true and
     * <li> Hotword detection is not already running and
     * <li> TelephonyManager is in CALL_STATE_IDLE
     *
     * If this method is called when the screen is off,
     * it attempts to stop hotwording if it's running.
     * it attempts to stop hotword detection if it's running.
     */
    private void maybeStartHotwordDetector() {
        if (FLAG_HOTWORD && mHotwordClient != null) {
        if (FLAG_HOTWORD && sHotwordClient != null) {
            if (DEBUG) Log.d(TAG, "maybeStartHotwordDetector()");
            // Don't start it if the screen is off or not showing
            PowerManager powerManager = (PowerManager) getContext().getSystemService(
@@ -347,8 +349,13 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
                if (DEBUG) Log.d(TAG, "Call underway, not starting");
                return;
            }
            if (!mHotwordClient.start()) {
                Log.w(TAG, "Failed to start the hotword detector");

            try {
                sHotwordClient.startRecognition(mHotwordCallback);
            } catch(Exception ex) {
                // Don't allow hotword errors to make the keyguard unusable
                Log.e(TAG, "Failed to start hotword recognition", ex);
                sHotwordClient = null;
            }
        }
    }
@@ -357,47 +364,59 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
     * Stop hotword detector if HOTWORDING_ENABLED is true.
     */
    private void maybeStopHotwordDetector() {
        if (FLAG_HOTWORD && mHotwordClient != null) {
        if (FLAG_HOTWORD && sHotwordClient != null) {
            if (DEBUG) Log.d(TAG, "maybeStopHotwordDetector()");
            mHotwordClient.stop();
            try {
                sHotwordClient.stopRecognition();
            } catch(Exception ex) {
                // Don't allow hotword errors to make the keyguard unusable
                Log.e(TAG, "Failed to start hotword recognition", ex);
            } finally {
                sHotwordClient = null;
            }
        }
    }

    private final HotwordServiceClient.Callback mHotwordCallback =
            new HotwordServiceClient.Callback() {
        private static final String TAG = "HotwordServiceClient.Callback";
    private final HotwordRecognitionListener mHotwordCallback = new HotwordRecognitionListener() {
        private static final String TAG = "HotwordRecognitionListener";

        @Override
        public void onServiceConnected() {
            if (DEBUG) Log.d(TAG, "onServiceConnected()");
        public void onHotwordRecognitionStarted() {
            if (DEBUG) Log.d(TAG, "onHotwordRecognitionStarted()");
        }

        @Override
        public void onServiceDisconnected() {
            if (DEBUG) Log.d(TAG, "onServiceDisconnected()");
        public void onHotwordRecognitionStopped() {
            if (DEBUG) Log.d(TAG, "onHotwordRecognitionStopped()");
        }

        @Override
        public void onHotwordDetectionStarted() {
            if (DEBUG) Log.d(TAG, "onHotwordDetectionStarted()");
            // TODO: Change the usage of SecurityMessageDisplay to a better visual indication.
            mSecurityMessageDisplay.setMessage("\"Ok Google...\"", true);
        public void onHotwordEvent(int eventType, Bundle eventBundle) {
            if (DEBUG) Log.d(TAG, "onHotwordEvent: " + eventType);
            if (eventType == HotwordRecognizer.EVENT_TYPE_STATE_CHANGED) {
                if (eventBundle != null && eventBundle.containsKey(HotwordRecognizer.PROMPT_TEXT)) {
                    mSecurityMessageDisplay.setMessage(
                            eventBundle.getString(HotwordRecognizer.PROMPT_TEXT), true);
                }
            }

        @Override
        public void onHotwordDetectionStopped() {
            if (DEBUG) Log.d(TAG, "onHotwordDetectionStopped()");
            // TODO: Change the usage of SecurityMessageDisplay to a better visual indication.
        }

        @Override
        public void onHotwordDetected(String action) {
            if (DEBUG) Log.d(TAG, "onHotwordDetected(" + action + ")");
            if (action != null) {
                Intent intent = new Intent(action);
                mActivityLauncher.launchActivity(intent, true, true, null, null);
        public void onHotwordRecognized(PendingIntent intent) {
            if (DEBUG) Log.d(TAG, "onHotwordRecognized");
            maybeStopHotwordDetector();
            if (intent != null) {
                try {
                    intent.send();
                } catch (PendingIntent.CanceledException e) {
                    Log.w(TAG, "Failed to launch PendingIntent. Encountered CanceledException");
                }
            }
            mCallback.userActivity(0);
            mCallback.dismiss(false);
        }

        public void onHotwordError(int errorCode) {
            if (DEBUG) Log.d(TAG, "onHotwordError: " + errorCode);
            // TODO: Inspect the error code and handle the errors appropriately
            // instead of blindly failing.
            maybeStopHotwordDetector();
        }
    };
}
+0 −35
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.google.android.search.service;

import com.google.android.search.service.IHotwordServiceCallback;

/**
 * Interface exposing hotword detector as a service.
 */
oneway interface IHotwordService {

    /**
     * Indicates a desire to start hotword detection.
     * It's best-effort and the client should rely on
     * the callbacks to figure out if hotwording was actually
     * started or not.
     *
     * @param a callback to notify of hotword events.
     */
    void requestHotwordDetection(in IHotwordServiceCallback callback);
}
+0 −34
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.google.android.search.service;

/**
 * Interface implemented by users of Hotword service to get callbacks
 * for hotword events.
 */
oneway interface IHotwordServiceCallback {

    /** Hotword detection start/stop callbacks */
    void onHotwordDetectionStarted();
    void onHotwordDetectionStopped();

    /**
     * Called back when hotword is detected.
     * The action tells the client what action to take, post hotword-detection.
     */
    void onHotwordDetected(in String action);
}