1 /* <lambda>null2 * Copyright (C) 2023 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 package com.android.systemui.keyguard.ui 17 18 import android.view.animation.Interpolator 19 import com.android.app.animation.Interpolators.LINEAR 20 import com.android.keyguard.logging.KeyguardTransitionAnimationLogger 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 23 import com.android.systemui.keyguard.shared.model.Edge 24 import com.android.systemui.keyguard.shared.model.KeyguardState 25 import com.android.systemui.keyguard.shared.model.TransitionState 26 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED 27 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED 28 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING 29 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED 30 import com.android.systemui.keyguard.shared.model.TransitionStep 31 import com.android.systemui.scene.shared.flag.SceneContainerFlag 32 import javax.inject.Inject 33 import kotlin.math.max 34 import kotlin.math.min 35 import kotlin.time.Duration 36 import kotlin.time.Duration.Companion.milliseconds 37 import kotlinx.coroutines.flow.Flow 38 import kotlinx.coroutines.flow.distinctUntilChanged 39 import kotlinx.coroutines.flow.mapNotNull 40 41 /** 42 * Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and 43 * then [sharedFlow] for each sub animation that should be trigged when the overall transition runs. 44 */ 45 @SysUISingleton 46 class KeyguardTransitionAnimationFlow 47 @Inject 48 constructor( 49 private val transitionInteractor: KeyguardTransitionInteractor, 50 private val logger: KeyguardTransitionAnimationLogger, 51 ) { 52 /** Invoke once per transition between FROM->TO states to get access to a shared flow. */ 53 fun setup(duration: Duration, edge: Edge): FlowBuilder { 54 return FlowBuilder(duration, edge) 55 } 56 57 inner class FlowBuilder(private val transitionDuration: Duration, private val edge: Edge) { 58 fun setupWithoutSceneContainer(edge: Edge.StateToState): FlowBuilder { 59 if (SceneContainerFlag.isEnabled) return this 60 return setup(this.transitionDuration, edge) 61 } 62 63 /** 64 * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted 65 * in the range of [0, 1]. View animations should begin and end within a subset of this 66 * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is 67 * valid. 68 * 69 * Note that [onStep] accepts a null return value. When null, no animation information will 70 * be emitted, effectively saying "do not change the value on this frame" 71 * 72 * Note that [onCancel] isn't used when the scene framework is enabled. 73 */ 74 fun sharedFlow( 75 duration: Duration = transitionDuration, 76 onStep: (Float) -> Float?, 77 startTime: Duration = 0.milliseconds, 78 onStart: (() -> Unit)? = null, 79 onCancel: (() -> Float)? = null, 80 onFinish: (() -> Float)? = null, 81 interpolator: Interpolator = LINEAR, 82 name: String? = null, 83 ): Flow<Float> { 84 return sharedFlowWithState( 85 duration = duration, 86 onStep = onStep, 87 startTime = startTime, 88 onStart = onStart, 89 onCancel = onCancel, 90 onFinish = onFinish, 91 interpolator = interpolator, 92 name = name, 93 ) 94 .mapNotNull { stateToValue -> stateToValue.value } 95 } 96 97 /** 98 * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted 99 * in the range of [0, 1]. View animations should begin and end within a subset of this 100 * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is 101 * valid. 102 * 103 * Will return a [StateToValue], which encompasses the calculated value as well as the 104 * transitionState that is associated with it. 105 */ 106 fun sharedFlowWithState( 107 duration: Duration, 108 onStep: (Float) -> Float?, 109 startTime: Duration = 0.milliseconds, 110 onStart: (() -> Unit)? = null, 111 onCancel: (() -> Float)? = null, 112 onFinish: (() -> Float)? = null, 113 interpolator: Interpolator = LINEAR, 114 name: String? = null, 115 ): Flow<StateToValue> { 116 if (!duration.isPositive()) { 117 throw IllegalArgumentException("duration must be a positive number: $duration") 118 } 119 if ((startTime + duration) > transitionDuration) { 120 throw IllegalArgumentException( 121 "startTime($startTime) + duration($duration) must be" + 122 " <= transitionDuration($transitionDuration)" 123 ) 124 } 125 126 val start = (startTime / transitionDuration).toFloat() 127 val chunks = (transitionDuration / duration).toFloat() 128 logger.logCreate(name, start) 129 130 fun stepToValue(step: TransitionStep): Float? { 131 val value = (step.value - start) * chunks 132 return when (step.transitionState) { 133 // When starting, make sure to always emit. If a transition is started from the 134 // middle, it is possible this animation is being skipped but we need to inform 135 // the ViewModels of the last update 136 STARTED -> { 137 onStart?.invoke() 138 max(0f, min(1f, value)) 139 } 140 // Always send a final value of 1. Because of rounding, [value] may never be 141 // exactly 1. 142 RUNNING -> 143 if (value >= 1f) { 144 1f 145 } else if (value >= 0f) { 146 value 147 } else { 148 null 149 } 150 else -> null 151 }?.let { onStep(interpolator.getInterpolation(it)) } 152 } 153 154 return transitionInteractor 155 .transition(edge) 156 .mapNotNull { step -> 157 if (SceneContainerFlag.isEnabled && step.transitionState == CANCELED) { 158 // When the scene framework is enabled, there's no need to emit an alpha 159 // value when the keyguard transition animation is canceled because there's 160 // always going to be a new, reversed keyguard transition animation back to 161 // the original KeyguardState that starts right when this one was canceled. 162 // 163 // For example, if swiping up slightly on the Lockscreen scene and then 164 // releasing before the transition to the Bouncer scene is committed, the 165 // KTF transition of LOCKSCREEN -> PRIMARY_BOUNCER received a CANCELED and 166 // the scene framework immediately starts a reversed transition of 167 // PRIMARY_BOUNCER -> LOCKSCREEN, which picks up where the previous one left 168 // off. 169 // 170 // If it were allowed for the CANCELED from the original KTF transition to 171 // emit a value, a race condition could form where the value from CANCELED 172 // arrives downstream _after_ the reversed transition is finished, causing 173 // the transition to end up in an incorrect state at rest. 174 null 175 } else { 176 StateToValue( 177 from = step.from, 178 to = step.to, 179 transitionState = step.transitionState, 180 value = 181 when (step.transitionState) { 182 STARTED -> stepToValue(step) 183 RUNNING -> stepToValue(step) 184 CANCELED -> onCancel?.invoke() 185 FINISHED -> onFinish?.invoke() 186 }, 187 ) 188 .also { logger.logTransitionStep(name, step, it.value) } 189 } 190 } 191 .distinctUntilChanged() 192 } 193 194 /** 195 * Immediately (after 1ms) emits the given value for every step of the KeyguardTransition. 196 */ 197 fun immediatelyTransitionTo(value: Float): Flow<Float> { 198 return sharedFlow( 199 duration = 1.milliseconds, 200 onStep = { value }, 201 onCancel = { value }, 202 onFinish = { value }, 203 ) 204 } 205 } 206 } 207 208 data class StateToValue( 209 val from: KeyguardState? = null, 210 val to: KeyguardState? = null, 211 val transitionState: TransitionState = TransitionState.FINISHED, 212 val value: Float? = 0f, 213 ) { isToOrFromnull214 fun isToOrFrom(state: KeyguardState) = from == state || to == state 215 } 216