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 17 package com.android.systemui.keyguard.domain.interactor 18 19 import android.animation.ValueAnimator 20 import android.util.MathUtils 21 import com.android.app.animation.Interpolators 22 import com.android.app.tracing.coroutines.launch 23 import com.android.systemui.Flags 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.dagger.qualifiers.Background 26 import com.android.systemui.dagger.qualifiers.Main 27 import com.android.systemui.keyguard.KeyguardWmStateRefactor 28 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 29 import com.android.systemui.keyguard.shared.model.Edge 30 import com.android.systemui.keyguard.shared.model.KeyguardState 31 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD 32 import com.android.systemui.keyguard.shared.model.TransitionInfo 33 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled 34 import com.android.systemui.keyguard.shared.model.TransitionState 35 import com.android.systemui.keyguard.shared.model.TransitionStep 36 import com.android.systemui.power.domain.interactor.PowerInteractor 37 import com.android.systemui.scene.shared.flag.SceneContainerFlag 38 import com.android.systemui.scene.shared.model.Scenes 39 import com.android.systemui.shade.data.repository.ShadeRepository 40 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine 41 import java.util.UUID 42 import javax.inject.Inject 43 import kotlin.time.Duration.Companion.milliseconds 44 import kotlin.time.Duration.Companion.seconds 45 import kotlinx.coroutines.CoroutineDispatcher 46 import kotlinx.coroutines.CoroutineScope 47 import kotlinx.coroutines.flow.Flow 48 import kotlinx.coroutines.flow.distinctUntilChanged 49 import kotlinx.coroutines.flow.filterNotNull 50 import kotlinx.coroutines.flow.map 51 import kotlinx.coroutines.flow.onStart 52 import kotlinx.coroutines.launch 53 54 @SysUISingleton 55 class FromLockscreenTransitionInteractor 56 @Inject 57 constructor( 58 override val transitionRepository: KeyguardTransitionRepository, 59 transitionInteractor: KeyguardTransitionInteractor, 60 @Background private val scope: CoroutineScope, 61 @Background bgDispatcher: CoroutineDispatcher, 62 @Main mainDispatcher: CoroutineDispatcher, 63 keyguardInteractor: KeyguardInteractor, 64 private val shadeRepository: ShadeRepository, 65 powerInteractor: PowerInteractor, 66 private val glanceableHubTransitions: GlanceableHubTransitions, 67 private val swipeToDismissInteractor: SwipeToDismissInteractor, 68 keyguardOcclusionInteractor: KeyguardOcclusionInteractor, 69 ) : 70 TransitionInteractor( 71 fromState = KeyguardState.LOCKSCREEN, 72 transitionInteractor = transitionInteractor, 73 mainDispatcher = mainDispatcher, 74 bgDispatcher = bgDispatcher, 75 powerInteractor = powerInteractor, 76 keyguardOcclusionInteractor = keyguardOcclusionInteractor, 77 keyguardInteractor = keyguardInteractor, 78 ) { 79 80 override fun start() { 81 listenForLockscreenToGone() 82 listenForLockscreenToGoneDragging() 83 listenForLockscreenToOccludedOrDreaming() 84 listenForLockscreenToAodOrDozing() 85 listenForLockscreenToPrimaryBouncer() 86 listenForLockscreenToDreaming() 87 listenForLockscreenToPrimaryBouncerDragging() 88 listenForLockscreenToAlternateBouncer() 89 listenForLockscreenTransitionToCamera() 90 listenForLockscreenToGlanceableHub() 91 } 92 93 /** 94 * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN, 95 * or null if we don't care and should just use a reasonable default. 96 * 97 * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from 98 * LOCKSCREEN is running. 99 */ 100 val surfaceBehindVisibility: Flow<Boolean?> = 101 transitionInteractor 102 .transition( 103 edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = Scenes.Gone), 104 edgeWithoutSceneContainer = 105 Edge.create(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) 106 ) 107 .map<TransitionStep, Boolean?> { 108 true // Make the surface visible during LS -> GONE transitions. 109 } 110 .onStart { 111 // Default to null ("don't care, use a reasonable default"). 112 emit(null) 113 } 114 .distinctUntilChanged() 115 116 private fun listenForLockscreenTransitionToCamera() { 117 listenForTransitionToCamera(scope, keyguardInteractor) 118 } 119 120 private fun listenForLockscreenToDreaming() { 121 if (KeyguardWmStateRefactor.isEnabled) { 122 return 123 } 124 125 val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING) 126 scope.launch("$TAG#listenForLockscreenToDreaming") { 127 keyguardInteractor.isAbleToDream 128 .filterRelevantKeyguardState() 129 .sampleCombine( 130 transitionInteractor.currentTransitionInfoInternal, 131 finishedKeyguardState, 132 keyguardInteractor.isActiveDreamLockscreenHosted, 133 ) 134 .collect { 135 ( 136 isAbleToDream, 137 transitionInfo, 138 finishedKeyguardState, 139 isActiveDreamLockscreenHosted) -> 140 val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN 141 val isTransitionInterruptible = 142 transitionInfo.to == KeyguardState.LOCKSCREEN && 143 !invalidFromStates.contains(transitionInfo.from) 144 if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) { 145 if (isActiveDreamLockscreenHosted) { 146 startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) 147 } else { 148 startTransitionTo(KeyguardState.DREAMING) 149 } 150 } 151 } 152 } 153 } 154 155 private fun listenForLockscreenToPrimaryBouncer() { 156 if (SceneContainerFlag.isEnabled) return 157 scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") { 158 keyguardInteractor.primaryBouncerShowing 159 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing } 160 .collect { 161 startTransitionTo( 162 KeyguardState.PRIMARY_BOUNCER, 163 ownerReason = "#listenForLockscreenToPrimaryBouncer" 164 ) 165 } 166 } 167 } 168 169 private fun listenForLockscreenToAlternateBouncer() { 170 scope.launch("$TAG#listenForLockscreenToAlternateBouncer") { 171 keyguardInteractor.alternateBouncerShowing 172 .filterRelevantKeyguardStateAnd { isAlternateBouncerShowing -> 173 isAlternateBouncerShowing 174 } 175 .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) } 176 } 177 } 178 179 /* Starts transitions when manually dragging up the bouncer from the lockscreen. */ 180 private fun listenForLockscreenToPrimaryBouncerDragging() { 181 if (SceneContainerFlag.isEnabled) return 182 var transitionId: UUID? = null 183 scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") { 184 shadeRepository.legacyShadeExpansion 185 .sampleCombine( 186 startedKeyguardTransitionStep, 187 transitionInteractor.currentTransitionInfoInternal, 188 keyguardInteractor.statusBarState, 189 keyguardInteractor.isKeyguardDismissible, 190 ) 191 .collect { 192 ( 193 shadeExpansion, 194 startedStep, 195 currentTransitionInfo, 196 statusBarState, 197 isKeyguardUnlocked) -> 198 val id = transitionId 199 if (id != null) { 200 if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) { 201 // An existing `id` means a transition is started, and calls to 202 // `updateTransition` will control it until FINISHED or CANCELED 203 var nextState = 204 if (shadeExpansion == 0f) { 205 TransitionState.FINISHED 206 } else if (shadeExpansion == 1f) { 207 TransitionState.CANCELED 208 } else { 209 TransitionState.RUNNING 210 } 211 transitionRepository.updateTransition( 212 id, 213 // This maps the shadeExpansion to a much faster curve, to match 214 // the existing logic 215 1f - MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, shadeExpansion), 216 nextState, 217 ) 218 219 if ( 220 nextState == TransitionState.CANCELED || 221 nextState == TransitionState.FINISHED 222 ) { 223 transitionId = null 224 } 225 226 // If canceled, just put the state back 227 // TODO(b/278086361): This logic should happen in 228 // FromPrimaryBouncerInteractor. 229 if (nextState == TransitionState.CANCELED) { 230 transitionRepository.startTransition( 231 TransitionInfo( 232 ownerName = name, 233 from = KeyguardState.PRIMARY_BOUNCER, 234 to = KeyguardState.LOCKSCREEN, 235 animator = 236 getDefaultAnimatorForTransitionsToState( 237 KeyguardState.LOCKSCREEN 238 ) 239 .apply { duration = 0 } 240 ) 241 ) 242 } 243 } 244 } else { 245 // TODO (b/251849525): Remove statusbarstate check when that state is 246 // integrated into KeyguardTransitionRepository 247 if ( 248 // Use currentTransitionInfo to decide whether to start the transition. 249 currentTransitionInfo.to == KeyguardState.LOCKSCREEN && 250 shadeRepository.legacyShadeTracking.value && 251 !isKeyguardUnlocked && 252 statusBarState == KEYGUARD 253 ) { 254 transitionId = 255 startTransitionTo( 256 toState = KeyguardState.PRIMARY_BOUNCER, 257 animator = null, // transition will be manually controlled, 258 ownerReason = "#listenForLockscreenToPrimaryBouncerDragging" 259 ) 260 } 261 } 262 } 263 } 264 } 265 266 fun dismissKeyguard() { 267 scope.launch("$TAG#dismissKeyguard") { 268 startTransitionTo(KeyguardState.GONE, ownerReason = "#dismissKeyguard()") 269 } 270 } 271 272 private fun listenForLockscreenToGone() { 273 if (KeyguardWmStateRefactor.isEnabled) { 274 return 275 } 276 277 scope.launch("$TAG#listenForLockscreenToGone") { 278 keyguardInteractor.isKeyguardGoingAway 279 .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } 280 .collect { 281 startTransitionTo( 282 KeyguardState.GONE, 283 modeOnCanceled = TransitionModeOnCanceled.RESET, 284 ) 285 } 286 } 287 } 288 289 private fun listenForLockscreenToGoneDragging() { 290 if (SceneContainerFlag.isEnabled) return 291 if (KeyguardWmStateRefactor.isEnabled) { 292 // When the refactor is enabled, we no longer use isKeyguardGoingAway. 293 scope.launch("$TAG#listenForLockscreenToGoneDragging") { 294 swipeToDismissInteractor.dismissFling 295 .filterNotNull() 296 .filterRelevantKeyguardState() 297 .collect { _ -> startTransitionTo(KeyguardState.GONE) } 298 } 299 } 300 } 301 302 private fun listenForLockscreenToOccludedOrDreaming() { 303 if (KeyguardWmStateRefactor.isEnabled) { 304 scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") { 305 keyguardOcclusionInteractor.showWhenLockedActivityInfo 306 .filterRelevantKeyguardStateAnd { it.isOnTop } 307 .collect { taskInfo -> 308 startTransitionTo( 309 if (taskInfo.isDream()) { 310 KeyguardState.DREAMING 311 } else { 312 KeyguardState.OCCLUDED 313 } 314 ) 315 } 316 } 317 } else { 318 scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") { 319 keyguardInteractor.isKeyguardOccluded 320 .filterRelevantKeyguardStateAnd { isOccluded -> isOccluded } 321 .collect { startTransitionTo(KeyguardState.OCCLUDED) } 322 } 323 } 324 } 325 326 private fun listenForLockscreenToAodOrDozing() { 327 scope.launch("$TAG#listenForLockscreenToAodOrDozing") { 328 listenForSleepTransition( 329 modeOnCanceledFromStartedStep = { startedStep -> 330 if ( 331 transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD && 332 startedStep.from == KeyguardState.AOD 333 ) { 334 TransitionModeOnCanceled.REVERSE 335 } else { 336 TransitionModeOnCanceled.LAST_VALUE 337 } 338 } 339 ) 340 } 341 } 342 343 /** 344 * Listens for transition from glanceable hub back to lock screen and directly drives the 345 * keyguard transition. 346 */ 347 private fun listenForLockscreenToGlanceableHub() { 348 // TODO(b/336576536): Check if adaptation for scene framework is needed 349 if (SceneContainerFlag.isEnabled) return 350 if (!Flags.communalHub()) { 351 return 352 } 353 scope.launch(mainDispatcher) { 354 glanceableHubTransitions.listenForGlanceableHubTransition( 355 transitionOwnerName = TAG, 356 fromState = KeyguardState.LOCKSCREEN, 357 toState = KeyguardState.GLANCEABLE_HUB, 358 ) 359 } 360 } 361 362 override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { 363 return ValueAnimator().apply { 364 interpolator = Interpolators.LINEAR 365 duration = 366 when (toState) { 367 // Adds 100ms to the overall delay to workaround legacy setOccluded calls 368 // being delayed in KeyguardViewMediator 369 KeyguardState.DREAMING -> TO_DREAMING_DURATION + 100.milliseconds 370 KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION 371 KeyguardState.AOD -> TO_AOD_DURATION 372 KeyguardState.DOZING -> TO_DOZING_DURATION 373 KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> TO_DREAMING_HOSTED_DURATION 374 KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION 375 else -> DEFAULT_DURATION 376 }.inWholeMilliseconds 377 } 378 } 379 380 companion object { 381 private const val TAG = "FromLockscreenTransitionInteractor" 382 private val DEFAULT_DURATION = 400.milliseconds 383 val TO_DOZING_DURATION = 500.milliseconds 384 val TO_DREAMING_DURATION = 933.milliseconds 385 val TO_DREAMING_HOSTED_DURATION = 933.milliseconds 386 val TO_OCCLUDED_DURATION = 450.milliseconds 387 val TO_AOD_DURATION = 500.milliseconds 388 val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION 389 val TO_GONE_DURATION = 633.milliseconds 390 val TO_GLANCEABLE_HUB_DURATION = 1.seconds 391 } 392 } 393