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