1 /* <lambda>null2 * Copyright (C) 2023 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.app.tracing.coroutines.launchTraced as launch 22 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor 23 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor 24 import com.android.systemui.communal.shared.model.CommunalScenes 25 import com.android.systemui.communal.shared.model.CommunalTransitionKeys 26 import com.android.systemui.communal.shared.model.EditModeState 27 import com.android.systemui.dagger.SysUISingleton 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.KeyguardState 33 import com.android.systemui.power.domain.interactor.PowerInteractor 34 import com.android.systemui.scene.shared.flag.SceneContainerFlag 35 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf 36 import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf 37 import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter 38 import com.android.systemui.util.kotlin.sample 39 import javax.inject.Inject 40 import kotlin.time.Duration.Companion.milliseconds 41 import kotlin.time.Duration.Companion.seconds 42 import kotlinx.coroutines.CoroutineDispatcher 43 import kotlinx.coroutines.CoroutineScope 44 import kotlinx.coroutines.FlowPreview 45 import kotlinx.coroutines.flow.combine 46 import kotlinx.coroutines.flow.debounce 47 48 @OptIn(FlowPreview::class) 49 @SysUISingleton 50 class FromGlanceableHubTransitionInteractor 51 @Inject 52 constructor( 53 @Background private val scope: CoroutineScope, 54 @Main mainDispatcher: CoroutineDispatcher, 55 @Background bgDispatcher: CoroutineDispatcher, 56 private val communalSettingsInteractor: CommunalSettingsInteractor, 57 keyguardInteractor: KeyguardInteractor, 58 private val communalSceneInteractor: CommunalSceneInteractor, 59 override val transitionRepository: KeyguardTransitionRepository, 60 override val internalTransitionInteractor: InternalKeyguardTransitionInteractor, 61 transitionInteractor: KeyguardTransitionInteractor, 62 powerInteractor: PowerInteractor, 63 keyguardOcclusionInteractor: KeyguardOcclusionInteractor, 64 ) : 65 TransitionInteractor( 66 fromState = KeyguardState.GLANCEABLE_HUB, 67 transitionInteractor = transitionInteractor, 68 mainDispatcher = mainDispatcher, 69 bgDispatcher = bgDispatcher, 70 powerInteractor = powerInteractor, 71 keyguardOcclusionInteractor = keyguardOcclusionInteractor, 72 keyguardInteractor = keyguardInteractor, 73 ) { 74 75 override fun start() { 76 if (SceneContainerFlag.isEnabled) return 77 if (!communalSettingsInteractor.isCommunalFlagEnabled()) { 78 return 79 } 80 listenForHubToAodOrDozing() 81 listenForHubToPrimaryBouncer() 82 listenForHubToAlternateBouncer() 83 listenForHubToOccluded() 84 listenForHubToGone() 85 listenForHubToDreaming() 86 } 87 88 override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { 89 return ValueAnimator().apply { 90 interpolator = Interpolators.LINEAR 91 duration = 92 when (toState) { 93 KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION 94 KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION 95 KeyguardState.ALTERNATE_BOUNCER -> TO_BOUNCER_DURATION 96 KeyguardState.PRIMARY_BOUNCER -> TO_BOUNCER_DURATION 97 else -> DEFAULT_DURATION 98 }.inWholeMilliseconds 99 } 100 } 101 102 private fun listenForHubToPrimaryBouncer() { 103 scope.launch("$TAG#listenForHubToPrimaryBouncer") { 104 keyguardInteractor.primaryBouncerShowing 105 .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing } 106 .collect { 107 // Bouncer shows on top of the hub, so do not change scenes here. 108 startTransitionTo(KeyguardState.PRIMARY_BOUNCER) 109 } 110 } 111 } 112 113 private fun listenForHubToAlternateBouncer() { 114 scope.launch("$TAG#listenForHubToAlternateBouncer") { 115 keyguardInteractor.alternateBouncerShowing 116 .filterRelevantKeyguardStateAnd { alternateBouncerShowing -> 117 alternateBouncerShowing 118 } 119 .collect { pair -> 120 // Bouncer shows on top of the hub, so do not change scenes here. 121 startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) 122 } 123 } 124 } 125 126 private fun listenForHubToAodOrDozing() { 127 scope.launch { 128 powerInteractor.isAsleep 129 .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep } 130 .collect { 131 communalSceneInteractor.changeScene( 132 newScene = CommunalScenes.Blank, 133 loggingReason = "hub to sleep", 134 keyguardState = keyguardInteractor.asleepKeyguardState.value, 135 ) 136 } 137 } 138 } 139 140 private fun listenForHubToDreaming() { 141 if (!communalSettingsInteractor.isV2FlagEnabled()) { 142 return 143 } 144 145 scope.launch { 146 keyguardInteractor.isAbleToDream 147 .filterRelevantKeyguardStateAnd { isAbleToDream -> isAbleToDream } 148 .collect { 149 communalSceneInteractor.changeScene( 150 newScene = CommunalScenes.Blank, 151 loggingReason = "hub to dreaming", 152 keyguardState = KeyguardState.DREAMING, 153 ) 154 } 155 } 156 } 157 158 private fun listenForHubToOccluded() { 159 if (KeyguardWmStateRefactor.isEnabled) { 160 scope.launch { 161 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop 162 .filterRelevantKeyguardStateAnd { onTop -> onTop } 163 .collect { 164 maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> 165 communalSceneInteractor.changeScene( 166 newScene = CommunalScenes.Blank, 167 loggingReason = "hub to occluded (KeyguardWmStateRefactor)", 168 transitionKey = CommunalTransitionKeys.SimpleFade, 169 keyguardState = state, 170 ) 171 null 172 } 173 } 174 } 175 } else { 176 scope.launch { 177 combine( 178 keyguardInteractor.isKeyguardOccluded, 179 keyguardInteractor.isDreaming, 180 ::Pair, 181 ) 182 // Debounce signals since there is a race condition between the occluded and 183 // dreaming signals when starting or stopping dreaming. We therefore add a small 184 // delay to give enough time for occluded to flip to false when the dream 185 // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream 186 // or when the dream starts underneath the hub. 187 .debounce(200.milliseconds) 188 .sampleFilter( 189 // When launching activities from widgets on the hub, we have a 190 // custom occlusion animation. 191 communalSceneInteractor.isLaunchingWidget 192 ) { launchingWidget -> 193 !launchingWidget 194 } 195 .filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) -> 196 isOccluded && !isDreaming 197 } 198 .collect { _ -> 199 communalSceneInteractor.changeScene( 200 newScene = CommunalScenes.Blank, 201 loggingReason = "hub to occluded", 202 transitionKey = CommunalTransitionKeys.SimpleFade, 203 keyguardState = KeyguardState.OCCLUDED, 204 ) 205 } 206 } 207 } 208 } 209 210 private fun listenForHubToGone() { 211 if (SceneContainerFlag.isEnabled) return 212 scope.launch { 213 allOf( 214 keyguardInteractor.isKeyguardGoingAway, 215 // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE 216 // state until after edit mode is ready to be shown. 217 noneOf( 218 // When launching activities from widgets on the hub, we wait to change 219 // scenes until the activity launch is complete. 220 communalSceneInteractor.isLaunchingWidget 221 ), 222 ) 223 .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } 224 .sample(communalSceneInteractor.editModeState, ::Pair) 225 .collect { (_, editModeState) -> 226 if ( 227 editModeState == EditModeState.STARTING || 228 editModeState == EditModeState.SHOWING 229 ) { 230 // Don't change scenes here as that is handled by the edit activity. 231 startTransitionTo(KeyguardState.GONE) 232 } else { 233 communalSceneInteractor.changeScene( 234 newScene = CommunalScenes.Blank, 235 loggingReason = "hub to gone", 236 transitionKey = CommunalTransitionKeys.SimpleFade, 237 keyguardState = KeyguardState.GONE, 238 ) 239 } 240 } 241 } 242 } 243 244 companion object { 245 const val TAG = "FromGlanceableHubTransitionInteractor" 246 247 /** 248 * DEFAULT_DURATION controls the timing for all animations other than those with overrides 249 * in [getDefaultAnimatorForTransitionsToState]. 250 * 251 * Set at 400ms for parity with [FromLockscreenTransitionInteractor] 252 */ 253 val DEFAULT_DURATION = 400.milliseconds 254 // To lockscreen duration must be at least 500ms to allow for potential screen rotation 255 // during the transition while the animation begins after 500ms. 256 val TO_LOCKSCREEN_DURATION = 1.seconds 257 val TO_BOUNCER_DURATION = 400.milliseconds 258 val TO_OCCLUDED_DURATION = 450.milliseconds 259 val TO_AOD_DURATION = 500.milliseconds 260 } 261 } 262