From e7ff086f5a1470deb6c794c7eeb513f1abaccd2f Mon Sep 17 00:00:00 2001 From: Juan Sebastian Martinez Date: Fri, 21 Jul 2023 22:17:30 +0000 Subject: [PATCH] Using performHapticFeedback on BiometricUnlockController Moving towards new haptics API that triggers haptics directly from the UI thread. The authentication success and authentication error trigger signals of CONFIRM and REJECT, respectively. This is while using the performHapticFeedback API from the VibratorHelper. The method requires a view and the view from the ViewRootImpl accessed from the keyguard controller was used. The changes are controlled by a feature flag. Test: Tests in BiometricsUnlockControllerTest pass with additional tests that involve oneway haptics on authentication success and failure. Bug: 245528624 Change-Id: I3b4e83d616e1c8ad5d64a4926975bd08379cd569 --- .../phone/BiometricUnlockController.java | 31 +++++-- .../phone/BiometricsUnlockControllerTest.java | 81 ++++++++++++++++++- 2 files changed, 106 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index ccb51898a333c..58d6bb0244767 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.SESSION_KEYGUARD; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME; import android.annotation.IntDef; @@ -30,6 +31,7 @@ import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; import android.os.Trace; +import android.view.HapticFeedbackConstants; import androidx.annotation.Nullable; @@ -51,6 +53,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -177,6 +180,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; + private final FeatureFlags mFeatureFlags; + private static final class PendingAuthenticated { public final int userId; public final BiometricSourceType biometricSourceType; @@ -280,7 +285,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp LatencyTracker latencyTracker, ScreenOffAnimationController screenOffAnimationController, VibratorHelper vibrator, - SystemClock systemClock + SystemClock systemClock, + FeatureFlags featureFlags ) { mPowerManager = powerManager; mUpdateMonitor = keyguardUpdateMonitor; @@ -308,6 +314,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mVibratorHelper = vibrator; mLogger = biometricUnlockLogger; mSystemClock = systemClock; + mFeatureFlags = featureFlags; dumpManager.registerDumpable(getClass().getName(), this); } @@ -750,8 +757,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mLogger.d("Skip auth success haptic. Power button was recently pressed."); return; } - mVibratorHelper.vibrateAuthSuccess( - getClass().getSimpleName() + ", type =" + type + "device-entry::success"); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + mVibratorHelper.performHapticFeedback( + mKeyguardViewController.getViewRootImpl().getView(), + HapticFeedbackConstants.CONFIRM + ); + } else { + mVibratorHelper.vibrateAuthSuccess( + getClass().getSimpleName() + ", type =" + type + "device-entry::success"); + } } private boolean lastWakeupFromPowerButtonWithinHapticThreshold() { @@ -764,8 +778,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } private void vibrateError(BiometricSourceType type) { - mVibratorHelper.vibrateAuthError( - getClass().getSimpleName() + ", type =" + type + "device-entry::error"); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + mVibratorHelper.performHapticFeedback( + mKeyguardViewController.getViewRootImpl().getView(), + HapticFeedbackConstants.REJECT + ); + } else { + mVibratorHelper.vibrateAuthError( + getClass().getSimpleName() + ", type =" + type + "device-entry::error"); + } } private void cleanup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 4f8de3eacf7a0..045a63cd44a02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; import static com.google.common.truth.Truth.assertThat; @@ -39,6 +40,8 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; +import android.view.HapticFeedbackConstants; +import android.view.ViewRootImpl; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.LatencyTracker; @@ -47,6 +50,7 @@ import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -118,8 +122,11 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private VibratorHelper mVibratorHelper; @Mock private BiometricUnlockLogger mLogger; + @Mock + private ViewRootImpl mViewRootImpl; private final FakeSystemClock mSystemClock = new FakeSystemClock(); private BiometricUnlockController mBiometricUnlockController; + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Before public void setUp() { @@ -142,11 +149,14 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, mAuthController, mStatusBarStateController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, - mSystemClock + mSystemClock, + mFeatureFlags ); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.addListener(mBiometricUnlockEventsListener); when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker); + when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(mViewRootImpl); + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); } @Test @@ -483,6 +493,31 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mVibratorHelper).vibrateAuthSuccess(anyString()); } + @Test + public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() { + // GIVEN oneway haptics is enabled + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + // GIVEN side fingerprint enrolled, last wake reason was power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + + // GIVEN last wake time was 500ms ago + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + mSystemClock.advanceTime(500); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN vibrate the device + verify(mVibratorHelper).performHapticFeedback( + any(), + eq(HapticFeedbackConstants.CONFIRM) + ); + } + @Test public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() { // GIVEN side fingerprint enrolled, wakeup just happened @@ -502,6 +537,30 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mVibratorHelper).vibrateAuthSuccess(anyString()); } + @Test + public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() { + //GIVEN oneway haptics is enabled + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + // GIVEN side fingerprint enrolled, wakeup just happened + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // GIVEN last wake reason was from a gesture + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_GESTURE); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN vibrate the device + verify(mVibratorHelper).performHapticFeedback( + any(), + eq(HapticFeedbackConstants.CONFIRM) + ); + } + @Test public void onSideFingerprintFail_alwaysPlaysHaptic() { // GIVEN side fingerprint enrolled, last wake reason was recent power button @@ -517,6 +576,26 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mVibratorHelper).vibrateAuthError(anyString()); } + @Test + public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() { + // GIVEN oneway haptics is enabled + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + // GIVEN side fingerprint enrolled, last wake reason was recent power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // WHEN biometric fingerprint fails + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + + // THEN always vibrate the device + verify(mVibratorHelper).performHapticFeedback( + any(), + eq(HapticFeedbackConstants.REJECT) + ); + } + @Test public void onFingerprintDetect_showBouncer() { // WHEN fingerprint detect occurs