diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 2cc564bf8452e..fabe92d2532d9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -24,6 +24,7 @@ import android.graphics.Matrix import android.view.RemoteAnimationTarget import android.view.SyncRtSurfaceTransactionApplier import android.view.View +import androidx.annotation.VisibleForTesting import androidx.core.math.MathUtils import com.android.internal.R import com.android.keyguard.KeyguardClockSwitchController @@ -32,6 +33,7 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController import com.android.systemui.flags.FeatureFlags +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject @@ -91,7 +93,8 @@ class KeyguardUnlockAnimationController @Inject constructor( private val keyguardViewMediator: Lazy, private val keyguardViewController: KeyguardViewController, private val smartspaceTransitionController: SmartspaceTransitionController, - private val featureFlags: FeatureFlags + private val featureFlags: FeatureFlags, + private val biometricUnlockController: BiometricUnlockController ) : KeyguardStateController.Callback { /** @@ -105,7 +108,8 @@ class KeyguardUnlockAnimationController @Inject constructor( * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned * animation is started in [notifyStartKeyguardExitAnimation]. */ - private var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null + @VisibleForTesting + var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null private var surfaceBehindRemoteAnimationStartTime: Long = 0 @@ -132,7 +136,8 @@ class KeyguardUnlockAnimationController @Inject constructor( * Animator that animates in the surface behind the keyguard. This is used to play a canned * animation on the surface, if we're not doing a swipe gesture. */ - private val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f) + @VisibleForTesting + val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f) /** Rounded corner radius to apply to the surface behind the keyguard. */ private var roundedCornerRadius = 0f @@ -220,7 +225,18 @@ class KeyguardUnlockAnimationController @Inject constructor( // to animate it in. Otherwise, the swipe touch events will continue animating it. if (!requestedShowSurfaceBehindKeyguard) { keyguardViewController.hide(startTime, 350) - surfaceBehindEntryAnimator.start() + + // If we're wake and unlocking, we don't want to animate the surface since we're going + // to do the light reveal scrim from the black AOD screen. Make it visible and end the + // remote aimation. + if (biometricUnlockController.isWakeAndUnlock) { + setSurfaceBehindAppearAmount(1f) + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } else { + // Otherwise, animate it in normally. + surfaceBehindEntryAnimator.start() + } } // Finish the keyguard remote animation if the dismiss amount has crossed the threshold. @@ -264,7 +280,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is * cancelled). */ - private fun setSurfaceBehindAppearAmount(amount: Float) { + fun setSurfaceBehindAppearAmount(amount: Float) { if (surfaceBehindRemoteAnimationTarget == null) { return } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt new file mode 100644 index 0000000000000..f3043e934c8ad --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -0,0 +1,134 @@ +package com.android.systemui.keyguard + +import android.app.ActivityManager +import android.app.WindowConfiguration +import android.graphics.Point +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.RemoteAnimationTarget +import android.view.SurfaceControl +import android.view.SyncRtSurfaceTransactionApplier +import android.view.ViewRootImpl +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardViewController +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController +import com.android.systemui.statusbar.phone.BiometricUnlockController +import com.android.systemui.statusbar.policy.KeyguardStateController +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor.forClass +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +@SmallTest +class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { + private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController + + @Mock + private lateinit var keyguardViewMediator: KeyguardViewMediator + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var keyguardViewController: KeyguardViewController + @Mock + private lateinit var smartspaceTransitionController: SmartspaceTransitionController + @Mock + private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var biometricUnlockController: BiometricUnlockController + @Mock + private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier + + private lateinit var remoteAnimationTarget: RemoteAnimationTarget + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + keyguardUnlockAnimationController = KeyguardUnlockAnimationController( + context, keyguardStateController, { keyguardViewMediator }, keyguardViewController, + smartspaceTransitionController, featureFlags, biometricUnlockController + ) + + `when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java)) + + // All of these fields are final, so we can't mock them, but are needed so that the surface + // appear amount setter doesn't short circuit. + remoteAnimationTarget = RemoteAnimationTarget( + 0, 0, null, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), + mock(WindowConfiguration::class.java), false, mock(SurfaceControl::class.java), Rect(), + mock(ActivityManager.RunningTaskInfo::class.java), false) + + // Set the surface applier to our mock so that we can verify the arguments passed to it. + // This applier does not have any side effects within the unlock animation controller, so + // this is a reasonable way to test. + keyguardUnlockAnimationController.surfaceTransactionApplier = surfaceTransactionApplier + } + + /** + * If we're wake and unlocking, we are animating from the black/AOD screen to the app/launcher + * underneath. The LightRevealScrim will animate circularly from the fingerprint reader, + * revealing the app/launcher below. In this case, we want to make sure we are not animating the + * surface, or the user will see the wallpaper briefly as the app animates in. + */ + @Test + fun noSurfaceAnimation_ifWakeAndUnlocking() { + `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) + + keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation( + remoteAnimationTarget, + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + val captor = forClass(SyncRtSurfaceTransactionApplier.SurfaceParams::class.java) + verify(surfaceTransactionApplier, times(1)).scheduleApply(captor.capture()) + + val params = captor.value + + // We expect that we've instantly set the surface behind to alpha = 1f, and have no + // transforms (translate, scale) on its matrix. + assertEquals(params.alpha, 1f) + assertTrue(params.matrix.isIdentity) + + // Also expect we've immediately asked the keyguard view mediator to finish the remote + // animation. + verify(keyguardViewMediator, times(1)).onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + + verifyNoMoreInteractions(surfaceTransactionApplier) + } + + /** + * If we are not wake and unlocking, we expect the unlock animation to play normally. + */ + @Test + fun surfaceAnimation_ifNotWakeAndUnlocking() { + `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false) + + keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation( + remoteAnimationTarget, + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + // Make sure the animator was started. + assertTrue(keyguardUnlockAnimationController.surfaceBehindEntryAnimator.isRunning) + + // Since the animation is running, we should not have finished the remote animation. + verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } +} \ No newline at end of file