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