1 /*
<lambda>null2  * Copyright (C) 2021 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.biometrics
18 
19 import android.graphics.Bitmap
20 import android.hardware.biometrics.BiometricManager.Authenticators
21 import android.hardware.biometrics.ComponentInfoInternal
22 import android.hardware.biometrics.PromptContentView
23 import android.hardware.biometrics.PromptInfo
24 import android.hardware.biometrics.SensorProperties
25 import android.hardware.biometrics.SensorPropertiesInternal
26 import android.hardware.face.FaceSensorProperties
27 import android.hardware.face.FaceSensorPropertiesInternal
28 import android.hardware.fingerprint.FingerprintSensorProperties
29 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
30 import com.android.keyguard.keyguardUpdateMonitor
31 import com.android.systemui.SysuiTestableContext
32 import com.android.systemui.biometrics.data.repository.biometricStatusRepository
33 import com.android.systemui.biometrics.shared.model.AuthenticationReason
34 import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
35 import com.android.systemui.kosmos.Kosmos
36 import com.android.systemui.res.R
37 import com.android.systemui.util.mockito.whenever
38 import kotlinx.coroutines.ExperimentalCoroutinesApi
39 import kotlinx.coroutines.test.TestScope
40 import kotlinx.coroutines.test.runCurrent
41 
42 /** Create [FingerprintSensorPropertiesInternal] for a test. */
43 internal fun fingerprintSensorPropertiesInternal(
44     ids: List<Int> = listOf(0),
45     strong: Boolean = true,
46     sensorType: Int = FingerprintSensorProperties.TYPE_REAR,
47 ): List<FingerprintSensorPropertiesInternal> {
48     val componentInfo =
49         listOf(
50             ComponentInfoInternal(
51                 "fingerprintSensor" /* componentId */,
52                 "vendor/model/revision" /* hardwareVersion */,
53                 "1.01" /* firmwareVersion */,
54                 "00000001" /* serialNumber */,
55                 "", /* softwareVersion */
56             ),
57             ComponentInfoInternal(
58                 "matchingAlgorithm" /* componentId */,
59                 "" /* hardwareVersion */,
60                 "" /* firmwareVersion */,
61                 "" /* serialNumber */,
62                 "vendor/version/revision", /* softwareVersion */
63             ),
64         )
65     return ids.map { id ->
66         FingerprintSensorPropertiesInternal(
67             id,
68             if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
69             5 /* maxEnrollmentsPerUser */,
70             componentInfo,
71             sensorType,
72             false, /* resetLockoutRequiresHardwareAuthToken */
73         )
74     }
75 }
76 
77 /** Create [FaceSensorPropertiesInternal] for a test. */
faceSensorPropertiesInternalnull78 internal fun faceSensorPropertiesInternal(
79     ids: List<Int> = listOf(1),
80     strong: Boolean = true,
81 ): List<FaceSensorPropertiesInternal> {
82     val componentInfo =
83         listOf(
84             ComponentInfoInternal(
85                 "faceSensor" /* componentId */,
86                 "vendor/model/revision" /* hardwareVersion */,
87                 "1.01" /* firmwareVersion */,
88                 "00000001" /* serialNumber */,
89                 "", /* softwareVersion */
90             ),
91             ComponentInfoInternal(
92                 "matchingAlgorithm" /* componentId */,
93                 "" /* hardwareVersion */,
94                 "" /* firmwareVersion */,
95                 "" /* serialNumber */,
96                 "vendor/version/revision", /* softwareVersion */
97             ),
98         )
99     return ids.map { id ->
100         FaceSensorPropertiesInternal(
101             id,
102             if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
103             2 /* maxEnrollmentsPerUser */,
104             componentInfo,
105             FaceSensorProperties.TYPE_RGB,
106             true /* supportsFaceDetection */,
107             true /* supportsSelfIllumination */,
108             false, /* resetLockoutRequiresHardwareAuthToken */
109         )
110     }
111 }
112 
113 @Authenticators.Types
extractAuthenticatorTypesnull114 internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes(): Int {
115     var authenticators = Authenticators.EMPTY_SET
116     mapNotNull { it?.sensorStrength }
117         .forEach { strength ->
118             authenticators =
119                 authenticators or
120                     when (strength) {
121                         SensorProperties.STRENGTH_CONVENIENCE ->
122                             Authenticators.BIOMETRIC_CONVENIENCE
123                         SensorProperties.STRENGTH_WEAK -> Authenticators.BIOMETRIC_WEAK
124                         SensorProperties.STRENGTH_STRONG -> Authenticators.BIOMETRIC_STRONG
125                         else -> Authenticators.EMPTY_SET
126                     }
127         }
128     return authenticators
129 }
130 
promptInfonull131 internal fun promptInfo(
132     logoRes: Int = -1,
133     logoBitmap: Bitmap? = null,
134     logoDescription: String? = null,
135     title: String = "title",
136     subtitle: String = "sub",
137     description: String = "desc",
138     contentView: PromptContentView? = null,
139     credentialTitle: String? = "cred title",
140     credentialSubtitle: String? = "cred sub",
141     credentialDescription: String? = "cred desc",
142     negativeButton: String = "neg",
143 ): PromptInfo {
144     val info = PromptInfo()
145     if (logoBitmap != null) {
146         info.setLogo(logoRes, logoBitmap)
147     }
148     info.logoDescription = logoDescription
149     info.title = title
150     info.subtitle = subtitle
151     info.description = description
152     info.contentView = contentView
153     credentialTitle?.let { info.deviceCredentialTitle = it }
154     credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
155     credentialDescription?.let { info.deviceCredentialDescription = it }
156     info.negativeButtonText = negativeButton
157     return info
158 }
159 
160 @OptIn(ExperimentalCoroutinesApi::class)
updateSfpsIndicatorRequestsnull161 internal fun TestScope.updateSfpsIndicatorRequests(
162     kosmos: Kosmos,
163     mContext: SysuiTestableContext,
164     primaryBouncerRequest: Boolean? = null,
165     alternateBouncerRequest: Boolean? = null,
166     biometricPromptRequest: Boolean? = null,
167     // TODO(b/365182034): update when rest to unlock feature is implemented
168     //    progressBarShowing: Boolean? = null
169 ) {
170     biometricPromptRequest?.let { hasBiometricPromptRequest ->
171         if (hasBiometricPromptRequest) {
172             kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
173                 AuthenticationReason.BiometricPromptAuthentication
174             )
175         } else {
176             kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
177                 AuthenticationReason.NotRunning
178             )
179         }
180     }
181 
182     primaryBouncerRequest?.let { hasPrimaryBouncerRequest ->
183         updatePrimaryBouncer(
184             kosmos,
185             mContext,
186             isShowing = hasPrimaryBouncerRequest,
187             isAnimatingAway = false,
188             fpsDetectionRunning = true,
189             isUnlockingWithFpAllowed = true,
190         )
191     }
192 
193     alternateBouncerRequest?.let { hasAlternateBouncerRequest ->
194         kosmos.keyguardBouncerRepository.setAlternateVisible(hasAlternateBouncerRequest)
195     }
196 
197     // TODO(b/365182034): set progress bar visibility when rest to unlock feature is implemented
198 
199     runCurrent()
200 }
201 
updatePrimaryBouncernull202 internal fun updatePrimaryBouncer(
203     kosmos: Kosmos,
204     mContext: SysuiTestableContext,
205     isShowing: Boolean,
206     isAnimatingAway: Boolean,
207     fpsDetectionRunning: Boolean,
208     isUnlockingWithFpAllowed: Boolean,
209 ) {
210     kosmos.keyguardBouncerRepository.setPrimaryShow(isShowing)
211     kosmos.keyguardBouncerRepository.setPrimaryStartingToHide(false)
212     val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
213     kosmos.keyguardBouncerRepository.setPrimaryStartDisappearAnimation(
214         primaryStartDisappearAnimation
215     )
216 
217     whenever(kosmos.keyguardUpdateMonitor.isFingerprintDetectionRunning)
218         .thenReturn(fpsDetectionRunning)
219     whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
220         .thenReturn(isUnlockingWithFpAllowed)
221     mContext.orCreateTestableResources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, true)
222 }
223