• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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