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