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