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.annotation.SuppressLint 24 import android.os.Trace 25 import android.util.Log 26 import com.android.app.tracing.coroutines.withContext 27 import com.android.systemui.dagger.SysUISingleton 28 import com.android.systemui.dagger.qualifiers.Main 29 import com.android.systemui.keyguard.shared.model.KeyguardState 30 import com.android.systemui.keyguard.shared.model.TransitionInfo 31 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled 32 import com.android.systemui.keyguard.shared.model.TransitionState 33 import com.android.systemui.keyguard.shared.model.TransitionStep 34 import java.util.UUID 35 import javax.inject.Inject 36 import kotlinx.coroutines.CoroutineDispatcher 37 import kotlinx.coroutines.channels.BufferOverflow 38 import kotlinx.coroutines.delay 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.flow.MutableSharedFlow 41 import kotlinx.coroutines.flow.MutableStateFlow 42 import kotlinx.coroutines.flow.StateFlow 43 import kotlinx.coroutines.flow.asSharedFlow 44 import kotlinx.coroutines.flow.asStateFlow 45 import kotlinx.coroutines.flow.distinctUntilChanged 46 import kotlinx.coroutines.flow.filter 47 import kotlinx.coroutines.sync.Mutex 48 49 /** 50 * The source of truth for all keyguard transitions. 51 * 52 * While the keyguard component is visible, it can undergo a number of transitions between different 53 * UI screens, such as AOD (Always-on Display), Bouncer, and others mentioned in [KeyguardState]. 54 * These UI elements should listen to events emitted by [transitions], to ensure a centrally 55 * coordinated experience. 56 * 57 * To create or modify logic that controls when and how transitions get created, look at 58 * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on 59 * this repository. 60 * 61 * To print all transitions to logcat to help with debugging, run this command: 62 * ``` 63 * adb shell cmd statusbar echo -b KeyguardLog:VERBOSE 64 * ``` 65 * 66 * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag. 67 */ 68 interface KeyguardTransitionRepository { 69 /** 70 * All events regarding transitions, as they start, run, and complete. [TransitionStep#value] is 71 * a float between [0, 1] representing progress towards completion. If this is a user driven 72 * transition, that value may not be a monotonic progression, as the user may swipe in any 73 * direction. 74 */ 75 val transitions: Flow<TransitionStep> 76 77 /** The [TransitionInfo] of the most recent call to [startTransition]. */ 78 val currentTransitionInfoInternal: StateFlow<TransitionInfo> 79 80 /** 81 * Interactors that require information about changes between [KeyguardState]s will call this to 82 * register themselves for flowable [TransitionStep]s when that transition occurs. 83 */ 84 fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> { 85 return transitions.filter { step -> step.from == from && step.to == to } 86 } 87 88 /** 89 * Begin a transition from one state to another. Transitions are interruptible, and will issue a 90 * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one. 91 */ 92 suspend fun startTransition(info: TransitionInfo): UUID? 93 94 /** 95 * Emits STARTED and FINISHED transition steps to the given state. This is used during boot to 96 * seed the repository with the appropriate initial state. 97 */ 98 suspend fun emitInitialStepsFromOff(to: KeyguardState) 99 100 /** 101 * Allows manual control of a transition. When calling [startTransition], the consumer must pass 102 * in a null animator. In return, it will get a unique [UUID] that will be validated to allow 103 * further updates. 104 * 105 * When the transition is over, TransitionState.FINISHED must be passed into the [state] 106 * parameter. 107 */ 108 fun updateTransition( 109 transitionId: UUID, 110 @FloatRange(from = 0.0, to = 1.0) value: Float, 111 state: TransitionState 112 ) 113 } 114 115 @SysUISingleton 116 class KeyguardTransitionRepositoryImpl 117 @Inject 118 constructor( 119 @Main val mainDispatcher: CoroutineDispatcher, 120 ) : KeyguardTransitionRepository { 121 /** 122 * Each transition between [KeyguardState]s will have an associated Flow. In order to collect 123 * these events, clients should call [transition]. 124 */ 125 @SuppressLint("SharedFlowCreation") 126 private val _transitions = 127 MutableSharedFlow<TransitionStep>( 128 replay = 2, 129 extraBufferCapacity = 20, 130 onBufferOverflow = BufferOverflow.DROP_OLDEST, 131 ) 132 override val transitions = _transitions.asSharedFlow().distinctUntilChanged() 133 private var lastStep: TransitionStep = TransitionStep() 134 private var lastAnimator: ValueAnimator? = null 135 136 private val _currentTransitionMutex = Mutex() 137 private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> = 138 MutableStateFlow( 139 TransitionInfo( 140 ownerName = "", 141 from = KeyguardState.OFF, 142 to = KeyguardState.OFF, 143 animator = null 144 ) 145 ) 146 override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow() 147 148 /* 149 * When manual control of the transition is requested, a unique [UUID] is used as the handle 150 * to permit calls to [updateTransition] 151 */ 152 private var updateTransitionId: UUID? = null 153 154 // Only used in a test environment 155 var forceDelayForRaceConditionTest = false 156 157 init { 158 // Start with a FINISHED transition in OFF. KeyguardBootInteractor will transition from OFF 159 // to either GONE or LOCKSCREEN once we're booted up and can determine which state we should 160 // start in. 161 emitTransition( 162 TransitionStep( 163 KeyguardState.OFF, 164 KeyguardState.OFF, 165 1f, 166 TransitionState.FINISHED, 167 ) 168 ) 169 } 170 startTransitionnull171 override suspend fun startTransition(info: TransitionInfo): UUID? { 172 _currentTransitionInfo.value = info 173 Log.d(TAG, "(Internal) Setting current transition info: $info") 174 175 // There is no fairness guarantee with 'withContext', which means that transitions could 176 // be processed out of order. Use a Mutex to guarantee ordering. 177 _currentTransitionMutex.lock() 178 179 // Only used in a test environment 180 if (forceDelayForRaceConditionTest) { 181 delay(50L) 182 } 183 184 // Animators must be started on the main thread. 185 return withContext("$TAG#startTransition", mainDispatcher) { 186 _currentTransitionMutex.unlock() 187 188 if (lastStep.from == info.from && lastStep.to == info.to) { 189 Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") 190 return@withContext null 191 } 192 val startingValue = 193 if (lastStep.transitionState != TransitionState.FINISHED) { 194 Log.i(TAG, "Transition still active: $lastStep, canceling") 195 when (info.modeOnCanceled) { 196 TransitionModeOnCanceled.LAST_VALUE -> lastStep.value 197 TransitionModeOnCanceled.RESET -> 0f 198 TransitionModeOnCanceled.REVERSE -> 1f - lastStep.value 199 } 200 } else { 201 0f 202 } 203 204 lastAnimator?.cancel() 205 lastAnimator = info.animator 206 207 // Cancel any existing manual transitions 208 updateTransitionId?.let { uuid -> 209 updateTransition(uuid, lastStep.value, TransitionState.CANCELED) 210 } 211 212 info.animator?.let { animator -> 213 // An animator was provided, so use it to run the transition 214 animator.setFloatValues(startingValue, 1f) 215 animator.duration = ((1f - startingValue) * animator.duration).toLong() 216 val updateListener = AnimatorUpdateListener { animation -> 217 emitTransition( 218 TransitionStep( 219 info, 220 (animation.animatedValue as Float), 221 TransitionState.RUNNING 222 ) 223 ) 224 } 225 226 val adapter = 227 object : AnimatorListenerAdapter() { 228 override fun onAnimationStart(animation: Animator) { 229 emitTransition( 230 TransitionStep(info, startingValue, TransitionState.STARTED) 231 ) 232 } 233 234 override fun onAnimationCancel(animation: Animator) { 235 endAnimation(lastStep.value, TransitionState.CANCELED) 236 } 237 238 override fun onAnimationEnd(animation: Animator) { 239 endAnimation(1f, TransitionState.FINISHED) 240 } 241 242 private fun endAnimation(value: Float, state: TransitionState) { 243 emitTransition(TransitionStep(info, value, state)) 244 animator.removeListener(this) 245 animator.removeUpdateListener(updateListener) 246 lastAnimator = null 247 } 248 } 249 animator.addListener(adapter) 250 animator.addUpdateListener(updateListener) 251 animator.start() 252 return@withContext null 253 } 254 ?: run { 255 emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED)) 256 257 // No animator, so it's manual. Provide a mechanism to callback 258 updateTransitionId = UUID.randomUUID() 259 return@withContext updateTransitionId 260 } 261 } 262 } 263 updateTransitionnull264 override fun updateTransition( 265 transitionId: UUID, 266 @FloatRange(from = 0.0, to = 1.0) value: Float, 267 state: TransitionState 268 ) { 269 if (updateTransitionId != transitionId) { 270 Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") 271 return 272 } 273 274 if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) { 275 updateTransitionId = null 276 } 277 278 val nextStep = lastStep.copy(value = value, transitionState = state) 279 emitTransition(nextStep, isManual = true) 280 } 281 emitTransitionnull282 private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) { 283 logAndTrace(nextStep, isManual) 284 _transitions.tryEmit(nextStep) 285 lastStep = nextStep 286 } 287 emitInitialStepsFromOffnull288 override suspend fun emitInitialStepsFromOff(to: KeyguardState) { 289 _currentTransitionInfo.value = 290 TransitionInfo( 291 ownerName = "KeyguardTransitionRepository(boot)", 292 from = KeyguardState.OFF, 293 to = to, 294 animator = null 295 ) 296 297 emitTransition( 298 TransitionStep( 299 KeyguardState.OFF, 300 to, 301 0f, 302 TransitionState.STARTED, 303 ownerName = "KeyguardTransitionRepository(boot)", 304 ) 305 ) 306 307 emitTransition( 308 TransitionStep( 309 KeyguardState.OFF, 310 to, 311 1f, 312 TransitionState.FINISHED, 313 ownerName = "KeyguardTransitionRepository(boot)", 314 ), 315 ) 316 } 317 logAndTracenull318 private fun logAndTrace(step: TransitionStep, isManual: Boolean) { 319 if (step.transitionState == TransitionState.RUNNING) { 320 return 321 } 322 val manualStr = if (isManual) " (manual)" else "" 323 val traceName = "Transition: ${step.from} -> ${step.to}$manualStr" 324 325 val traceCookie = traceName.hashCode() 326 when (step.transitionState) { 327 TransitionState.STARTED -> Trace.beginAsyncSection(traceName, traceCookie) 328 TransitionState.FINISHED -> Trace.endAsyncSection(traceName, traceCookie) 329 TransitionState.CANCELED -> Trace.endAsyncSection(traceName, traceCookie) 330 else -> {} 331 } 332 333 Log.i(TAG, "${step.transitionState.name} transition: $step$manualStr") 334 } 335 336 companion object { 337 private const val TAG = "KeyguardTransitionRepository" 338 } 339 } 340