• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 
18 package com.android.systemui.keyguard.data.repository
19 
20 import android.annotation.FloatRange
21 import com.android.systemui.Flags.transitionRaceCondition
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.keyguard.shared.model.KeyguardState
24 import com.android.systemui.keyguard.shared.model.TransitionInfo
25 import com.android.systemui.keyguard.shared.model.TransitionState
26 import com.android.systemui.keyguard.shared.model.TransitionStep
27 import dagger.Binds
28 import dagger.Module
29 import java.util.UUID
30 import javax.inject.Inject
31 import junit.framework.Assert.fail
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.Job
34 import kotlinx.coroutines.channels.BufferOverflow
35 import kotlinx.coroutines.flow.MutableSharedFlow
36 import kotlinx.coroutines.flow.MutableStateFlow
37 import kotlinx.coroutines.flow.SharedFlow
38 import kotlinx.coroutines.flow.asStateFlow
39 import kotlinx.coroutines.launch
40 import kotlinx.coroutines.test.TestCoroutineScheduler
41 import kotlinx.coroutines.test.TestScope
42 import kotlinx.coroutines.test.runCurrent
43 
44 /**
45  * Fake implementation of [KeyguardTransitionRepository].
46  *
47  * By default, will be seeded with a transition from OFF -> LOCKSCREEN, which is the most common
48  * case. If the lockscreen is disabled, or we're in setup wizard, the repository will initialize
49  * with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
50  */
51 @SysUISingleton
52 class FakeKeyguardTransitionRepository(
53     private val initInLockscreen: Boolean = true,
54 
55     /**
56      * Initial value for [FakeKeyguardTransitionRepository.sendTransitionStepsOnStartTransition].
57      * This needs to be configurable in the constructor since some transitions are triggered on
58      * init, before a test has the chance to set sendTransitionStepsOnStartTransition to false.
59      */
60     private val initiallySendTransitionStepsOnStartTransition: Boolean = true,
61     private val testScope: TestScope,
62 ) : KeyguardTransitionRepository {
63 
64     /**
65      * If true, calls to [startTransition] will automatically emit STARTED, RUNNING, and FINISHED
66      * transition steps from/to the given states.
67      *
68      * [startTransition] is what the From*TransitionInteractors call, so this more closely emulates
69      * the behavior of the real KeyguardTransitionRepository, and reduces the work needed to
70      * manually set up the repository state in each test. For example, setting dreaming=true will
71      * automatically cause FromDreamingTransitionInteractor to call startTransition(DREAMING), and
72      * then we'll send STARTED/RUNNING/FINISHED DREAMING TransitionSteps.
73      *
74      * If your test needs to make assertions at specific points between STARTED/FINISHED, or if it's
75      * difficult to set up all of the conditions to make the transition interactors actually call
76      * startTransition, set this value to false.
77      */
78     var sendTransitionStepsOnStartTransition = initiallySendTransitionStepsOnStartTransition
79 
80     private val _transitions =
81         MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
82     override val transitions: SharedFlow<TransitionStep> = _transitions
83 
84     @Inject
85     constructor(
86         testScope: TestScope
87     ) : this(
88         initInLockscreen = true,
89         initiallySendTransitionStepsOnStartTransition = true,
90         testScope,
91     )
92 
93     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
94         MutableStateFlow(
95             TransitionInfo(
96                 ownerName = "",
97                 from = KeyguardState.OFF,
98                 to = KeyguardState.LOCKSCREEN,
99                 animator = null,
100             )
101         )
102     override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
103     override var currentTransitionInfo =
104         TransitionInfo(
105             ownerName = "",
106             from = KeyguardState.OFF,
107             to = KeyguardState.LOCKSCREEN,
108             animator = null,
109         )
110 
111     init {
112         // Seed with a FINISHED transition in OFF, same as the real repository.
113         _transitions.tryEmit(
114             TransitionStep(KeyguardState.OFF, KeyguardState.OFF, 1f, TransitionState.FINISHED)
115         )
116 
117         if (initInLockscreen) {
118             tryEmitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
119         } else {
120             tryEmitInitialStepsFromOff(KeyguardState.OFF)
121         }
122     }
123 
124     /**
125      * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
126      *
127      * By default, sends steps through FINISHED (STARTED, RUNNING @0.5f, RUNNING @1f, FINISHED) but
128      * can be halted part way using [throughTransitionState].
129      */
130     suspend fun sendTransitionSteps(
131         from: KeyguardState,
132         to: KeyguardState,
133         testScope: TestScope,
134         throughTransitionState: TransitionState = TransitionState.FINISHED,
135     ) {
136         sendTransitionSteps(from, to, testScope.testScheduler, throughTransitionState)
137     }
138 
139     /**
140      * Sends a STARTED step between [from] and [to], followed by two RUNNING steps at value
141      * [throughValue] / 2 and [throughValue], calling [runCurrent] after each step.
142      */
143     suspend fun sendTransitionStepsThroughRunning(
144         from: KeyguardState,
145         to: KeyguardState,
146         testScope: TestScope,
147         throughValue: Float = 1f,
148     ) {
149         sendTransitionSteps(
150             from,
151             to,
152             testScope.testScheduler,
153             TransitionState.RUNNING,
154             throughValue,
155         )
156     }
157 
158     /**
159      * Sends the provided [step] and makes sure that all previous [TransitionState]'s are sent when
160      * [fillInSteps] is true. e.g. when a step FINISHED is provided, a step with STARTED and RUNNING
161      * is also sent.
162      */
163     suspend fun sendTransitionSteps(
164         step: TransitionStep,
165         testScope: TestScope,
166         fillInSteps: Boolean = true,
167     ) {
168         if (fillInSteps && step.transitionState != TransitionState.STARTED) {
169             sendTransitionStep(
170                 step =
171                     TransitionStep(
172                         transitionState = TransitionState.STARTED,
173                         from = step.from,
174                         to = step.to,
175                         value = 0f,
176                     )
177             )
178             testScope.testScheduler.runCurrent()
179 
180             if (step.transitionState != TransitionState.RUNNING) {
181                 sendTransitionStep(
182                     step =
183                         TransitionStep(
184                             transitionState = TransitionState.RUNNING,
185                             from = step.from,
186                             to = step.to,
187                             value = 0.6f,
188                         )
189                 )
190                 testScope.testScheduler.runCurrent()
191             }
192         }
193         sendTransitionStep(step = step)
194         testScope.testScheduler.runCurrent()
195     }
196 
197     /**
198      * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
199      *
200      * By default, sends steps through FINISHED (STARTED, RUNNING @0.5f, RUNNING @1f, FINISHED) but
201      * can be halted part way using [throughTransitionState].
202      */
203     suspend fun sendTransitionSteps(
204         from: KeyguardState,
205         to: KeyguardState,
206         testScheduler: TestCoroutineScheduler,
207         throughTransitionState: TransitionState = TransitionState.FINISHED,
208         throughTransitionValue: Float = 1f,
209     ) {
210         val lastStep = _transitions.replayCache.lastOrNull()
211         if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) {
212             sendTransitionStep(
213                 step =
214                     TransitionStep(
215                         transitionState = TransitionState.CANCELED,
216                         from = lastStep.from,
217                         to = lastStep.to,
218                         value = 0f,
219                     )
220             )
221             testScheduler.runCurrent()
222         }
223 
224         sendTransitionStep(
225             step =
226                 TransitionStep(
227                     transitionState = TransitionState.STARTED,
228                     from = from,
229                     to = to,
230                     value = 0f,
231                 )
232         )
233         testScheduler.runCurrent()
234 
235         if (
236             throughTransitionState == TransitionState.RUNNING ||
237                 throughTransitionState == TransitionState.FINISHED
238         ) {
239             // Send two steps to better simulate RUNNING transitions.
240             sendTransitionStep(
241                 step =
242                     TransitionStep(
243                         transitionState = TransitionState.RUNNING,
244                         from = from,
245                         to = to,
246                         value = throughTransitionValue / 2f,
247                     )
248             )
249             testScheduler.runCurrent()
250 
251             sendTransitionStep(
252                 step =
253                     TransitionStep(
254                         transitionState = TransitionState.RUNNING,
255                         from = from,
256                         to = to,
257                         value = throughTransitionValue,
258                     )
259             )
260             testScheduler.runCurrent()
261         }
262 
263         if (throughTransitionState == TransitionState.FINISHED) {
264             sendTransitionStep(
265                 step =
266                     TransitionStep(
267                         transitionState = TransitionState.FINISHED,
268                         from = from,
269                         to = to,
270                         value = 1f,
271                     )
272             )
273             testScheduler.runCurrent()
274         }
275     }
276 
277     suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
278         this.sendTransitionStep(
279             step = step,
280             validateStep = validateStep,
281             ownerName = step.ownerName,
282         )
283     }
284 
285     /**
286      * Directly emits the provided TransitionStep, which can be useful in tests for testing behavior
287      * during specific phases of a transition (such as asserting values while a transition has
288      * STARTED but not FINISHED).
289      *
290      * WARNING: You can get the transition repository into undefined states using this method - for
291      * example, you could send a FINISHED step to LOCKSCREEN having never sent a STARTED step. This
292      * can get flows that combine startedStep/finishedStep into a bad state.
293      *
294      * If you are just trying to get the transition repository FINISHED in a certain state, use
295      * [sendTransitionSteps] - this will send STARTED, RUNNING, and FINISHED steps for you which
296      * ensures that [KeyguardTransitionInteractor] flows will be in the correct state.
297      *
298      * If you're testing something involving transitions themselves and are sure you want to send
299      * only a FINISHED step, override [validateStep].
300      */
301     suspend fun sendTransitionStep(
302         from: KeyguardState = KeyguardState.OFF,
303         to: KeyguardState = KeyguardState.OFF,
304         value: Float = 0f,
305         transitionState: TransitionState = TransitionState.FINISHED,
306         ownerName: String = "",
307         step: TransitionStep =
308             TransitionStep(
309                 from = from,
310                 to = to,
311                 value = value,
312                 transitionState = transitionState,
313                 ownerName = ownerName,
314             ),
315         validateStep: Boolean = true,
316     ) {
317         if (step.transitionState == TransitionState.STARTED) {
318             if (transitionRaceCondition()) {
319                 currentTransitionInfo =
320                     TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
321             } else {
322                 _currentTransitionInfo.value =
323                     TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
324             }
325         }
326 
327         _transitions.replayCache.last().let { lastStep ->
328             if (
329                 validateStep &&
330                     step.transitionState == TransitionState.FINISHED &&
331                     !(lastStep.transitionState == TransitionState.STARTED ||
332                         lastStep.transitionState == TransitionState.RUNNING)
333             ) {
334                 fail(
335                     "Attempted to send a FINISHED TransitionStep without a prior " +
336                         "STARTED/RUNNING step. This leaves the FakeKeyguardTransitionRepository " +
337                         "in an undefined state and should not be done. Pass " +
338                         "allowInvalidStep=true to sendTransitionStep if you are trying to test " +
339                         "this specific and" +
340                         "incorrect state."
341                 )
342             }
343         }
344         _transitions.emit(step)
345     }
346 
347     /** Version of [sendTransitionStep] that's usable from Java tests. */
348     fun sendTransitionStepJava(
349         coroutineScope: CoroutineScope,
350         step: TransitionStep,
351         validateStep: Boolean = true,
352     ): Job {
353         return coroutineScope.launch {
354             sendTransitionStep(step = step, validateStep = validateStep)
355         }
356     }
357 
358     suspend fun sendTransitionSteps(
359         steps: List<TransitionStep>,
360         testScope: TestScope,
361         validateSteps: Boolean = true,
362     ) {
363         steps.forEach {
364             sendTransitionStep(step = it, validateStep = validateSteps)
365             testScope.testScheduler.runCurrent()
366         }
367     }
368 
369     override suspend fun startTransition(info: TransitionInfo): UUID? {
370         if (transitionRaceCondition()) {
371             currentTransitionInfo = info
372         } else {
373             _currentTransitionInfo.value = info
374         }
375 
376         if (sendTransitionStepsOnStartTransition) {
377             sendTransitionSteps(from = info.from, to = info.to, testScope = testScope)
378         }
379 
380         return if (info.animator == null) UUID.randomUUID() else null
381     }
382 
383     override suspend fun emitInitialStepsFromOff(to: KeyguardState, testSetup: Boolean) {
384         tryEmitInitialStepsFromOff(to)
385     }
386 
387     private fun tryEmitInitialStepsFromOff(to: KeyguardState) {
388         _transitions.tryEmit(
389             TransitionStep(
390                 KeyguardState.OFF,
391                 to,
392                 0f,
393                 TransitionState.STARTED,
394                 ownerName = "KeyguardTransitionRepository(boot)",
395             )
396         )
397 
398         _transitions.tryEmit(
399             TransitionStep(
400                 KeyguardState.OFF,
401                 to,
402                 1f,
403                 TransitionState.FINISHED,
404                 ownerName = "KeyguardTransitionRepository(boot)",
405             )
406         )
407     }
408 
409     override suspend fun updateTransition(
410         transitionId: UUID,
411         @FloatRange(from = 0.0, to = 1.0) value: Float,
412         state: TransitionState,
413     ) = Unit
414 
415     override suspend fun forceFinishCurrentTransition() {
416         _transitions.tryEmit(
417             TransitionStep(
418                 _currentTransitionInfo.value.from,
419                 _currentTransitionInfo.value.to,
420                 1f,
421                 TransitionState.FINISHED,
422                 ownerName = _currentTransitionInfo.value.ownerName,
423             )
424         )
425     }
426 
427     suspend fun transitionTo(from: KeyguardState, to: KeyguardState) {
428         sendTransitionStep(TransitionStep(from, to, 0f, TransitionState.STARTED))
429         sendTransitionStep(TransitionStep(from, to, 0.5f, TransitionState.RUNNING))
430         sendTransitionStep(TransitionStep(from, to, 1f, TransitionState.FINISHED))
431     }
432 }
433 
434 @Module
435 interface FakeKeyguardTransitionRepositoryModule {
bindFakenull436     @Binds fun bindFake(fake: FakeKeyguardTransitionRepository): KeyguardTransitionRepository
437 }
438