import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AlertDialog;
+import android.app.PendingIntent;
import android.app.SearchManager;
import android.app.admin.DevicePolicyManager;
import android.appwidget.AppWidgetHost;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.media.RemoteControlClient;
+import android.os.Bundle;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.os.UserManager;
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;
import android.util.Slog;
public class KeyguardHostView extends KeyguardViewBase {
private static final String TAG = "KeyguardHostView";
+ // Don't enable hotword on limited-memory devices.
+ private static final boolean ENABLE_HOTWORD = !ActivityManager.isLowRamDeviceStatic();
+ // Indicates if hotword is enabled, should it also be available on secure keyguard(s).
+ private static final boolean ENABLE_HOTWORD_SECURE = false;
+
// Transport control states.
static final int TRANSPORT_GONE = 0;
static final int TRANSPORT_INVISIBLE = 1;
// Found in KeyguardAppWidgetPickActivity.java
static final int APPWIDGET_HOST_ID = 0x4B455947;
+ // TODO: Fix this to be non-static.
+ // We need to be careful here to make stopRecognition calls on the same instance
+ // that started it. Since KeyguardHostView is a view, it keeps getting
+ // recreated every now and then, and unless we figure out a better way,
+ // this needs to be a static field.
+ private static HotwordRecognizer sHotwordClient;
+
private final int MAX_WIDGETS = 5;
private AppWidgetHost mAppWidgetHost;
private KeyguardMultiUserSelectorView mKeyguardMultiUserSelectorView;
+ private boolean mIsScreenOn;
+
protected int mClientGeneration;
protected boolean mShowSecurityWhenReturn;
if ((mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0) {
Log.v(TAG, "Keyguard secure camera disabled by DPM");
}
+
+ // Create Hotword recognizer, for the first time.
+ if (ENABLE_HOTWORD && sHotwordClient == null) {
+ sHotwordClient = HotwordRecognizer.createHotwordRecognizer(getContext());
+ }
}
private void getInitialTransportState() {
}
}
}
+ @Override
+ public void onPhoneStateChanged(int phoneState) {
+ // We need to stop hotword detection when a call state is not idle anymore.
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)
+ && TelephonyManager.CALL_STATE_IDLE != phoneState) {
+ if (DEBUG) Log.d(TAG, "Stopping due to call state not being idle");
+ maybeStopHotwordDetector();
+ }
+ }
+ @Override
+ public void onUserSwitching(int userId) {
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
+ maybeStopHotwordDetector();
+ }
+ }
};
private static final boolean isMusicPlaying(int playbackState) {
// If the alternate unlock was suppressed, it can now be safely
// enabled because the user has left keyguard.
KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)){
+ maybeStopHotwordDetector();
+ }
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
// Emulate Activity life cycle
if (oldView != null) {
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
+ maybeStopHotwordDetector();
+ }
oldView.onPause();
oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view
}
@Override
public void onScreenTurnedOn() {
if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
+ mIsScreenOn = true;
showPrimarySecurityScreen(false);
getSecurityView(mCurrentSecuritySelection).onResume(KeyguardSecurityView.SCREEN_ON);
if (mViewStateManager != null) {
mViewStateManager.showUsabilityHints();
}
+
+ // Start hotword detection on insecure Keyguard.
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
+ maybeStartHotwordDetector();
+ }
+
requestFocus();
}
public void onScreenTurnedOff() {
if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
+ mIsScreenOn = false;
// Once the screen turns off, we no longer consider this to be first boot and we want the
// biometric unlock to start next time keyguard is shown.
KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
if (cameraPage != null) {
cameraPage.onScreenTurnedOff();
}
+
+ // Stop hotword detection on insecure Keyguard.
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
+ maybeStopHotwordDetector();
+ }
+
clearFocus();
}
new CameraWidgetFrame.Callbacks() {
@Override
public void onLaunchingCamera() {
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
+ maybeStopHotwordDetector();
+ }
setSliderHandleAlpha(0);
}
}
public void showAssistant() {
+ if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
+ maybeStopHotwordDetector();
+ }
final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
mActivityLauncher.launchActivityWithAnimation(
intent, false, opts.toBundle(), null, null);
}
+
+
+ /**
+ * Start the hotword detector if:
+ * <li> ENABLE_HOTWORD is true and
+ * <li> Hotword detection is not already running and
+ * <li> TelephonyManager is in CALL_STATE_IDLE
+ * <li> and Screen is turned on.
+ */
+ private void maybeStartHotwordDetector() {
+ if (!ENABLE_HOTWORD) return;
+
+ if (sHotwordClient != null) {
+ if (DEBUG) Log.d(TAG, "maybeStartHotwordDetector()");
+ // Don't start hotword detection if the screen is off.
+ if (!mIsScreenOn) {
+ if (DEBUG) Log.d(TAG, "screen was off, not starting");
+ return;
+ }
+
+ KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext());
+ if (monitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE) {
+ if (DEBUG) Log.d(TAG, "Call underway, not starting");
+ return;
+ }
+
+ 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;
+ }
+ }
+ }
+
+ /**
+ * Stop hotword detector if:
+ * <li> ENABLE_HOTWORD is true
+ * <li> and hotword is running.
+ */
+ private void maybeStopHotwordDetector() {
+ if (!ENABLE_HOTWORD) return;
+
+ if (sHotwordClient != null) {
+ if (DEBUG) Log.d(TAG, "maybeStopHotwordDetector()");
+ 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 HotwordRecognitionListener mHotwordCallback = new HotwordRecognitionListener() {
+ private static final String TAG = "HotwordRecognitionListener";
+
+ public void onHotwordRecognitionStarted() {
+ if (DEBUG) Log.d(TAG, "onHotwordRecognitionStarted()");
+ }
+
+ public void onHotwordRecognitionStopped() {
+ if (DEBUG) Log.d(TAG, "onHotwordRecognitionStopped()");
+ }
+
+ 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)) {
+ new KeyguardMessageArea.Helper(
+ (View) getSecurityView(mCurrentSecuritySelection))
+ .setMessage(eventBundle.getString(HotwordRecognizer.PROMPT_TEXT),true);
+ }
+ }
+ }
+
+ public void onHotwordRecognized(final PendingIntent intent) {
+ if (DEBUG) Log.d(TAG, "onHotwordRecognized");
+ maybeStopHotwordDetector();
+ if (SecurityMode.None == mCurrentSecuritySelection) {
+ 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);
+ } else if (ENABLE_HOTWORD_SECURE && mLockPatternUtils.isSecure()) {
+ setOnDismissAction(new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ if (intent != null) {
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.w(TAG, "Failed to launch PendingIntent."
+ + "Encountered CanceledException");
+ }
+ }
+ return false;
+ }
+ });
+ getSecurityView(mCurrentSecuritySelection).showBouncer(0);
+ }
+ }
+
+ 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();
+ }
+ };
+
+ private boolean shouldRunHotwordInSecurityMode(SecurityMode mode) {
+ // Enable hotoword for insecure keyguard,
+ // and for pattern unlock if ENABLE_HOTWORD_SECURE is true.
+ return ENABLE_HOTWORD
+ && ((SecurityMode.None == mode)
+ || (ENABLE_HOTWORD_SECURE && mLockPatternUtils.isSecure()));
+ }
}
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;
private static final String ASSIST_ICON_METADATA_NAME =
"com.android.systemui.action_assist_icon";
- // Don't enable hotword on limited-memory devices.
- private static final boolean ENABLE_HOTWORD = !ActivityManager.isLowRamDeviceStatic();
-
- // TODO: Fix this to be non-static.
- private static HotwordRecognizer sHotwordClient;
-
private KeyguardSecurityCallback mCallback;
private GlowPadView mGlowPadView;
private ObjectAnimator mAnim;
public void onTrigger(View v, int target) {
final int resId = mGlowPadView.getResourceIdForTarget(target);
- maybeStopHotwordDetector();
switch (resId) {
case R.drawable.ic_action_assist_generic:
public void onSimStateChanged(State simState) {
updateTargets();
}
-
- @Override
- public void onPhoneStateChanged(int phoneState) {
- if (ENABLE_HOTWORD) {
- // 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();
- }
- }
- }
-
- @Override
- public void onUserSwitching(int userId) {
- maybeStopHotwordDetector();
- }
};
private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() {
mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
View bouncerFrameView = findViewById(R.id.keyguard_selector_view_frame);
mBouncerFrame = bouncerFrameView.getBackground();
- if (ENABLE_HOTWORD && sHotwordClient == null) {
- sHotwordClient = HotwordRecognizer.createHotwordRecognizer(getContext());
- }
}
public void setCarrierArea(View carrierArea) {
@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.
- // 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();
- }
- }
}
@Override
KeyguardSecurityViewHelper.
hideBouncer(mSecurityMessageDisplay, mFadeView, mBouncerFrame, duration);
}
-
- /**
- * Start the hotword detector if:
- * <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 hotword detection if it's running.
- */
- private void maybeStartHotwordDetector() {
- if (ENABLE_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(
- Context.POWER_SERVICE);
- if (!powerManager.isScreenOn()) {
- if (DEBUG) Log.d(TAG, "screen was off, not starting");
- return;
- }
-
- KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext());
- if (monitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE) {
- if (DEBUG) Log.d(TAG, "Call underway, not starting");
- return;
- }
-
- 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;
- }
- }
- }
-
- /**
- * Stop hotword detector if HOTWORDING_ENABLED is true.
- */
- private void maybeStopHotwordDetector() {
- if (ENABLE_HOTWORD && sHotwordClient != null) {
- if (DEBUG) Log.d(TAG, "maybeStopHotwordDetector()");
- 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 HotwordRecognitionListener mHotwordCallback = new HotwordRecognitionListener() {
- private static final String TAG = "HotwordRecognitionListener";
-
- public void onHotwordRecognitionStarted() {
- if (DEBUG) Log.d(TAG, "onHotwordRecognitionStarted()");
- }
-
- public void onHotwordRecognitionStopped() {
- if (DEBUG) Log.d(TAG, "onHotwordRecognitionStopped()");
- }
-
- 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);
- }
- }
- }
-
- 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();
- }
- };
}