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