1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.unfold 18 19 import android.os.PowerManager 20 import android.os.SystemProperties 21 import android.testing.TestableLooper 22 import androidx.test.ext.junit.runners.AndroidJUnit4 23 import androidx.test.filters.SmallTest 24 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider 25 import com.android.internal.jank.Cuj.CUJ_FOLD_ANIM 26 import com.android.internal.jank.InteractionJankMonitor 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.animation.AnimatorTestRule 29 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState 30 import com.android.systemui.display.data.repository.fakeDeviceStateRepository 31 import com.android.systemui.kosmos.testDispatcher 32 import com.android.systemui.kosmos.testScope 33 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest 34 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState 35 import com.android.systemui.power.domain.interactor.powerInteractor 36 import com.android.systemui.power.shared.model.ScreenPowerState 37 import com.android.systemui.statusbar.LightRevealScrim 38 import com.android.systemui.testKosmos 39 import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository 40 import kotlinx.coroutines.test.TestScope 41 import kotlinx.coroutines.test.advanceTimeBy 42 import kotlinx.coroutines.test.runTest 43 import org.junit.Before 44 import org.junit.Rule 45 import org.junit.Test 46 import org.junit.runner.RunWith 47 import org.mockito.Mockito.atLeast 48 import org.mockito.Mockito.never 49 import org.mockito.Mockito.times 50 import org.mockito.Mockito.verify 51 import org.mockito.MockitoAnnotations 52 import org.mockito.kotlin.any 53 import org.mockito.kotlin.clearInvocations 54 import org.mockito.kotlin.eq 55 import org.mockito.kotlin.mock 56 import org.mockito.kotlin.whenever 57 58 @SmallTest 59 @TestableLooper.RunWithLooper(setAsMainLooper = true) 60 @RunWith(AndroidJUnit4::class) 61 class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { 62 @get:Rule val animatorTestRule = AnimatorTestRule(this) 63 64 private val kosmos = testKosmos() 65 private val testScope: TestScope = kosmos.testScope 66 private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository 67 private val powerInteractor = kosmos.powerInteractor 68 private val fakeAnimationStatusRepository = kosmos.fakeAnimationStatusRepository 69 private val mockControllerFactory = kosmos.fullscreenLightRevealAnimationControllerFactory 70 private val mockFullScreenController = kosmos.fullscreenLightRevealAnimationController 71 private val mockFoldLockSettingAvailabilityProvider = 72 mock<FoldLockSettingAvailabilityProvider>() 73 private val onOverlayReady = mock<Runnable>() 74 private val mockJankMonitor = mock<InteractionJankMonitor>() 75 private val mockScrimView = mock<LightRevealScrim>() 76 private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation 77 78 @Before setupnull79 fun setup() { 80 MockitoAnnotations.initMocks(this) 81 whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) 82 .thenReturn(true) 83 fakeAnimationStatusRepository.onAnimationStatusChanged(true) 84 whenever(mockFullScreenController.scrimView).thenReturn(mockScrimView) 85 whenever(mockJankMonitor.begin(any(), eq(CUJ_FOLD_ANIM))).thenReturn(true) 86 87 foldLightRevealAnimation = 88 FoldLightRevealOverlayAnimation( 89 kosmos.testDispatcher, 90 fakeDeviceStateRepository, 91 powerInteractor, 92 testScope.backgroundScope, 93 fakeAnimationStatusRepository, 94 mockControllerFactory, 95 mockFoldLockSettingAvailabilityProvider, 96 mockJankMonitor, 97 ) 98 foldLightRevealAnimation.init() 99 } 100 101 @Test foldToScreenOn_playFoldAnimationnull102 fun foldToScreenOn_playFoldAnimation() = 103 testScope.runTest { 104 foldDeviceToScreenOff() 105 turnScreenOn() 106 107 verifyFoldAnimationPlayed() 108 } 109 110 @Test foldToAod_doNotPlayFoldAnimationnull111 fun foldToAod_doNotPlayFoldAnimation() = 112 testScope.runTest { 113 foldDeviceToScreenOff() 114 emitLastWakefulnessEventStartingToSleep() 115 advanceTime(SHORT_DELAY_MS) 116 turnScreenOn() 117 advanceTime(ANIMATION_DURATION) 118 119 verifyFoldAnimationDidNotPlay() 120 } 121 122 @Test foldToScreenOff_doNotPlayFoldAnimationnull123 fun foldToScreenOff_doNotPlayFoldAnimation() = 124 testScope.runTest { 125 foldDeviceToScreenOff() 126 emitLastWakefulnessEventStartingToSleep() 127 advanceTime(SHORT_DELAY_MS) 128 advanceTime(ANIMATION_DURATION) 129 130 verifyFoldAnimationDidNotPlay() 131 } 132 133 @Test foldToScreenOnWithDelay_doNotPlayFoldAnimationnull134 fun foldToScreenOnWithDelay_doNotPlayFoldAnimation() = 135 testScope.runTest { 136 foldDeviceToScreenOff() 137 foldLightRevealAnimation.onScreenTurningOn {} 138 advanceTime(WAIT_FOR_ANIMATION_TIMEOUT_MS) 139 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 140 advanceTime(SHORT_DELAY_MS) 141 advanceTime(ANIMATION_DURATION) 142 143 verifyFoldAnimationDidNotPlay() 144 } 145 146 @Test immediateUnfoldAfterFold_removeOverlayAfterCancellationnull147 fun immediateUnfoldAfterFold_removeOverlayAfterCancellation() = 148 testScope.runTest { 149 foldDeviceToScreenOff() 150 foldLightRevealAnimation.onScreenTurningOn {} 151 advanceTime(SHORT_DELAY_MS) 152 clearInvocations(mockFullScreenController) 153 154 // Unfold the device 155 fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) 156 advanceTime(SHORT_DELAY_MS) 157 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 158 159 verifyOverlayWasRemoved() 160 } 161 162 @Test foldToScreenOn_removeOverlayAfterCompletionnull163 fun foldToScreenOn_removeOverlayAfterCompletion() = 164 testScope.runTest { 165 foldDeviceToScreenOff() 166 turnScreenOn() 167 clearInvocations(mockFullScreenController) 168 advanceTime(ANIMATION_DURATION) 169 170 verifyOverlayWasRemoved() 171 } 172 173 @Test foldToScreenOn_jankCujIsStartednull174 fun foldToScreenOn_jankCujIsStarted() = 175 testScope.runTest { 176 foldDeviceToScreenOff() 177 turnScreenOn() 178 // Cuj has started but not ended 179 verify(mockJankMonitor, times(1)).begin(any(), eq(CUJ_FOLD_ANIM)) 180 verify(mockJankMonitor, never()).end(eq(CUJ_FOLD_ANIM)) 181 } 182 183 @Test foldToScreenOn_animationFinished_jankCujIsFinishednull184 fun foldToScreenOn_animationFinished_jankCujIsFinished() = 185 testScope.runTest { 186 foldDeviceToScreenOff() 187 turnScreenOn() 188 189 advanceTime(ANIMATION_DURATION) 190 verify(mockJankMonitor, times(1)).end(eq(CUJ_FOLD_ANIM)) 191 } 192 advanceTimenull193 private fun TestScope.advanceTime(timeMs: Long) { 194 if (timeMs == ANIMATION_DURATION) { 195 animatorTestRule.advanceAnimationDuration(timeMs) 196 } else { 197 animatorTestRule.advanceTimeBy(timeMs) 198 } 199 advanceTimeBy(timeMs) 200 } 201 202 @Test unfold_immediatelyRunRunnablenull203 fun unfold_immediatelyRunRunnable() = 204 testScope.runTest { 205 foldLightRevealAnimation.onScreenTurningOn(onOverlayReady) 206 207 verify(onOverlayReady).run() 208 } 209 foldDeviceToScreenOffnull210 private suspend fun TestScope.foldDeviceToScreenOff() { 211 fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) 212 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 213 advanceTime(SHORT_DELAY_MS) 214 fakeDeviceStateRepository.emit(DeviceState.FOLDED) 215 advanceTime(SHORT_DELAY_MS) 216 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF) 217 advanceTime(SHORT_DELAY_MS) 218 } 219 TestScopenull220 private fun TestScope.turnScreenOn() { 221 foldLightRevealAnimation.onScreenTurningOn {} 222 advanceTime(SHORT_DELAY_MS) 223 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 224 advanceTime(SHORT_DELAY_MS) 225 } 226 emitLastWakefulnessEventStartingToSleepnull227 private fun emitLastWakefulnessEventStartingToSleep() = 228 powerInteractor.setAsleepForTest(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD) 229 230 private fun verifyFoldAnimationPlayed() = 231 verify(mockFullScreenController, atLeast(1)).updateRevealAmount(any()) 232 233 private fun verifyFoldAnimationDidNotPlay() = 234 verify(mockFullScreenController, never()).updateRevealAmount(any()) 235 236 private fun verifyOverlayWasRemoved() = 237 verify(mockFullScreenController, atLeast(1)).ensureOverlayRemoved() 238 239 private companion object { 240 const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L 241 val ANIMATION_DURATION: Long 242 get() = SystemProperties.getLong("persist.fold_animation_duration", 200L) 243 244 const val SHORT_DELAY_MS = 50L 245 } 246 } 247