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.tracing.coroutines.launchTraced as launch 21 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor 22 import com.android.systemui.communal.domain.interactor.CommunalInteractor 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.Background 28 import com.android.systemui.dagger.qualifiers.Main 29 import com.android.systemui.keyguard.KeyguardWmStateRefactor 30 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 31 import com.android.systemui.keyguard.shared.model.Edge 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.scene.shared.model.Scenes 36 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine 37 import com.android.wm.shell.shared.animation.Interpolators 38 import javax.inject.Inject 39 import kotlin.time.Duration.Companion.milliseconds 40 import kotlinx.coroutines.CoroutineDispatcher 41 import kotlinx.coroutines.CoroutineScope 42 import kotlinx.coroutines.delay 43 import kotlinx.coroutines.flow.Flow 44 import kotlinx.coroutines.flow.distinctUntilChanged 45 import kotlinx.coroutines.flow.drop 46 import kotlinx.coroutines.flow.emptyFlow 47 import kotlinx.coroutines.flow.filter 48 import kotlinx.coroutines.flow.flatMapLatest 49 import kotlinx.coroutines.flow.map 50 import kotlinx.coroutines.flow.merge 51 import kotlinx.coroutines.flow.onEach 52 import kotlinx.coroutines.flow.onStart 53 54 @SysUISingleton 55 class FromAlternateBouncerTransitionInteractor 56 @Inject 57 constructor( 58 override val transitionRepository: KeyguardTransitionRepository, 59 override val internalTransitionInteractor: InternalKeyguardTransitionInteractor, 60 transitionInteractor: KeyguardTransitionInteractor, 61 @Background private val scope: CoroutineScope, 62 @Background bgDispatcher: CoroutineDispatcher, 63 @Main mainDispatcher: CoroutineDispatcher, 64 keyguardInteractor: KeyguardInteractor, 65 private val communalInteractor: CommunalInteractor, 66 private val communalSettingsInteractor: CommunalSettingsInteractor, 67 private val communalSceneInteractor: CommunalSceneInteractor, 68 powerInteractor: PowerInteractor, 69 keyguardOcclusionInteractor: KeyguardOcclusionInteractor, 70 private val primaryBouncerInteractor: PrimaryBouncerInteractor, 71 ) : 72 TransitionInteractor( 73 fromState = KeyguardState.ALTERNATE_BOUNCER, 74 transitionInteractor = transitionInteractor, 75 mainDispatcher = mainDispatcher, 76 bgDispatcher = bgDispatcher, 77 powerInteractor = powerInteractor, 78 keyguardOcclusionInteractor = keyguardOcclusionInteractor, 79 keyguardInteractor = keyguardInteractor, 80 ) { 81 82 override fun start() { 83 listenForAlternateBouncerToGone() 84 listenForAlternateBouncerToLockscreenHubAodOrDozing() 85 listenForAlternateBouncerToPrimaryBouncer() 86 listenForTransitionToCamera(scope, keyguardInteractor) 87 } 88 89 val surfaceBehindVisibility: Flow<Boolean?> = 90 transitionInteractor 91 .transition( 92 edge = Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = Scenes.Gone), 93 edgeWithoutSceneContainer = 94 Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE), 95 ) 96 .map { 97 // The alt bouncer is pretty fast to hide, so start the surface behind animation 98 // around 30%. 99 it.value > 0.3f 100 } 101 .onStart<Boolean?> { 102 // Default to null ("don't care, use a reasonable default"). 103 emit(null) 104 } 105 .distinctUntilChanged() 106 107 private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() { 108 scope.launch { 109 keyguardInteractor.alternateBouncerShowing 110 // Add a slight delay, as alternateBouncer and primaryBouncer showing event changes 111 // will arrive with a small gap in time. This prevents a transition to LOCKSCREEN 112 // happening prematurely. 113 // This should eventually be removed in favor of 114 // [KeyguardTransitionInteractor#startDismissKeyguardTransition] 115 .onEach { delay(150L) } 116 .sampleCombine( 117 keyguardInteractor.primaryBouncerShowing, 118 powerInteractor.isAwake, 119 keyguardInteractor.isAodAvailable, 120 communalSceneInteractor.isIdleOnCommunal, 121 keyguardInteractor.isDreaming, 122 keyguardInteractor.isKeyguardOccluded, 123 ) 124 .filterRelevantKeyguardStateAnd { 125 (isAlternateBouncerShowing, isPrimaryBouncerShowing, _, _, _, _) -> 126 !isAlternateBouncerShowing && !isPrimaryBouncerShowing 127 } 128 .collect { (_, _, isAwake, isAodAvailable, isIdleOnCommunal, isDreaming, isOccluded) 129 -> 130 // When unlocking over glanceable hub to enter edit mode, transitioning directly 131 // to GONE prevents the lockscreen flash. Let listenForAlternateBouncerToGone 132 // handle it. 133 if (communalInteractor.editModeOpen.value) return@collect 134 val hubV2 = communalSettingsInteractor.isV2FlagEnabled() 135 val to = 136 if (!isAwake) { 137 if (isAodAvailable) { 138 KeyguardState.AOD 139 } else { 140 KeyguardState.DOZING 141 } 142 } else { 143 if (!hubV2 && isIdleOnCommunal) { 144 if (SceneContainerFlag.isEnabled) return@collect 145 KeyguardState.GLANCEABLE_HUB 146 } else if (isOccluded && !isDreaming) { 147 KeyguardState.OCCLUDED 148 } else if (isDreaming) { 149 KeyguardState.DREAMING 150 } else if (hubV2 && isIdleOnCommunal) { 151 if (SceneContainerFlag.isEnabled) return@collect 152 KeyguardState.GLANCEABLE_HUB 153 } else { 154 KeyguardState.LOCKSCREEN 155 } 156 } 157 158 if (hubV2 && to != KeyguardState.GLANCEABLE_HUB && isIdleOnCommunal) { 159 // If bouncer is showing over the hub, we need to make sure we 160 // properly dismiss the hub when transitioning away. 161 communalSceneInteractor.changeScene( 162 newScene = CommunalScenes.Blank, 163 loggingReason = "alternate bouncer no longer showing over GH", 164 keyguardState = to, 165 ) 166 } else { 167 startTransitionTo(to) 168 } 169 } 170 } 171 } 172 173 private fun listenForAlternateBouncerToGone() { 174 if (SceneContainerFlag.isEnabled) return 175 if (KeyguardWmStateRefactor.isEnabled) { 176 // Handled via #dismissAlternateBouncer. 177 return 178 } 179 180 scope.launch { 181 merge( 182 keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit 183 keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded -> 184 if (keyguardOccluded) { 185 primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled 186 // drop the initial state 187 .drop(1) 188 } else { 189 emptyFlow() 190 } 191 }, 192 ) 193 .filterRelevantKeyguardState() 194 .collect { startTransitionTo(KeyguardState.GONE) } 195 } 196 } 197 198 private fun listenForAlternateBouncerToPrimaryBouncer() { 199 if (SceneContainerFlag.isEnabled) return 200 scope.launch { 201 keyguardInteractor.primaryBouncerShowing 202 .filterRelevantKeyguardStateAnd { isPrimaryBouncerShowing -> 203 isPrimaryBouncerShowing 204 } 205 .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) } 206 } 207 } 208 209 override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { 210 return ValueAnimator().apply { 211 interpolator = Interpolators.LINEAR 212 duration = 213 when (toState) { 214 KeyguardState.AOD -> TO_AOD_DURATION 215 KeyguardState.DOZING -> TO_DOZING_DURATION 216 KeyguardState.GONE -> TO_GONE_DURATION 217 KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION 218 KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION 219 KeyguardState.PRIMARY_BOUNCER -> TO_PRIMARY_BOUNCER_DURATION 220 else -> TRANSITION_DURATION_MS 221 }.inWholeMilliseconds 222 } 223 } 224 225 fun dismissAlternateBouncer() { 226 scope.launch { startTransitionTo(KeyguardState.GONE) } 227 } 228 229 companion object { 230 const val TAG = "FromAlternateBouncerTransitionInteractor" 231 val TRANSITION_DURATION_MS = 300.milliseconds 232 val TO_AOD_DURATION = TRANSITION_DURATION_MS 233 val TO_DOZING_DURATION = TRANSITION_DURATION_MS 234 val TO_GONE_DURATION = 500.milliseconds 235 val TO_LOCKSCREEN_DURATION = 300.milliseconds 236 val TO_OCCLUDED_DURATION = TRANSITION_DURATION_MS 237 val TO_PRIMARY_BOUNCER_DURATION = TRANSITION_DURATION_MS 238 } 239 } 240