1 /* 2 * 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.compatibility.common.deviceinfo; 18 19 import static android.Manifest.permission.TEST_BIOMETRIC; 20 21 import android.annotation.TargetApi; 22 import android.content.pm.PackageManager; 23 import android.hardware.biometrics.BiometricManager; 24 import android.hardware.biometrics.SensorProperties; 25 import android.hardware.biometrics.SensorProperties.ComponentInfo; 26 import android.os.Build; 27 import android.util.Log; 28 29 import com.android.compatibility.common.util.ApiLevelUtil; 30 import com.android.compatibility.common.util.DeviceInfoStore; 31 import com.android.compatibility.common.util.SystemUtil; 32 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 39 /** 40 * Biometrics info collector. 41 */ 42 @TargetApi(Build.VERSION_CODES.S) 43 public class BiometricsDeviceInfo extends DeviceInfo { 44 private static final String TAG = "BiometricsDeviceInfo"; 45 46 private static final String DUMPSYS_BIOMETRIC = "dumpsys biometric"; 47 48 // BiometricAuthenticator.Modality.TYPE_FINGERPRINT 49 private static final int AUTHENTICATOR_TYPE_FINGERPRINT = 1 << 1; 50 // BiometricAuthenticator.Modality.TYPE_IRIS 51 private static final int AUTHENTICATOR_TYPE_IRIS = 1 << 2; 52 // BiometricAuthenticator.Modality.TYPE_FACE 53 private static final int AUTHENTICATOR_TYPE_FACE = 1 << 3; 54 55 // BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE 56 private static final int AUTHENTICATOR_BIOMETRIC_CONVENIENCE = 0x0FFF; 57 // BiometricManager.Authenticators.BIOMETRIC_WEAK 58 private static final int AUTHENTICATOR_BIOMETRIC_WEAK = 0x00FF; 59 // BiometricManager.Authenticators.BIOMETRIC_STRONG 60 private static final int AUTHENTICATOR_BIOMETRIC_STRONG = 0x000F; 61 62 // SensorProperties.STRENGTH_CONVENIENCE 63 private static final int SENSOR_PROPERTIES_STRENGTH_CONVENIENCE = 0; 64 // SensorProperties.STRENGTH_WEAK 65 private static final int SENSOR_PROPERTIES_STRENGTH_WEAK = 1; 66 // SensorProperties.STRENGTH_STRONG 67 private static final int SENSOR_PROPERTIES_STRENGTH_STRONG = 2; 68 69 private static final String BIOMETRIC_PROPERTIES = "biometric_properties"; 70 private static final String SENSOR_ID = "sensor_id"; 71 72 private static final String SENSOR_MODALITY = "modality"; 73 private static final int MODALITY_UNKNOWN = 0; 74 private static final int MODALITY_FINGERPRINT = 1; // 1 << 0 75 private static final int MODALITY_IRIS = 2; // 1 << 1 76 private static final int MODALITY_FACE = 4; // 1 << 2 77 78 private static final String STRENGTH = "strength"; 79 private static final String CURRENT_STRENGTH = "current_strength"; 80 private static final int STRENGTH_UNKNOWN = 0; 81 private static final int STRENGTH_CONVENIENCE = 1; 82 private static final int STRENGTH_WEAK = 2; 83 private static final int STRENGTH_STRONG = 3; 84 85 private static final String COMPONENT_INFO = "component_info"; 86 private static final String COMPONENT_INFO_COMPONENT_ID = "component_id"; 87 private static final String COMPONENT_INFO_HARDWARE_VERSION = "hardware_version"; 88 private static final String COMPONENT_INFO_FIRMWARE_VERSION = "firmware_version"; 89 private static final String COMPONENT_INFO_SERIAL_NUMBER = "serial_number"; 90 private static final String COMPONENT_INFO_SOFTWARE_VERSION = "software_version"; 91 92 /** 93 * Information of a single sensor. 94 */ 95 private static class SensorInfo { 96 private final int mModality; 97 private final int mCurStrength; 98 SensorInfo(int modality, int curStrength)99 private SensorInfo(int modality, int curStrength) { 100 mModality = modality; 101 mCurStrength = curStrength; 102 } 103 } 104 105 @Override collectDeviceInfo(DeviceInfoStore store)106 protected void collectDeviceInfo(DeviceInfoStore store) throws Exception { 107 if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) { 108 Log.d(TAG, "Skipping test for devices not launching with at least Android S"); 109 return; 110 } 111 112 final PackageManager pm = getContext().getPackageManager(); 113 if (!(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) 114 || pm.hasSystemFeature(PackageManager.FEATURE_FACE) 115 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS))) { 116 Log.d(TAG, "Skipping test for devices without biometric features"); 117 return; 118 } 119 120 final BiometricManager bm = getContext().getSystemService(BiometricManager.class); 121 final List<SensorProperties> sensorProperties = 122 SystemUtil.callWithShellPermissionIdentity( 123 () -> bm != null ? bm.getSensorProperties() : null, TEST_BIOMETRIC 124 ); 125 if (sensorProperties == null) { 126 Log.d(TAG, "Cannot get sensor properties"); 127 return; 128 } 129 final Map<Integer, SensorInfo> sensors = getSensorInfo(); 130 131 store.startArray(BIOMETRIC_PROPERTIES); 132 for (SensorProperties props : sensorProperties) { 133 final SensorInfo sensorInfo = sensors.getOrDefault(props.getSensorId(), null); 134 store.startGroup(); 135 store.addResult(SENSOR_ID, props.getSensorId()); 136 store.addResult(SENSOR_MODALITY, 137 sensorInfo != null ? sensorInfo.mModality : MODALITY_UNKNOWN); 138 store.addResult(STRENGTH, propertyStrengthToSensorStrength(props.getSensorStrength())); 139 store.addResult(CURRENT_STRENGTH, 140 sensorInfo != null ? sensorInfo.mCurStrength : STRENGTH_UNKNOWN); 141 store.startArray(COMPONENT_INFO); 142 for (ComponentInfo info : props.getComponentInfo()) { 143 store.startGroup(); 144 store.addResult(COMPONENT_INFO_COMPONENT_ID, info.getComponentId()); 145 store.addResult(COMPONENT_INFO_HARDWARE_VERSION, info.getHardwareVersion()); 146 store.addResult(COMPONENT_INFO_FIRMWARE_VERSION, info.getFirmwareVersion()); 147 store.addResult(COMPONENT_INFO_SERIAL_NUMBER, info.getSerialNumber()); 148 store.addResult(COMPONENT_INFO_SOFTWARE_VERSION, info.getSoftwareVersion()); 149 store.endGroup(); 150 } 151 store.endArray(); // "component_info" 152 store.endGroup(); 153 } 154 store.endArray(); // "biometric_properties" 155 } 156 157 /** 158 * A helper function to get information of each sensor. 159 * @return Mapping of sensor ID to the corresponding sensor information 160 */ getSensorInfo()161 private Map<Integer, SensorInfo> getSensorInfo() { 162 final Map<Integer, SensorInfo> sensors = new HashMap<>(); 163 164 final String output = SystemUtil.runShellCommand(DUMPSYS_BIOMETRIC); 165 if (output == null || output.isEmpty()) { 166 Log.d(TAG, "dumpsys biometric does not generate anything"); 167 return sensors; 168 } 169 170 Pattern pattern = Pattern.compile("ID\\((?<ID>\\d+)\\).*" 171 + "oemStrength:\\s*(?<strength>\\d+).*" 172 + "updatedStrength:\\s*(?<curStrength>\\d+).*" 173 + "modality\\s*(?<modality>\\d+)"); 174 Matcher matcher = pattern.matcher(output); 175 boolean matched = false; 176 while (matcher.find()) { 177 matched = true; 178 final int sensorId = Integer.parseInt(matcher.group("ID")); 179 final int modality = toModality(Integer.parseInt(matcher.group("modality"))); 180 final int strength = authenticatorStrengthToSensorStrength(Integer.parseInt( 181 matcher.group("strength"))); 182 final int curStrength = authenticatorStrengthToSensorStrength(Integer.parseInt( 183 matcher.group("curStrength"))); 184 assertTrue("The current strength cannot be stronger than the original strength", 185 curStrength <= strength); 186 sensors.put(sensorId, new SensorInfo(modality, curStrength)); 187 } 188 if (!matched) { 189 Log.d(TAG, "No matched sensor info: dumpsys output=" + output); 190 } 191 return sensors; 192 } 193 194 /** 195 * Convert a modality (BiometricAuthenticator.Modality) to the corresponding enum to be stored. 196 * 197 * @param modality See BiometricAuthenticator.Modality 198 * @return The enum to be stored 199 */ toModality(int modality)200 private int toModality(int modality) { 201 switch (modality) { 202 case AUTHENTICATOR_TYPE_FINGERPRINT: 203 return MODALITY_FINGERPRINT; 204 case AUTHENTICATOR_TYPE_IRIS: 205 return MODALITY_IRIS; 206 case AUTHENTICATOR_TYPE_FACE: 207 return MODALITY_FACE; 208 default: 209 return MODALITY_UNKNOWN; 210 } 211 } 212 213 /** 214 * Convert an authenticator strength (BiometricManager.Authenticators.Types) to the 215 * corresponding enum to be stored. 216 * 217 * @param strength See BiometricManager.Authenticators.Types 218 * @return The enum to be stored 219 */ authenticatorStrengthToSensorStrength(int strength)220 private int authenticatorStrengthToSensorStrength(int strength) { 221 switch (strength) { 222 case AUTHENTICATOR_BIOMETRIC_CONVENIENCE: 223 return STRENGTH_CONVENIENCE; 224 case AUTHENTICATOR_BIOMETRIC_WEAK: 225 return STRENGTH_WEAK; 226 case AUTHENTICATOR_BIOMETRIC_STRONG: 227 return STRENGTH_STRONG; 228 default: 229 return STRENGTH_UNKNOWN; 230 } 231 } 232 233 /** 234 * Convert a sensor property strength (SensorProperties.Strength) to the corresponding enum to 235 * be stored. 236 * 237 * @param strength See SensorProperties.Strength 238 * @return The enum to be stored 239 */ propertyStrengthToSensorStrength(int strength)240 private int propertyStrengthToSensorStrength(int strength) { 241 switch (strength) { 242 case SENSOR_PROPERTIES_STRENGTH_CONVENIENCE: 243 return STRENGTH_CONVENIENCE; 244 case SENSOR_PROPERTIES_STRENGTH_WEAK: 245 return STRENGTH_WEAK; 246 case SENSOR_PROPERTIES_STRENGTH_STRONG: 247 return STRENGTH_STRONG; 248 default: 249 return STRENGTH_UNKNOWN; 250 } 251 } 252 } 253