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 com.android.app.animation.Interpolators 21 import com.android.systemui.communal.domain.interactor.CommunalInteractor 22 import com.android.systemui.dagger.SysUISingleton 23 import com.android.systemui.dagger.qualifiers.Background 24 import com.android.systemui.dagger.qualifiers.Main 25 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository 26 import com.android.systemui.keyguard.KeyguardWmStateRefactor 27 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 28 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock 29 import com.android.systemui.keyguard.shared.model.KeyguardState 30 import com.android.systemui.power.domain.interactor.PowerInteractor 31 import com.android.systemui.util.kotlin.Utils.Companion.sample 32 import com.android.systemui.util.kotlin.sample 33 import javax.inject.Inject 34 import kotlin.time.Duration.Companion.milliseconds 35 import kotlinx.coroutines.CoroutineDispatcher 36 import kotlinx.coroutines.CoroutineScope 37 import kotlinx.coroutines.flow.Flow 38 import kotlinx.coroutines.flow.combine 39 import kotlinx.coroutines.flow.debounce 40 import kotlinx.coroutines.launch 41 42 @SysUISingleton 43 class FromDozingTransitionInteractor 44 @Inject 45 constructor( 46 override val transitionRepository: KeyguardTransitionRepository, 47 transitionInteractor: KeyguardTransitionInteractor, 48 @Background private val scope: CoroutineScope, 49 @Background bgDispatcher: CoroutineDispatcher, 50 @Main mainDispatcher: CoroutineDispatcher, 51 keyguardInteractor: KeyguardInteractor, 52 powerInteractor: PowerInteractor, 53 private val communalInteractor: CommunalInteractor, 54 keyguardOcclusionInteractor: KeyguardOcclusionInteractor, 55 val deviceEntryRepository: DeviceEntryRepository, 56 ) : 57 TransitionInteractor( 58 fromState = KeyguardState.DOZING, 59 transitionInteractor = transitionInteractor, 60 mainDispatcher = mainDispatcher, 61 bgDispatcher = bgDispatcher, 62 powerInteractor = powerInteractor, 63 keyguardOcclusionInteractor = keyguardOcclusionInteractor, 64 keyguardInteractor = keyguardInteractor, 65 ) { 66 67 override fun start() { 68 listenForDozingToAny() 69 listenForDozingToGoneViaBiometrics() 70 listenForWakeFromDozing() 71 listenForTransitionToCamera(scope, keyguardInteractor) 72 } 73 74 private val canTransitionToGoneOnWake: Flow<Boolean> = 75 combine( 76 keyguardInteractor.isKeyguardShowing, 77 keyguardInteractor.isKeyguardDismissible, 78 ) { isKeyguardShowing, isKeyguardDismissible -> 79 isKeyguardDismissible && !isKeyguardShowing 80 } 81 82 private fun listenForDozingToGoneViaBiometrics() { 83 if (KeyguardWmStateRefactor.isEnabled) { 84 return 85 } 86 87 // This is separate from `listenForDozingToAny` because any delay on wake and unlock will 88 // cause a noticeable issue with animations 89 scope.launch { 90 powerInteractor.isAwake 91 .filterRelevantKeyguardStateAnd { isAwake -> isAwake } 92 .sample( 93 keyguardInteractor.biometricUnlockState, 94 ::Pair, 95 ) 96 .collect { 97 ( 98 _, 99 biometricUnlockState, 100 ) -> 101 if (isWakeAndUnlock(biometricUnlockState.mode)) { 102 startTransitionTo( 103 KeyguardState.GONE, 104 ownerReason = "biometric wake and unlock", 105 ) 106 } 107 } 108 } 109 } 110 111 private fun listenForDozingToAny() { 112 if (KeyguardWmStateRefactor.isEnabled) { 113 return 114 } 115 116 scope.launch { 117 powerInteractor.isAwake 118 .debounce(50L) 119 .filterRelevantKeyguardStateAnd { isAwake -> isAwake } 120 .sample( 121 keyguardInteractor.isKeyguardOccluded, 122 communalInteractor.isIdleOnCommunal, 123 canTransitionToGoneOnWake, 124 keyguardInteractor.primaryBouncerShowing, 125 ) 126 .collect { 127 ( 128 _, 129 occluded, 130 isIdleOnCommunal, 131 canTransitionToGoneOnWake, 132 primaryBouncerShowing) -> 133 startTransitionTo( 134 if (!deviceEntryRepository.isLockscreenEnabled()) { 135 KeyguardState.GONE 136 } else if (canTransitionToGoneOnWake) { 137 KeyguardState.GONE 138 } else if (primaryBouncerShowing) { 139 KeyguardState.PRIMARY_BOUNCER 140 } else if (occluded) { 141 KeyguardState.OCCLUDED 142 } else if (isIdleOnCommunal) { 143 KeyguardState.GLANCEABLE_HUB 144 } else { 145 KeyguardState.LOCKSCREEN 146 } 147 ) 148 } 149 } 150 } 151 152 /** Figure out what state to transition to when we awake from DOZING. */ 153 private fun listenForWakeFromDozing() { 154 if (!KeyguardWmStateRefactor.isEnabled) { 155 return 156 } 157 158 scope.launch { 159 powerInteractor.detailedWakefulness 160 .filterRelevantKeyguardStateAnd { it.isAwake() } 161 .sample( 162 communalInteractor.isIdleOnCommunal, 163 keyguardInteractor.biometricUnlockState, 164 canTransitionToGoneOnWake, 165 keyguardInteractor.primaryBouncerShowing, 166 ) 167 .collect { 168 ( 169 _, 170 isIdleOnCommunal, 171 biometricUnlockState, 172 canDismissLockscreen, 173 primaryBouncerShowing) -> 174 if ( 175 !maybeStartTransitionToOccludedOrInsecureCamera() && 176 // Handled by dismissFromDozing(). 177 !isWakeAndUnlock(biometricUnlockState.mode) 178 ) { 179 startTransitionTo( 180 if (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) { 181 KeyguardState.GONE 182 } else if ( 183 KeyguardWmStateRefactor.isEnabled && 184 !deviceEntryRepository.isLockscreenEnabled() 185 ) { 186 KeyguardState.GONE 187 } else if (primaryBouncerShowing) { 188 KeyguardState.PRIMARY_BOUNCER 189 } else if (isIdleOnCommunal) { 190 KeyguardState.GLANCEABLE_HUB 191 } else { 192 KeyguardState.LOCKSCREEN 193 }, 194 ownerReason = "waking from dozing" 195 ) 196 } 197 } 198 } 199 } 200 201 /** Dismisses keyguard from the DOZING state. */ 202 fun dismissFromDozing() { 203 scope.launch { startTransitionTo(KeyguardState.GONE) } 204 } 205 206 override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { 207 return ValueAnimator().apply { 208 interpolator = Interpolators.LINEAR 209 duration = DEFAULT_DURATION.inWholeMilliseconds 210 } 211 } 212 213 companion object { 214 const val TAG = "FromDozingTransitionInteractor" 215 private val DEFAULT_DURATION = 500.milliseconds 216 val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION 217 val TO_GONE_DURATION = DEFAULT_DURATION 218 val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION 219 } 220 } 221