1 /*
2  * 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.annotation.IdRes
20 import android.content.Context
21 import android.hardware.biometrics.BiometricManager.Authenticators
22 import android.hardware.biometrics.ComponentInfoInternal
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 android.os.Bundle
31 import android.testing.ViewUtils
32 import android.view.LayoutInflater
33 
34 /**
35  * Inflate the given BiometricPrompt layout and initialize it with test parameters.
36  *
37  * This attaches the view so be sure to call [destroyDialog] at the end of the test.
38  */
39 @IdRes
40 internal fun <T : AuthBiometricView> Int.asTestAuthBiometricView(
41     context: Context,
42     callback: AuthBiometricView.Callback,
43     panelController: AuthPanelController,
44     allowDeviceCredential: Boolean = false,
45     savedState: Bundle? = null,
46     hideDelay: Int = 0
47 ): T {
48     val view = LayoutInflater.from(context).inflate(this, null, false) as T
49     view.mAnimationDurationLong = 0
50     view.mAnimationDurationShort = 0
51     view.mAnimationDurationHideDialog = hideDelay
52     view.setPromptInfo(buildPromptInfo(allowDeviceCredential))
53     view.setCallback(callback)
54     view.restoreState(savedState)
55     view.setPanelController(panelController)
56 
57     ViewUtils.attachView(view)
58 
59     return view
60 }
61 
62 private fun buildPromptInfo(allowDeviceCredential: Boolean): PromptInfo {
63     val promptInfo = PromptInfo()
64     promptInfo.title = "Title"
65     var authenticators = Authenticators.BIOMETRIC_WEAK
66     if (allowDeviceCredential) {
67         authenticators = authenticators or Authenticators.DEVICE_CREDENTIAL
68     } else {
69         promptInfo.negativeButtonText = "Negative"
70     }
71     promptInfo.authenticators = authenticators
72     return promptInfo
73 }
74 
75 /** Detach the view, if needed. */
76 internal fun AuthBiometricView?.destroyDialog() {
77     if (this != null && isAttachedToWindow) {
78         ViewUtils.detachView(this)
79     }
80 }
81 
82 /** Create [FingerprintSensorPropertiesInternal] for a test. */
83 internal fun fingerprintSensorPropertiesInternal(
84     ids: List<Int> = listOf(0),
85     strong: Boolean = true,
86 ): List<FingerprintSensorPropertiesInternal> {
87     val componentInfo =
88         listOf(
89             ComponentInfoInternal(
90                 "fingerprintSensor" /* componentId */,
91                 "vendor/model/revision" /* hardwareVersion */,
92                 "1.01" /* firmwareVersion */,
93                 "00000001" /* serialNumber */,
94                 "" /* softwareVersion */
95             ),
96             ComponentInfoInternal(
97                 "matchingAlgorithm" /* componentId */,
98                 "" /* hardwareVersion */,
99                 "" /* firmwareVersion */,
100                 "" /* serialNumber */,
101                 "vendor/version/revision" /* softwareVersion */
102             )
103         )
104     return ids.map { id ->
105         FingerprintSensorPropertiesInternal(
106             id,
107             if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
108             5 /* maxEnrollmentsPerUser */,
109             componentInfo,
110             FingerprintSensorProperties.TYPE_REAR,
111             false /* resetLockoutRequiresHardwareAuthToken */
112         )
113     }
114 }
115 
116 /** Create [FaceSensorPropertiesInternal] for a test. */
117 internal fun faceSensorPropertiesInternal(
118     ids: List<Int> = listOf(1),
119     strong: Boolean = true,
120 ): List<FaceSensorPropertiesInternal> {
121     val componentInfo =
122         listOf(
123             ComponentInfoInternal(
124                 "faceSensor" /* componentId */,
125                 "vendor/model/revision" /* hardwareVersion */,
126                 "1.01" /* firmwareVersion */,
127                 "00000001" /* serialNumber */,
128                 "" /* softwareVersion */
129             ),
130             ComponentInfoInternal(
131                 "matchingAlgorithm" /* componentId */,
132                 "" /* hardwareVersion */,
133                 "" /* firmwareVersion */,
134                 "" /* serialNumber */,
135                 "vendor/version/revision" /* softwareVersion */
136             )
137         )
138     return ids.map { id ->
139         FaceSensorPropertiesInternal(
140             id,
141             if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
142             2 /* maxEnrollmentsPerUser */,
143             componentInfo,
144             FaceSensorProperties.TYPE_RGB,
145             true /* supportsFaceDetection */,
146             true /* supportsSelfIllumination */,
147             false /* resetLockoutRequiresHardwareAuthToken */
148         )
149     }
150 }
151 
152 @Authenticators.Types
153 internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes(): Int {
154     var authenticators = Authenticators.EMPTY_SET
155     mapNotNull { it?.sensorStrength }
156         .forEach { strength ->
157             authenticators =
158                 authenticators or
159                     when (strength) {
160                         SensorProperties.STRENGTH_CONVENIENCE ->
161                             Authenticators.BIOMETRIC_CONVENIENCE
162                         SensorProperties.STRENGTH_WEAK -> Authenticators.BIOMETRIC_WEAK
163                         SensorProperties.STRENGTH_STRONG -> Authenticators.BIOMETRIC_STRONG
164                         else -> Authenticators.EMPTY_SET
165                     }
166         }
167     return authenticators
168 }
169 
170 internal fun promptInfo(
171     title: String = "title",
172     subtitle: String = "sub",
173     description: String = "desc",
174     credentialTitle: String? = "cred title",
175     credentialSubtitle: String? = "cred sub",
176     credentialDescription: String? = "cred desc",
177     negativeButton: String = "neg",
178 ): PromptInfo {
179     val info = PromptInfo()
180     info.title = title
181     info.subtitle = subtitle
182     info.description = description
183     credentialTitle?.let { info.deviceCredentialTitle = it }
184     credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
185     credentialDescription?.let { info.deviceCredentialDescription = it }
186     info.negativeButtonText = negativeButton
187     return info
188 }
189