1 /*
2  *  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 
18 package com.android.systemui.keyguard.domain.interactor
19 
20 import android.app.StatusBarManager
21 import android.graphics.Point
22 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
23 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
24 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
25 import com.android.systemui.common.shared.model.Position
26 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
27 import com.android.systemui.dagger.SysUISingleton
28 import com.android.systemui.flags.FeatureFlags
29 import com.android.systemui.flags.Flags
30 import com.android.systemui.keyguard.data.repository.KeyguardRepository
31 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
32 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
33 import com.android.systemui.keyguard.shared.model.DozeStateModel
34 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
35 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
36 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
37 import com.android.systemui.keyguard.shared.model.KeyguardState
38 import com.android.systemui.keyguard.shared.model.ScreenModel
39 import com.android.systemui.keyguard.shared.model.StatusBarState
40 import com.android.systemui.keyguard.shared.model.WakefulnessModel
41 import com.android.systemui.statusbar.CommandQueue
42 import com.android.systemui.util.kotlin.sample
43 import kotlinx.coroutines.channels.awaitClose
44 import kotlinx.coroutines.delay
45 import kotlinx.coroutines.flow.Flow
46 import kotlinx.coroutines.flow.MutableStateFlow
47 import kotlinx.coroutines.flow.StateFlow
48 import kotlinx.coroutines.flow.combine
49 import kotlinx.coroutines.flow.distinctUntilChanged
50 import kotlinx.coroutines.flow.filter
51 import kotlinx.coroutines.flow.flatMapLatest
52 import kotlinx.coroutines.flow.flow
53 import kotlinx.coroutines.flow.flowOf
54 import kotlinx.coroutines.flow.merge
55 import kotlinx.coroutines.flow.onStart
56 import javax.inject.Inject
57 
58 /**
59  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
60  */
61 @SysUISingleton
62 class KeyguardInteractor
63 @Inject
64 constructor(
65     private val repository: KeyguardRepository,
66     private val commandQueue: CommandQueue,
67     featureFlags: FeatureFlags,
68     bouncerRepository: KeyguardBouncerRepository,
69     configurationRepository: ConfigurationRepository,
70 ) {
71 
72     data class PreviewMode(
73         val isInPreviewMode: Boolean = false,
74         val shouldHighlightSelectedAffordance: Boolean = false,
75     )
76 
77     /**
78      * Whether this view-model instance is powering the preview experience that renders exclusively
79      * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
80      * experience.
81      */
82     val previewMode = MutableStateFlow(PreviewMode())
83     /**
84      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
85      * all.
86      */
87     val dozeAmount: Flow<Float> = repository.linearDozeAmount
88     /** Whether the system is in doze mode. */
89     val isDozing: Flow<Boolean> = repository.isDozing
90     /** Receive an event for doze time tick */
91     val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
92     /** Whether Always-on Display mode is available. */
93     val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
94     /** Doze transition information. */
95     val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
96     /**
97      * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
98      * but not vice-versa.
99      */
100     val isDreaming: Flow<Boolean> = repository.isDreaming
101     /** Whether the system is dreaming with an overlay active */
102     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
103     /** Whether the system is dreaming and the active dream is hosted in lockscreen */
104     val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
105     /** Event for when the camera gesture is detected */
106     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
107         val callback =
108             object : CommandQueue.Callbacks {
109                 override fun onCameraLaunchGestureDetected(source: Int) {
110                     trySendWithFailureLogging(
111                         cameraLaunchSourceIntToModel(source),
112                         TAG,
113                         "updated onCameraLaunchGestureDetected"
114                     )
115                 }
116             }
117 
118         commandQueue.addCallback(callback)
119 
120         awaitClose { commandQueue.removeCallback(callback) }
121     }
122 
123     /** The device wake/sleep state */
124     val wakefulnessModel: StateFlow<WakefulnessModel> = repository.wakefulness
125 
126     /** The device screen state */
127     val screenModel: StateFlow<ScreenModel> = repository.screenModel
128 
129     /**
130      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
131      * that doze mode is not running and DREAMING is ok to commence.
132      *
133      * Allow a brief moment to prevent rapidly oscillating between true/false signals.
134      */
135     val isAbleToDream: Flow<Boolean> =
136         merge(isDreaming, isDreamingWithOverlay)
137             .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
138                 isDreaming && isDozeOff(dozeTransitionModel.to)
139             }
140             .sample(wakefulnessModel) { isAbleToDream, wakefulnessModel ->
141                 isAbleToDream && wakefulnessModel.isStartingToWakeOrAwake()
142             }
143             .flatMapLatest { isAbleToDream ->
144                 flow {
145                     delay(50)
146                     emit(isAbleToDream)
147                 }
148             }
149             .distinctUntilChanged()
150 
151     /** Whether the keyguard is showing or not. */
152     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
153     /** Whether the keyguard is unlocked or not. */
154     val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
155     /** Whether the keyguard is occluded (covered by an activity). */
156     val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
157     /** Whether the keyguard is going away. */
158     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
159     /** Whether the primary bouncer is showing or not. */
160     val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
161     /** Whether the alternate bouncer is showing or not. */
162     val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
163     /** Observable for the [StatusBarState] */
164     val statusBarState: Flow<StatusBarState> = repository.statusBarState
165     /**
166      * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
167      * side, under display) is used to unlock the device.
168      */
169     val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
170 
171     /** Keyguard is present and is not occluded. */
172     val isKeyguardVisible: Flow<Boolean> =
173         combine(isKeyguardShowing, isKeyguardOccluded) { showing, occluded -> showing && !occluded }
174 
175     /** Whether camera is launched over keyguard. */
176     var isSecureCameraActive =
177         if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
178             combine(
179                     isKeyguardVisible,
180                     primaryBouncerShowing,
181                     onCameraLaunchDetected,
182                 ) { isKeyguardVisible, isPrimaryBouncerShowing, cameraLaunchEvent ->
183                     when {
184                         isKeyguardVisible -> false
185                         isPrimaryBouncerShowing -> false
186                         else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
187                     }
188                 }
189                 .onStart { emit(false) }
190         } else {
191             flowOf(false)
192         }
193 
194     /** The approximate location on the screen of the fingerprint sensor, if one is available. */
195     val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
196 
197     /** The approximate location on the screen of the face unlock sensor, if one is available. */
198     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
199 
200     /** Notifies when a new configuration is set */
201     val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
202 
203     /** Represents the current state of the KeyguardRootView visibility */
204     val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
205         repository.keyguardRootViewVisibility
206 
207     /** The position of the keyguard clock. */
208     val clockPosition: Flow<Position> = repository.clockPosition
209 
210     val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
211 
212     /** Whether to animate the next doze mode transition. */
213     val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
214 
215     fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
216         return dozeTransitionModel.filter { states.contains(it.to) }
217     }
218     fun isKeyguardShowing(): Boolean {
219         return repository.isKeyguardShowing()
220     }
221 
222     private fun cameraLaunchSourceIntToModel(value: Int): CameraLaunchSourceModel {
223         return when (value) {
224             StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchSourceModel.WIGGLE
225             StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP ->
226                 CameraLaunchSourceModel.POWER_DOUBLE_TAP
227             StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER ->
228                 CameraLaunchSourceModel.LIFT_TRIGGER
229             StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE ->
230                 CameraLaunchSourceModel.QUICK_AFFORDANCE
231             else -> throw IllegalArgumentException("Invalid CameraLaunchSourceModel value: $value")
232         }
233     }
234 
235     fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
236         repository.setIsActiveDreamLockscreenHosted(isLockscreenHosted)
237     }
238 
239     /** Sets whether quick settings or quick-quick settings is visible. */
240     fun setQuickSettingsVisible(isVisible: Boolean) {
241         repository.setQuickSettingsVisible(isVisible)
242     }
243 
244     fun setKeyguardRootVisibility(
245         statusBarState: Int,
246         goingToFullShade: Boolean,
247         isOcclusionTransitionRunning: Boolean
248     ) {
249         repository.setKeyguardVisibility(
250             statusBarState,
251             goingToFullShade,
252             isOcclusionTransitionRunning
253         )
254     }
255 
256     fun setClockPosition(x: Int, y: Int) {
257         repository.setClockPosition(x, y)
258     }
259 
260     fun setAlpha(alpha: Float) {
261         repository.setKeyguardAlpha(alpha)
262     }
263 
264     fun setAnimateDozingTransitions(animate: Boolean) {
265         repository.setAnimateDozingTransitions(animate)
266     }
267 
268     fun isKeyguardDismissable(): Boolean {
269         return repository.isKeyguardUnlocked.value
270     }
271 
272     companion object {
273         private const val TAG = "KeyguardInteractor"
274 
275         fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
276             return when (state) {
277                 KeyguardState.OFF -> true
278                 KeyguardState.DOZING -> true
279                 KeyguardState.DREAMING -> true
280                 KeyguardState.AOD -> true
281                 KeyguardState.ALTERNATE_BOUNCER -> true
282                 KeyguardState.PRIMARY_BOUNCER -> true
283                 KeyguardState.LOCKSCREEN -> true
284                 KeyguardState.GONE -> false
285                 KeyguardState.OCCLUDED -> true
286                 KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
287             }
288         }
289     }
290 }
291