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.domain.interactor
19 
20 import android.annotation.FloatRange
21 import android.annotation.SuppressLint
22 import android.util.Log
23 import com.android.compose.animation.scene.SceneKey
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Application
26 import com.android.systemui.keyguard.data.repository.KeyguardRepository
27 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
28 import com.android.systemui.keyguard.shared.model.Edge
29 import com.android.systemui.keyguard.shared.model.KeyguardState
30 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
31 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
32 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
33 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
34 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
35 import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
36 import com.android.systemui.keyguard.shared.model.TransitionInfo
37 import com.android.systemui.keyguard.shared.model.TransitionState
38 import com.android.systemui.keyguard.shared.model.TransitionStep
39 import com.android.systemui.scene.domain.interactor.SceneInteractor
40 import com.android.systemui.scene.shared.flag.SceneContainerFlag
41 import com.android.systemui.scene.shared.model.Scenes
42 import com.android.systemui.util.kotlin.pairwise
43 import java.util.UUID
44 import javax.inject.Inject
45 import kotlinx.coroutines.CoroutineScope
46 import kotlinx.coroutines.ExperimentalCoroutinesApi
47 import kotlinx.coroutines.channels.BufferOverflow
48 import kotlinx.coroutines.flow.Flow
49 import kotlinx.coroutines.flow.MutableSharedFlow
50 import kotlinx.coroutines.flow.SharedFlow
51 import kotlinx.coroutines.flow.SharingStarted
52 import kotlinx.coroutines.flow.StateFlow
53 import kotlinx.coroutines.flow.distinctUntilChanged
54 import kotlinx.coroutines.flow.filter
55 import kotlinx.coroutines.flow.map
56 import kotlinx.coroutines.flow.mapLatest
57 import kotlinx.coroutines.flow.onStart
58 import kotlinx.coroutines.flow.shareIn
59 import kotlinx.coroutines.flow.stateIn
60 import kotlinx.coroutines.launch
61 
62 /** Encapsulates business-logic related to the keyguard transitions. */
63 @OptIn(ExperimentalCoroutinesApi::class)
64 @SysUISingleton
65 class KeyguardTransitionInteractor
66 @Inject
67 constructor(
68     @Application val scope: CoroutineScope,
69     private val keyguardRepository: KeyguardRepository,
70     private val repository: KeyguardTransitionRepository,
71     private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
72     private val fromPrimaryBouncerTransitionInteractor:
73         dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
74     private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
75     private val fromAlternateBouncerTransitionInteractor:
76         dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
77     private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
78     private val sceneInteractor: dagger.Lazy<SceneInteractor>,
79 ) {
80     private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
81 
82     /**
83      * Numerous flows are derived from, or care directly about, the transition value in and out of a
84      * single state. This prevent the redundant filters from running.
85      */
86     private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
87 
88     @SuppressLint("SharedFlowCreation")
89     private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
90         return transitionValueCache.getOrPut(state) {
91             MutableSharedFlow<Float>(
92                     replay = 1,
93                     extraBufferCapacity = 2,
94                     onBufferOverflow = BufferOverflow.DROP_OLDEST
95                 )
96                 .also { it.tryEmit(0f) }
97         }
98     }
99 
100     @Deprecated("Not performant - Use something else in this class")
101     val transitions = repository.transitions
102 
103     val transitionState: StateFlow<TransitionStep> =
104         transitions.stateIn(scope, SharingStarted.Eagerly, TransitionStep())
105 
106     /**
107      * A pair of the most recent STARTED step, and the transition step immediately preceding it. The
108      * transition framework enforces that the previous step is either a CANCELED or FINISHED step,
109      * and that the previous step was *to* the state the STARTED step is *from*.
110      *
111      * This flow can be used to access the previous step to determine whether it was CANCELED or
112      * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming
113      * from when we were canceled.
114      */
115     @SuppressLint("SharedFlowCreation")
116     val startedStepWithPrecedingStep =
117         repository.transitions
118             .pairwise()
119             .filter { it.newValue.transitionState == TransitionState.STARTED }
120             .shareIn(scope, SharingStarted.Eagerly)
121 
122     init {
123         // Collect non-canceled steps and emit transition values.
124         scope.launch {
125             repository.transitions
126                 .filter { it.transitionState != TransitionState.CANCELED }
127                 .collect { step ->
128                     getTransitionValueFlow(step.from).emit(1f - step.value)
129                     getTransitionValueFlow(step.to).emit(step.value)
130                 }
131         }
132 
133         scope.launch {
134             repository.transitions.collect {
135                 // FROM->TO
136                 transitionMap[Edge.create(it.from, it.to)]?.emit(it)
137                 // FROM->(ANY)
138                 transitionMap[Edge.create(it.from, null)]?.emit(it)
139                 // (ANY)->TO
140                 transitionMap[Edge.create(null, it.to)]?.emit(it)
141             }
142         }
143 
144         // If a transition from state A -> B is canceled in favor of a transition from B -> C, we
145         // need to ensure we emit transitionValue(A) = 0f, since no further steps will be emitted
146         // where the from or to states are A. This would leave transitionValue(A) stuck at an
147         // arbitrary non-zero value.
148         scope.launch {
149             startedStepWithPrecedingStep.collect { (prevStep, startedStep) ->
150                 if (
151                     prevStep.transitionState == TransitionState.CANCELED &&
152                         startedStep.to != prevStep.from
153                 ) {
154                     getTransitionValueFlow(prevStep.from).emit(0f)
155                 }
156             }
157         }
158     }
159 
160     fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> {
161         return transition(if (SceneContainerFlag.isEnabled) edge else edgeWithoutSceneContainer)
162     }
163 
164     /** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */
165     @SuppressLint("SharedFlowCreation")
166     fun transition(edge: Edge): Flow<TransitionStep> {
167         edge.verifyValidKeyguardStates()
168         val mappedEdge = getMappedEdge(edge)
169 
170         val flow: Flow<TransitionStep> =
171             transitionMap.getOrPut(mappedEdge) {
172                 MutableSharedFlow(
173                     extraBufferCapacity = 10,
174                     onBufferOverflow = BufferOverflow.DROP_OLDEST
175                 )
176             }
177 
178         return if (SceneContainerFlag.isEnabled) {
179             flow.filter {
180                 val fromScene =
181                     when (edge) {
182                         is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
183                         is Edge.StateToScene -> edge.from?.mapToSceneContainerScene()
184                         is Edge.SceneToState -> edge.from
185                     }
186 
187                 val toScene =
188                     when (edge) {
189                         is Edge.StateToState -> edge.to?.mapToSceneContainerScene()
190                         is Edge.StateToScene -> edge.to
191                         is Edge.SceneToState -> edge.to?.mapToSceneContainerScene()
192                     }
193 
194                 fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
195 
196                 return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) ||
197                     sceneInteractor.get().transitionState.value.isTransitioning(fromScene, toScene)
198             }
199         } else {
200             flow
201         }
202     }
203 
204     /**
205      * Converts old KTF states to UNDEFINED when [SceneContainerFlag] is enabled.
206      *
207      * Does nothing otherwise.
208      *
209      * This method should eventually be removed when new code is only written for scene container.
210      * Even when all edges are ported today, there is still development on going in production that
211      * might utilize old states.
212      */
213     private fun getMappedEdge(edge: Edge): Edge.StateToState {
214         if (!SceneContainerFlag.isEnabled) return edge as Edge.StateToState
215         return when (edge) {
216             is Edge.StateToState ->
217                 Edge.create(
218                     from = edge.from?.mapToSceneContainerState(),
219                     to = edge.to?.mapToSceneContainerState()
220                 )
221             is Edge.SceneToState -> Edge.create(UNDEFINED, edge.to)
222             is Edge.StateToScene -> Edge.create(edge.from, UNDEFINED)
223         }
224     }
225 
226     fun transitionValue(
227         scene: SceneKey,
228         stateWithoutSceneContainer: KeyguardState,
229     ): Flow<Float> {
230         return if (SceneContainerFlag.isEnabled) {
231             sceneInteractor.get().transitionProgress(scene)
232         } else {
233             transitionValue(stateWithoutSceneContainer)
234         }
235     }
236 
237     /**
238      * The amount of transition into or out of the given [KeyguardState].
239      *
240      * The value will be `0` (or close to `0`, due to float point arithmetic) if not in this step or
241      * `1` when fully in the given state.
242      */
243     fun transitionValue(
244         state: KeyguardState,
245     ): Flow<Float> {
246         if (SceneContainerFlag.isEnabled && state != state.mapToSceneContainerState()) {
247             Log.e(TAG, "SceneContainer is enabled but a deprecated state $state is used.")
248             return transitionValue(state.mapToSceneContainerScene()!!, state)
249         }
250         return getTransitionValueFlow(state)
251     }
252 
253     /** The last [TransitionStep] with a [TransitionState] of STARTED */
254     val startedKeyguardTransitionStep: Flow<TransitionStep> =
255         repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
256 
257     /** The last [TransitionStep] with a [TransitionState] of FINISHED */
258     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
259         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
260 
261     /** The destination state of the last [TransitionState.STARTED] transition. */
262     @SuppressLint("SharedFlowCreation")
263     val startedKeyguardState: SharedFlow<KeyguardState> =
264         startedKeyguardTransitionStep
265             .map { step -> step.to }
266             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
267 
268     /** The from state of the last [TransitionState.STARTED] transition. */
269     // TODO: is it performant to have several SharedFlows side by side instead of one?
270     @SuppressLint("SharedFlowCreation")
271     val startedKeyguardFromState: SharedFlow<KeyguardState> =
272         startedKeyguardTransitionStep
273             .map { step -> step.from }
274             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
275 
276     /** Which keyguard state to use when the device goes to sleep. */
277     val asleepKeyguardState: StateFlow<KeyguardState> =
278         keyguardRepository.isAodAvailable
279             .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
280             .stateIn(scope, SharingStarted.Eagerly, DOZING)
281 
282     /**
283      * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
284      *
285      * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a
286      * value when a subsequent transition is STARTED. It will *only* emit once we have finally
287      * FINISHED in a state. This can have unintuitive implications.
288      *
289      * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in
290      * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain
291      * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition
292      * finishes (at which point we'll be FINISHED in LOCKSCREEN).
293      *
294      * Since there's no real limit to how many consecutive transitions can be canceled, it's even
295      * possible for the FINISHED state to be the same as the STARTED state while still
296      * transitioning.
297      *
298      * For example:
299      * 1. We're finished in GONE.
300      * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still
301      *    FINISHED in GONE.
302      * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING ->
303      *    LOCKSCREEN transition. We're still FINISHED in GONE.
304      * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this
305      *    starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also
306      *    STARTED a transition *to* GONE.
307      * 5. We'll emit KeyguardState.GONE again once the transition finishes.
308      *
309      * If you just need to know when we eventually settle into a state, this flow is likely
310      * sufficient. However, if you're having issues with state *during* transitions started after
311      * one or more canceled transitions, you probably need to use [currentKeyguardState].
312      */
313     @SuppressLint("SharedFlowCreation")
314     val finishedKeyguardState: SharedFlow<KeyguardState> =
315         finishedKeyguardTransitionStep
316             .map { step -> step.to }
317             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
318 
319     /**
320      * The [KeyguardState] we're currently in.
321      *
322      * If we're not in transition, this is simply the [finishedKeyguardState]. If we're in
323      * transition, this is the state we're transitioning *from*.
324      *
325      * Absent CANCELED transitions, [currentKeyguardState] and [finishedKeyguardState] are always
326      * identical - if a transition FINISHES in a given state, the subsequent state we START a
327      * transition *from* would always be that same previously FINISHED state.
328      *
329      * However, if a transition is CANCELED, the next transition will START from a state we never
330      * FINISHED in. For example, if we transition from GONE -> DOZING, but CANCEL that transition in
331      * favor of DOZING -> LOCKSCREEN, we've STARTED a transition *from* DOZING despite never
332      * FINISHING in DOZING. Thus, the current state will be DOZING but the FINISHED state will still
333      * be GONE.
334      *
335      * In this example, if there was DOZING-related state that needs to be set up in order to
336      * properly render a DOZING -> LOCKSCREEN transition, it would never be set up if we were
337      * listening for [finishedKeyguardState] to emit DOZING. However, [currentKeyguardState] would
338      * emit DOZING immediately upon STARTING DOZING -> LOCKSCREEN, allowing us to set up the state.
339      *
340      * Whether you want to use [currentKeyguardState] or [finishedKeyguardState] depends on your
341      * specific use case and how you want to handle cancellations. In general, if you're dealing
342      * with state/UI present across multiple [KeyguardState]s, you probably want
343      * [currentKeyguardState]. If you're dealing with state/UI encapsulated within a single state,
344      * you likely want [finishedKeyguardState].
345      *
346      * As an example, let's say you want to animate in a message on the lockscreen UI after waking
347      * up, and that TextView is not involved in animations between states. You'd want to collect
348      * [finishedKeyguardState], so you'll only animate it in once we're settled on the lockscreen.
349      * If you use [currentKeyguardState] in this case, a DOZING -> LOCKSCREEN transition that is
350      * interrupted by a LOCKSCREEN -> GONE transition would cause the message to become visible
351      * immediately upon LOCKSCREEN -> GONE STARTING, as the current state would become LOCKSCREEN in
352      * that case. That's likely not what you want.
353      *
354      * On the other hand, let's say you're animating the smartspace from alpha 0f to 1f during
355      * DOZING -> LOCKSCREEN, but the transition is interrupted by LOCKSCREEN -> GONE. LS -> GONE
356      * needs the smartspace to be alpha=1f so that it can play the shared-element unlock animation.
357      * In this case, we'd want to collect [currentKeyguardState] and ensure the smartspace is
358      * visible when the current state is LOCKSCREEN. If you use [finishedKeyguardState] in this
359      * case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace
360      * during the LS -> GONE transition.
361      *
362      * As a helpful footnote, here's the values of [finishedKeyguardState] and
363      * [currentKeyguardState] during a sequence with two cancellations:
364      * 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE.
365      * 2. We START a transition from GONE -> DOZING. currentKeyguardState=GONE;
366      *    finishedKeyguardState=GONE.
367      * 3. We CANCEL this transition and START a transition from DOZING -> LOCKSCREEN.
368      *    currentKeyguardState=DOZING; finishedKeyguardState=GONE.
369      * 4. We subsequently also CANCEL DOZING -> LOCKSCREEN and START LOCKSCREEN -> GONE.
370      *    currentKeyguardState=LOCKSCREEN finishedKeyguardState=GONE.
371      * 5. LOCKSCREEN -> GONE is allowed to FINISH. currentKeyguardState=GONE;
372      *    finishedKeyguardState=GONE.
373      */
374     val currentKeyguardState: SharedFlow<KeyguardState> =
375         repository.transitions
376             .mapLatest {
377                 if (it.transitionState == TransitionState.FINISHED) {
378                     it.to
379                 } else {
380                     it.from
381                 }
382             }
383             .distinctUntilChanged()
384             .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
385 
386     /**
387      * The [TransitionInfo] of the most recent call to
388      * [KeyguardTransitionRepository.startTransition].
389      *
390      * This should only be used by keyguard transition internals (From*TransitionInteractor and
391      * related classes). Other consumers of keyguard state in System UI should use
392      * [startedKeyguardState], [currentKeyguardState], and related flows.
393      *
394      * Keyguard internals use this to determine the most up-to-date KeyguardState that we've
395      * requested a transition to, even if the animator running the transition on the main thread has
396      * not yet emitted the STARTED TransitionStep.
397      *
398      * For example: if we're finished in GONE and press the power button twice very quickly, we may
399      * request a transition to AOD, but then receive the second power button press prior to the
400      * STARTED -> AOD transition step emitting. We still need the FromAodTransitionInteractor to
401      * request a transition from AOD -> LOCKSCREEN in response to the power press, even though the
402      * main thread animator hasn't emitted STARTED > AOD yet (which means [startedKeyguardState] is
403      * still GONE, which is not relevant to FromAodTransitionInteractor). In this case, the
404      * interactor can use this current transition info to determine that a STARTED -> AOD step
405      * *will* be emitted, and therefore that it can safely request an AOD -> LOCKSCREEN transition
406      * which will subsequently cancel GONE -> AOD.
407      */
408     internal val currentTransitionInfoInternal: StateFlow<TransitionInfo> =
409         repository.currentTransitionInfoInternal
410 
411     /** Whether we've currently STARTED a transition and haven't yet FINISHED it. */
412     val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
413 
414     /**
415      * Called to start a transition that will ultimately dismiss the keyguard from the current
416      * state.
417      *
418      * This is called exclusively by sources that can authoritatively say we should be unlocked,
419      * including KeyguardSecurityContainerController and WindowManager.
420      */
421     fun startDismissKeyguardTransition(reason: String = "") {
422         // TODO(b/336576536): Check if adaptation for scene framework is needed
423         if (SceneContainerFlag.isEnabled) return
424         Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
425         when (val startedState = currentTransitionInfoInternal.value.to) {
426             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
427             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
428             ALTERNATE_BOUNCER ->
429                 fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
430             AOD -> fromAodTransitionInteractor.get().dismissAod()
431             DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
432             KeyguardState.GONE ->
433                 Log.i(
434                     TAG,
435                     "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
436                 )
437             else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
438         }
439     }
440 
441     /**
442      * Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet
443      * completed it.
444      *
445      * Provide [edgeWithoutSceneContainer] when the edge is different from what it is without it. If
446      * the edges are equal before and after the flag it is sufficient to provide just [edge].
447      */
448     fun isInTransition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<Boolean> {
449         return if (SceneContainerFlag.isEnabled) {
450                 if (edge.isSceneWildcardEdge()) {
451                     sceneInteractor.get().transitionState.map {
452                         when (edge) {
453                             is Edge.StateToState ->
454                                 throw IllegalStateException("Should not be reachable.")
455                             is Edge.SceneToState -> it.isTransitioning(from = edge.from)
456                             is Edge.StateToScene -> it.isTransitioning(to = edge.to)
457                         }
458                     }
459                 } else {
460                     transition(edge).mapLatest { it.transitionState.isTransitioning() }
461                 }
462             } else {
463                 transition(edgeWithoutSceneContainer ?: edge).mapLatest {
464                     it.transitionState.isTransitioning()
465                 }
466             }
467             .onStart { emit(false) }
468             .distinctUntilChanged()
469     }
470 
471     /**
472      * Whether we're in a transition to a [KeyguardState] that matches the given predicate, but
473      * haven't yet completed it.
474      *
475      * If you only care about a single state, instead use the optimized [isInTransition].
476      */
477     fun isInTransitionToStateWhere(
478         stateMatcher: (KeyguardState) -> Boolean,
479     ): Flow<Boolean> {
480         return isInTransitionWhere(fromStatePredicate = { true }, toStatePredicate = stateMatcher)
481     }
482 
483     /**
484      * Whether we're in a transition out of a [KeyguardState] that matches the given predicate, but
485      * haven't yet completed it.
486      *
487      * If you only care about a single state, instead use the optimized [isInTransition].
488      */
489     fun isInTransitionFromStateWhere(
490         stateMatcher: (KeyguardState) -> Boolean,
491     ): Flow<Boolean> {
492         return isInTransitionWhere(fromStatePredicate = stateMatcher, toStatePredicate = { true })
493     }
494 
495     /**
496      * Whether we're in a transition between two [KeyguardState]s that match the given predicates,
497      * but haven't yet completed it.
498      *
499      * If you only care about a single state for both from and to, instead use the optimized
500      * [isInTransition].
501      */
502     fun isInTransitionWhere(
503         fromStatePredicate: (KeyguardState) -> Boolean,
504         toStatePredicate: (KeyguardState) -> Boolean,
505     ): Flow<Boolean> {
506         return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) }
507     }
508 
509     /**
510      * Whether we're in a transition between two [KeyguardState]s that match the given predicates,
511      * but haven't yet completed it.
512      *
513      * If you only care about a single state for both from and to, instead use the optimized
514      * [isInTransition].
515      */
516     private fun isInTransitionWhere(
517         fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
518     ): Flow<Boolean> {
519         return repository.transitions
520             .filter { it.transitionState != TransitionState.CANCELED }
521             .mapLatest {
522                 it.transitionState != TransitionState.FINISHED &&
523                     fromToStatePredicate(it.from, it.to)
524             }
525             .distinctUntilChanged()
526     }
527 
528     /** Whether we've FINISHED a transition to a state that matches the given predicate. */
529     fun isFinishedInStateWhere(stateMatcher: (KeyguardState) -> Boolean): Flow<Boolean> {
530         return finishedKeyguardState.map { stateMatcher(it) }.distinctUntilChanged()
531     }
532 
533     /** Whether we've FINISHED a transition to a state that matches the given predicate. */
534     fun isFinishedInState(state: KeyguardState): Flow<Boolean> {
535         return finishedKeyguardState.map { it == state }.distinctUntilChanged()
536     }
537 
538     /**
539      * Whether we've FINISHED a transition to a state that matches the given predicate. Consider
540      * using [isFinishedInStateWhere] whenever possible instead
541      */
542     fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
543         stateMatcher(finishedKeyguardState.replayCache.last())
544 
545     fun getCurrentState(): KeyguardState {
546         return currentKeyguardState.replayCache.last()
547     }
548 
549     fun getStartedFromState(): KeyguardState {
550         return startedKeyguardFromState.replayCache.last()
551     }
552 
553     fun getFinishedState(): KeyguardState {
554         return finishedKeyguardState.replayCache.last()
555     }
556 
557     suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
558 
559     fun updateTransition(
560         transitionId: UUID,
561         @FloatRange(from = 0.0, to = 1.0) value: Float,
562         state: TransitionState
563     ) = repository.updateTransition(transitionId, value, state)
564 
565     companion object {
566         private val TAG = KeyguardTransitionInteractor::class.simpleName
567     }
568 }
569