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.dagger.SysUISingleton
22 import com.android.systemui.keyguard.shared.model.KeyguardState
23 import com.android.systemui.keyguard.shared.model.TransitionInfo
24 import com.android.systemui.keyguard.shared.model.TransitionState
25 import com.android.systemui.keyguard.shared.model.TransitionStep
26 import dagger.Binds
27 import dagger.Module
28 import java.util.UUID
29 import javax.inject.Inject
30 import junit.framework.Assert.fail
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.Job
33 import kotlinx.coroutines.channels.BufferOverflow
34 import kotlinx.coroutines.flow.MutableSharedFlow
35 import kotlinx.coroutines.flow.MutableStateFlow
36 import kotlinx.coroutines.flow.SharedFlow
37 import kotlinx.coroutines.flow.asStateFlow
38 import kotlinx.coroutines.launch
39 import kotlinx.coroutines.test.TestCoroutineScheduler
40 import kotlinx.coroutines.test.TestScope
41 import kotlinx.coroutines.test.runCurrent
42 
43 /**
44  * Fake implementation of [KeyguardTransitionRepository].
45  *
46  * By default, will be seeded with a transition from OFF -> LOCKSCREEN, which is the most common
47  * case. If the lockscreen is disabled, or we're in setup wizard, the repository will initialize
48  * with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
49  */
50 @SysUISingleton
51 class FakeKeyguardTransitionRepository(
52     private val initInLockscreen: Boolean = true,
53 ) : KeyguardTransitionRepository {
54     private val _transitions =
55         MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
56     override val transitions: SharedFlow<TransitionStep> = _transitions
57 
58     @Inject constructor() : this(initInLockscreen = true)
59 
60     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
61         MutableStateFlow(
62             TransitionInfo(
63                 ownerName = "",
64                 from = KeyguardState.OFF,
65                 to = KeyguardState.LOCKSCREEN,
66                 animator = null
67             )
68         )
69     override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
70 
71     init {
72         // Seed with a FINISHED transition in OFF, same as the real repository.
73         _transitions.tryEmit(
74             TransitionStep(
75                 KeyguardState.OFF,
76                 KeyguardState.OFF,
77                 1f,
78                 TransitionState.FINISHED,
79             )
80         )
81 
82         if (initInLockscreen) {
83             tryEmitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
84         } else {
85             tryEmitInitialStepsFromOff(KeyguardState.OFF)
86         }
87     }
88 
89     /**
90      * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
91      *
92      * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
93      * way using [throughTransitionState].
94      */
95     suspend fun sendTransitionSteps(
96         from: KeyguardState,
97         to: KeyguardState,
98         testScope: TestScope,
99         throughTransitionState: TransitionState = TransitionState.FINISHED,
100     ) {
101         sendTransitionSteps(from, to, testScope.testScheduler, throughTransitionState)
102     }
103 
104     /**
105      * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
106      *
107      * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
108      * way using [throughTransitionState].
109      */
110     suspend fun sendTransitionSteps(
111         from: KeyguardState,
112         to: KeyguardState,
113         testScheduler: TestCoroutineScheduler,
114         throughTransitionState: TransitionState = TransitionState.FINISHED,
115     ) {
116         sendTransitionStep(
117             step =
118                 TransitionStep(
119                     transitionState = TransitionState.STARTED,
120                     from = from,
121                     to = to,
122                     value = 0f,
123                 )
124         )
125         testScheduler.runCurrent()
126 
127         if (
128             throughTransitionState == TransitionState.RUNNING ||
129                 throughTransitionState == TransitionState.FINISHED
130         ) {
131             sendTransitionStep(
132                 step =
133                     TransitionStep(
134                         transitionState = TransitionState.RUNNING,
135                         from = from,
136                         to = to,
137                         value = 0.5f
138                     )
139             )
140             testScheduler.runCurrent()
141 
142             sendTransitionStep(
143                 step =
144                     TransitionStep(
145                         transitionState = TransitionState.RUNNING,
146                         from = from,
147                         to = to,
148                         value = 1f
149                     )
150             )
151             testScheduler.runCurrent()
152         }
153 
154         if (throughTransitionState == TransitionState.FINISHED) {
155             sendTransitionStep(
156                 step =
157                     TransitionStep(
158                         transitionState = TransitionState.FINISHED,
159                         from = from,
160                         to = to,
161                         value = 1f,
162                     )
163             )
164             testScheduler.runCurrent()
165         }
166     }
167 
168     suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
169         this.sendTransitionStep(
170             step = step,
171             validateStep = validateStep,
172             ownerName = step.ownerName
173         )
174     }
175 
176     /**
177      * Directly emits the provided TransitionStep, which can be useful in tests for testing behavior
178      * during specific phases of a transition (such as asserting values while a transition has
179      * STARTED but not FINISHED).
180      *
181      * WARNING: You can get the transition repository into undefined states using this method - for
182      * example, you could send a FINISHED step to LOCKSCREEN having never sent a STARTED step. This
183      * can get flows that combine startedStep/finishedStep into a bad state.
184      *
185      * If you are just trying to get the transition repository FINISHED in a certain state, use
186      * [sendTransitionSteps] - this will send STARTED, RUNNING, and FINISHED steps for you which
187      * ensures that [KeyguardTransitionInteractor] flows will be in the correct state.
188      *
189      * If you're testing something involving transitions themselves and are sure you want to send
190      * only a FINISHED step, override [validateStep].
191      */
192     suspend fun sendTransitionStep(
193         from: KeyguardState = KeyguardState.OFF,
194         to: KeyguardState = KeyguardState.OFF,
195         value: Float = 0f,
196         transitionState: TransitionState = TransitionState.FINISHED,
197         ownerName: String = "",
198         step: TransitionStep =
199             TransitionStep(
200                 from = from,
201                 to = to,
202                 value = value,
203                 transitionState = transitionState,
204                 ownerName = ownerName
205             ),
206         validateStep: Boolean = true
207     ) {
208         if (step.transitionState == TransitionState.STARTED) {
209             _currentTransitionInfo.value =
210                 TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
211         }
212 
213         _transitions.replayCache.last().let { lastStep ->
214             if (
215                 validateStep &&
216                     step.transitionState == TransitionState.FINISHED &&
217                     !(lastStep.transitionState == TransitionState.STARTED ||
218                         lastStep.transitionState == TransitionState.RUNNING)
219             ) {
220                 fail(
221                     "Attempted to send a FINISHED TransitionStep without a prior " +
222                         "STARTED/RUNNING step. This leaves the FakeKeyguardTransitionRepository " +
223                         "in an undefined state and should not be done. Pass " +
224                         "allowInvalidStep=true to sendTransitionStep if you are trying to test " +
225                         "this specific and" +
226                         "incorrect state."
227                 )
228             }
229         }
230         _transitions.emit(step)
231     }
232 
233     /** Version of [sendTransitionStep] that's usable from Java tests. */
234     fun sendTransitionStepJava(
235         coroutineScope: CoroutineScope,
236         step: TransitionStep,
237         validateStep: Boolean = true
238     ): Job {
239         return coroutineScope.launch {
240             sendTransitionStep(step = step, validateStep = validateStep)
241         }
242     }
243 
244     suspend fun sendTransitionSteps(
245         steps: List<TransitionStep>,
246         testScope: TestScope,
247         validateSteps: Boolean = true
248     ) {
249         steps.forEach {
250             sendTransitionStep(step = it, validateStep = validateSteps)
251             testScope.testScheduler.runCurrent()
252         }
253     }
254 
255     override suspend fun startTransition(info: TransitionInfo): UUID? {
256         _currentTransitionInfo.value = info
257         return if (info.animator == null) UUID.randomUUID() else null
258     }
259 
260     override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
261         tryEmitInitialStepsFromOff(to)
262     }
263 
264     private fun tryEmitInitialStepsFromOff(to: KeyguardState) {
265         _transitions.tryEmit(
266             TransitionStep(
267                 KeyguardState.OFF,
268                 to,
269                 0f,
270                 TransitionState.STARTED,
271                 ownerName = "KeyguardTransitionRepository(boot)",
272             )
273         )
274 
275         _transitions.tryEmit(
276             TransitionStep(
277                 KeyguardState.OFF,
278                 to,
279                 1f,
280                 TransitionState.FINISHED,
281                 ownerName = "KeyguardTransitionRepository(boot)",
282             ),
283         )
284     }
285 
286     override fun updateTransition(
287         transitionId: UUID,
288         @FloatRange(from = 0.0, to = 1.0) value: Float,
289         state: TransitionState
290     ) = Unit
291 }
292 
293 @Module
294 interface FakeKeyguardTransitionRepositoryModule {
bindFakenull295     @Binds fun bindFake(fake: FakeKeyguardTransitionRepository): KeyguardTransitionRepository
296 }
297