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.map 40 import kotlinx.coroutines.flow.mapNotNull 41 42 /** 43 * Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and 44 * then [sharedFlow] for each sub animation that should be trigged when the overall transition runs. 45 */ 46 @SysUISingleton 47 class KeyguardTransitionAnimationFlow 48 @Inject 49 constructor( 50 private val transitionInteractor: KeyguardTransitionInteractor, 51 private val logger: KeyguardTransitionAnimationLogger, 52 ) { 53 /** Invoke once per transition between FROM->TO states to get access to a shared flow. */ 54 fun setup( 55 duration: Duration, 56 edge: Edge, 57 ): FlowBuilder { 58 return FlowBuilder(duration, edge) 59 } 60 61 inner class FlowBuilder( 62 private val transitionDuration: Duration, 63 private val edge: Edge, 64 ) { 65 fun setupWithoutSceneContainer(edge: Edge.StateToState): FlowBuilder { 66 if (SceneContainerFlag.isEnabled) return this 67 return setup(this.transitionDuration, edge) 68 } 69 70 /** 71 * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted 72 * in the range of [0, 1]. View animations should begin and end within a subset of this 73 * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is 74 * valid. 75 */ 76 fun sharedFlow( 77 duration: Duration, 78 onStep: (Float) -> Float, 79 startTime: Duration = 0.milliseconds, 80 onStart: (() -> Unit)? = null, 81 onCancel: (() -> Float)? = null, 82 onFinish: (() -> Float)? = null, 83 interpolator: Interpolator = LINEAR, 84 name: String? = null 85 ): Flow<Float> { 86 return sharedFlowWithState( 87 duration = duration, 88 onStep = onStep, 89 startTime = startTime, 90 onStart = onStart, 91 onCancel = onCancel, 92 onFinish = onFinish, 93 interpolator = interpolator, 94 name = name, 95 ) 96 .mapNotNull { stateToValue -> stateToValue.value } 97 } 98 99 /** 100 * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted 101 * in the range of [0, 1]. View animations should begin and end within a subset of this 102 * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is 103 * valid. 104 * 105 * Will return a [StateToValue], which encompasses the calculated value as well as the 106 * transitionState that is associated with it. 107 */ 108 fun sharedFlowWithState( 109 duration: Duration, 110 onStep: (Float) -> Float, 111 startTime: Duration = 0.milliseconds, 112 onStart: (() -> Unit)? = null, 113 onCancel: (() -> Float)? = null, 114 onFinish: (() -> Float)? = null, 115 interpolator: Interpolator = LINEAR, 116 name: String? = null 117 ): Flow<StateToValue> { 118 if (!duration.isPositive()) { 119 throw IllegalArgumentException("duration must be a positive number: $duration") 120 } 121 if ((startTime + duration) > transitionDuration) { 122 throw IllegalArgumentException( 123 "startTime($startTime) + duration($duration) must be" + 124 " <= transitionDuration($transitionDuration)" 125 ) 126 } 127 128 val start = (startTime / transitionDuration).toFloat() 129 val chunks = (transitionDuration / duration).toFloat() 130 logger.logCreate(name, start) 131 132 fun stepToValue(step: TransitionStep): Float? { 133 val value = (step.value - start) * chunks 134 return when (step.transitionState) { 135 // When starting, make sure to always emit. If a transition is started from the 136 // middle, it is possible this animation is being skipped but we need to inform 137 // the ViewModels of the last update 138 STARTED -> { 139 onStart?.invoke() 140 max(0f, min(1f, value)) 141 } 142 // Always send a final value of 1. Because of rounding, [value] may never be 143 // exactly 1. 144 RUNNING -> 145 if (value >= 1f) { 146 1f 147 } else if (value >= 0f) { 148 value 149 } else { 150 null 151 } 152 else -> null 153 }?.let { onStep(interpolator.getInterpolation(it)) } 154 } 155 156 return transitionInteractor 157 .transition(edge) 158 .map { step -> 159 StateToValue( 160 from = step.from, 161 to = step.to, 162 transitionState = step.transitionState, 163 value = 164 when (step.transitionState) { 165 STARTED -> stepToValue(step) 166 RUNNING -> stepToValue(step) 167 CANCELED -> onCancel?.invoke() 168 FINISHED -> onFinish?.invoke() 169 } 170 ) 171 .also { logger.logTransitionStep(name, step, it.value) } 172 } 173 .distinctUntilChanged() 174 } 175 176 /** 177 * Immediately (after 1ms) emits the given value for every step of the KeyguardTransition. 178 */ 179 fun immediatelyTransitionTo(value: Float): Flow<Float> { 180 return sharedFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value }) 181 } 182 } 183 } 184 185 data class StateToValue( 186 val from: KeyguardState? = null, 187 val to: KeyguardState? = null, 188 val transitionState: TransitionState = TransitionState.FINISHED, 189 val value: Float? = 0f, 190 ) { isToOrFromnull191 fun isToOrFrom(state: KeyguardState) = from == state || to == state 192 } 193