1 /* <lambda>null2 * Copyright (C) 2024 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 android.provider.Settings 20 import android.util.Log 21 import androidx.annotation.VisibleForTesting 22 import com.android.systemui.CoreStartable 23 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor 24 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.dagger.qualifiers.Application 27 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository 28 import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason 29 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource 30 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus 31 import com.android.systemui.flags.SystemPropertiesHelper 32 import com.android.systemui.keyguard.KeyguardViewMediator 33 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 34 import com.android.systemui.keyguard.domain.interactor.TrustInteractor 35 import com.android.systemui.lifecycle.ExclusiveActivatable 36 import com.android.systemui.log.table.TableLogBuffer 37 import com.android.systemui.log.table.logDiffsForTable 38 import com.android.systemui.power.domain.interactor.PowerInteractor 39 import com.android.systemui.power.shared.model.WakeSleepReason 40 import com.android.systemui.scene.domain.SceneFrameworkTableLog 41 import com.android.systemui.scene.shared.flag.SceneContainerFlag 42 import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository 43 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated 44 import javax.inject.Inject 45 import kotlinx.coroutines.CancellationException 46 import kotlinx.coroutines.CoroutineScope 47 import kotlinx.coroutines.awaitCancellation 48 import kotlinx.coroutines.channels.Channel 49 import kotlinx.coroutines.coroutineScope 50 import kotlinx.coroutines.delay 51 import kotlinx.coroutines.flow.Flow 52 import kotlinx.coroutines.flow.StateFlow 53 import kotlinx.coroutines.flow.asStateFlow 54 import kotlinx.coroutines.flow.collect 55 import kotlinx.coroutines.flow.collectLatest 56 import kotlinx.coroutines.flow.combine 57 import kotlinx.coroutines.flow.distinctUntilChanged 58 import kotlinx.coroutines.flow.distinctUntilChangedBy 59 import kotlinx.coroutines.flow.emptyFlow 60 import kotlinx.coroutines.flow.filter 61 import kotlinx.coroutines.flow.flatMapLatest 62 import kotlinx.coroutines.flow.flowOf 63 import kotlinx.coroutines.flow.map 64 import kotlinx.coroutines.flow.merge 65 import kotlinx.coroutines.flow.receiveAsFlow 66 import kotlinx.coroutines.launch 67 68 @SysUISingleton 69 class DeviceUnlockedInteractor 70 @Inject 71 constructor( 72 private val authenticationInteractor: AuthenticationInteractor, 73 private val repository: DeviceEntryRepository, 74 private val trustInteractor: TrustInteractor, 75 faceAuthInteractor: DeviceEntryFaceAuthInteractor, 76 fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, 77 private val powerInteractor: PowerInteractor, 78 private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, 79 private val systemPropertiesHelper: SystemPropertiesHelper, 80 private val userAwareSecureSettingsRepository: UserAwareSecureSettingsRepository, 81 private val keyguardInteractor: KeyguardInteractor, 82 @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, 83 ) : ExclusiveActivatable() { 84 85 private val deviceUnlockSource = 86 merge( 87 fingerprintAuthInteractor.fingerprintSuccess.map { DeviceUnlockSource.Fingerprint }, 88 faceAuthInteractor.isAuthenticated 89 .filter { it } 90 .map { 91 if (repository.isBypassEnabled.value) { 92 DeviceUnlockSource.FaceWithBypass 93 } else { 94 DeviceUnlockSource.FaceWithoutBypass 95 } 96 }, 97 trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent }, 98 authenticationInteractor.onAuthenticationResult 99 .filter { it } 100 .map { DeviceUnlockSource.BouncerInput }, 101 ) 102 103 private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled 104 private val fingerprintEnrolledAndEnabled = 105 biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled 106 private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled 107 108 private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> = 109 combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple) 110 111 /** 112 * Reason why device entry is restricted to certain authentication methods for the current user. 113 * 114 * Emits null when there are no device entry restrictions active. 115 */ 116 val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> = 117 faceOrFingerprintOrTrustEnabled.flatMapLatest { 118 (faceEnabled, fingerprintEnabled, trustEnabled) -> 119 if (faceEnabled || fingerprintEnabled || trustEnabled) { 120 combine( 121 biometricSettingsInteractor.authenticationFlags, 122 faceAuthInteractor.isLockedOut, 123 fingerprintAuthInteractor.isLockedOut, 124 trustInteractor.isTrustAgentCurrentlyAllowed, 125 ) { authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged -> 126 when { 127 authFlags.isPrimaryAuthRequiredAfterReboot && 128 wasRebootedForMainlineUpdate() -> 129 DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate 130 authFlags.isPrimaryAuthRequiredAfterReboot -> 131 DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot 132 authFlags.isPrimaryAuthRequiredAfterDpmLockdown -> 133 DeviceEntryRestrictionReason.PolicyLockdown 134 authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown 135 authFlags.isPrimaryAuthRequiredForUnattendedUpdate -> 136 DeviceEntryRestrictionReason.UnattendedUpdate 137 authFlags.isPrimaryAuthRequiredAfterTimeout -> 138 DeviceEntryRestrictionReason.SecurityTimeout 139 isFingerprintLockedOut -> 140 DeviceEntryRestrictionReason.StrongBiometricsLockedOut 141 isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() -> 142 DeviceEntryRestrictionReason.StrongBiometricsLockedOut 143 isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut 144 authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest -> 145 DeviceEntryRestrictionReason.AdaptiveAuthRequest 146 (trustEnabled && !trustManaged) && 147 (authFlags.someAuthRequiredAfterTrustAgentExpired || 148 authFlags.someAuthRequiredAfterUserRequest) -> 149 DeviceEntryRestrictionReason.TrustAgentDisabled 150 authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout -> 151 DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout 152 else -> null 153 } 154 } 155 } else { 156 biometricSettingsInteractor.authenticationFlags.map { authFlags -> 157 when { 158 authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown 159 authFlags.isPrimaryAuthRequiredAfterDpmLockdown -> 160 DeviceEntryRestrictionReason.PolicyLockdown 161 else -> null 162 } 163 } 164 } 165 } 166 167 /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */ 168 val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() } 169 170 /** 171 * Whether the device is unlocked or not, along with the information about the authentication 172 * method that was used to unlock the device. 173 * 174 * A device that is not yet unlocked requires unlocking by completing an authentication 175 * challenge according to the current authentication method, unless in cases when the current 176 * authentication method is not "secure" (for example, None and Swipe); in such cases, the value 177 * of this flow will always be an instance of [DeviceUnlockStatus] with 178 * [DeviceUnlockStatus.deviceUnlockSource] as null and [DeviceUnlockStatus.isUnlocked] set to 179 * true, even if the lockscreen is showing and still needs to be dismissed by the user to 180 * proceed. 181 */ 182 val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> = 183 repository.deviceUnlockStatus.asStateFlow() 184 185 /** A [Channel] of "lock now" requests where the values are the debugging reasons. */ 186 private val lockNowRequests = Channel<String>() 187 188 override suspend fun onActivated(): Nothing { 189 coroutineScope { 190 launch { 191 authenticationInteractor.authenticationMethod.collectLatest { authMethod -> 192 if (!authMethod.isSecure) { 193 // Device remains unlocked as long as the authentication method is not 194 // secure. 195 Log.d(TAG, "remaining unlocked because auth method not secure") 196 repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null) 197 } else if (authMethod == AuthenticationMethodModel.Sim) { 198 // Device remains locked while SIM is locked. 199 Log.d(TAG, "remaining locked because SIM locked") 200 repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) 201 } else { 202 handleLockAndUnlockEvents() 203 } 204 } 205 } 206 207 launch { 208 deviceUnlockStatus 209 .map { it.isUnlocked } 210 .logDiffsForTable( 211 tableLogBuffer = tableLogBuffer, 212 columnName = "isUnlocked", 213 initialValue = deviceUnlockStatus.value.isUnlocked, 214 ) 215 .collect() 216 } 217 } 218 219 awaitCancellation() 220 } 221 222 /** Locks the device instantly. */ 223 fun lockNow(debuggingReason: String) { 224 lockNowRequests.trySend(debuggingReason) 225 } 226 227 private suspend fun handleLockAndUnlockEvents() { 228 try { 229 Log.d(TAG, "started watching for lock and unlock events") 230 coroutineScope { 231 launch { handleUnlockEvents() } 232 launch { handleLockEvents() } 233 } 234 } finally { 235 Log.d(TAG, "stopped watching for lock and unlock events") 236 } 237 } 238 239 private suspend fun handleUnlockEvents() { 240 // Unlock the device when a new unlock source is detected. 241 deviceUnlockSource.collect { 242 Log.d(TAG, "unlocking due to \"$it\"") 243 repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, it) 244 } 245 } 246 247 private suspend fun handleLockEvents() { 248 merge( 249 trustInteractor.isTrusted.flatMapLatestConflated { isTrusted -> 250 if (isTrusted) { 251 // When entering a trusted environment, power-related lock events are 252 // ignored. 253 Log.d(TAG, "In trusted environment, ignoring power-related lock events") 254 flowOf(CancelDelayedLock("in trusted environment")) 255 } else { 256 // When not in a trusted environment, power-related lock events are treated 257 // as normal. 258 Log.d( 259 TAG, 260 "Not in trusted environment, power-related lock events treated as" + 261 " normal", 262 ) 263 merge( 264 // Device wakefulness events. 265 powerInteractor.detailedWakefulness 266 .map { Pair(it.isAsleep(), it.lastSleepReason) } 267 .distinctUntilChangedBy { it.first } 268 .map { (isAsleep, lastSleepReason) -> 269 if (isAsleep) { 270 if ( 271 (lastSleepReason == WakeSleepReason.POWER_BUTTON) && 272 authenticationInteractor 273 .getPowerButtonInstantlyLocks() 274 ) { 275 LockImmediately("locked instantly from power button") 276 } else if ( 277 lastSleepReason == WakeSleepReason.SLEEP_BUTTON 278 ) { 279 LockImmediately("locked instantly from sleep button") 280 } else { 281 LockWithDelay("entering sleep") 282 } 283 } else { 284 CancelDelayedLock("waking up") 285 } 286 }, 287 // Started dreaming 288 powerInteractor.isInteractive.flatMapLatestConflated { isInteractive -> 289 // Only respond to dream state changes while the device is 290 // interactive. 291 if (isInteractive) { 292 keyguardInteractor.isDreamingAny.distinctUntilChanged().map { 293 isDreaming -> 294 if (isDreaming) { 295 LockWithDelay("started dreaming") 296 } else { 297 CancelDelayedLock("stopped dreaming") 298 } 299 } 300 } else { 301 emptyFlow() 302 } 303 }, 304 ) 305 } 306 }, 307 // Device enters lockdown. 308 isInLockdown 309 .distinctUntilChanged() 310 .filter { it } 311 .map { LockImmediately("lockdown") }, 312 lockNowRequests.receiveAsFlow().map { reason -> LockImmediately(reason) }, 313 ) 314 .collectLatest(::onLockEvent) 315 } 316 317 private suspend fun onLockEvent(event: LockEvent) { 318 val debugReason = event.debugReason 319 when (event) { 320 is LockImmediately -> { 321 Log.d(TAG, "locking without delay due to \"$debugReason\"") 322 repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) 323 } 324 325 is LockWithDelay -> { 326 val lockDelay = lockDelay() 327 Log.d(TAG, "locking in ${lockDelay}ms due to \"$debugReason\"") 328 try { 329 delay(lockDelay) 330 Log.d( 331 TAG, 332 "locking after having waited for ${lockDelay}ms due to \"$debugReason\"", 333 ) 334 repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) 335 } catch (_: CancellationException) { 336 Log.d( 337 TAG, 338 "delayed locking canceled, original delay was ${lockDelay}ms and reason was \"$debugReason\"", 339 ) 340 } 341 } 342 343 is CancelDelayedLock -> { 344 // Do nothing, the mere receipt of this inside of a "latest" block means that any 345 // previous coroutine is automatically canceled. 346 } 347 } 348 } 349 350 /** 351 * Returns the amount of time to wait before locking down the device after the device has been 352 * put to sleep by the user, in milliseconds. 353 */ 354 private suspend fun lockDelay(): Long { 355 val lockAfterScreenTimeoutSetting = 356 userAwareSecureSettingsRepository 357 .getInt( 358 Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 359 KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, 360 ) 361 .toLong() 362 Log.d(TAG, "Lock after screen timeout setting set to ${lockAfterScreenTimeoutSetting}ms") 363 364 val maxTimeToLockDevicePolicy = authenticationInteractor.getMaximumTimeToLock() 365 Log.d(TAG, "Device policy max set to ${maxTimeToLockDevicePolicy}ms") 366 367 if (maxTimeToLockDevicePolicy <= 0) { 368 // No device policy enforced maximum. 369 Log.d(TAG, "No device policy max, delay is ${lockAfterScreenTimeoutSetting}ms") 370 return lockAfterScreenTimeoutSetting 371 } 372 373 val screenOffTimeoutSetting = 374 userAwareSecureSettingsRepository 375 .getInt( 376 Settings.System.SCREEN_OFF_TIMEOUT, 377 KeyguardViewMediator.KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, 378 ) 379 .coerceAtLeast(0) 380 .toLong() 381 Log.d(TAG, "Screen off timeout setting set to ${screenOffTimeoutSetting}ms") 382 383 return (maxTimeToLockDevicePolicy - screenOffTimeoutSetting) 384 .coerceIn(minimumValue = 0, maximumValue = lockAfterScreenTimeoutSetting) 385 .also { Log.d(TAG, "Device policy max enforced, delay is ${it}ms") } 386 } 387 388 private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean { 389 return when (this) { 390 DeviceEntryRestrictionReason.UserLockdown -> true 391 DeviceEntryRestrictionReason.PolicyLockdown -> true 392 393 // Add individual enum value instead of using "else" so new reasons are guaranteed 394 // to be added here at compile-time. 395 null -> false 396 DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false 397 DeviceEntryRestrictionReason.BouncerLockedOut -> false 398 DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false 399 DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false 400 DeviceEntryRestrictionReason.TrustAgentDisabled -> false 401 DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false 402 DeviceEntryRestrictionReason.SecurityTimeout -> false 403 DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false 404 DeviceEntryRestrictionReason.UnattendedUpdate -> false 405 DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false 406 } 407 } 408 409 private fun wasRebootedForMainlineUpdate(): Boolean { 410 return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE 411 } 412 413 /** [CoreStartable] that activates the [DeviceUnlockedInteractor]. */ 414 class Activator 415 @Inject 416 constructor( 417 @Application private val applicationScope: CoroutineScope, 418 private val interactor: DeviceUnlockedInteractor, 419 ) : CoreStartable { 420 override fun start() { 421 if (!SceneContainerFlag.isEnabled) return 422 423 applicationScope.launch { interactor.activate() } 424 } 425 } 426 427 private sealed interface LockEvent { 428 val debugReason: String 429 } 430 431 private data class LockImmediately(override val debugReason: String) : LockEvent 432 433 private data class LockWithDelay(override val debugReason: String) : LockEvent 434 435 private data class CancelDelayedLock(override val debugReason: String) : LockEvent 436 437 companion object { 438 private val TAG = "DeviceUnlockedInteractor" 439 @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last" 440 @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update" 441 } 442 } 443