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