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.app.tracing.coroutines.launch
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.keyguard.shared.model.TransitionModeOnCanceled
31 import com.android.systemui.power.domain.interactor.PowerInteractor
32 import com.android.systemui.scene.shared.flag.SceneContainerFlag
33 import com.android.systemui.util.kotlin.Utils.Companion.sample
34 import javax.inject.Inject
35 import kotlin.time.Duration.Companion.milliseconds
36 import kotlinx.coroutines.CoroutineDispatcher
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.combine
40 import kotlinx.coroutines.flow.debounce
41 
42 @SysUISingleton
43 class FromAodTransitionInteractor
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     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
54     val deviceEntryRepository: DeviceEntryRepository,
55 ) :
56     TransitionInteractor(
57         fromState = KeyguardState.AOD,
58         transitionInteractor = transitionInteractor,
59         mainDispatcher = mainDispatcher,
60         bgDispatcher = bgDispatcher,
61         powerInteractor = powerInteractor,
62         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
63         keyguardInteractor = keyguardInteractor,
64     ) {
65 
66     override fun start() {
67         listenForAodToAwake()
68         listenForAodToOccluded()
69         listenForAodToPrimaryBouncer()
70         listenForTransitionToCamera(scope, keyguardInteractor)
71     }
72 
73     private val canDismissLockscreen: Flow<Boolean> =
74         combine(
75             keyguardInteractor.isKeyguardShowing,
76             keyguardInteractor.isKeyguardDismissible,
77             keyguardInteractor.biometricUnlockState,
78         ) { isKeyguardShowing, isKeyguardDismissible, biometricUnlockState ->
79             (isWakeAndUnlock(biometricUnlockState.mode) ||
80                 (!isKeyguardShowing && isKeyguardDismissible))
81         }
82 
83     /**
84      * Listen for the signal that we're waking up and figure what state we need to transition to.
85      */
86     private fun listenForAodToAwake() {
87         // Use PowerInteractor's wakefulness, which is the earliest wake signal available. We
88         // have all of the information we need at this time to make a decision about where to
89         // transition.
90         scope.launch("$TAG#listenForAodToAwake") {
91             powerInteractor.detailedWakefulness
92                 .filterRelevantKeyguardStateAnd { wakefulness -> wakefulness.isAwake() }
93                 .debounce(50L)
94                 .sample(
95                     startedKeyguardTransitionStep,
96                     keyguardInteractor.biometricUnlockState,
97                     keyguardInteractor.primaryBouncerShowing,
98                     keyguardInteractor.isKeyguardOccluded,
99                     canDismissLockscreen,
100                 )
101                 .collect {
102                     (
103                         _,
104                         startedStep,
105                         biometricUnlockState,
106                         primaryBouncerShowing,
107                         isKeyguardOccludedLegacy,
108                         canDismissLockscreen,
109                     ) ->
110                     if (!maybeHandleInsecurePowerGesture()) {
111                         val shouldTransitionToLockscreen =
112                             if (KeyguardWmStateRefactor.isEnabled) {
113                                 // Check with the superclass to see if an occlusion transition is
114                                 // needed. Also, don't react to wake and unlock events, as we'll be
115                                 // receiving a call to #dismissAod() shortly when the authentication
116                                 // completes.
117                                 !maybeStartTransitionToOccludedOrInsecureCamera() &&
118                                     !isWakeAndUnlock(biometricUnlockState.mode) &&
119                                     !primaryBouncerShowing
120                             } else {
121                                 !isKeyguardOccludedLegacy &&
122                                     !isWakeAndUnlock(biometricUnlockState.mode) &&
123                                     !primaryBouncerShowing
124                             }
125 
126                         // With the refactor enabled, maybeStartTransitionToOccludedOrInsecureCamera
127                         // handles transitioning to OCCLUDED.
128                         val shouldTransitionToOccluded =
129                             !KeyguardWmStateRefactor.isEnabled && isKeyguardOccludedLegacy
130 
131                         val shouldTransitionToGone =
132                             (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) ||
133                                 (KeyguardWmStateRefactor.isEnabled &&
134                                     !deviceEntryRepository.isLockscreenEnabled())
135 
136                         if (shouldTransitionToGone) {
137                             startTransitionTo(
138                                 toState = KeyguardState.GONE,
139                             )
140                         } else if (shouldTransitionToLockscreen) {
141                             val modeOnCanceled =
142                                 if (startedStep.from == KeyguardState.LOCKSCREEN) {
143                                     TransitionModeOnCanceled.REVERSE
144                                 } else if (startedStep.from == KeyguardState.GONE) {
145                                     TransitionModeOnCanceled.RESET
146                                 } else {
147                                     TransitionModeOnCanceled.LAST_VALUE
148                                 }
149 
150                             startTransitionTo(
151                                 toState = KeyguardState.LOCKSCREEN,
152                                 modeOnCanceled = modeOnCanceled,
153                                 ownerReason = "listen for aod to awake"
154                             )
155                         } else if (shouldTransitionToOccluded) {
156                             startTransitionTo(
157                                 toState = KeyguardState.OCCLUDED,
158                                 ownerReason = "waking up and isOccluded=true",
159                             )
160                         }
161                     }
162                 }
163         }
164     }
165 
166     /**
167      * There are cases where the transition to AOD begins but never completes, such as tapping power
168      * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
169      * run AOD->OCCLUDED.
170      */
171     private fun listenForAodToOccluded() {
172         if (KeyguardWmStateRefactor.isEnabled) {
173             // Handled by calls to maybeStartTransitionToOccludedOrInsecureCamera on waking.
174             return
175         }
176 
177         scope.launch("$TAG#listenForAodToOccluded") {
178             keyguardInteractor.isKeyguardOccluded
179                 .filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
180                 .collect {
181                     if (!maybeHandleInsecurePowerGesture()) {
182                         startTransitionTo(
183                             toState = KeyguardState.OCCLUDED,
184                             modeOnCanceled = TransitionModeOnCanceled.RESET,
185                             ownerReason = "isOccluded = true",
186                         )
187                     }
188                 }
189         }
190     }
191 
192     /**
193      * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
194      * PRIMARY_BOUNCER.
195      */
196     private fun listenForAodToPrimaryBouncer() {
197         if (SceneContainerFlag.isEnabled) return
198         scope.launch("$TAG#listenForAodToPrimaryBouncer") {
199             keyguardInteractor.primaryBouncerShowing
200                 .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
201                 .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) }
202         }
203     }
204 
205     /**
206      * Dismisses AOD and transitions to GONE. This is called whenever authentication occurs while on
207      * AOD.
208      */
209     fun dismissAod() {
210         scope.launch("$TAG#dismissAod") { startTransitionTo(KeyguardState.GONE) }
211     }
212 
213     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
214         return ValueAnimator().apply {
215             interpolator = Interpolators.LINEAR
216             duration =
217                 when (toState) {
218                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
219                     else -> DEFAULT_DURATION
220                 }.inWholeMilliseconds
221         }
222     }
223 
224     companion object {
225         private const val TAG = "FromAodTransitionInteractor"
226         private val DEFAULT_DURATION = 500.milliseconds
227         val TO_LOCKSCREEN_DURATION = 500.milliseconds
228         val TO_GONE_DURATION = DEFAULT_DURATION
229         val TO_OCCLUDED_DURATION = DEFAULT_DURATION
230         val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
231     }
232 }
233