1 /* <lambda>null2 * Copyright (C) 2023 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 android.app.StatusBarManager.SESSION_KEYGUARD 20 import com.android.compose.animation.scene.SceneKey 21 import com.android.internal.logging.UiEventLogger 22 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor 23 import com.android.systemui.authentication.domain.interactor.AuthenticationResult 24 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password 25 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern 26 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin 27 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim 28 import com.android.systemui.bouncer.data.repository.BouncerRepository 29 import com.android.systemui.bouncer.shared.logging.BouncerUiEvent 30 import com.android.systemui.classifier.FalsingClassifier 31 import com.android.systemui.classifier.domain.interactor.FalsingInteractor 32 import com.android.systemui.dagger.SysUISingleton 33 import com.android.systemui.dagger.qualifiers.Application 34 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor 35 import com.android.systemui.log.SessionTracker 36 import com.android.systemui.power.domain.interactor.PowerInteractor 37 import com.android.systemui.scene.domain.interactor.SceneBackInteractor 38 import com.android.systemui.scene.shared.model.Scenes 39 import javax.inject.Inject 40 import kotlinx.coroutines.CoroutineScope 41 import kotlinx.coroutines.async 42 import kotlinx.coroutines.flow.Flow 43 import kotlinx.coroutines.flow.MutableSharedFlow 44 import kotlinx.coroutines.flow.SharedFlow 45 import kotlinx.coroutines.flow.StateFlow 46 import kotlinx.coroutines.flow.filter 47 import kotlinx.coroutines.flow.map 48 49 /** Encapsulates business logic and application state accessing use-cases. */ 50 @SysUISingleton 51 class BouncerInteractor 52 @Inject 53 constructor( 54 @Application private val applicationScope: CoroutineScope, 55 private val repository: BouncerRepository, 56 private val authenticationInteractor: AuthenticationInteractor, 57 private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, 58 private val falsingInteractor: FalsingInteractor, 59 private val powerInteractor: PowerInteractor, 60 private val uiEventLogger: UiEventLogger, 61 private val sessionTracker: SessionTracker, 62 sceneBackInteractor: SceneBackInteractor, 63 ) { 64 private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>() 65 val onIncorrectBouncerInput: SharedFlow<Unit> = _onIncorrectBouncerInput 66 67 /** Whether the auto confirm feature is enabled for the currently-selected user. */ 68 val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled 69 70 /** The length of the hinted PIN, or `null`, if pin length hint should not be shown. */ 71 val hintedPinLength: StateFlow<Int?> = authenticationInteractor.hintedPinLength 72 73 /** Whether the pattern should be visible for the currently-selected user. */ 74 val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible 75 76 /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ 77 val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = 78 authenticationInteractor.isPinEnhancedPrivacyEnabled 79 80 /** Whether the user switcher should be displayed within the bouncer UI on large screens. */ 81 val isUserSwitcherVisible: Boolean 82 get() = repository.isUserSwitcherVisible 83 84 private val _onImeHiddenByUser = MutableSharedFlow<Unit>() 85 /** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */ 86 val onImeHiddenByUser: SharedFlow<Unit> = _onImeHiddenByUser 87 88 /** Emits a [Unit] each time a lockout is started for the selected user. */ 89 val onLockoutStarted: Flow<Unit> = 90 authenticationInteractor.onAuthenticationResult 91 .filter { successfullyAuthenticated -> 92 !successfullyAuthenticated && authenticationInteractor.lockoutEndTimestamp != null 93 } 94 .map {} 95 96 /** The scene to show when bouncer is dismissed. */ 97 val dismissDestination: Flow<SceneKey> = 98 sceneBackInteractor.backScene 99 .filter { it != Scenes.Bouncer } 100 .map { it ?: Scenes.Lockscreen } 101 102 /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */ 103 fun onDown() { 104 falsingInteractor.avoidGesture() 105 } 106 107 /** 108 * Notifies of "intentional" (i.e. non-false) user interaction with the UI which is very likely 109 * to be real user interaction with the bouncer and not the result of a false touch in the 110 * user's pocket or by the user's face while holding their device up to their ear. 111 */ 112 fun onIntentionalUserInput() { 113 deviceEntryFaceAuthInteractor.onPrimaryBouncerUserInput() 114 powerInteractor.onUserTouch() 115 falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6)) 116 } 117 118 /** 119 * Notifies of false input which is very likely to be the result of a false touch in the user's 120 * pocket or by the user's face while holding their device up to their ear. 121 */ 122 fun onFalseUserInput() { 123 falsingInteractor.updateFalseConfidence( 124 FalsingClassifier.Result.falsed( 125 /* confidence= */ 0.7, 126 /* context= */ javaClass.simpleName, 127 /* reason= */ "empty pattern input", 128 ) 129 ) 130 } 131 132 /** 133 * Attempts to authenticate based on the given user input. 134 * 135 * If the input is correct, the device will be unlocked and the lock screen and bouncer will be 136 * dismissed and hidden. 137 * 138 * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method 139 * supports auto-confirming, and the input's length is at least the required length. Otherwise, 140 * `AuthenticationResult.SKIPPED` is returned. 141 * 142 * @param input The input from the user to try to authenticate with. This can be a list of 143 * different things, based on the current authentication method. 144 * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit 145 * request to validate. 146 * @return The result of this authentication attempt. 147 */ 148 suspend fun authenticate( 149 input: List<Any>, 150 tryAutoConfirm: Boolean = false, 151 ): AuthenticationResult { 152 if (input.isEmpty()) { 153 return AuthenticationResult.SKIPPED 154 } 155 156 if (authenticationInteractor.getAuthenticationMethod() == Sim) { 157 // SIM is authenticated in SimBouncerInteractor. 158 return AuthenticationResult.SKIPPED 159 } 160 161 // Switching to the application scope here since this method is often called from 162 // view-models, whose lifecycle (and thus scope) is shorter than this interactor. 163 // This allows the task to continue running properly even when the calling scope has been 164 // cancelled. 165 val authResult = 166 applicationScope 167 .async { authenticationInteractor.authenticate(input, tryAutoConfirm) } 168 .await() 169 170 if ( 171 authResult == AuthenticationResult.FAILED || 172 (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm) 173 ) { 174 _onIncorrectBouncerInput.emit(Unit) 175 } 176 177 if (authenticationInteractor.getAuthenticationMethod() in setOf(Pin, Password, Pattern)) { 178 if (authResult == AuthenticationResult.SUCCEEDED) { 179 uiEventLogger.log(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS) 180 } else if (authResult == AuthenticationResult.FAILED) { 181 uiEventLogger.log( 182 BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, 183 sessionTracker.getSessionId(SESSION_KEYGUARD) 184 ) 185 } 186 } 187 188 return authResult 189 } 190 191 /** Notifies that the input method editor (software keyboard) has been hidden by the user. */ 192 suspend fun onImeHiddenByUser() { 193 _onImeHiddenByUser.emit(Unit) 194 } 195 } 196