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