Use biometricUnlockController state change to show the unlock ripple.

Bug: 270989070
Test: atest AuthRippleControllerTest
Test: manually
  1. Enroll side fps with touch to unlock anytime enabled
  2. Press the power button to lock the device
  3. Touch the power button quickly after that
  4. Device should unlock and show launcher with unlock animation ripple
  5. Repeat this 10 times as the time taken between onStartedGoingToSleep & onFinishedGoingToSleep is not always the same.
  6. Also verify that the auth ripple is visible while using SFPS from AlternateBouncer (by tapping a notification on lockscreen) and by using SFPS from bouncer.

Change-Id: I1ad09b328b77d22646a5a74baaac9ec3b1da6a2e
This commit is contained in:
Chandru S
2023-02-27 13:37:36 -08:00
parent f597a6c3ec
commit 553827df76
2 changed files with 102 additions and 139 deletions

View File

@@ -41,8 +41,8 @@ import com.android.systemui.statusbar.NotificationShadeWindowController
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.BiometricUnlockController.MODE_DISMISS_BOUNCER
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
@@ -60,7 +60,9 @@ import javax.inject.Provider
* The ripple uses the accent color of the current theme.
*/
@CentralSurfacesScope
class AuthRippleController @Inject constructor(
class AuthRippleController
@Inject
constructor(
private val centralSurfaces: CentralSurfaces,
private val sysuiContext: Context,
private val authController: AuthController,
@@ -70,18 +72,18 @@ 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<UdfpsController>,
private val statusBarStateController: StatusBarStateController,
private val featureFlags: FeatureFlags,
private val logger: KeyguardLogger,
rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
rippleView: AuthRippleView?
) :
ViewController<AuthRippleView>(rippleView),
KeyguardStateController.Callback,
WakefulnessLifecycle.Observer {
@VisibleForTesting
internal var startLightRevealScrimOnKeyguardFadingAway = false
@VisibleForTesting internal var startLightRevealScrimOnKeyguardFadingAway = false
var lightRevealScrimAnimator: ValueAnimator? = null
var fingerprintSensorLocation: Point? = null
private var faceSensorLocation: Point? = null
@@ -90,6 +92,16 @@ class AuthRippleController @Inject constructor(
private var udfpsController: UdfpsController? = null
private var udfpsRadius: Float = -1f
private val biometricModeListener = object : BiometricUnlockController.BiometricModeListener {
override fun onModeChanged(mode: Int) {
// isBiometricUnlock does not cover the scenario when biometrics unlocks
// the device while the bouncer is showing.
if (biometricUnlockController.isBiometricUnlock || mode == MODE_DISMISS_BOUNCER) {
showUnlockRipple(biometricUnlockController.biometricType)
}
}
}
override fun onInit() {
mView.setAlphaInDuration(sysuiContext.resources.getInteger(
R.integer.auth_ripple_alpha_in_duration).toLong())
@@ -106,6 +118,7 @@ class AuthRippleController @Inject constructor(
keyguardStateController.addCallback(this)
wakefulnessLifecycle.addObserver(this)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
biometricUnlockController.addBiometricModeListener(biometricModeListener)
}
@VisibleForTesting
@@ -117,6 +130,7 @@ class AuthRippleController @Inject constructor(
keyguardStateController.removeCallback(this)
wakefulnessLifecycle.removeObserver(this)
commandRegistry.unregisterCommand("auth-ripple")
biometricUnlockController.removeBiometricModeListener(biometricModeListener)
notificationShadeWindowController.setForcePluginOpen(false, this)
}
@@ -147,10 +161,7 @@ class AuthRippleController @Inject constructor(
showUnlockedRipple()
}
} else if (biometricSourceType == BiometricSourceType.FACE) {
if (!bypassController.canBypass() && !authController.isUdfpsFingerDown) {
return
}
faceSensorLocation?.let {
faceSensorLocation?.let {
mView.setSensorLocation(it)
circleReveal = CircleReveal(
it.x,
@@ -271,7 +282,6 @@ class AuthRippleController @Inject constructor(
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
mView.fadeDwellRipple()
}
showUnlockRipple(biometricSourceType)
}
override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {

View File

@@ -17,14 +17,14 @@
package com.android.systemui.biometrics
import android.graphics.Point
import android.hardware.biometrics.BiometricSourceType
import android.hardware.biometrics.BiometricSourceType.FACE
import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.logcatLogBuffer
@@ -36,11 +36,11 @@ 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
import com.android.systemui.util.mockito.any
import javax.inject.Provider
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -50,15 +50,15 @@ 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
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
import javax.inject.Provider
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -75,7 +75,6 @@ class AuthRippleControllerTest : SysuiTestCase() {
@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<UdfpsController>
@Mock private lateinit var udfpsController: UdfpsController
@@ -84,10 +83,15 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var lightRevealScrim: LightRevealScrim
@Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@Captor
private lateinit var biometricModeListener:
ArgumentCaptor<BiometricUnlockController.BiometricModeListener>
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
staticMockSession = mockitoSession()
staticMockSession =
mockitoSession()
.mockStatic(RotationUtils::class.java)
.strictness(Strictness.LENIENT)
.startMocking()
@@ -96,24 +100,24 @@ class AuthRippleControllerTest : SysuiTestCase() {
`when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp))
`when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
controller = AuthRippleController(
mCentralSurfaces,
context,
authController,
configurationController,
keyguardUpdateMonitor,
keyguardStateController,
wakefulnessLifecycle,
commandRegistry,
notificationShadeWindowController,
bypassController,
biometricUnlockController,
udfpsControllerProvider,
statusBarStateController,
featureFlags,
KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
rippleView
)
controller =
AuthRippleController(
mCentralSurfaces,
context,
authController,
configurationController,
keyguardUpdateMonitor,
keyguardStateController,
wakefulnessLifecycle,
commandRegistry,
notificationShadeWindowController,
biometricUnlockController,
udfpsControllerProvider,
statusBarStateController,
featureFlags,
KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
rippleView
)
controller.init()
`when`(mCentralSurfaces.lightRevealScrim).thenReturn(lightRevealScrim)
}
@@ -130,16 +134,14 @@ class AuthRippleControllerTest : SysuiTestCase() {
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(eq(FINGERPRINT)))
.thenReturn(true)
`when`(biometricUnlockController.isBiometricUnlock).thenReturn(true)
`when`(biometricUnlockController.biometricType).thenReturn(FINGERPRINT)
// 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 */)
// WHEN unlocked with fingerprint
verify(biometricUnlockController).addBiometricModeListener(biometricModeListener.capture())
biometricModeListener.value.onModeChanged(/* mode= */ 0)
// THEN update sensor location and show ripple
verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
@@ -152,17 +154,15 @@ class AuthRippleControllerTest : SysuiTestCase() {
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(eq(FINGERPRINT)))
.thenReturn(true)
`when`(biometricUnlockController.isBiometricUnlock).thenReturn(true)
`when`(biometricUnlockController.biometricType).thenReturn(FINGERPRINT)
// WHEN keyguard is NOT showing & fingerprint authenticated
`when`(keyguardStateController.isShowing).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
0 /* userId */,
BiometricSourceType.FINGERPRINT /* type */,
false /* isStrongBiometric */)
verify(biometricUnlockController).addBiometricModeListener(biometricModeListener.capture())
biometricModeListener.value.onModeChanged(/* mode= */ 0)
// THEN no ripple
verify(rippleView, never()).startUnlockedRipple(any())
@@ -175,61 +175,14 @@ class AuthRippleControllerTest : SysuiTestCase() {
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isBiometricUnlock).thenReturn(true)
`when`(biometricUnlockController.biometricType).thenReturn(FINGERPRINT)
// WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
0 /* userId */,
BiometricSourceType.FINGERPRINT /* type */,
false /* isStrongBiometric */)
// THEN no ripple
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 */)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(eq(FINGERPRINT)))
.thenReturn(false)
verify(biometricUnlockController).addBiometricModeListener(biometricModeListener.capture())
biometricModeListener.value.onModeChanged(/* mode= */ 0)
// THEN no ripple
verify(rippleView, never()).startUnlockedRipple(any())
@@ -239,14 +192,12 @@ class AuthRippleControllerTest : SysuiTestCase() {
fun testNullFaceSensorLocationDoesNothing() {
`when`(authController.faceSensorLocation).thenReturn(null)
controller.onViewAttached()
`when`(biometricUnlockController.biometricType).thenReturn(FACE)
`when`(biometricUnlockController.isBiometricUnlock).thenReturn(true)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
verify(biometricUnlockController).addBiometricModeListener(biometricModeListener.capture())
biometricModeListener.value.onModeChanged(/* mode= */ 0)
captor.value.onBiometricAuthenticated(
0 /* userId */,
BiometricSourceType.FACE /* type */,
false /* isStrongBiometric */)
verify(rippleView, never()).startUnlockedRipple(any())
}
@@ -254,25 +205,21 @@ class AuthRippleControllerTest : SysuiTestCase() {
fun testNullFingerprintSensorLocationDoesNothing() {
`when`(authController.fingerprintSensorLocation).thenReturn(null)
controller.onViewAttached()
`when`(biometricUnlockController.biometricType).thenReturn(FINGERPRINT)
`when`(biometricUnlockController.isBiometricUnlock).thenReturn(true)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
verify(biometricUnlockController).addBiometricModeListener(biometricModeListener.capture())
biometricModeListener.value.onModeChanged(/* mode= */ 0)
captor.value.onBiometricAuthenticated(
0 /* userId */,
BiometricSourceType.FINGERPRINT /* type */,
false /* isStrongBiometric */)
verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
fun registersAndDeregisters() {
controller.onViewAttached()
val captor = ArgumentCaptor
.forClass(KeyguardStateController.Callback::class.java)
val captor = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
verify(keyguardStateController).addCallback(captor.capture())
val captor2 = ArgumentCaptor
.forClass(WakefulnessLifecycle.Observer::class.java)
val captor2 = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor2.capture())
controller.onViewDetached()
verify(keyguardStateController).removeCallback(any())
@@ -286,17 +233,20 @@ class AuthRippleControllerTest : SysuiTestCase() {
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
BiometricSourceType.FINGERPRINT)).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(FINGERPRINT)).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
assertTrue("reveal didn't start on keyguardFadingAway",
controller.startLightRevealScrimOnKeyguardFadingAway)
controller.showUnlockRipple(FINGERPRINT)
assertTrue(
"reveal didn't start on keyguardFadingAway",
controller.startLightRevealScrimOnKeyguardFadingAway
)
`when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
controller.onKeyguardFadingAwayChanged()
assertFalse("reveal triggers multiple times",
controller.startLightRevealScrimOnKeyguardFadingAway)
assertFalse(
"reveal triggers multiple times",
controller.startLightRevealScrimOnKeyguardFadingAway
)
}
@Test
@@ -308,23 +258,26 @@ class AuthRippleControllerTest : SysuiTestCase() {
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
`when`(authController.isUdfpsFingerDown).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
eq(BiometricSourceType.FACE))).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(eq(FACE))).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FACE)
assertTrue("reveal didn't start on keyguardFadingAway",
controller.startLightRevealScrimOnKeyguardFadingAway)
controller.showUnlockRipple(FACE)
assertTrue(
"reveal didn't start on keyguardFadingAway",
controller.startLightRevealScrimOnKeyguardFadingAway
)
`when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
controller.onKeyguardFadingAwayChanged()
assertFalse("reveal triggers multiple times",
controller.startLightRevealScrimOnKeyguardFadingAway)
assertFalse(
"reveal triggers multiple times",
controller.startLightRevealScrimOnKeyguardFadingAway
)
}
@Test
fun testUpdateRippleColor() {
controller.onViewAttached()
val captor = ArgumentCaptor
.forClass(ConfigurationController.ConfigurationListener::class.java)
val captor =
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
verify(configurationController).addCallback(captor.capture())
reset(rippleView)