• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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