package com.android.systemui.keyguard import android.app.ActivityManager import android.app.WallpaperManager import android.app.WindowConfiguration import android.graphics.Point import android.graphics.Rect import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.RemoteAnimationTarget import android.view.SurfaceControl import android.view.SyncRtSurfaceTransactionApplier import android.view.View 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.ILauncherUnlockAnimationController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.whenever import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations import java.util.function.Predicate @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 featureFlags: FeatureFlags @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var wallpaperManager: WallpaperManager @Mock private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub private var surfaceControl1 = mock(SurfaceControl::class.java) private var remoteTarget1 = RemoteAnimationTarget( 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false) private var surfaceControl2 = mock(SurfaceControl::class.java) private var remoteTarget2 = RemoteAnimationTarget( 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false) private lateinit var remoteAnimationTargets: Array private var surfaceControlWp = mock(SurfaceControl::class.java) private var wallpaperTarget = RemoteAnimationTarget( 2 /* taskId */, 0, surfaceControlWp, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), mock(WindowConfiguration::class.java), false, surfaceControlWp, Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false) private lateinit var wallpaperTargets: Array @Before fun setUp() { MockitoAnnotations.initMocks(this) keyguardUnlockAnimationController = KeyguardUnlockAnimationController( context, keyguardStateController, { keyguardViewMediator }, keyguardViewController, featureFlags, { biometricUnlockController }, statusBarStateController, notificationShadeWindowController, powerManager, wallpaperManager ) keyguardUnlockAnimationController.setLauncherUnlockController( launcherUnlockAnimationController) whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java)) whenever(powerManager.isInteractive).thenReturn(true) whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false) // 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. remoteAnimationTargets = arrayOf(remoteTarget1) wallpaperTargets = arrayOf(wallpaperTarget) // 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 } @After fun tearDown() { keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true) } /** * 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() { whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) val captorSb = ArgThatCaptor() verify(surfaceTransactionApplier, times(1)).scheduleApply( captorSb.capture { sp -> sp.surface == surfaceControl1 }) val params = captorSb.getLastValue() // We expect that we've instantly set the surface behind to alpha = 1f, and have no // transforms (translate, scale) on its matrix. assertEquals(1f, params.alpha) assertTrue(params.matrix.isIdentity) // Also expect we've immediately asked the keyguard view mediator to finish the remote // animation. verify(keyguardViewMediator, times(1)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation( false /* cancelled */) verifyNoMoreInteractions(surfaceTransactionApplier) } /** * If we are not wake and unlocking, we expect the unlock animation to play normally. */ @Test fun surfaceAnimation_ifNotWakeAndUnlocking() { whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) // Since the animation is running, we should not have finished the remote animation. verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation( false /* cancelled */) } @Test fun onWakeAndUnlock_notifiesListenerWithTrue() { whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true) whenever(biometricUnlockController.mode).thenReturn( BiometricUnlockController.MODE_WAKE_AND_UNLOCK) val listener = mock( KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java) keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) verify(listener).onUnlockAnimationStarted(any(), eq(true), any(), any()) } @Test fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() { whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true) whenever(biometricUnlockController.mode).thenReturn( BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) val listener = mock( KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java) keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) verify(listener).onUnlockAnimationStarted(any(), eq(false), any(), any()) } /** * If we requested that the surface behind be made visible, and we're not flinging away the * keyguard, it means that we're swiping to unlock and want the surface visible so it can follow * the user's touch event as they swipe to unlock. * * In this case, we should verify that the surface was made visible via the alpha fade in * animator, and verify that we did not start the canned animation to animate the surface in * (since it's supposed to be following the touch events). */ @Test fun fadeInSurfaceBehind_ifRequestedShowSurface_butNotFlinging() { whenever(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) assertFalse(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) } /** * We requested the surface behind to be made visible, but we're now flinging to dismiss the * keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and * lifted their finger while we were requesting the surface be made visible. * * In this case, we should verify that we are playing the canned unlock animation and not * simply fading in the surface. */ @Test fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() { whenever(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) } /** * We never requested the surface behind to be made visible, which means no swiping to unlock * ever happened and we're just playing the simple canned animation (happens via UDFPS unlock, * long press on the lock icon, etc). * * In this case, we should verify that we are playing the canned unlock animation and not * simply fading in the surface. */ @Test fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) } @Test fun doNotPlayCannedUnlockAnimation_ifLaunchingApp() { whenever(notificationShadeWindowController.isLaunchingActivity).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) assertFalse(keyguardUnlockAnimationController.canPerformInWindowLauncherAnimations()) assertFalse(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) } @Test fun playCannedUnlockAnimation_nullSmartspaceView_doesNotThrowExecption() { keyguardUnlockAnimationController.lockscreenSmartspace = null keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) } /** * If we are not wake and unlocking, we expect the unlock animation to play normally. */ @Test fun surfaceAnimation_multipleTargets() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( arrayOf(remoteTarget1, remoteTarget2), wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) // Set appear to 50%, we'll just verify that we're not applying the identity matrix which // means an animation is in progress. keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f) val captorSb = ArgThatCaptor() verify(surfaceTransactionApplier, times(2)).scheduleApply(captorSb .capture { sp -> sp.surface == surfaceControl1 || sp.surface == surfaceControl2 }) val captorWp = ArgThatCaptor() verify(surfaceTransactionApplier, times(1).description( "WallpaperSurface was expected to receive scheduleApply once" )).scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp}) val allParams = captorSb.getAllValues() val remainingTargets = mutableListOf(surfaceControl1, surfaceControl2) allParams.forEach { params -> assertTrue(!params.matrix.isIdentity) remainingTargets.remove(params.surface) } // Make sure we called applyParams with each of the surface controls once. The order does // not matter, so don't explicitly check for that. assertTrue(remainingTargets.isEmpty()) // Since the animation is running, we should not have finished the remote animation. verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation( false /* cancelled */) } @Test fun surfaceBehindAlphaOverriddenTo0_ifNotInteractive() { whenever(powerManager.isInteractive).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f) keyguardUnlockAnimationController.setWallpaperAppearAmount(1f) val captorSb = ArgThatCaptor() verify(surfaceTransactionApplier, times(1)).scheduleApply( captorSb.capture { sp -> sp.surface == surfaceControl1}) val captorWp = ArgThatCaptor() verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")).scheduleApply( captorWp.capture { sp -> sp.surface == surfaceControlWp }) val params = captorSb.getLastValue() // We expect that we've set the surface behind to alpha = 0f since we're not interactive. assertEquals(0f, params.alpha) assertTrue(params.matrix.isIdentity) verifyNoMoreInteractions(surfaceTransactionApplier) } @Test fun surfaceBehindAlphaNotOverriddenTo0_ifInteractive() { whenever(powerManager.isInteractive).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f) keyguardUnlockAnimationController.setWallpaperAppearAmount(1f) val captorSb = ArgThatCaptor() verify(surfaceTransactionApplier, times(1)).scheduleApply( captorSb.capture { sp -> sp.surface == surfaceControl1 }) val captorWp = ArgThatCaptor() verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")).scheduleApply( captorWp.capture { sp -> sp.surface == surfaceControlWp }) val params = captorSb.getLastValue() assertEquals(1f, params.alpha) assertTrue(params.matrix.isIdentity) assertEquals("Wallpaper surface was expected to have opacity 1", 1f, captorWp.getLastValue().alpha) verifyNoMoreInteractions(surfaceTransactionApplier) } @Test fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() { val mockLockscreenSmartspaceView = mock(View::class.java) whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE) keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations() verify(mockLockscreenSmartspaceView).visibility = View.INVISIBLE } @Test fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() { val mockLockscreenSmartspaceView = mock(View::class.java) whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE) keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations() verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE } @Test fun unlockToLauncherWithInWindowAnimations_ssViewIsGone() { val mockLockscreenSmartspaceView = mock(View::class.java) whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE) keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations() verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE } @Test fun notifyFinishedKeyguardExitAnimation_ssViewIsInvisibleAndCancelledIsTrue() { val mockLockscreenSmartspaceView = mock(View::class.java) whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE) keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true) verify(mockLockscreenSmartspaceView).visibility = View.VISIBLE } @Test fun notifyFinishedKeyguardExitAnimation_ssViewIsGoneAndCancelledIsTrue() { val mockLockscreenSmartspaceView = mock(View::class.java) whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE) keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true) verify(mockLockscreenSmartspaceView, never()).visibility = View.VISIBLE } @Test fun notifyFinishedKeyguardExitAnimation_ssViewIsInvisibleAndCancelledIsFalse() { val mockLockscreenSmartspaceView = mock(View::class.java) whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE) keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(false) verify(mockLockscreenSmartspaceView).visibility = View.VISIBLE } @Test fun notifyFinishedKeyguardExitAnimation_ssViewIsGoneAndCancelledIsFalse() { val mockLockscreenSmartspaceView = mock(View::class.java) whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE) keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(false) verify(mockLockscreenSmartspaceView, never()).visibility = View.VISIBLE } private class ArgThatCaptor { private var allArgs: MutableList = mutableListOf() fun capture(predicate: Predicate): T { return argThat{x: T -> if (predicate.test(x)) { allArgs.add(x) return@argThat true } return@argThat false } } fun getLastValue(): T { return allArgs.last() } fun getAllValues(): List { return allArgs } } }