OSDN Git Service

Start using the Hotword recognition APIs
authorSandeep Siddhartha <sansid@google.com>
Tue, 27 Aug 2013 03:58:53 +0000 (20:58 -0700)
committerSandeep Siddhartha <sansid@google.com>
Wed, 28 Aug 2013 23:40:32 +0000 (23:40 +0000)
- Gets rid of all assumptions about the hotword service
- Fixes bug where the hotword detection would keep running accidentally
  even when the screen got turned off

Change-Id: Ie86c1a4f4343bdf7e61f7c21114fd3b287bd5401

packages/Keyguard/AndroidManifest.xml
packages/Keyguard/src/com/android/keyguard/HotwordServiceClient.java [deleted file]
packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java
packages/Keyguard/src/com/google/android/search/service/IHotwordService.aidl [deleted file]
packages/Keyguard/src/com/google/android/search/service/IHotwordServiceCallback.aidl [deleted file]

index f3106da..7d77c48 100644 (file)
@@ -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" >
diff --git a/packages/Keyguard/src/com/android/keyguard/HotwordServiceClient.java b/packages/Keyguard/src/com/android/keyguard/HotwordServiceClient.java
deleted file mode 100644 (file)
index 94733d4..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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();
-        }
-    };
-}
index 4fe83fb..fbae8b1 100644 (file)
@@ -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();
-            }
+            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);
-        }
-
-        @Override
-        public void onHotwordDetectionStopped() {
-            if (DEBUG) Log.d(TAG, "onHotwordDetectionStopped()");
-            // TODO: Change the usage of SecurityMessageDisplay to a better visual indication.
+        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 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();
         }
     };
 }
diff --git a/packages/Keyguard/src/com/google/android/search/service/IHotwordService.aidl b/packages/Keyguard/src/com/google/android/search/service/IHotwordService.aidl
deleted file mode 100644 (file)
index e053d7d..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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);
-}
diff --git a/packages/Keyguard/src/com/google/android/search/service/IHotwordServiceCallback.aidl b/packages/Keyguard/src/com/google/android/search/service/IHotwordServiceCallback.aidl
deleted file mode 100644 (file)
index 7b3765f..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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);
-}