diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 868ffcf54fe14..e0b9f01bf6621 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -34,6 +34,7 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal @@ -43,7 +44,6 @@ import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -52,7 +52,7 @@ import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider -/*** +/** * Controls two ripple effects: * 1. Unlocked ripple: shows when authentication is successful * 2. UDFPS dwell ripple: shows when the user has their finger down on the UDFPS area and reacts @@ -71,14 +71,15 @@ class AuthRippleController @Inject constructor( private val wakefulnessLifecycle: WakefulnessLifecycle, private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, - private val bypassController: KeyguardBypassController, - private val biometricUnlockController: BiometricUnlockController, private val udfpsControllerProvider: Provider, private val statusBarStateController: StatusBarStateController, private val featureFlags: FeatureFlags, private val logger: KeyguardLogger, - rippleView: AuthRippleView? -) : ViewController(rippleView), KeyguardStateController.Callback, + private val biometricUnlockController: BiometricUnlockController, + rippleView: AuthRippleView? +) : + ViewController(rippleView), + KeyguardStateController.Callback, WakefulnessLifecycle.Observer { @VisibleForTesting @@ -102,8 +103,24 @@ class AuthRippleController @Inject constructor( keyguardStateController.addCallback(this) wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } + biometricUnlockController.addListener(biometricModeListener) } + private val biometricModeListener = + object : BiometricUnlockController.BiometricUnlockEventsListener { + override fun onBiometricUnlockedWithKeyguardDismissal( + biometricSourceType: BiometricSourceType? + ) { + if (biometricSourceType != null) { + showUnlockRipple(biometricSourceType) + } else { + logger.log(TAG, + LogLevel.ERROR, + "Unexpected scenario where biometricSourceType is null") + } + } + } + @VisibleForTesting public override fun onViewDetached() { udfpsController?.removeCallback(udfpsControllerCallback) @@ -113,6 +130,7 @@ class AuthRippleController @Inject constructor( keyguardStateController.removeCallback(this) wakefulnessLifecycle.removeObserver(this) commandRegistry.unregisterCommand("auth-ripple") + biometricUnlockController.removeListener(biometricModeListener) notificationShadeWindowController.setForcePluginOpen(false, this) } @@ -143,9 +161,6 @@ class AuthRippleController @Inject constructor( showUnlockedRipple() } } else if (biometricSourceType == BiometricSourceType.FACE) { - if (!bypassController.canBypass() && !authController.isUdfpsFingerDown) { - return - } faceSensorLocation?.let { mView.setSensorLocation(it) circleReveal = CircleReveal( @@ -267,7 +282,6 @@ class AuthRippleController @Inject constructor( if (biometricSourceType == BiometricSourceType.FINGERPRINT) { mView.fadeDwellRipple() } - showUnlockRipple(biometricSourceType) } override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 6bc9837e87516..3567d814f63e7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -471,7 +471,7 @@ constructor( } val callback = - object : BiometricUnlockController.BiometricModeListener { + object : BiometricUnlockController.BiometricUnlockEventsListener { override fun onModeChanged(@WakeAndUnlockMode mode: Int) { dispatchUpdate() } @@ -481,10 +481,10 @@ constructor( } } - biometricUnlockController.addBiometricModeListener(callback) + biometricUnlockController.addListener(callback) dispatchUpdate() - awaitClose { biometricUnlockController.removeBiometricModeListener(callback) } + awaitClose { biometricUnlockController.removeListener(callback) } } override val wakefulness: Flow = conflatedCallbackFlow { 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 cf5ecdddf854a..6742e4f3041e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -163,7 +163,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private PendingAuthenticated mPendingAuthenticated = null; private boolean mHasScreenTurnedOnSinceAuthenticating; private boolean mFadedAwayAfterWakeAndUnlock; - private Set mBiometricModeListeners = new HashSet<>(); + private Set mBiometricUnlockEventsListeners = new HashSet<>(); private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; @@ -314,14 +314,14 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardViewController = keyguardViewController; } - /** Adds a {@link BiometricModeListener}. */ - public void addBiometricModeListener(BiometricModeListener listener) { - mBiometricModeListeners.add(listener); + /** Adds a {@link BiometricUnlockEventsListener}. */ + public void addListener(BiometricUnlockEventsListener listener) { + mBiometricUnlockEventsListeners.add(listener); } - /** Removes a {@link BiometricModeListener}. */ - public void removeBiometricModeListener(BiometricModeListener listener) { - mBiometricModeListeners.remove(listener); + /** Removes a {@link BiometricUnlockEventsListener}. */ + public void removeListener(BiometricUnlockEventsListener listener) { + mBiometricUnlockEventsListeners.remove(listener); } private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() { @@ -387,7 +387,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp @Override public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric) { - Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated"); + Trace.beginSection("BiometricUnlockController#onBiometricUnlocked"); if (mUpdateMonitor.isGoingToSleep()) { mLogger.deferringAuthenticationDueToSleep(userId, biometricSourceType, @@ -411,10 +411,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardViewMediator.userActivity(); startWakeAndUnlock(biometricSourceType, isStrongBiometric); } else { - mLogger.d("onBiometricAuthenticated aborted by bypass controller"); + mLogger.d("onBiometricUnlocked aborted by bypass controller"); } } + /** + * Wake and unlock the device in response to successful authentication using biometrics. + * @param biometricSourceType Biometric source that was used to authenticate. + * @param isStrongBiometric + */ public void startWakeAndUnlock(BiometricSourceType biometricSourceType, boolean isStrongBiometric) { int mode = calculateMode(biometricSourceType, isStrongBiometric); @@ -422,6 +427,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { vibrateSuccess(biometricSourceType); + onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); } startWakeAndUnlock(mode); } @@ -502,11 +508,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } private void onModeChanged(@WakeAndUnlockMode int mode) { - for (BiometricModeListener listener : mBiometricModeListeners) { + for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { listener.onModeChanged(mode); } } + private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) { + for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { + listener.onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); + } + } + public boolean hasPendingAuthentication() { return mPendingAuthenticated != null && mUpdateMonitor @@ -777,7 +789,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mMode = MODE_NONE; mBiometricType = null; mNotificationShadeWindowController.setForceDozeBrightness(false); - for (BiometricModeListener listener : mBiometricModeListeners) { + for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { listener.onResetMode(); } mNumConsecutiveFpFailures = 0; @@ -895,10 +907,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } /** An interface to interact with the {@link BiometricUnlockController}. */ - public interface BiometricModeListener { + public interface BiometricUnlockEventsListener { /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */ default void onResetMode() {} /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */ default void onModeChanged(@WakeAndUnlockMode int mode) {} + + /** + * Called when the device is unlocked successfully using biometrics with the keyguard also + * being dismissed. + */ + default void onBiometricUnlockedWithKeyguardDismissal( + BiometricSourceType biometricSourceType) { } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index aabe0cb9794c8..0c8e9e56b04af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1675,8 +1675,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mStatusBarStateController.addCallback(mStateListener, SysuiStatusBarStateController.RANK_STATUS_BAR); mBiometricUnlockController = mBiometricUnlockControllerLazy.get(); - mBiometricUnlockController.addBiometricModeListener( - new BiometricUnlockController.BiometricModeListener() { + mBiometricUnlockController.addListener( + new BiometricUnlockController.BiometricUnlockEventsListener() { @Override public void onResetMode() { setWakeAndUnlocking(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index a245c01d74de6..7d9ccb642e477 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -36,7 +36,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils @@ -50,6 +49,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.never @@ -73,16 +73,28 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock private lateinit var bypassController: KeyguardBypassController - @Mock private lateinit var biometricUnlockController: BiometricUnlockController - @Mock private lateinit var udfpsControllerProvider: Provider - @Mock private lateinit var udfpsController: UdfpsController - @Mock private lateinit var statusBarStateController: StatusBarStateController - @Mock private lateinit var featureFlags: FeatureFlags - @Mock private lateinit var lightRevealScrim: LightRevealScrim - @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal + @Mock + private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock + private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var biometricUnlockController: BiometricUnlockController + @Mock + private lateinit var udfpsControllerProvider: Provider + @Mock + private lateinit var udfpsController: UdfpsController + @Mock + private lateinit var statusBarStateController: StatusBarStateController + @Mock + private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var lightRevealScrim: LightRevealScrim + @Mock + private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal + + @Captor + private lateinit var biometricUnlockListener: + ArgumentCaptor @Before fun setUp() { @@ -106,13 +118,12 @@ class AuthRippleControllerTest : SysuiTestCase() { wakefulnessLifecycle, commandRegistry, notificationShadeWindowController, - bypassController, - biometricUnlockController, udfpsControllerProvider, statusBarStateController, featureFlags, KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), - rippleView + biometricUnlockController, + rippleView, ) controller.init() `when`(mCentralSurfaces.lightRevealScrim).thenReturn(lightRevealScrim) @@ -134,12 +145,9 @@ class AuthRippleControllerTest : SysuiTestCase() { eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) // WHEN fingerprint authenticated - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) + verify(biometricUnlockController).addListener(biometricUnlockListener.capture()) + biometricUnlockListener.value + .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT) // THEN update sensor location and show ripple verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) @@ -190,51 +198,6 @@ class AuthRippleControllerTest : SysuiTestCase() { verify(rippleView, never()).startUnlockedRipple(any()) } - @Test - fun testFaceTriggerBypassEnabled_Ripple() { - // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed - val faceLocation = Point(5, 5) - `when`(authController.faceSensorLocation).thenReturn(faceLocation) - controller.onViewAttached() - - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FACE)).thenReturn(true) - - // WHEN bypass is enabled & face authenticated - `when`(bypassController.canBypass()).thenReturn(true) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */) - - // THEN show ripple - verify(rippleView).setSensorLocation(faceLocation) - verify(rippleView).startUnlockedRipple(any()) - } - - @Test - fun testFaceTriggerNonBypass_NoRipple() { - // GIVEN face auth sensor exists - val faceLocation = Point(5, 5) - `when`(authController.faceSensorLocation).thenReturn(faceLocation) - controller.onViewAttached() - - // WHEN bypass isn't enabled & face authenticated - `when`(bypassController.canBypass()).thenReturn(false) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */) - - // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any()) - } - @Test fun testNullFaceSensorLocationDoesNothing() { `when`(authController.faceSensorLocation).thenReturn(null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index ce17167f587d1..3fd97da23157c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -461,8 +461,8 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this) runCurrent() - val captor = argumentCaptor() - verify(biometricUnlockController).addBiometricModeListener(captor.capture()) + val captor = argumentCaptor() + verify(biometricUnlockController).addListener(captor.capture()) listOf( BiometricUnlockController.MODE_NONE, @@ -498,7 +498,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() runCurrent() - verify(biometricUnlockController).removeBiometricModeListener(captor.value) + verify(biometricUnlockController).removeListener(captor.value) } @Test 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 68d67ca200070..89f8bdbfe05bb 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 @@ -89,7 +89,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { @Mock private KeyguardViewMediator mKeyguardViewMediator; @Mock - private BiometricUnlockController.BiometricModeListener mBiometricModeListener; + private BiometricUnlockController.BiometricUnlockEventsListener mBiometricUnlockEventsListener; @Mock private KeyguardStateController mKeyguardStateController; @Mock @@ -145,7 +145,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mSystemClock ); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); - mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener); + mBiometricUnlockController.addListener(mBiometricUnlockEventsListener); when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker); }