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