• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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