From fc26e8a0e039c2ac1f349980988d2665ea8d840b Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Thu, 1 Oct 2020 11:51:31 -0700 Subject: [PATCH] Add wireframe for biometric TestApis 1) Adds BiometricTestSession and empty implementations 2) Adds TEST_BIOMETRIC permission 3) Grants Shell USE_BIOMETRIC and TEST_BIOMETRIC permission Bug: 169459906 Test: make -j update-api && make -j Change-Id: Iea9fd37d7234eb7e5e34a7a2698cbf0ca7a33a77 --- api/test-current.txt | 38 ++++ .../hardware/biometrics/BiometricManager.java | 14 ++ .../biometrics/BiometricTestSession.java | 202 ++++++++++++++++++ .../hardware/biometrics/ITestService.aidl | 57 +++++ .../hardware/biometrics/SensorProperties.java | 7 +- .../biometrics/SensorPropertiesInternal.aidl | 18 ++ .../fingerprint/FingerprintManager.java | 19 ++ .../fingerprint/IFingerprintService.aidl | 5 + core/res/AndroidManifest.xml | 6 + data/etc/privapp-permissions-platform.xml | 3 + packages/Shell/AndroidManifest.xml | 6 + .../fingerprint/FingerprintService.java | 66 ++++++ 12 files changed, 436 insertions(+), 5 deletions(-) create mode 100644 core/java/android/hardware/biometrics/BiometricTestSession.java create mode 100644 core/java/android/hardware/biometrics/ITestService.aidl create mode 100644 core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl diff --git a/api/test-current.txt b/api/test-current.txt index 4e79dc28a0ea0..b8c0c806d30d7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -23,6 +23,7 @@ package android { field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; + field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; @@ -1223,6 +1224,35 @@ package android.graphics.drawable { } +package android.hardware.biometrics { + + public class BiometricManager { + method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession getTestSession(); + } + + public class BiometricTestSession implements java.lang.AutoCloseable { + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void authenticateReject(int, int); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void authenticateSuccess(int, int); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void close(); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void enableTestHal(int, boolean); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void enrollFinish(int, int); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void enrollStart(int, int); + method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List getSensorProperties(); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void internalCleanup(int, int); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyAcquired(int, int); + method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyError(int, int); + } + + public class SensorProperties { + method public int getSensorId(); + method public int getSensorStrength(); + field public static final int STRENGTH_CONVENIENCE = 0; // 0x0 + field public static final int STRENGTH_STRONG = 2; // 0x2 + field public static final int STRENGTH_WEAK = 1; // 0x1 + } + +} + package android.hardware.camera2 { public abstract class CameraDevice implements java.lang.AutoCloseable { @@ -1328,6 +1358,14 @@ package android.hardware.display { } +package android.hardware.fingerprint { + + @Deprecated public class FingerprintManager { + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession getTestSession(); + } + +} + package android.hardware.hdmi { public final class HdmiControlManager { diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 8d0cc68056f87..35ef53b528913 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -16,14 +16,17 @@ package android.hardware.biometrics; +import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.os.RemoteException; import android.security.keystore.KeyGenParameterSpec; @@ -195,6 +198,17 @@ public class BiometricManager { mService = service; } + /** + * Retrieves a test session for BiometricManager/BiometricPrompt. + * @hide + */ + @TestApi + @NonNull + @RequiresPermission(TEST_BIOMETRIC) + public BiometricTestSession getTestSession() { + return null; // TODO(169459906) + } + /** * Determine if biometrics can be used. In other words, determine if * {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled, diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java new file mode 100644 index 0000000000000..719efa8ce6dda --- /dev/null +++ b/core/java/android/hardware/biometrics/BiometricTestSession.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import static android.Manifest.permission.TEST_BIOMETRIC; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.content.Context; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and + * {@link android.hardware.fingerprint.FingerprintManager}. + * @hide + */ +@TestApi +public class BiometricTestSession implements AutoCloseable { + + private static final String TAG = "TestManager"; + + private final Context mContext; + private final ITestService mTestService; + + /** + * @hide + */ + public BiometricTestSession(@NonNull Context context, @NonNull ITestService testService) { + mContext = context; + mTestService = testService; + } + + /** + * @return A list of {@link SensorProperties} + */ + @NonNull + @RequiresPermission(TEST_BIOMETRIC) + public List getSensorProperties() { + try { + final List internalProps = + mTestService.getSensorPropertiesInternal(mContext.getOpPackageName()); + final List props = new ArrayList<>(); + for (SensorPropertiesInternal internalProp : internalProps) { + props.add(new SensorProperties(internalProp.sensorId, internalProp.sensorStrength)); + } + return props; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Switches the specified sensor to use a test HAL. In this mode, the framework will not invoke + * any methods on the real HAL implementation. This allows the framework to test a substantial + * portion of the framework code that would otherwise require human interaction. Note that + * secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its + * equivalent for the secret key. + * + * @param sensorId Sensor that this command applies to. + * @param enableTestHal If true, enable testing with a fake HAL instead of the real HAL. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void enableTestHal(int sensorId, boolean enableTestHal) { + try { + mTestService.enableTestHal(sensorId, enableTestHal); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + /** + * Starts the enrollment process. This should generally be used when the test HAL is enabled. + * + * @param sensorId Sensor that this command applies to. + * @param userId User that this command applies to. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void enrollStart(int sensorId, int userId) { + try { + mTestService.enrollStart(sensorId, userId); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + /** + * Finishes the enrollment process. Simulates the HAL's callback. + * + * @param sensorId Sensor that this command applies to. + * @param userId User that this command applies to. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void enrollFinish(int sensorId, int userId) { + try { + mTestService.enrollFinish(sensorId, userId); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + /** + * Simulates a successful authentication, but does not provide a valid HAT. + * + * @param sensorId Sensor that this command applies to. + * @param userId User that this command applies to. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void authenticateSuccess(int sensorId, int userId) { + try { + mTestService.authenticateSuccess(sensorId, userId); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + /** + * Simulates a rejected attempt. + * + * @param sensorId Sensor that this command applies to. + * @param userId User that this command applies to. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void authenticateReject(int sensorId, int userId) { + try { + mTestService.authenticateReject(sensorId, userId); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + /** + * Simulates an acquired message from the HAL. + * + * @param sensorId Sensor that this command applies to. + * @param userId User that this command applies to. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void notifyAcquired(int sensorId, int userId) { + try { + mTestService.notifyAcquired(sensorId, userId); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + /** + * Simulates an error message from the HAL. + * + * @param sensorId Sensor that this command applies to. + * @param userId User that this command applies to. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void notifyError(int sensorId, int userId) { + try { + mTestService.notifyError(sensorId, userId); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + /** + * Matches the framework's cached enrollments against the HAL's enrollments. Any enrollment + * that isn't known by both sides are deleted. This should generally be used when the test + * HAL is disabled (e.g. to clean up after a test). + * + * @param sensorId Sensor that this command applies to. + * @param userId User that this command applies to. + */ + @RequiresPermission(TEST_BIOMETRIC) + public void internalCleanup(int sensorId, int userId) { + try { + mTestService.internalCleanup(sensorId, userId); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); + } + } + + @Override + @RequiresPermission(TEST_BIOMETRIC) + public void close() { + + } +} diff --git a/core/java/android/hardware/biometrics/ITestService.aidl b/core/java/android/hardware/biometrics/ITestService.aidl new file mode 100644 index 0000000000000..6373132660225 --- /dev/null +++ b/core/java/android/hardware/biometrics/ITestService.aidl @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +import android.hardware.biometrics.SensorPropertiesInternal; + +/** + * A test service for FingerprintManager and BiometricPrompt. + * @hide + */ +interface ITestService { + // Returns a list of sensor properties supported by the interface. + List getSensorPropertiesInternal(String opPackageName); + + // Switches the specified sensor to use a test HAL. In this mode, the framework will not invoke + // any methods on the real HAL implementation. This allows the framework to test a substantial + // portion of the framework code that would otherwise require human interaction. Note that + // secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its + // equivalent for the secret key. + void enableTestHal(int sensorId, boolean enableTestHal); + + // Starts the enrollment process. This should generally be used when the test HAL is enabled. + void enrollStart(int sensorId, int userId); + + // Finishes the enrollment process. Simulates the HAL's callback. + void enrollFinish(int sensorId, int userId); + + // Simulates a successful authentication, but does not provide a valid HAT. + void authenticateSuccess(int sensorId, int userId); + + // Simulates a rejected attempt. + void authenticateReject(int sensorId, int userId); + + // Simulates an acquired message from the HAL. + void notifyAcquired(int sensorId, int userId); + + // Simulates an error message from the HAL. + void notifyError(int sensorId, int userId); + + // Matches the framework's cached enrollments against the HAL's enrollments. Any enrollment + // that isn't known by both sides are deleted. This should generally be used when the test + // HAL is disabled (e.g. to clean up after a test). + void internalCleanup(int sensorId, int userId); +} diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java index b3dcb8f705327..5b1b5e612c734 100644 --- a/core/java/android/hardware/biometrics/SensorProperties.java +++ b/core/java/android/hardware/biometrics/SensorProperties.java @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.annotation.IntDef; +import android.annotation.TestApi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -25,19 +26,18 @@ import java.lang.annotation.RetentionPolicy; * The base class containing all modality-agnostic information. * @hide */ +@TestApi public class SensorProperties { /** * A sensor that meets the requirements for Class 1 biometrics as defined in the CDD. This does * not correspond to a public BiometricManager.Authenticators constant. Sensors of this strength * are not available to applications via the public API surface. - * @hide */ public static final int STRENGTH_CONVENIENCE = 0; /** * A sensor that meets the requirements for Class 2 biometrics as defined in the CDD. * Corresponds to BiometricManager.Authenticators.BIOMETRIC_WEAK. - * @hide */ public static final int STRENGTH_WEAK = 1; @@ -46,7 +46,6 @@ public class SensorProperties { * Corresponds to BiometricManager.Authenticators.BIOMETRIC_STRONG. * * Notably, this is the only strength that allows generation of HardwareAuthToken(s). - * @hide */ public static final int STRENGTH_STRONG = 2; @@ -70,7 +69,6 @@ public class SensorProperties { /** * @return The sensor's unique identifier. - * @hide */ public int getSensorId() { return mSensorId; @@ -78,7 +76,6 @@ public class SensorProperties { /** * @return The sensor's strength. - * @hide */ @Strength public int getSensorStrength() { diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl b/core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl new file mode 100644 index 0000000000000..d85c78821f53a --- /dev/null +++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +parcelable SensorPropertiesInternal; \ No newline at end of file diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index b76b877234d78..84c5ea52d5559 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -19,6 +19,7 @@ package android.hardware.fingerprint; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_FINGERPRINT; import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT; +import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; @@ -28,6 +29,7 @@ import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -35,6 +37,7 @@ import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.BiometricTestSession; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.os.Binder; import android.os.CancellationSignal; @@ -94,6 +97,22 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing private Fingerprint mRemovalFingerprint; private Handler mHandler; + /** + * Retrieves a test session for FingerprintManager. + * @hide + */ + @TestApi + @NonNull + @RequiresPermission(TEST_BIOMETRIC) + public BiometricTestSession getTestSession() { + try { + return new BiometricTestSession(mContext, + mService.getTestService(mContext.getOpPackageName())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private class OnEnrollCancelListener implements OnCancelListener { @Override public void onCancel() { diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index f467b1dbd412b..518e3cade1723 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -17,6 +17,7 @@ package android.hardware.fingerprint; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.ITestService; import android.hardware.fingerprint.IFingerprintClientActiveCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -30,6 +31,10 @@ import java.util.List; * @hide */ interface IFingerprintService { + + // Retrieves a test service + ITestService getTestService(String opPackageName); + // Retrieve static sensor properties for all fingerprint sensors List getSensorPropertiesInternal(String opPackageName); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2ac2a1f33545a..e2f4f2f9294b8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4541,6 +4541,12 @@ + + + diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 81da5c865f50b..4c3b36f78eec8 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -437,6 +437,9 @@ applications that come with the platform + + + diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index a9279971ce1e8..bba29dba08271 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -329,6 +329,12 @@ + + + + + + mServiceProviders; + @Nullable private TestService mTestService; + + private final class TestService extends ITestService.Stub { + + @Override + public List getSensorPropertiesInternal( + String opPackageName) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + return null; + } + + @Override + public void enableTestHal(int sensorId, boolean enableTestHal) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + + @Override + public void enrollStart(int sensorId, int userId) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + + @Override + public void enrollFinish(int sensorId, int userId) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + + @Override + public void authenticateSuccess(int sensorId, int userId) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + + @Override + public void authenticateReject(int sensorId, int userId) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + + @Override + public void notifyAcquired(int sensorId, int userId) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + + @Override + public void notifyError(int sensorId, int userId) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + + @Override + public void internalCleanup(int sensorId, int userId) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } + } /** * Receives the incoming binder calls from FingerprintManager. */ private final class FingerprintServiceWrapper extends IFingerprintService.Stub { + @Override + public ITestService getTestService(String opPackageName) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + + synchronized (this) { + if (mTestService == null) { + mTestService = new TestService(); + } + } + return mTestService; + } + @Override // Binder call public List getSensorPropertiesInternal( String opPackageName) {