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.deviceentry.domain.interactor 18 19 import androidx.annotation.VisibleForTesting 20 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor 21 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel 22 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.dagger.qualifiers.Application 25 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository 26 import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason 27 import com.android.systemui.flags.SystemPropertiesHelper 28 import com.android.systemui.keyguard.domain.interactor.TrustInteractor 29 import com.android.systemui.scene.domain.interactor.SceneInteractor 30 import com.android.systemui.scene.shared.model.Scenes 31 import com.android.systemui.util.kotlin.Quad 32 import com.android.systemui.utils.coroutines.flow.mapLatestConflated 33 import javax.inject.Inject 34 import kotlinx.coroutines.CoroutineScope 35 import kotlinx.coroutines.ExperimentalCoroutinesApi 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.SharingStarted 38 import kotlinx.coroutines.flow.StateFlow 39 import kotlinx.coroutines.flow.combine 40 import kotlinx.coroutines.flow.filter 41 import kotlinx.coroutines.flow.first 42 import kotlinx.coroutines.flow.flatMapLatest 43 import kotlinx.coroutines.flow.flowOf 44 import kotlinx.coroutines.flow.map 45 import kotlinx.coroutines.flow.stateIn 46 import kotlinx.coroutines.launch 47 48 /** 49 * Hosts application business logic related to device entry. 50 * 51 * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless 52 * of the authentication method used. 53 */ 54 @ExperimentalCoroutinesApi 55 @SysUISingleton 56 class DeviceEntryInteractor 57 @Inject 58 constructor( 59 @Application private val applicationScope: CoroutineScope, 60 private val repository: DeviceEntryRepository, 61 private val authenticationInteractor: AuthenticationInteractor, 62 private val sceneInteractor: SceneInteractor, 63 faceAuthInteractor: DeviceEntryFaceAuthInteractor, 64 private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, 65 private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, 66 private val trustInteractor: TrustInteractor, 67 private val deviceUnlockedInteractor: DeviceUnlockedInteractor, 68 private val systemPropertiesHelper: SystemPropertiesHelper, 69 private val alternateBouncerInteractor: AlternateBouncerInteractor, 70 ) { 71 /** 72 * Whether the device is unlocked. 73 * 74 * A device that is not yet unlocked requires unlocking by completing an authentication 75 * challenge according to the current authentication method, unless in cases when the current 76 * authentication method is not "secure" (for example, None and Swipe); in such cases, the value 77 * of this flow will always be `true`, even if the lockscreen is showing and still needs to be 78 * dismissed by the user to proceed. 79 */ 80 val isUnlocked: StateFlow<Boolean> = 81 deviceUnlockedInteractor.deviceUnlockStatus 82 .map { it.isUnlocked } 83 .stateIn( 84 scope = applicationScope, 85 started = SharingStarted.WhileSubscribed(), 86 initialValue = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked, 87 ) 88 89 /** 90 * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method). 91 * This can be `false` when the device is unlocked, e.g. when the user still needs to swipe away 92 * the non-secure lockscreen, even though they've already authenticated. 93 * 94 * Note: This does not imply that the lockscreen is visible or not. 95 */ 96 val isDeviceEntered: StateFlow<Boolean> = 97 sceneInteractor.currentScene 98 .filter { currentScene -> 99 currentScene == Scenes.Gone || currentScene == Scenes.Lockscreen 100 } 101 .mapLatestConflated { scene -> 102 if (scene == Scenes.Gone) { 103 // Make sure device unlock status is definitely unlocked before we consider the 104 // device "entered". 105 deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked } 106 true 107 } else { 108 false 109 } 110 } 111 .stateIn( 112 scope = applicationScope, 113 started = SharingStarted.Eagerly, 114 initialValue = false, 115 ) 116 117 /** 118 * Whether it's currently possible to swipe up to enter the device without requiring 119 * authentication or when the device is already authenticated using a passive authentication 120 * mechanism like face or trust manager. This returns `false` whenever the lockscreen has been 121 * dismissed. 122 * 123 * A value of `null` is meaningless and is used as placeholder while the actual value is still 124 * being loaded in the background. 125 * 126 * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other 127 * UI. 128 */ 129 val canSwipeToEnter: StateFlow<Boolean?> = 130 combine( 131 // This is true when the user has chosen to show the lockscreen but has not made it 132 // secure. 133 authenticationInteractor.authenticationMethod.map { 134 it == AuthenticationMethodModel.None && repository.isLockscreenEnabled() 135 }, 136 deviceUnlockedInteractor.deviceUnlockStatus, 137 isDeviceEntered 138 ) { isSwipeAuthMethod, deviceUnlockStatus, isDeviceEntered -> 139 (isSwipeAuthMethod || 140 (deviceUnlockStatus.isUnlocked && 141 deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) && 142 !isDeviceEntered 143 } 144 .stateIn( 145 scope = applicationScope, 146 started = SharingStarted.Eagerly, 147 // Starts as null to prevent downstream collectors from falsely assuming that the 148 // user can or cannot swipe to enter the device while the real value is being loaded 149 // from upstream data sources. 150 initialValue = null, 151 ) 152 153 private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled 154 private val fingerprintEnrolledAndEnabled = 155 biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled 156 private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled 157 158 private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> = 159 combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple) 160 161 /** 162 * Reason why device entry is restricted to certain authentication methods for the current user. 163 * 164 * Emits null when there are no device entry restrictions active. 165 */ 166 val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> = 167 faceOrFingerprintOrTrustEnabled.flatMapLatest { 168 (faceEnabled, fingerprintEnabled, trustEnabled) -> 169 if (faceEnabled || fingerprintEnabled || trustEnabled) { 170 combine( 171 biometricSettingsInteractor.authenticationFlags, 172 faceAuthInteractor.isLockedOut, 173 fingerprintAuthInteractor.isLockedOut, 174 trustInteractor.isTrustAgentCurrentlyAllowed, 175 ::Quad 176 ) 177 .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) -> 178 when { 179 authFlags.isPrimaryAuthRequiredAfterReboot && 180 wasRebootedForMainlineUpdate -> 181 DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate 182 authFlags.isPrimaryAuthRequiredAfterReboot -> 183 DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot 184 authFlags.isPrimaryAuthRequiredAfterDpmLockdown -> 185 DeviceEntryRestrictionReason.PolicyLockdown 186 authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown 187 authFlags.isPrimaryAuthRequiredForUnattendedUpdate -> 188 DeviceEntryRestrictionReason.UnattendedUpdate 189 authFlags.isPrimaryAuthRequiredAfterTimeout -> 190 DeviceEntryRestrictionReason.SecurityTimeout 191 authFlags.isPrimaryAuthRequiredAfterLockout -> 192 DeviceEntryRestrictionReason.BouncerLockedOut 193 isFingerprintLockedOut -> 194 DeviceEntryRestrictionReason.StrongBiometricsLockedOut 195 isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() -> 196 DeviceEntryRestrictionReason.StrongBiometricsLockedOut 197 isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut 198 authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest -> 199 DeviceEntryRestrictionReason.AdaptiveAuthRequest 200 (trustEnabled && !trustManaged) && 201 (authFlags.someAuthRequiredAfterTrustAgentExpired || 202 authFlags.someAuthRequiredAfterUserRequest) -> 203 DeviceEntryRestrictionReason.TrustAgentDisabled 204 authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout -> 205 DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout 206 else -> null 207 } 208 } 209 } else { 210 flowOf(null) 211 } 212 } 213 214 /** 215 * Attempt to enter the device and dismiss the lockscreen. If authentication is required to 216 * unlock the device it will transition to bouncer. 217 */ 218 fun attemptDeviceEntry() { 219 // TODO (b/307768356), 220 // 1. Check if the device is already authenticated by trust agent/passive biometrics 221 // 2. Show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show 222 // 3. For face auth only setups trigger face auth, delay transitioning to bouncer for 223 // a small amount of time. 224 // 4. Transition to bouncer scene 225 applicationScope.launch { 226 if (isAuthenticationRequired()) { 227 if (alternateBouncerInteractor.canShowAlternateBouncer.value) { 228 alternateBouncerInteractor.forceShow() 229 } else { 230 sceneInteractor.changeScene( 231 toScene = Scenes.Bouncer, 232 loggingReason = "request to unlock device while authentication required", 233 ) 234 } 235 } else { 236 sceneInteractor.changeScene( 237 toScene = Scenes.Gone, 238 loggingReason = "request to unlock device while authentication isn't required", 239 ) 240 } 241 } 242 } 243 244 /** 245 * Returns `true` if the device currently requires authentication before entry is granted; 246 * `false` if the device can be entered without authenticating first. 247 */ 248 suspend fun isAuthenticationRequired(): Boolean { 249 return !deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked && 250 authenticationInteractor.getAuthenticationMethod().isSecure 251 } 252 253 /** 254 * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has 255 * chosen any secure authentication method and even if they set the lockscreen to be dismissed 256 * when the user swipes on it. 257 */ 258 suspend fun isLockscreenEnabled(): Boolean { 259 return repository.isLockscreenEnabled() 260 } 261 262 /** 263 * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically 264 * dismissed once the authentication challenge is completed. For example, completing a biometric 265 * authentication challenge via face unlock or fingerprint sensor can automatically bypass the 266 * lockscreen. 267 */ 268 val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled 269 270 private val wasRebootedForMainlineUpdate 271 get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE 272 273 companion object { 274 @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last" 275 @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update" 276 } 277 } 278