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.SuppressLint 21 import android.util.Log 22 import com.android.app.tracing.coroutines.flow.filterTraced 23 import com.android.app.tracing.coroutines.flow.shareInTraced 24 import com.android.app.tracing.coroutines.flow.stateInTraced 25 import com.android.app.tracing.coroutines.flow.traceAs 26 import com.android.app.tracing.coroutines.launchTraced as launch 27 import com.android.app.tracing.coroutines.traceCoroutine 28 import com.android.compose.animation.scene.ContentKey 29 import com.android.compose.animation.scene.ObservableTransitionState 30 import com.android.compose.animation.scene.SceneKey 31 import com.android.systemui.Flags.keyguardTransitionForceFinishOnScreenOff 32 import com.android.systemui.dagger.SysUISingleton 33 import com.android.systemui.dagger.qualifiers.Application 34 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 35 import com.android.systemui.keyguard.shared.model.Edge 36 import com.android.systemui.keyguard.shared.model.KeyguardState 37 import com.android.systemui.keyguard.shared.model.KeyguardState.OFF 38 import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED 39 import com.android.systemui.keyguard.shared.model.TransitionState 40 import com.android.systemui.keyguard.shared.model.TransitionStep 41 import com.android.systemui.power.domain.interactor.PowerInteractor 42 import com.android.systemui.power.shared.model.ScreenPowerState 43 import com.android.systemui.scene.domain.interactor.SceneInteractor 44 import com.android.systemui.scene.shared.flag.SceneContainerFlag 45 import com.android.systemui.scene.shared.model.Scenes 46 import com.android.systemui.util.kotlin.WithPrev 47 import com.android.systemui.util.kotlin.pairwise 48 import javax.inject.Inject 49 import kotlinx.coroutines.CoroutineScope 50 import kotlinx.coroutines.Job 51 import kotlinx.coroutines.cancelAndJoin 52 import kotlinx.coroutines.channels.BufferOverflow 53 import kotlinx.coroutines.coroutineScope 54 import kotlinx.coroutines.flow.Flow 55 import kotlinx.coroutines.flow.MutableSharedFlow 56 import kotlinx.coroutines.flow.SharedFlow 57 import kotlinx.coroutines.flow.SharingStarted 58 import kotlinx.coroutines.flow.StateFlow 59 import kotlinx.coroutines.flow.channelFlow 60 import kotlinx.coroutines.flow.combine 61 import kotlinx.coroutines.flow.distinctUntilChanged 62 import kotlinx.coroutines.flow.emitAll 63 import kotlinx.coroutines.flow.filter 64 import kotlinx.coroutines.flow.flow 65 import kotlinx.coroutines.flow.flowOf 66 import kotlinx.coroutines.flow.map 67 import kotlinx.coroutines.flow.mapLatest 68 import kotlinx.coroutines.flow.onStart 69 70 /** Encapsulates business-logic related to the keyguard transitions. */ 71 @SysUISingleton 72 class KeyguardTransitionInteractor 73 @Inject 74 constructor( 75 @Application val scope: CoroutineScope, 76 private val repository: KeyguardTransitionRepository, 77 private val sceneInteractor: SceneInteractor, 78 private val powerInteractor: PowerInteractor, 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 .traceAs("KTF-${state.name}") 98 } 99 } 100 101 @Deprecated("Not performant - Use something else in this class") 102 val transitions = repository.transitions 103 104 val transitionState: StateFlow<TransitionStep> = 105 transitions.stateInTraced( 106 "KTF-transitionState", 107 scope, 108 SharingStarted.Eagerly, 109 TransitionStep(), 110 ) 111 112 private val sceneTransitionPair = 113 sceneInteractor.transitionState 114 .pairwise() 115 .stateInTraced( 116 "KTF-sceneTransitionPair", 117 scope, 118 SharingStarted.Eagerly, 119 WithPrev( 120 sceneInteractor.transitionState.value, 121 sceneInteractor.transitionState.value, 122 ), 123 ) 124 125 /** 126 * A pair of the most recent STARTED step, and the transition step immediately preceding it. The 127 * transition framework enforces that the previous step is either a CANCELED or FINISHED step, 128 * and that the previous step was *to* the state the STARTED step is *from*. 129 * 130 * This flow can be used to access the previous step to determine whether it was CANCELED or 131 * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming 132 * from when we were canceled. 133 */ 134 @SuppressLint("SharedFlowCreation") 135 val startedStepWithPrecedingStep = 136 repository.transitions 137 .pairwise() 138 .filter { it.newValue.transitionState == TransitionState.STARTED } 139 .shareInTraced( 140 "KTF-startedStepWithPrecedingStep", 141 scope, 142 SharingStarted.Eagerly, 143 replay = 1, 144 ) 145 146 init { 147 // Collect non-canceled steps and emit transition values. 148 scope.launch("KTF-update-non-canceled") { 149 repository.transitions 150 .filter { it.transitionState != TransitionState.CANCELED } 151 .collect { step -> 152 val value = 153 if (step.transitionState == TransitionState.FINISHED) 1f else step.value 154 getTransitionValueFlow(step.from).emit(1f - value) 155 getTransitionValueFlow(step.to).emit(value) 156 } 157 } 158 159 scope.launch("KTF-update-transitionMap") { 160 repository.transitions.collect { 161 // FROM->TO 162 transitionMap[Edge.create(it.from, it.to)]?.emit(it) 163 // FROM->(ANY) 164 transitionMap[Edge.create(it.from, null)]?.emit(it) 165 // (ANY)->TO 166 transitionMap[Edge.create(null, it.to)]?.emit(it) 167 } 168 } 169 170 // If a transition from state A -> B is canceled in favor of a transition from B -> C, we 171 // need to ensure we emit transitionValue(A) = 0f, since no further steps will be emitted 172 // where the from or to states are A. This would leave transitionValue(A) stuck at an 173 // arbitrary non-zero value. 174 scope.launch("KTF-update-canceled") { 175 startedStepWithPrecedingStep.collect { (prevStep, startedStep) -> 176 if ( 177 prevStep.transitionState == TransitionState.CANCELED && 178 startedStep.to != prevStep.from 179 ) { 180 getTransitionValueFlow(prevStep.from).emit(0f) 181 } else if (prevStep.transitionState == TransitionState.RUNNING) { 182 Log.e( 183 TAG, 184 "STARTED step ($startedStep) was preceded by a RUNNING step " + 185 "($prevStep), which should never happen. Things could go badly here.", 186 ) 187 } 188 } 189 } 190 191 // Safety: When any transition is FINISHED, ensure all other transitionValue flows other 192 // than the FINISHED state are reset to a value of 0f. There have been rare but severe 193 // bugs that get the device stuck in a bad state when these are not properly reset. 194 scope.launch("KTF-update-finished") { 195 repository.transitions 196 .filter { it.transitionState == TransitionState.FINISHED } 197 .collect { 198 for (state in KeyguardState.entries) { 199 if (state != it.to) { 200 val flow = getTransitionValueFlow(state) 201 val replayCache = flow.replayCache 202 if (!replayCache.isEmpty() && replayCache.last() != 0f) { 203 flow.emit(0f) 204 } 205 } 206 } 207 } 208 } 209 210 if (keyguardTransitionForceFinishOnScreenOff()) { 211 /** 212 * If the screen is turning off, finish the current transition immediately. Further 213 * frames won't be visible anyway. 214 */ 215 scope.launch("KTF-force-finish") { 216 powerInteractor.screenPowerState 217 .filter { it == ScreenPowerState.SCREEN_TURNING_OFF } 218 .collect { repository.forceFinishCurrentTransition() } 219 } 220 } 221 } 222 223 fun transition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<TransitionStep> { 224 return transition( 225 if (SceneContainerFlag.isEnabled || edgeWithoutSceneContainer == null) { 226 edge 227 } else { 228 edgeWithoutSceneContainer 229 } 230 ) 231 } 232 233 /** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */ 234 @SuppressLint("SharedFlowCreation") 235 fun transition(edge: Edge): Flow<TransitionStep> { 236 edge.verifyValidKeyguardStates() 237 val mappedEdge = getMappedEdge(edge) 238 239 val flow: Flow<TransitionStep> = 240 transitionMap.getOrPut(mappedEdge) { 241 MutableSharedFlow<TransitionStep>( 242 extraBufferCapacity = 10, 243 onBufferOverflow = BufferOverflow.DROP_OLDEST, 244 ) 245 .traceAs("KTF-${mappedEdge.from}-to-${mappedEdge.to}") 246 } 247 248 if (!SceneContainerFlag.isEnabled) { 249 return flow 250 } 251 if (edge.isContentWildcardEdge()) { 252 return simulateTransitionStepsForSceneTransitions(edge) 253 } 254 return flow.filterTraced("stl-filter") { step -> 255 val fromContent = 256 when (edge) { 257 is Edge.StateToState -> edge.from?.mapToSceneContainerContent() 258 is Edge.StateToContent -> edge.from?.mapToSceneContainerContent() 259 is Edge.ContentToState -> edge.from 260 } 261 262 val toContent = 263 when (edge) { 264 is Edge.StateToState -> edge.to?.mapToSceneContainerContent() 265 is Edge.StateToContent -> edge.to 266 is Edge.ContentToState -> edge.to?.mapToSceneContainerContent() 267 } 268 269 val isTransitioningBetweenLockscreenStates = 270 fromContent.isLockscreenOrNull() && toContent.isLockscreenOrNull() 271 val isTransitioningBetweenDesiredScenes = 272 sceneInteractor.transitionState.value.isTransitioning(fromContent, toContent) 273 274 // When in STL A -> B settles in A we can't do the same in KTF as KTF requires us to 275 // start B -> A to get back to A. [LockscreenSceneTransitionInteractor] will emit these 276 // steps but because STL is Idle(A) at this point (and never even started a B -> A in 277 // the first place) [isTransitioningBetweenDesiredScenes] will not be satisfied. We need 278 // this condition to not filter out the STARTED and FINISHED step of the "artificially" 279 // reversed B -> A transition. 280 val belongsToInstantReversedTransition = 281 sceneInteractor.topmostContent.value == toContent && 282 sceneTransitionPair.value.previousValue.isTransitioning(toContent, fromContent) 283 284 // We can't compare the terminal step with the current sceneTransition because 285 // a) STL has no guarantee that it will settle in Idle() when finished/canceled 286 // b) Comparing to Idle(toScene) would make any other FINISHED step settling in 287 // toScene pass as well 288 val terminalStepBelongsToPreviousTransition = 289 (step.transitionState == TransitionState.FINISHED || 290 step.transitionState == TransitionState.CANCELED) && 291 sceneTransitionPair.value.previousValue.isTransitioning(fromContent, toContent) 292 293 return@filterTraced isTransitioningBetweenLockscreenStates || 294 isTransitioningBetweenDesiredScenes || 295 terminalStepBelongsToPreviousTransition || 296 belongsToInstantReversedTransition 297 } 298 } 299 300 private fun ContentKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null 301 302 /** 303 * This function will return a flow that simulates TransitionSteps based on STL movements 304 * filtered by [edge]. 305 * 306 * STL transitions outside of Lockscreen Transitions are not tracked in KTI. This is an issue 307 * for wildcard edges, as this means that Scenes.Bouncer -> Scenes.Gone would not appear while 308 * AOD -> Scenes.Bouncer would appear. 309 * 310 * This function will track STL transitions only when a wildcard edge is provided and emit a 311 * RUNNING step for each update to [Transition.progress]. It will also emit a STARTED and 312 * FINISHED step when the transitions starts and finishes. 313 * 314 * All TransitionSteps will have UNDEFINED as to and from state even when one of them is the 315 * Lockscreen Scene. It indicates that both are scenes but it should not be relevant to 316 * consumers of the [transition] API as usually all viewModels are just interested in the 317 * progress value. The correct filtering based on the provided [edge] is always the 318 * responsibility of KTI and therefore only proper [TransitionStep]s are emitted. The filter is 319 * applied within this function. 320 */ 321 private fun simulateTransitionStepsForSceneTransitions(edge: Edge) = 322 sceneInteractor.transitionState.flatMapLatestWithFinished { 323 when (it) { 324 is ObservableTransitionState.Idle -> { 325 flowOf() 326 } 327 is ObservableTransitionState.Transition -> { 328 val isMatchingTransition = 329 when (edge) { 330 is Edge.StateToState -> 331 throw IllegalStateException("Should not be reachable.") 332 is Edge.ContentToState -> it.isTransitioning(from = edge.from) 333 is Edge.StateToContent -> it.isTransitioning(to = edge.to) 334 } 335 if (!isMatchingTransition) { 336 return@flatMapLatestWithFinished flowOf() 337 } 338 flow { 339 emit( 340 TransitionStep( 341 from = UNDEFINED, 342 to = UNDEFINED, 343 value = 0f, 344 transitionState = TransitionState.STARTED, 345 ) 346 ) 347 emitAll( 348 it.progress.map { progress -> 349 TransitionStep( 350 from = UNDEFINED, 351 to = UNDEFINED, 352 value = progress, 353 transitionState = TransitionState.RUNNING, 354 ) 355 } 356 ) 357 } 358 } 359 } 360 } 361 .traceAs("KTF-transition-simulator") 362 363 /** 364 * This function is similar to flatMapLatest but it will additionally emit a FINISHED 365 * TransitionStep whenever the flattened innerFlow emitted a STARTED step and is now being 366 * replaced by a new innerFlow. 367 * 368 * This is to make sure that every STARTED step will receive a corresponding FINISHED step. 369 * 370 * We can't simply write this into a flow {} block because Transition.progress doesn't complete. 371 * We also can't emit the FINISHED step simply when an Idle state is reached because a) 372 * Transitions are not guaranteed to finish in Idle and b) There can be multiple Idle 373 * transitions after another 374 */ 375 private fun <T> Flow<T>.flatMapLatestWithFinished( 376 transform: suspend (T) -> Flow<TransitionStep> 377 ): Flow<TransitionStep> = 378 channelFlow { 379 var job: Job? = null 380 var startedEmitted = false 381 382 coroutineScope { 383 collect { value -> 384 traceCoroutine("cancelAndJoin") { job?.cancelAndJoin() } 385 386 job = 387 launch("KTF-flatMapLatestWithFinished") { 388 val innerFlow = transform(value) 389 try { 390 innerFlow.collect { step -> 391 if (step.transitionState == TransitionState.STARTED) { 392 startedEmitted = true 393 } 394 traceCoroutine("send($step)") { send(step) } 395 } 396 } finally { 397 if (startedEmitted) { 398 val step = 399 TransitionStep( 400 from = UNDEFINED, 401 to = UNDEFINED, 402 value = 1f, 403 transitionState = TransitionState.FINISHED, 404 ) 405 traceCoroutine("send($step)") { send(step) } 406 startedEmitted = false 407 } 408 } 409 } 410 } 411 } 412 } 413 414 /** 415 * Converts old KTF states to UNDEFINED when [SceneContainerFlag] is enabled. 416 * 417 * Does nothing otherwise. 418 * 419 * This method should eventually be removed when new code is only written for scene container. 420 * Even when all edges are ported today, there is still development on going in production that 421 * might utilize old states. 422 */ 423 private fun getMappedEdge(edge: Edge): Edge.StateToState { 424 if (!SceneContainerFlag.isEnabled) return edge as Edge.StateToState 425 return when (edge) { 426 is Edge.StateToState -> 427 Edge.create( 428 from = edge.from?.mapToSceneContainerState(), 429 to = edge.to?.mapToSceneContainerState(), 430 ) 431 is Edge.ContentToState -> Edge.create(UNDEFINED, edge.to) 432 is Edge.StateToContent -> Edge.create(edge.from, UNDEFINED) 433 } 434 } 435 436 fun transitionValue( 437 content: ContentKey? = null, 438 stateWithoutSceneContainer: KeyguardState, 439 ): Flow<Float> { 440 return if (SceneContainerFlag.isEnabled && content != null) { 441 sceneInteractor.transitionProgress(content) 442 } else { 443 transitionValue(stateWithoutSceneContainer) 444 } 445 } 446 447 /** 448 * The amount of transition into or out of the given [KeyguardState]. 449 * 450 * The value will be `0` (or close to `0`, due to float point arithmetic) if not in this step or 451 * `1` when fully in the given state. 452 */ 453 fun transitionValue(state: KeyguardState): Flow<Float> { 454 if (SceneContainerFlag.isEnabled && state != state.mapToSceneContainerState()) { 455 Log.e(TAG, "SceneContainer is enabled but a deprecated state $state is used.") 456 return transitionValue(state.mapToSceneContainerContent()!!, state) 457 } 458 return getTransitionValueFlow(state) 459 } 460 461 /** The last [TransitionStep] with a [TransitionState] of STARTED */ 462 val startedKeyguardTransitionStep: StateFlow<TransitionStep> = 463 repository.transitions 464 .filter { step -> step.transitionState == TransitionState.STARTED } 465 .stateInTraced( 466 "KTF-startedKeyguardTransitionStep", 467 scope, 468 SharingStarted.Eagerly, 469 TransitionStep(), 470 ) 471 472 /** 473 * The [KeyguardState] we're currently in. 474 * 475 * If we're not in transition, this is simply the [finishedKeyguardState]. If we're in 476 * transition, this is the state we're transitioning *from*. 477 * 478 * Absent CANCELED transitions, [currentKeyguardState] and [finishedKeyguardState] are always 479 * identical - if a transition FINISHES in a given state, the subsequent state we START a 480 * transition *from* would always be that same previously FINISHED state. 481 * 482 * However, if a transition is CANCELED, the next transition will START from a state we never 483 * FINISHED in. For example, if we transition from GONE -> DOZING, but CANCEL that transition in 484 * favor of DOZING -> LOCKSCREEN, we've STARTED a transition *from* DOZING despite never 485 * FINISHING in DOZING. Thus, the current state will be DOZING but the FINISHED state will still 486 * be GONE. 487 * 488 * In this example, if there was DOZING-related state that needs to be set up in order to 489 * properly render a DOZING -> LOCKSCREEN transition, it would never be set up if we were 490 * listening for [finishedKeyguardState] to emit DOZING. However, [currentKeyguardState] would 491 * emit DOZING immediately upon STARTING DOZING -> LOCKSCREEN, allowing us to set up the state. 492 * 493 * Whether you want to use [currentKeyguardState] or [finishedKeyguardState] depends on your 494 * specific use case and how you want to handle cancellations. In general, if you're dealing 495 * with state/UI present across multiple [KeyguardState]s, you probably want 496 * [currentKeyguardState]. If you're dealing with state/UI encapsulated within a single state, 497 * you likely want [finishedKeyguardState]. 498 * 499 * As an example, let's say you want to animate in a message on the lockscreen UI after waking 500 * up, and that TextView is not involved in animations between states. You'd want to collect 501 * [finishedKeyguardState], so you'll only animate it in once we're settled on the lockscreen. 502 * If you use [currentKeyguardState] in this case, a DOZING -> LOCKSCREEN transition that is 503 * interrupted by a LOCKSCREEN -> GONE transition would cause the message to become visible 504 * immediately upon LOCKSCREEN -> GONE STARTING, as the current state would become LOCKSCREEN in 505 * that case. That's likely not what you want. 506 * 507 * On the other hand, let's say you're animating the smartspace from alpha 0f to 1f during 508 * DOZING -> LOCKSCREEN, but the transition is interrupted by LOCKSCREEN -> GONE. LS -> GONE 509 * needs the smartspace to be alpha=1f so that it can play the shared-element unlock animation. 510 * In this case, we'd want to collect [currentKeyguardState] and ensure the smartspace is 511 * visible when the current state is LOCKSCREEN. If you use [finishedKeyguardState] in this 512 * case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace 513 * during the LS -> GONE transition. 514 * 515 * As a helpful footnote, here's the values of [finishedKeyguardState] and 516 * [currentKeyguardState] during a sequence with two cancellations: 517 * 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE. 518 * 2. We START a transition from GONE -> DOZING. currentKeyguardState=GONE; 519 * finishedKeyguardState=GONE. 520 * 3. We CANCEL this transition and START a transition from DOZING -> LOCKSCREEN. 521 * currentKeyguardState=DOZING; finishedKeyguardState=GONE. 522 * 4. We subsequently also CANCEL DOZING -> LOCKSCREEN and START LOCKSCREEN -> GONE. 523 * currentKeyguardState=LOCKSCREEN finishedKeyguardState=GONE. 524 * 5. LOCKSCREEN -> GONE is allowed to FINISH. currentKeyguardState=GONE; 525 * finishedKeyguardState=GONE. 526 */ 527 val currentKeyguardState: SharedFlow<KeyguardState> = 528 repository.transitions 529 .mapLatest { 530 if (it.transitionState == TransitionState.FINISHED) { 531 it.to 532 } else { 533 it.from 534 } 535 } 536 .stateInTraced("KTF-currentKeyguardState", scope, SharingStarted.Eagerly, OFF) 537 538 val isInTransition = 539 combine(isInTransitionWhere({ true }, { true }), sceneInteractor.transitionState) { 540 isKeyguardTransitioning, 541 sceneTransitionState -> 542 isKeyguardTransitioning || 543 (SceneContainerFlag.isEnabled && sceneTransitionState.isTransitioning()) 544 } 545 546 /** 547 * Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet 548 * completed it. 549 * 550 * Provide [edgeWithoutSceneContainer] when the edge is different from what it is without it. If 551 * the edges are equal before and after the flag it is sufficient to provide just [edge]. 552 */ 553 fun isInTransition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<Boolean> { 554 return if (SceneContainerFlag.isEnabled) { 555 if (edge.isContentWildcardEdge()) { 556 sceneInteractor.transitionState.map { 557 when (edge) { 558 is Edge.StateToState -> 559 throw IllegalStateException("Should not be reachable.") 560 is Edge.ContentToState -> it.isTransitioning(from = edge.from) 561 is Edge.StateToContent -> it.isTransitioning(to = edge.to) 562 } 563 } 564 } else { 565 transition(edge).mapLatest { it.transitionState.isTransitioning() } 566 } 567 } else { 568 transition(edgeWithoutSceneContainer ?: edge).mapLatest { 569 it.transitionState.isTransitioning() 570 } 571 } 572 .onStart { emit(false) } 573 .traceAs("isInTransition-$edge-$edgeWithoutSceneContainer") 574 .distinctUntilChanged() 575 } 576 577 /** 578 * Whether we're in a transition between two [KeyguardState]s that match the given predicates, 579 * but haven't yet completed it. 580 * 581 * If you only care about a single state for both from and to, instead use the optimized 582 * [isInTransition]. 583 */ 584 fun isInTransitionWhere( 585 fromStatePredicate: (KeyguardState) -> Boolean = { true }, 586 toStatePredicate: (KeyguardState) -> Boolean = { true }, 587 ): Flow<Boolean> { 588 return repository.transitions 589 .filter { it.transitionState != TransitionState.CANCELED } 590 .mapLatest { 591 it.transitionState != TransitionState.FINISHED && 592 fromStatePredicate(it.from) && 593 toStatePredicate(it.to) 594 } 595 .distinctUntilChanged() 596 } 597 598 /** Whether we've FINISHED a transition to a state that matches the given predicate. */ 599 fun isFinishedInStateWhere(stateMatcher: (KeyguardState) -> Boolean): Flow<Boolean> { 600 return finishedKeyguardState.map { stateMatcher(it) }.distinctUntilChanged() 601 } 602 603 fun isFinishedIn( 604 content: ContentKey, 605 stateWithoutSceneContainer: KeyguardState, 606 ): Flow<Boolean> { 607 return if (SceneContainerFlag.isEnabled) { 608 combine(sceneInteractor.topmostContent, sceneInteractor.transitionState) { 609 topmostContent, 610 state -> 611 topmostContent == content || state.isTransitioning(from = content) 612 } 613 } else { 614 isFinishedIn(stateWithoutSceneContainer) 615 } 616 .distinctUntilChanged() 617 } 618 619 /** Whether we've FINISHED a transition to a state */ 620 fun isFinishedIn(state: KeyguardState): Flow<Boolean> { 621 state.checkValidState() 622 return finishedKeyguardState.map { it == state }.distinctUntilChanged() 623 } 624 625 fun isCurrentlyIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> { 626 return if (SceneContainerFlag.isEnabled) { 627 // In STL there is no difference between finished/currentState 628 isFinishedIn(scene, stateWithoutSceneContainer) 629 } else { 630 stateWithoutSceneContainer.checkValidState() 631 currentKeyguardState.map { it == stateWithoutSceneContainer } 632 } 633 .distinctUntilChanged() 634 } 635 636 fun getCurrentState(): KeyguardState { 637 return currentKeyguardState.replayCache.last() 638 } 639 640 fun getStartedState(): KeyguardState { 641 return startedKeyguardTransitionStep.value.to 642 } 643 644 private val finishedKeyguardState: StateFlow<KeyguardState> = 645 repository.transitions 646 .filter { it.transitionState == TransitionState.FINISHED } 647 .map { it.to } 648 .stateInTraced("KTF-finishedKeyguardState", scope, SharingStarted.Eagerly, OFF) 649 650 companion object { 651 private val TAG = KeyguardTransitionInteractor::class.simpleName 652 } 653 } 654