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