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.AndroidTestingRunner
22 import android.testing.TestableLooper
23 import androidx.test.filters.SmallTest
24 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
27 import com.android.systemui.display.data.repository.fakeDeviceStateRepository
28 import com.android.systemui.kosmos.Kosmos
29 import com.android.systemui.kosmos.testDispatcher
30 import com.android.systemui.kosmos.testScope
31 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
32 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState
33 import com.android.systemui.power.domain.interactor.powerInteractor
34 import com.android.systemui.power.shared.model.ScreenPowerState
35 import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
36 import com.android.systemui.util.mockito.any
37 import com.android.systemui.util.mockito.mock
38 import com.android.systemui.util.mockito.whenever
39 import kotlinx.coroutines.ExperimentalCoroutinesApi
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.Test
45 import org.junit.runner.RunWith
46 import org.mockito.Mockito.atLeast
47 import org.mockito.Mockito.never
48 import org.mockito.Mockito.verify
49 import org.mockito.MockitoAnnotations
50 
51 @SmallTest
52 @TestableLooper.RunWithLooper(setAsMainLooper = true)
53 @RunWith(AndroidTestingRunner::class)
54 @OptIn(ExperimentalCoroutinesApi::class)
55 class FoldLightRevealOverlayAnimationTest : SysuiTestCase() {
56     private val kosmos = Kosmos()
57     private val testScope: TestScope = kosmos.testScope
58     private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository
59     private val powerInteractor = kosmos.powerInteractor
60     private val fakeAnimationStatusRepository = kosmos.fakeAnimationStatusRepository
61     private val mockControllerFactory = kosmos.fullscreenLightRevealAnimationControllerFactory
62     private val mockFullScreenController = kosmos.fullscreenLightRevealAnimationController
63     private val mockFoldLockSettingAvailabilityProvider =
64         mock<FoldLockSettingAvailabilityProvider>()
65     private val onOverlayReady = mock<Runnable>()
66     private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation
67 
68     @Before
setupnull69     fun setup() {
70         MockitoAnnotations.initMocks(this)
71         whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable)
72             .thenReturn(true)
73         fakeAnimationStatusRepository.onAnimationStatusChanged(true)
74 
75         foldLightRevealAnimation =
76             FoldLightRevealOverlayAnimation(
77                 kosmos.testDispatcher,
78                 fakeDeviceStateRepository,
79                 powerInteractor,
80                 testScope.backgroundScope,
81                 fakeAnimationStatusRepository,
82                 mockControllerFactory,
83                 mockFoldLockSettingAvailabilityProvider
84             )
85         foldLightRevealAnimation.init()
86     }
87 
88     @Test
foldToScreenOn_playFoldAnimationnull89     fun foldToScreenOn_playFoldAnimation() =
90         testScope.runTest {
91             foldDeviceToScreenOff()
92             turnScreenOn()
93 
94             verifyFoldAnimationPlayed()
95         }
96 
97     @Test
foldToAod_doNotPlayFoldAnimationnull98     fun foldToAod_doNotPlayFoldAnimation() =
99         testScope.runTest {
100             foldDeviceToScreenOff()
101             emitLastWakefulnessEventStartingToSleep()
102             advanceTimeBy(SHORT_DELAY_MS)
103             turnScreenOn()
104             advanceTimeBy(ANIMATION_DURATION)
105 
106             verifyFoldAnimationDidNotPlay()
107         }
108 
109     @Test
foldToScreenOff_doNotPlayFoldAnimationnull110     fun foldToScreenOff_doNotPlayFoldAnimation() =
111         testScope.runTest {
112             foldDeviceToScreenOff()
113             emitLastWakefulnessEventStartingToSleep()
114             advanceTimeBy(SHORT_DELAY_MS)
115             advanceTimeBy(ANIMATION_DURATION)
116 
117             verifyFoldAnimationDidNotPlay()
118         }
119 
120     @Test
foldToScreenOnWithDelay_doNotPlayFoldAnimationnull121     fun foldToScreenOnWithDelay_doNotPlayFoldAnimation() =
122         testScope.runTest {
123             foldDeviceToScreenOff()
124             foldLightRevealAnimation.onScreenTurningOn {}
125             advanceTimeBy(WAIT_FOR_ANIMATION_TIMEOUT_MS)
126             powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
127             advanceTimeBy(SHORT_DELAY_MS)
128             advanceTimeBy(ANIMATION_DURATION)
129 
130             verifyFoldAnimationDidNotPlay()
131         }
132 
133     @Test
immediateUnfoldAfterFold_removeOverlayAfterCancellationnull134     fun immediateUnfoldAfterFold_removeOverlayAfterCancellation() =
135         testScope.runTest {
136             foldDeviceToScreenOff()
137             foldLightRevealAnimation.onScreenTurningOn {}
138             advanceTimeBy(SHORT_DELAY_MS)
139             advanceTimeBy(ANIMATION_DURATION)
140             fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
141             advanceTimeBy(SHORT_DELAY_MS)
142             powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
143 
144             verifyOverlayWasRemoved()
145         }
146 
147     @Test
foldToScreenOn_removeOverlayAfterCompletionnull148     fun foldToScreenOn_removeOverlayAfterCompletion() =
149         testScope.runTest {
150             foldDeviceToScreenOff()
151             turnScreenOn()
152             advanceTimeBy(ANIMATION_DURATION)
153 
154             verifyOverlayWasRemoved()
155         }
156 
157     @Test
unfold_immediatelyRunRunnablenull158     fun unfold_immediatelyRunRunnable() =
159         testScope.runTest {
160             foldLightRevealAnimation.onScreenTurningOn(onOverlayReady)
161 
162             verify(onOverlayReady).run()
163         }
164 
foldDeviceToScreenOffnull165     private suspend fun TestScope.foldDeviceToScreenOff() {
166         fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
167         powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
168         advanceTimeBy(SHORT_DELAY_MS)
169         fakeDeviceStateRepository.emit(DeviceState.FOLDED)
170         advanceTimeBy(SHORT_DELAY_MS)
171         powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF)
172         advanceTimeBy(SHORT_DELAY_MS)
173     }
174 
TestScopenull175     private fun TestScope.turnScreenOn() {
176         foldLightRevealAnimation.onScreenTurningOn {}
177         advanceTimeBy(SHORT_DELAY_MS)
178         powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
179         advanceTimeBy(SHORT_DELAY_MS)
180     }
181 
emitLastWakefulnessEventStartingToSleepnull182     private fun emitLastWakefulnessEventStartingToSleep() =
183         powerInteractor.setAsleepForTest(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
184 
185     private fun verifyFoldAnimationPlayed() =
186         verify(mockFullScreenController, atLeast(1)).updateRevealAmount(any())
187 
188     private fun verifyFoldAnimationDidNotPlay() =
189         verify(mockFullScreenController, never()).updateRevealAmount(any())
190 
191     private fun verifyOverlayWasRemoved() =
192         verify(mockFullScreenController, atLeast(1)).ensureOverlayRemoved()
193 
194     private companion object {
195         const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
196         val ANIMATION_DURATION: Long
197             get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
198         const val SHORT_DELAY_MS = 50L
199     }
200 }
201