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

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

Merge "Move the hotword code to KeyguardHostView" into klp-dev

parents 7f71206d eec83dca
Loading
Loading
Loading
Loading
+191 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.app.Activity;
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;
@@ -40,6 +41,7 @@ import android.content.res.Resources;
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;
@@ -47,6 +49,9 @@ import android.os.SystemClock;
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;
@@ -63,6 +68,11 @@ import java.util.List;

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;
@@ -77,6 +87,13 @@ public class KeyguardHostView extends KeyguardViewBase {
    // 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;
@@ -117,6 +134,8 @@ public class KeyguardHostView extends KeyguardViewBase {

    private KeyguardMultiUserSelectorView mKeyguardMultiUserSelectorView;

    private boolean mIsScreenOn;

    protected int mClientGeneration;

    protected boolean mShowSecurityWhenReturn;
@@ -198,6 +217,11 @@ public class KeyguardHostView extends KeyguardViewBase {
        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() {
@@ -295,6 +319,21 @@ public class KeyguardHostView extends KeyguardViewBase {
                }
            }
        }
        @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) {
@@ -778,6 +817,9 @@ public class KeyguardHostView extends KeyguardViewBase {
            // 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.
@@ -942,6 +984,9 @@ public class KeyguardHostView extends KeyguardViewBase {

        // Emulate Activity life cycle
        if (oldView != null) {
            if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
                maybeStopHotwordDetector();
            }
            oldView.onPause();
            oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view
        }
@@ -982,6 +1027,7 @@ public class KeyguardHostView extends KeyguardViewBase {
    @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);

@@ -993,6 +1039,12 @@ public class KeyguardHostView extends KeyguardViewBase {
        if (mViewStateManager != null) {
            mViewStateManager.showUsabilityHints();
        }

        // Start hotword detection on insecure Keyguard.
        if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
            maybeStartHotwordDetector();
        }

        requestFocus();
    }

@@ -1000,6 +1052,7 @@ public class KeyguardHostView extends KeyguardViewBase {
    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);
@@ -1013,6 +1066,12 @@ public class KeyguardHostView extends KeyguardViewBase {
        if (cameraPage != null) {
            cameraPage.onScreenTurnedOff();
        }

        // Stop hotword detection on insecure Keyguard.
        if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
            maybeStopHotwordDetector();
        }

        clearFocus();
    }

@@ -1097,6 +1156,9 @@ public class KeyguardHostView extends KeyguardViewBase {
        new CameraWidgetFrame.Callbacks() {
            @Override
            public void onLaunchingCamera() {
                if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
                    maybeStopHotwordDetector();
                }
                setSliderHandleAlpha(0);
            }

@@ -1626,6 +1688,9 @@ public class KeyguardHostView extends KeyguardViewBase {
    }

    public void showAssistant() {
        if (shouldRunHotwordInSecurityMode(mCurrentSecuritySelection)) {
            maybeStopHotwordDetector();
        }
        final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
          .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);

@@ -1640,4 +1705,130 @@ public class KeyguardHostView extends KeyguardViewBase {
        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()));
    }
}
+0 −133
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@ 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;
@@ -48,12 +46,6 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
    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;
@@ -69,7 +61,6 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri

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

            switch (resId) {
                case R.drawable.ic_action_assist_generic:
@@ -129,22 +120,6 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
        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() {
@@ -183,9 +158,6 @@ 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 (ENABLE_HOTWORD && sHotwordClient == null) {
            sHotwordClient = HotwordRecognizer.createHotwordRecognizer(getContext());
        }
    }

    public void setCarrierArea(View carrierArea) {
@@ -289,20 +261,11 @@ 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.
        // 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
@@ -323,100 +286,4 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
        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();
        }
    };
}