diff options
author | Sandeep Siddhartha <sansid@google.com> | 2013-08-26 20:58:53 -0700 |
---|---|---|
committer | Sandeep Siddhartha <sansid@google.com> | 2013-08-28 23:40:32 +0000 |
commit | 42c3e02c52fb487676251b297461f6ff7beff3ef (patch) | |
tree | fadce7f86faea34618c4f2251a9a48b55c811c2e /packages | |
parent | bd31a483d358b02e4a16949b576560d4e0672ea3 (diff) | |
download | frameworks_base-42c3e02c52fb487676251b297461f6ff7beff3ef.zip frameworks_base-42c3e02c52fb487676251b297461f6ff7beff3ef.tar.gz frameworks_base-42c3e02c52fb487676251b297461f6ff7beff3ef.tar.bz2 |
Start using the Hotword recognition APIs
- 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
Diffstat (limited to 'packages')
5 files changed, 69 insertions, 330 deletions
diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml index f3106da..7d77c48 100644 --- a/packages/Keyguard/AndroidManifest.xml +++ b/packages/Keyguard/AndroidManifest.xml @@ -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 index 94733d4..0000000 --- a/packages/Keyguard/src/com/android/keyguard/HotwordServiceClient.java +++ /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(); - } - }; -} diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java index 4fe83fb..fbae8b1 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java @@ -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 index e053d7d..0000000 --- a/packages/Keyguard/src/com/google/android/search/service/IHotwordService.aidl +++ /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 index 7b3765f..0000000 --- a/packages/Keyguard/src/com/google/android/search/service/IHotwordServiceCallback.aidl +++ /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); -} |