• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.bouncer.domain.interactor
18 
19 import com.android.keyguard.KeyguardUpdateMonitor
20 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
21 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
25 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
26 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
27 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
28 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
29 import com.android.systemui.keyguard.shared.model.KeyguardState
30 import com.android.systemui.plugins.statusbar.StatusBarStateController
31 import com.android.systemui.scene.domain.interactor.SceneInteractor
32 import com.android.systemui.scene.shared.flag.SceneContainerFlag
33 import com.android.systemui.scene.shared.model.Scenes
34 import com.android.systemui.statusbar.policy.KeyguardStateController
35 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
36 import com.android.systemui.util.time.SystemClock
37 import dagger.Lazy
38 import javax.inject.Inject
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.flow.Flow
41 import kotlinx.coroutines.flow.SharingStarted
42 import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
43 import kotlinx.coroutines.flow.StateFlow
44 import kotlinx.coroutines.flow.combine
45 import kotlinx.coroutines.flow.distinctUntilChanged
46 import kotlinx.coroutines.flow.flatMapLatest
47 import kotlinx.coroutines.flow.flowOf
48 import kotlinx.coroutines.flow.map
49 import kotlinx.coroutines.flow.stateIn
50 
51 /** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
52 @SysUISingleton
53 class AlternateBouncerInteractor
54 @Inject
55 constructor(
56     private val statusBarStateController: StatusBarStateController,
57     private val keyguardStateController: KeyguardStateController,
58     private val bouncerRepository: KeyguardBouncerRepository,
59     fingerprintPropertyRepository: FingerprintPropertyRepository,
60     private val biometricSettingsRepository: BiometricSettingsRepository,
61     private val systemClock: SystemClock,
62     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
63     private val deviceEntryFingerprintAuthInteractor: Lazy<DeviceEntryFingerprintAuthInteractor>,
64     private val keyguardInteractor: Lazy<KeyguardInteractor>,
65     keyguardTransitionInteractor: Lazy<KeyguardTransitionInteractor>,
66     sceneInteractor: Lazy<SceneInteractor>,
67     @Application scope: CoroutineScope,
68 ) {
69     var receivedDownTouch = false
70     val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
71     private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
72     val alternateBouncerSupported: StateFlow<Boolean> =
73         if (DeviceEntryUdfpsRefactor.isEnabled) {
74             fingerprintPropertyRepository.sensorType
75                 .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() }
76                 .stateIn(
77                     scope = scope,
78                     started = SharingStarted.Eagerly,
79                     initialValue = false,
80                 )
81         } else {
82             bouncerRepository.alternateBouncerUIAvailable
83         }
84     private val isDozingOrAod: Flow<Boolean> =
85         anyOf(
86                 keyguardTransitionInteractor.get().transitionValue(KeyguardState.DOZING).map {
87                     it > 0f
88                 },
89                 keyguardTransitionInteractor.get().transitionValue(KeyguardState.AOD).map {
90                     it > 0f
91                 },
92             )
93             .distinctUntilChanged()
94 
95     /**
96      * Whether the current biometric, bouncer, and keyguard states allow the alternate bouncer to
97      * show.
98      */
99     val canShowAlternateBouncer: StateFlow<Boolean> =
100         alternateBouncerSupported
101             .flatMapLatest { alternateBouncerSupported ->
102                 if (alternateBouncerSupported) {
103                     combine(
104                             keyguardTransitionInteractor.get().currentKeyguardState,
105                             if (SceneContainerFlag.isEnabled) {
106                                 sceneInteractor.get().currentScene
107                             } else {
108                                 flowOf(Scenes.Lockscreen)
109                             },
110                             ::Pair
111                         )
112                         .flatMapLatest { (currentKeyguardState, transitionState) ->
113                             if (currentKeyguardState == KeyguardState.GONE) {
114                                 flowOf(false)
115                             } else if (
116                                 SceneContainerFlag.isEnabled && transitionState == Scenes.Gone
117                             ) {
118                                 flowOf(false)
119                             } else {
120                                 combine(
121                                     deviceEntryFingerprintAuthInteractor
122                                         .get()
123                                         .isFingerprintAuthCurrentlyAllowed,
124                                     keyguardInteractor.get().isKeyguardDismissible,
125                                     bouncerRepository.primaryBouncerShow,
126                                     isDozingOrAod
127                                 ) {
128                                     fingerprintAllowed,
129                                     keyguardDismissible,
130                                     primaryBouncerShowing,
131                                     dozing ->
132                                     fingerprintAllowed &&
133                                         !keyguardDismissible &&
134                                         !primaryBouncerShowing &&
135                                         !dozing
136                                 }
137                             }
138                         }
139                 } else {
140                     flowOf(false)
141                 }
142             }
143             .stateIn(
144                 scope = scope,
145                 started = WhileSubscribed(),
146                 initialValue = false,
147             )
148 
149     /**
150      * Always shows the alternate bouncer. Requesters must check [canShowAlternateBouncer]` before
151      * calling this.
152      */
153     fun forceShow() {
154         if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
155             show()
156             return
157         }
158         bouncerRepository.setAlternateVisible(true)
159     }
160 
161     /**
162      * Sets the correct bouncer states to show the alternate bouncer if it can show.
163      *
164      * @return whether alternateBouncer is visible
165      * @deprecated use [forceShow] and manually check [canShowAlternateBouncer] beforehand
166      */
167     fun show(): Boolean {
168         DeviceEntryUdfpsRefactor.assertInLegacyMode()
169         bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
170         return isVisibleState()
171     }
172 
173     /**
174      * Sets the correct bouncer states to hide the bouncer. Should only be called through
175      * StatusBarKeyguardViewManager until ScrimController is refactored to use
176      * alternateBouncerInteractor.
177      *
178      * @return true if the alternate bouncer was newly hidden, else false.
179      */
180     fun hide(): Boolean {
181         receivedDownTouch = false
182         val wasAlternateBouncerVisible = isVisibleState()
183         bouncerRepository.setAlternateVisible(false)
184         return wasAlternateBouncerVisible && !isVisibleState()
185     }
186 
187     fun isVisibleState(): Boolean {
188         return bouncerRepository.alternateBouncerVisible.value
189     }
190 
191     fun setAlternateBouncerUIAvailable(isAvailable: Boolean, token: String) {
192         DeviceEntryUdfpsRefactor.assertInLegacyMode()
193         if (isAvailable) {
194             alternateBouncerUiAvailableFromSource.add(token)
195         } else {
196             alternateBouncerUiAvailableFromSource.remove(token)
197         }
198         bouncerRepository.setAlternateBouncerUIAvailable(
199             alternateBouncerUiAvailableFromSource.isNotEmpty()
200         )
201     }
202 
203     fun canShowAlternateBouncerForFingerprint(): Boolean {
204         if (DeviceEntryUdfpsRefactor.isEnabled) {
205             return canShowAlternateBouncer.value
206         }
207         return alternateBouncerSupported.value &&
208             biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
209             !keyguardUpdateMonitor.isFingerprintLockedOut &&
210             !keyguardStateController.isUnlocked &&
211             !statusBarStateController.isDozing &&
212             !bouncerRepository.primaryBouncerShow.value
213     }
214 
215     /**
216      * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the
217      * alternate bouncer and show the primary bouncer.
218      */
219     fun hasAlternateBouncerShownWithMinTime(): Boolean {
220         return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
221             MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
222     }
223     /**
224      * Should only be called through StatusBarKeyguardViewManager which propagates the source of
225      * truth to other concerned controllers. Will hide the alternate bouncer if it's no longer
226      * allowed to show.
227      *
228      * @return true if the alternate bouncer was newly hidden, else false.
229      */
230     fun maybeHide(): Boolean {
231         if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) {
232             return hide()
233         }
234         return false
235     }
236 
237     companion object {
238         private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
239     }
240 }
241