• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.systemui.keyguard.data.repository
17 
18 import android.animation.Animator
19 import android.animation.AnimatorListenerAdapter
20 import android.animation.ValueAnimator
21 import android.animation.ValueAnimator.AnimatorUpdateListener
22 import android.annotation.FloatRange
23 import android.os.Trace
24 import android.util.Log
25 import com.android.systemui.dagger.SysUISingleton
26 import com.android.systemui.keyguard.shared.model.KeyguardState
27 import com.android.systemui.keyguard.shared.model.TransitionInfo
28 import com.android.systemui.keyguard.shared.model.TransitionState
29 import com.android.systemui.keyguard.shared.model.TransitionStep
30 import java.util.UUID
31 import javax.inject.Inject
32 import kotlinx.coroutines.channels.BufferOverflow
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.MutableSharedFlow
35 import kotlinx.coroutines.flow.asSharedFlow
36 import kotlinx.coroutines.flow.distinctUntilChanged
37 import kotlinx.coroutines.flow.filter
38 
39 /**
40  * The source of truth for all keyguard transitions.
41  *
42  * While the keyguard component is visible, it can undergo a number of transitions between different
43  * UI screens, such as AOD (Always-on Display), Bouncer, and others mentioned in [KeyguardState].
44  * These UI elements should listen to events emitted by [transitions], to ensure a centrally
45  * coordinated experience.
46  *
47  * To create or modify logic that controls when and how transitions get created, look at
48  * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
49  * this repository.
50  */
51 interface KeyguardTransitionRepository {
52     /**
53      * All events regarding transitions, as they start, run, and complete. [TransitionStep#value] is
54      * a float between [0, 1] representing progress towards completion. If this is a user driven
55      * transition, that value may not be a monotonic progression, as the user may swipe in any
56      * direction.
57      */
58     val transitions: Flow<TransitionStep>
59 
60     /**
61      * Interactors that require information about changes between [KeyguardState]s will call this to
62      * register themselves for flowable [TransitionStep]s when that transition occurs.
63      */
64     fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
65         return transitions.filter { step -> step.from == from && step.to == to }
66     }
67 
68     /**
69      * Begin a transition from one state to another. Transitions are interruptible, and will issue a
70      * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
71      *
72      * When canceled, there are two options: to continue from the current position of the prior
73      * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
74      */
75     fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
76 
77     /**
78      * Allows manual control of a transition. When calling [startTransition], the consumer must pass
79      * in a null animator. In return, it will get a unique [UUID] that will be validated to allow
80      * further updates.
81      *
82      * When the transition is over, TransitionState.FINISHED must be passed into the [state]
83      * parameter.
84      */
85     fun updateTransition(
86         transitionId: UUID,
87         @FloatRange(from = 0.0, to = 1.0) value: Float,
88         state: TransitionState
89     )
90 }
91 
92 @SysUISingleton
93 class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitionRepository {
94     /*
95      * Each transition between [KeyguardState]s will have an associated Flow.
96      * In order to collect these events, clients should call [transition].
97      */
98     private val _transitions =
99         MutableSharedFlow<TransitionStep>(
100             replay = 2,
101             extraBufferCapacity = 10,
102             onBufferOverflow = BufferOverflow.DROP_OLDEST,
103         )
104     override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
105     private var lastStep: TransitionStep = TransitionStep()
106     private var lastAnimator: ValueAnimator? = null
107 
108     /*
109      * When manual control of the transition is requested, a unique [UUID] is used as the handle
110      * to permit calls to [updateTransition]
111      */
112     private var updateTransitionId: UUID? = null
113 
114     init {
115         // Seed with transitions signaling a boot into lockscreen state
116         emitTransition(
117             TransitionStep(
118                 KeyguardState.OFF,
119                 KeyguardState.LOCKSCREEN,
120                 0f,
121                 TransitionState.STARTED,
122                 KeyguardTransitionRepositoryImpl::class.simpleName!!,
123             )
124         )
125         emitTransition(
126             TransitionStep(
127                 KeyguardState.OFF,
128                 KeyguardState.LOCKSCREEN,
129                 1f,
130                 TransitionState.FINISHED,
131                 KeyguardTransitionRepositoryImpl::class.simpleName!!,
132             )
133         )
134     }
135 
startTransitionnull136     override fun startTransition(
137         info: TransitionInfo,
138         resetIfCanceled: Boolean,
139     ): UUID? {
140         if (lastStep.from == info.from && lastStep.to == info.to) {
141             Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
142             return null
143         }
144         val startingValue =
145             if (lastStep.transitionState != TransitionState.FINISHED) {
146                 Log.i(TAG, "Transition still active: $lastStep, canceling")
147                 if (resetIfCanceled) {
148                     0f
149                 } else {
150                     lastStep.value
151                 }
152             } else {
153                 0f
154             }
155 
156         lastAnimator?.cancel()
157         lastAnimator = info.animator
158 
159         info.animator?.let { animator ->
160             // An animator was provided, so use it to run the transition
161             animator.setFloatValues(startingValue, 1f)
162             animator.duration = ((1f - startingValue) * animator.duration).toLong()
163             val updateListener =
164                 object : AnimatorUpdateListener {
165                     override fun onAnimationUpdate(animation: ValueAnimator) {
166                         emitTransition(
167                             TransitionStep(
168                                 info,
169                                 (animation.getAnimatedValue() as Float),
170                                 TransitionState.RUNNING
171                             )
172                         )
173                     }
174                 }
175             val adapter =
176                 object : AnimatorListenerAdapter() {
177                     override fun onAnimationStart(animation: Animator) {
178                         emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
179                     }
180                     override fun onAnimationCancel(animation: Animator) {
181                         endAnimation(animation, lastStep.value, TransitionState.CANCELED)
182                     }
183                     override fun onAnimationEnd(animation: Animator) {
184                         endAnimation(animation, 1f, TransitionState.FINISHED)
185                     }
186 
187                     private fun endAnimation(
188                         animation: Animator,
189                         value: Float,
190                         state: TransitionState
191                     ) {
192                         emitTransition(TransitionStep(info, value, state))
193                         animator.removeListener(this)
194                         animator.removeUpdateListener(updateListener)
195                         lastAnimator = null
196                     }
197                 }
198             animator.addListener(adapter)
199             animator.addUpdateListener(updateListener)
200             animator.start()
201             return@startTransition null
202         }
203             ?: run {
204                 emitTransition(TransitionStep(info, 0f, TransitionState.STARTED))
205 
206                 // No animator, so it's manual. Provide a mechanism to callback
207                 updateTransitionId = UUID.randomUUID()
208                 return@startTransition updateTransitionId
209             }
210     }
211 
updateTransitionnull212     override fun updateTransition(
213         transitionId: UUID,
214         @FloatRange(from = 0.0, to = 1.0) value: Float,
215         state: TransitionState
216     ) {
217         if (updateTransitionId != transitionId) {
218             Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
219             return
220         }
221 
222         if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) {
223             updateTransitionId = null
224         }
225 
226         val nextStep = lastStep.copy(value = value, transitionState = state)
227         emitTransition(nextStep, isManual = true)
228     }
229 
emitTransitionnull230     private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) {
231         trace(nextStep, isManual)
232         val emitted = _transitions.tryEmit(nextStep)
233         if (!emitted) {
234             Log.w(TAG, "Failed to emit next value without suspending")
235         }
236         lastStep = nextStep
237     }
238 
tracenull239     private fun trace(step: TransitionStep, isManual: Boolean) {
240         if (step.transitionState == TransitionState.RUNNING) {
241             return
242         }
243         val traceName =
244             "Transition: ${step.from} -> ${step.to} " +
245                 if (isManual) {
246                     "(manual)"
247                 } else {
248                     ""
249                 }
250         val traceCookie = traceName.hashCode()
251         if (step.transitionState == TransitionState.STARTED) {
252             Trace.beginAsyncSection(traceName, traceCookie)
253         } else if (
254             step.transitionState == TransitionState.FINISHED ||
255                 step.transitionState == TransitionState.CANCELED
256         ) {
257             Trace.endAsyncSection(traceName, traceCookie)
258         }
259     }
260 
261     companion object {
262         private const val TAG = "KeyguardTransitionRepository"
263     }
264 }
265