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 package com.android.systemui.biometrics.domain.interactor
18 
19 import android.hardware.biometrics.AuthenticateOptions
20 import android.hardware.biometrics.IBiometricContextListener
21 import android.util.Log
22 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
23 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Application
26 import com.android.systemui.keyguard.WakefulnessLifecycle
27 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
28 import com.android.systemui.keyguard.shared.model.KeyguardState
29 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
30 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
31 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
32 import com.android.systemui.unfold.updates.FoldStateProvider
33 import javax.inject.Inject
34 import kotlinx.coroutines.CoroutineScope
35 import kotlinx.coroutines.Job
36 import kotlinx.coroutines.cancel
37 import kotlinx.coroutines.channels.awaitClose
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.SharingStarted
40 import kotlinx.coroutines.flow.catch
41 import kotlinx.coroutines.flow.distinctUntilChanged
42 import kotlinx.coroutines.flow.launchIn
43 import kotlinx.coroutines.flow.map
44 import kotlinx.coroutines.flow.onEach
45 import kotlinx.coroutines.flow.shareIn
46 import kotlinx.coroutines.launch
47 
48 /**
49  * Aggregates UI/device state that is not directly related to biometrics, but is often useful for
50  * logging or optimization purposes (fold state, screen state, etc.)
51  */
52 interface LogContextInteractor {
53 
54     /** If the device is showing aod. */
55     val isAod: Flow<Boolean>
56 
57     /** If the device is currently awake with the screen on. */
58     val isAwake: Flow<Boolean>
59 
60     /** Current device fold state, defined as [IBiometricContextListener.FoldState]. */
61     val foldState: Flow<Int>
62 
63     /** Current display state, defined as [AuthenticateOptions.DisplayState] */
64     val displayState: Flow<Int>
65 
66     /**
67      * Add a permanent context listener.
68      *
69      * Use this method for registering remote context listeners. Use the properties exposed via this
70      * class directly within SysUI.
71      */
72     fun addBiometricContextListener(listener: IBiometricContextListener): Job
73 }
74 
75 @SysUISingleton
76 class LogContextInteractorImpl
77 @Inject
78 constructor(
79     @Application private val applicationScope: CoroutineScope,
80     private val foldProvider: FoldStateProvider,
81     keyguardTransitionInteractor: KeyguardTransitionInteractor,
82 ) : LogContextInteractor {
83 
84     init {
85         applicationScope.launch {
86             foldProvider.start()
87         }
88     }
89 
90     override val displayState =
91         keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
92             when (it.to) {
93                 KeyguardState.LOCKSCREEN,
94                 KeyguardState.OCCLUDED,
95                 KeyguardState.ALTERNATE_BOUNCER,
96                 KeyguardState.PRIMARY_BOUNCER -> AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN
97                 KeyguardState.AOD -> AuthenticateOptions.DISPLAY_STATE_AOD
98                 KeyguardState.OFF,
99                 KeyguardState.DOZING -> AuthenticateOptions.DISPLAY_STATE_NO_UI
100                 KeyguardState.DREAMING -> AuthenticateOptions.DISPLAY_STATE_SCREENSAVER
101                 else -> AuthenticateOptions.DISPLAY_STATE_UNKNOWN
102             }
103         }
104 
105     override val isAod =
106         displayState.map { it == AuthenticateOptions.DISPLAY_STATE_AOD }.distinctUntilChanged()
107 
108     override val isAwake =
109         displayState
110             .map {
111                 when (it) {
112                     AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
113                     AuthenticateOptions.DISPLAY_STATE_SCREENSAVER,
114                     AuthenticateOptions.DISPLAY_STATE_UNKNOWN -> true
115                     else -> false
116                 }
117             }
118             .distinctUntilChanged()
119 
120     override val foldState: Flow<Int> =
121         conflatedCallbackFlow {
122                 val callback =
123                     object : FoldStateProvider.FoldUpdatesListener {
124                         override fun onHingeAngleUpdate(angle: Float) {}
125 
126                         override fun onFoldUpdate(@FoldStateProvider.FoldUpdate update: Int) {
127                             val loggedState =
128                                 when (update) {
129                                     FOLD_UPDATE_FINISH_HALF_OPEN ->
130                                         IBiometricContextListener.FoldState.HALF_OPENED
131                                     FOLD_UPDATE_FINISH_FULL_OPEN ->
132                                         IBiometricContextListener.FoldState.FULLY_OPENED
133                                     FOLD_UPDATE_FINISH_CLOSED ->
134                                         IBiometricContextListener.FoldState.FULLY_CLOSED
135                                     else -> null
136                                 }
137                             if (loggedState != null) {
138                                 trySendWithFailureLogging(loggedState, TAG)
139                             }
140                         }
141                     }
142 
143                 foldProvider.addCallback(callback)
144                 trySendWithFailureLogging(IBiometricContextListener.FoldState.UNKNOWN, TAG)
145                 awaitClose { foldProvider.removeCallback(callback) }
146             }
147             .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
148 
149     override fun addBiometricContextListener(listener: IBiometricContextListener): Job {
150         return applicationScope.launch {
151             foldState
152                 .onEach { state -> listener.onFoldChanged(state) }
153                 .catch { t -> Log.w(TAG, "failed to notify new fold state", t) }
154                 .launchIn(this)
155 
156             displayState
157                 .distinctUntilChanged()
158                 .onEach { state -> listener.onDisplayStateChanged(state) }
159                 .catch { t -> Log.w(TAG, "failed to notify new display state", t) }
160                 .launchIn(this)
161 
162             listener.asBinder().linkToDeath({ cancel() }, 0)
163         }
164     }
165 
166     companion object {
167         private const val TAG = "ContextRepositoryImpl"
168     }
169 }
170 
171 private val WakefulnessLifecycle.isAwake: Boolean
172     get() = wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
173