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
This commit is contained in:
Juan Sebastian Martinez
2023-07-21 22:17:30 +00:00
parent e9557c2541
commit e7ff086f5a
2 changed files with 106 additions and 6 deletions

View File

@@ -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() {

View File

@@ -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