1 /* 2 * 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 17 package com.android.systemui.keyguard.domain.interactor 18 19 import android.animation.ValueAnimator 20 import com.android.app.animation.Interpolators 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.dagger.qualifiers.Application 23 import com.android.systemui.flags.FeatureFlags 24 import com.android.systemui.flags.Flags 25 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 26 import com.android.systemui.keyguard.shared.model.KeyguardState 27 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel 28 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD 29 import com.android.systemui.keyguard.shared.model.TransitionInfo 30 import com.android.systemui.keyguard.shared.model.TransitionState 31 import com.android.systemui.keyguard.shared.model.WakefulnessState 32 import com.android.systemui.shade.data.repository.ShadeRepository 33 import com.android.systemui.util.kotlin.Utils.Companion.toQuad 34 import com.android.systemui.util.kotlin.Utils.Companion.toTriple 35 import com.android.systemui.util.kotlin.sample 36 import java.util.UUID 37 import javax.inject.Inject 38 import kotlin.time.Duration.Companion.milliseconds 39 import kotlinx.coroutines.CoroutineScope 40 import kotlinx.coroutines.flow.Flow 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.distinctUntilChanged 43 import kotlinx.coroutines.flow.map 44 import kotlinx.coroutines.flow.onStart 45 import kotlinx.coroutines.launch 46 47 @SysUISingleton 48 class FromLockscreenTransitionInteractor 49 @Inject 50 constructor( 51 override val transitionRepository: KeyguardTransitionRepository, 52 override val transitionInteractor: KeyguardTransitionInteractor, 53 @Application private val scope: CoroutineScope, 54 private val keyguardInteractor: KeyguardInteractor, 55 private val flags: FeatureFlags, 56 private val shadeRepository: ShadeRepository, 57 ) : 58 TransitionInteractor( 59 fromState = KeyguardState.LOCKSCREEN, 60 ) { 61 62 override fun start() { 63 listenForLockscreenToGone() 64 listenForLockscreenToGoneDragging() 65 listenForLockscreenToOccluded() 66 listenForLockscreenToCamera() 67 listenForLockscreenToAodOrDozing() 68 listenForLockscreenToPrimaryBouncer() 69 listenForLockscreenToDreaming() 70 listenForLockscreenToPrimaryBouncerDragging() 71 listenForLockscreenToAlternateBouncer() 72 } 73 74 /** 75 * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN, 76 * or null if we don't care and should just use a reasonable default. 77 * 78 * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from 79 * LOCKSCREEN is running. 80 */ 81 val surfaceBehindVisibility: Flow<Boolean?> = 82 transitionInteractor.startedKeyguardTransitionStep 83 .map { startedStep -> 84 if (startedStep.to != KeyguardState.GONE) { 85 // LOCKSCREEN to anything but GONE does not require any special surface 86 // visibility handling. 87 return@map null 88 } 89 90 true // TODO(b/278086361): Implement continuous swipe to unlock. 91 } 92 .onStart { 93 // Default to null ("don't care, use a reasonable default"). 94 emit(null) 95 } 96 .distinctUntilChanged() 97 98 /** 99 * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't 100 * care and should use a reasonable default. 101 */ 102 val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> = 103 combine( 104 transitionInteractor.startedKeyguardTransitionStep, 105 transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN) 106 ) { startedStep, fromLockscreenStep -> 107 if (startedStep.to != KeyguardState.GONE) { 108 // Only LOCKSCREEN -> GONE has specific surface params (for the unlock 109 // animation). 110 return@combine null 111 } else if (fromLockscreenStep.value > 0.5f) { 112 // Start the animation once we're 50% transitioned to GONE. 113 KeyguardSurfaceBehindModel( 114 animateFromAlpha = 0f, 115 alpha = 1f, 116 animateFromTranslationY = 500f, 117 translationY = 0f 118 ) 119 } else { 120 KeyguardSurfaceBehindModel( 121 alpha = 0f, 122 ) 123 } 124 } 125 .onStart { 126 // Default to null ("don't care, use a reasonable default"). 127 emit(null) 128 } 129 .distinctUntilChanged() 130 131 private fun listenForLockscreenToDreaming() { 132 val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING) 133 scope.launch { 134 keyguardInteractor.isAbleToDream 135 .sample( 136 combine( 137 transitionInteractor.startedKeyguardTransitionStep, 138 transitionInteractor.finishedKeyguardState, 139 keyguardInteractor.isActiveDreamLockscreenHosted, 140 ::Triple 141 ), 142 ::toQuad 143 ) 144 .collect { 145 ( 146 isAbleToDream, 147 lastStartedTransition, 148 finishedKeyguardState, 149 isActiveDreamLockscreenHosted) -> 150 val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN 151 val isTransitionInterruptible = 152 lastStartedTransition.to == KeyguardState.LOCKSCREEN && 153 !invalidFromStates.contains(lastStartedTransition.from) 154 if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) { 155 if (isActiveDreamLockscreenHosted) { 156 startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) 157 } else { 158 startTransitionTo(KeyguardState.DREAMING) 159 } 160 } 161 } 162 } 163 } 164 165 private fun listenForLockscreenToPrimaryBouncer() { 166 scope.launch { 167 keyguardInteractor.primaryBouncerShowing 168 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) 169 .collect { pair -> 170 val (isBouncerShowing, lastStartedTransitionStep) = pair 171 if ( 172 isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN 173 ) { 174 startTransitionTo(KeyguardState.PRIMARY_BOUNCER) 175 } 176 } 177 } 178 } 179 180 private fun listenForLockscreenToAlternateBouncer() { 181 scope.launch { 182 keyguardInteractor.alternateBouncerShowing 183 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) 184 .collect { pair -> 185 val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair 186 if ( 187 isAlternateBouncerShowing && 188 lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN 189 ) { 190 startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) 191 } 192 } 193 } 194 } 195 196 /* Starts transitions when manually dragging up the bouncer from the lockscreen. */ 197 private fun listenForLockscreenToPrimaryBouncerDragging() { 198 var transitionId: UUID? = null 199 scope.launch { 200 shadeRepository.shadeModel 201 .sample( 202 combine( 203 transitionInteractor.startedKeyguardTransitionStep, 204 keyguardInteractor.statusBarState, 205 keyguardInteractor.isKeyguardUnlocked, 206 ::Triple 207 ), 208 ::toQuad 209 ) 210 .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) -> 211 val id = transitionId 212 if (id != null) { 213 if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) { 214 // An existing `id` means a transition is started, and calls to 215 // `updateTransition` will control it until FINISHED or CANCELED 216 var nextState = 217 if (shadeModel.expansionAmount == 0f) { 218 TransitionState.FINISHED 219 } else if (shadeModel.expansionAmount == 1f) { 220 TransitionState.CANCELED 221 } else { 222 TransitionState.RUNNING 223 } 224 transitionRepository.updateTransition( 225 id, 226 1f - shadeModel.expansionAmount, 227 nextState, 228 ) 229 230 if ( 231 nextState == TransitionState.CANCELED || 232 nextState == TransitionState.FINISHED 233 ) { 234 transitionId = null 235 } 236 237 // If canceled, just put the state back 238 // TODO(b/278086361): This logic should happen in 239 // FromPrimaryBouncerInteractor. 240 if (nextState == TransitionState.CANCELED) { 241 transitionRepository.startTransition( 242 TransitionInfo( 243 ownerName = name, 244 from = KeyguardState.PRIMARY_BOUNCER, 245 to = KeyguardState.LOCKSCREEN, 246 animator = 247 getDefaultAnimatorForTransitionsToState( 248 KeyguardState.LOCKSCREEN 249 ) 250 .apply { duration = 0 } 251 ) 252 ) 253 } 254 } 255 } else { 256 // TODO (b/251849525): Remove statusbarstate check when that state is 257 // integrated into KeyguardTransitionRepository 258 if ( 259 keyguardState.to == KeyguardState.LOCKSCREEN && 260 shadeModel.isUserDragging && 261 !isKeyguardUnlocked && 262 statusBarState == KEYGUARD 263 ) { 264 transitionId = startTransitionTo(KeyguardState.PRIMARY_BOUNCER) 265 } 266 } 267 } 268 } 269 } 270 271 fun dismissKeyguard() { 272 startTransitionTo(KeyguardState.GONE) 273 } 274 275 private fun listenForLockscreenToGone() { 276 if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { 277 return 278 } 279 280 scope.launch { 281 keyguardInteractor.isKeyguardGoingAway 282 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) 283 .collect { pair -> 284 val (isKeyguardGoingAway, lastStartedStep) = pair 285 if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) { 286 startTransitionTo(KeyguardState.GONE) 287 } 288 } 289 } 290 } 291 292 private fun listenForLockscreenToGoneDragging() { 293 if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { 294 return 295 } 296 297 scope.launch { 298 keyguardInteractor.isKeyguardGoingAway 299 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) 300 .collect { pair -> 301 val (isKeyguardGoingAway, lastStartedStep) = pair 302 if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) { 303 startTransitionTo(KeyguardState.GONE) 304 } 305 } 306 } 307 } 308 309 private fun listenForLockscreenToOccluded() { 310 scope.launch { 311 keyguardInteractor.isKeyguardOccluded 312 .sample( 313 combine( 314 transitionInteractor.finishedKeyguardState, 315 keyguardInteractor.isDreaming, 316 ::Pair 317 ), 318 ::toTriple 319 ) 320 .collect { (isOccluded, keyguardState, isDreaming) -> 321 if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { 322 startTransitionTo(KeyguardState.OCCLUDED) 323 } 324 } 325 } 326 } 327 328 /** This signal may come in before the occlusion signal, and can provide a custom transition */ 329 private fun listenForLockscreenToCamera() { 330 scope.launch { 331 keyguardInteractor.onCameraLaunchDetected 332 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) 333 .collect { (_, lastStartedStep) -> 334 // DREAMING/AOD/OFF may trigger on the first power button push, so include this 335 // state in order to cancel and correct the transition 336 if ( 337 lastStartedStep.to == KeyguardState.LOCKSCREEN || 338 lastStartedStep.to == KeyguardState.DREAMING || 339 lastStartedStep.to == KeyguardState.DOZING || 340 lastStartedStep.to == KeyguardState.AOD || 341 lastStartedStep.to == KeyguardState.OFF 342 ) { 343 startTransitionTo(KeyguardState.OCCLUDED) 344 } 345 } 346 } 347 } 348 349 private fun listenForLockscreenToAodOrDozing() { 350 scope.launch { 351 keyguardInteractor.wakefulnessModel 352 .sample( 353 combine( 354 transitionInteractor.startedKeyguardTransitionStep, 355 keyguardInteractor.isAodAvailable, 356 ::Pair 357 ), 358 ::toTriple 359 ) 360 .collect { (wakefulnessState, lastStartedStep, isAodAvailable) -> 361 if ( 362 lastStartedStep.to == KeyguardState.LOCKSCREEN && 363 wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP 364 ) { 365 startTransitionTo( 366 if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING 367 ) 368 } 369 } 370 } 371 } 372 373 override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { 374 return ValueAnimator().apply { 375 interpolator = Interpolators.LINEAR 376 duration = 377 when (toState) { 378 KeyguardState.DREAMING -> TO_DREAMING_DURATION 379 KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION 380 else -> DEFAULT_DURATION 381 }.inWholeMilliseconds 382 } 383 } 384 385 companion object { 386 private val DEFAULT_DURATION = 400.milliseconds 387 val TO_DREAMING_DURATION = 933.milliseconds 388 val TO_OCCLUDED_DURATION = 450.milliseconds 389 } 390 } 391