1 /*
2  * Copyright (C) 2022 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.hardware.devicestate.DeviceStateManager
20 import android.hardware.devicestate.DeviceStateManager.FoldStateListener
21 import android.os.PowerManager
22 import android.testing.AndroidTestingRunner
23 import android.view.ViewGroup
24 import android.view.ViewTreeObserver
25 import androidx.test.filters.SmallTest
26 import com.android.internal.util.LatencyTracker
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.flags.FakeFeatureFlags
29 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
30 import com.android.systemui.keyguard.WakefulnessLifecycle
31 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
32 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
33 import com.android.systemui.shade.ShadeFoldAnimator
34 import com.android.systemui.shade.ShadeViewController
35 import com.android.systemui.statusbar.LightRevealScrim
36 import com.android.systemui.statusbar.phone.CentralSurfaces
37 import com.android.systemui.unfold.util.FoldableDeviceStates
38 import com.android.systemui.unfold.util.FoldableTestUtils
39 import com.android.systemui.util.concurrency.FakeExecutor
40 import com.android.systemui.util.mockito.any
41 import com.android.systemui.util.settings.GlobalSettings
42 import com.android.systemui.util.time.FakeSystemClock
43 import kotlinx.coroutines.Dispatchers
44 import kotlinx.coroutines.runBlocking
45 import kotlinx.coroutines.yield
46 import org.junit.Before
47 import org.junit.Test
48 import org.junit.runner.RunWith
49 import org.mockito.ArgumentCaptor
50 import org.mockito.Captor
51 import org.mockito.Mock
52 import org.mockito.Mockito.never
53 import org.mockito.Mockito.reset
54 import org.mockito.Mockito.verify
55 import org.mockito.Mockito.verifyNoMoreInteractions
56 import org.mockito.Mockito.`when` as whenever
57 import org.mockito.MockitoAnnotations
58 
59 @RunWith(AndroidTestingRunner::class)
60 @SmallTest
61 class FoldAodAnimationControllerTest : SysuiTestCase() {
62 
63     @Mock lateinit var deviceStateManager: DeviceStateManager
64 
65     @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
66 
67     @Mock lateinit var globalSettings: GlobalSettings
68 
69     @Mock lateinit var latencyTracker: LatencyTracker
70 
71     @Mock lateinit var centralSurfaces: CentralSurfaces
72 
73     @Mock lateinit var lightRevealScrim: LightRevealScrim
74 
75     @Mock lateinit var shadeViewController: ShadeViewController
76 
77     @Mock lateinit var viewGroup: ViewGroup
78 
79     @Mock lateinit var viewTreeObserver: ViewTreeObserver
80 
81     @Mock lateinit var shadeFoldAnimator: ShadeFoldAnimator
82 
83     @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
84 
85     private lateinit var deviceStates: FoldableDeviceStates
86 
87     lateinit var keyguardRepository: FakeKeyguardRepository
88 
89     lateinit var underTest: FoldAodAnimationController
90     private val fakeExecutor = FakeExecutor(FakeSystemClock())
91 
92     @Before
93     fun setup() {
94         MockitoAnnotations.initMocks(this)
95 
96         deviceStates = FoldableTestUtils.findDeviceStates(context)
97 
98         // TODO(b/254878364): remove this call to NPVC.getView()
99         whenever(shadeViewController.shadeFoldAnimator).thenReturn(shadeFoldAnimator)
100         whenever(shadeFoldAnimator.view).thenReturn(viewGroup)
101         whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
102         whenever(wakefulnessLifecycle.lastSleepReason)
103             .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
104         whenever(shadeFoldAnimator.startFoldToAodAnimation(any(), any(), any())).then {
105             val onActionStarted = it.arguments[0] as Runnable
106             onActionStarted.run()
107         }
108 
109         val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
110         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
111         val keyguardInteractor = withDeps.keyguardInteractor
112         keyguardRepository = withDeps.repository
113 
114         // Needs to be run on the main thread
115         runBlocking(IMMEDIATE) {
116             underTest =
117                 FoldAodAnimationController(
118                         fakeExecutor,
119                         context,
120                         deviceStateManager,
121                         wakefulnessLifecycle,
122                         globalSettings,
123                         latencyTracker,
124                         { keyguardInteractor },
125                     )
126                     .apply { initialize(centralSurfaces, shadeViewController, lightRevealScrim) }
127 
128             verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
129 
130             setAodEnabled(enabled = true)
131             sendFoldEvent(folded = false)
132         }
133     }
134 
135     @Test
136     fun onFolded_aodDisabled_doesNotLogLatency() =
137         runBlocking(IMMEDIATE) {
138             val job = underTest.listenForDozing(this)
139             keyguardRepository.setIsDozing(true)
140             setAodEnabled(enabled = false)
141 
142             yield()
143 
144             fold()
145             simulateScreenTurningOn()
146 
147             verifyNoMoreInteractions(latencyTracker)
148 
149             job.cancel()
150         }
151 
152     @Test
153     fun onFolded_aodEnabled_logsLatency() =
154         runBlocking(IMMEDIATE) {
155             val job = underTest.listenForDozing(this)
156             keyguardRepository.setIsDozing(true)
157             setAodEnabled(enabled = true)
158 
159             yield()
160 
161             fold()
162             simulateScreenTurningOn()
163 
164             verify(latencyTracker).onActionStart(any())
165             verify(latencyTracker).onActionEnd(any())
166 
167             job.cancel()
168         }
169 
170     @Test
171     fun onFolded_onScreenTurningOnInvokedTwice_doesNotLogLatency() =
172         runBlocking(IMMEDIATE) {
173             val job = underTest.listenForDozing(this)
174             keyguardRepository.setIsDozing(true)
175             setAodEnabled(enabled = true)
176 
177             yield()
178 
179             fold()
180             simulateScreenTurningOn()
181             reset(latencyTracker)
182 
183             // This can happen > 1 time if the prox sensor is covered
184             simulateScreenTurningOn()
185 
186             verify(latencyTracker, never()).onActionStart(any())
187             verify(latencyTracker, never()).onActionEnd(any())
188 
189             job.cancel()
190         }
191 
192     @Test
193     fun onFolded_onScreenTurningOnWithoutDozingThenWithDozing_doesNotLogLatency() =
194         runBlocking(IMMEDIATE) {
195             val job = underTest.listenForDozing(this)
196             keyguardRepository.setIsDozing(false)
197             setAodEnabled(enabled = true)
198 
199             yield()
200 
201             fold()
202             simulateScreenTurningOn()
203             reset(latencyTracker)
204 
205             // Now enable dozing and trigger a second run through the aod animation code. It should
206             // not rerun the animation
207             keyguardRepository.setIsDozing(true)
208             yield()
209             simulateScreenTurningOn()
210 
211             verify(latencyTracker, never()).onActionStart(any())
212             verify(latencyTracker, never()).onActionEnd(any())
213 
214             job.cancel()
215         }
216 
217     @Test
218     fun onFolded_animationCancelled_doesNotLogLatency() =
219         runBlocking(IMMEDIATE) {
220             val job = underTest.listenForDozing(this)
221             keyguardRepository.setIsDozing(true)
222             setAodEnabled(enabled = true)
223 
224             yield()
225 
226             fold()
227             underTest.onScreenTurningOn({})
228             // The body of onScreenTurningOn is executed on fakeExecutor,
229             // run all pending tasks before calling the next method
230             fakeExecutor.runAllReady()
231             underTest.onStartedWakingUp()
232 
233             verify(latencyTracker).onActionStart(any())
234             verify(latencyTracker).onActionCancel(any())
235 
236             job.cancel()
237         }
238 
239     private fun simulateScreenTurningOn() {
240         underTest.onScreenTurningOn({})
241         underTest.onScreenTurnedOn()
242         fakeExecutor.runAllReady()
243     }
244 
245     private fun fold() = sendFoldEvent(folded = true)
246 
247     private fun setAodEnabled(enabled: Boolean) = underTest.onAlwaysOnChanged(alwaysOn = enabled)
248 
249     private fun sendFoldEvent(folded: Boolean) {
250         val state = if (folded) deviceStates.folded else deviceStates.unfolded
251         foldStateListenerCaptor.value.onStateChanged(state)
252     }
253 
254     companion object {
255         private val IMMEDIATE = Dispatchers.Main.immediate
256     }
257 }
258