summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Hao Dong <spdonghao@google.com> 2025-03-11 00:52:10 +0000
committer Hao Dong <spdonghao@google.com> 2025-03-21 01:37:03 +0000
commit02adb885fe493c461c35ae085f741e6f7cbb1d95 (patch)
tree52c8fc8c1d68283014192af85ac0eccc23e8a2fd
parent81ab9862188b7bd01347086e2c2be43d1a86562f (diff)
Add some new modality-specific APIs in BiometricManager.
- getEnrolledFingerprintCount() - getEnrolledFingerprintCount() Bug: 399438509 Test: atest AuthServiceTest Test: atest BiometricSimpleTests Flag: android.hardware.biometrics.move_fm_api_to_bm Change-Id: I8bdc8fdf965e65d1ecd3ed34fe469d4f7c7f3201
-rw-r--r--core/api/system-current.txt14
-rw-r--r--core/java/android/hardware/biometrics/BiometricEnrollmentStatus.aidl19
-rw-r--r--core/java/android/hardware/biometrics/BiometricEnrollmentStatus.java108
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java67
-rw-r--r--core/java/android/hardware/biometrics/IAuthService.aidl4
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java50
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java39
7 files changed, 299 insertions, 2 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 35720fd17769..41097630918b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4925,6 +4925,20 @@ package android.hardware {
package android.hardware.biometrics {
+ @FlaggedApi("android.hardware.biometrics.move_fm_api_to_bm") public final class BiometricEnrollmentStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method @IntRange(from=0) public int getEnrollCount();
+ method public int getModality();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.BiometricEnrollmentStatus> CREATOR;
+ }
+
+ public class BiometricManager {
+ method @FlaggedApi("android.hardware.biometrics.move_fm_api_to_bm") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public java.util.Set<android.hardware.biometrics.BiometricEnrollmentStatus> getEnrollmentStatus();
+ field @FlaggedApi("android.hardware.biometrics.move_fm_api_to_bm") @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public static final int TYPE_FACE = 8; // 0x8
+ field @FlaggedApi("android.hardware.biometrics.move_fm_api_to_bm") @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public static final int TYPE_FINGERPRINT = 2; // 0x2
+ }
+
public static interface BiometricManager.Authenticators {
field @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static final int BIOMETRIC_CONVENIENCE = 4095; // 0xfff
field @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static final int EMPTY_SET = 0; // 0x0
diff --git a/core/java/android/hardware/biometrics/BiometricEnrollmentStatus.aidl b/core/java/android/hardware/biometrics/BiometricEnrollmentStatus.aidl
new file mode 100644
index 000000000000..2d28f3dddb83
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricEnrollmentStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2025 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;
+
+// @hide
+parcelable BiometricEnrollmentStatus;
diff --git a/core/java/android/hardware/biometrics/BiometricEnrollmentStatus.java b/core/java/android/hardware/biometrics/BiometricEnrollmentStatus.java
new file mode 100644
index 000000000000..f883b9f72d7c
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricEnrollmentStatus.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2025 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.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains enrollment information. It keeps track of the modality type (e.g.
+ * fingerprint, face) and the number of times the biometric has been enrolled.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_MOVE_FM_API_TO_BM)
+public final class BiometricEnrollmentStatus implements Parcelable {
+ @BiometricManager.BiometricModality
+ private final int mModality;
+ private final int mEnrollCount;
+
+ /**
+ * @hide
+ */
+ public BiometricEnrollmentStatus(
+ @BiometricManager.BiometricModality int modality, int enrollCount) {
+ mModality = modality;
+ mEnrollCount = enrollCount;
+ }
+
+ /**
+ * Returns the modality associated with this enrollment status.
+ *
+ * @return The int value representing the biometric sensor type, e.g.
+ * {@link BiometricManager#TYPE_FACE} or
+ * {@link BiometricManager#TYPE_FINGERPRINT}.
+ */
+ @BiometricManager.BiometricModality
+ public int getModality() {
+ return mModality;
+ }
+
+ /**
+ * Returns the number of enrolled biometric for the associated modality.
+ *
+ * @return The number of enrolled biometric.
+ */
+ @IntRange(from = 0)
+ public int getEnrollCount() {
+ return mEnrollCount;
+ }
+
+ private BiometricEnrollmentStatus(Parcel in) {
+ this(in.readInt(), in.readInt());
+ }
+
+ @NonNull
+ public static final Creator<BiometricEnrollmentStatus> CREATOR = new Creator<>() {
+ @Override
+ public BiometricEnrollmentStatus createFromParcel(Parcel in) {
+ return new BiometricEnrollmentStatus(in);
+ }
+
+ @Override
+ public BiometricEnrollmentStatus[] newArray(int size) {
+ return new BiometricEnrollmentStatus[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mModality);
+ dest.writeInt(mEnrollCount);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ String modality = "";
+ if (mModality == BiometricManager.TYPE_FINGERPRINT) {
+ modality = "Fingerprint";
+ } else if (mModality == BiometricManager.TYPE_FACE) {
+ modality = "Face";
+ }
+ return "Modality: " + modality + ", Enrolled Count: " + mEnrollCount;
+ }
+}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index cefe20c15ced..8ac24c81a9f0 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -16,6 +16,7 @@
package android.hardware.biometrics;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -45,7 +46,9 @@ import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* A class that contains biometric utilities. For authentication, see {@link BiometricPrompt}.
@@ -143,6 +146,37 @@ public class BiometricManager {
public @interface BiometricError {}
/**
+ * Constant representing fingerprint.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_MOVE_FM_API_TO_BM)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+ public static final int TYPE_FINGERPRINT = BiometricAuthenticator.TYPE_FINGERPRINT;
+
+ /**
+ * Constant representing face.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_MOVE_FM_API_TO_BM)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+ public static final int TYPE_FACE = BiometricAuthenticator.TYPE_FACE;
+
+ /**
+ * An {@link IntDef} representing the biometric modalities.
+ * @hide
+ */
+ @IntDef(flag = true, value = {
+ TYPE_FINGERPRINT,
+ TYPE_FACE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface BiometricModality {
+ }
+
+
+ /**
* Types of authenticators, defined at a level of granularity supported by
* {@link BiometricManager} and {@link BiometricPrompt}.
*
@@ -406,8 +440,6 @@ public class BiometricManager {
/**
* @hide
- * @param context
- * @param service
*/
public BiometricManager(@NonNull Context context, @NonNull IAuthService service) {
mContext = context;
@@ -567,6 +599,37 @@ public class BiometricManager {
}
/**
+ * Return the current biometrics enrollment status set.
+ *
+ * <p>Returning more than one status indicates that the device supports multiple biometric
+ * modalities (e.g., fingerprint, face, iris). Each {@link BiometricEnrollmentStatus} object
+ * within the returned collection provides detailed information about the enrollment state for a
+ * particular modality.
+ *
+ * <p>This method is intended for system apps, such as settings or device setup, which require
+ * detailed enrollment information to show or hide features or to encourage users to enroll
+ * in a specific modality. Applications should instead use
+ * {@link BiometricManager#canAuthenticate(int)} to check the enrollment status and use the
+ * enroll intent, when needed to allow users to enroll. That ensures that users are presented
+ * with a consistent set of options across all of their apps and can be redirected to a
+ * single system-managed settings surface.</p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
+ @FlaggedApi(Flags.FLAG_MOVE_FM_API_TO_BM)
+ @NonNull
+ public Set<BiometricEnrollmentStatus> getEnrollmentStatus() {
+ try {
+ return new HashSet<BiometricEnrollmentStatus>(
+ mService.getEnrollmentStatus(mContext.getOpPackageName()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* @param userId
* @return
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 8514f98fbf0d..b6ce79af00fe 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -22,6 +22,7 @@ import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.BiometricEnrollmentStatus;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
@@ -64,6 +65,9 @@ interface IAuthService {
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId, String opPackageName);
+ // Return the current biometrics enrollment status.
+ List<BiometricEnrollmentStatus> getEnrollmentStatus(String opPackageName);
+
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b6768c9c087a..eede4c9c59d0 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -36,6 +36,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricEnrollmentStatus;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IAuthService;
@@ -417,6 +418,55 @@ public class AuthService extends SystemService {
}
@Override
+ public List<BiometricEnrollmentStatus> getEnrollmentStatus(String opPackageName)
+ throws RemoteException {
+ checkBiometricAdvancedPermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int userId = UserHandle.myUserId();
+ final List<BiometricEnrollmentStatus> enrollmentStatusList =
+ new ArrayList<>();
+ final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+ if (fingerprintService != null) {
+ final List<FingerprintSensorPropertiesInternal> fpProps =
+ fingerprintService.getSensorPropertiesInternal(opPackageName);
+ if (!fpProps.isEmpty()) {
+ int fpCount = fingerprintService.getEnrolledFingerprints(userId,
+ opPackageName, getContext().getAttributionTag()).size();
+ enrollmentStatusList.add(
+ new BiometricEnrollmentStatus(
+ BiometricManager.TYPE_FINGERPRINT, fpCount));
+ } else {
+ Slog.e(TAG, "No fingerprint sensors");
+ }
+ } else {
+ Slog.e(TAG, "No fingerprint sensors");
+ }
+
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ final List<FaceSensorPropertiesInternal> faceProps =
+ faceService.getSensorPropertiesInternal(opPackageName);
+ if (!faceProps.isEmpty()) {
+ int faceCount = faceService.getEnrolledFaces(faceProps.getFirst().sensorId,
+ userId, opPackageName).size();
+ enrollmentStatusList.add(
+ new BiometricEnrollmentStatus(
+ BiometricManager.TYPE_FACE, faceCount));
+ } else {
+ Slog.e(TAG, "No face sensors");
+ }
+ } else {
+ Slog.e(TAG, "No face sensors");
+ }
+
+ return enrollmentStatusList;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void registerEnabledOnKeyguardCallback(
IBiometricEnabledOnKeyguardCallback callback) throws RemoteException {
checkInternalPermission();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index c7efa318af99..cebdce9ed6cc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -49,11 +49,14 @@ import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.face.FaceSensorConfigurations;
+import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintSensorConfigurations;
+import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.iris.IIrisService;
@@ -84,6 +87,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Stubber;
+import java.util.ArrayList;
import java.util.List;
@Presubmit
@@ -519,6 +523,41 @@ public class AuthServiceTest {
verify(mBiometricService).getLastAuthenticationTime(eq(mUserId), eq(authenticators));
}
+ @Test
+ public void testGetEnrollmentStatus_callsFingerprintAndFaceService() throws Exception {
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+ List<FaceSensorPropertiesInternal> faceProps = List.of(new FaceSensorPropertiesInternal(
+ 0 /* id */,
+ FaceSensorProperties.STRENGTH_STRONG,
+ 1 /* maxTemplatesAllowed */,
+ new ArrayList<>() /* componentInfo */,
+ FaceSensorProperties.TYPE_UNKNOWN,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */));
+ List<FingerprintSensorPropertiesInternal> fpProps = List.of(
+ new FingerprintSensorPropertiesInternal(1 /* id */,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ new ArrayList<>() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ false /* resetLockoutRequiresHardwareAuthToken */));
+ when(mFaceService.getSensorPropertiesInternal(eq(TEST_OP_PACKAGE_NAME))).thenReturn(
+ faceProps);
+ when(mFingerprintService.getSensorPropertiesInternal(eq(TEST_OP_PACKAGE_NAME))).thenReturn(
+ fpProps);
+ when(mContext.getAttributionTag()).thenReturn("tag");
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ mAuthService.mImpl.getEnrollmentStatus(TEST_OP_PACKAGE_NAME);
+
+ waitForIdle();
+ verify(mFaceService).getEnrolledFaces(eq(0), eq(mUserId), eq(TEST_OP_PACKAGE_NAME));
+ verify(mFingerprintService).getEnrolledFingerprints(eq(mUserId), eq(TEST_OP_PACKAGE_NAME),
+ eq("tag"));
+ }
+
private static void setInternalAndTestBiometricPermissions(
Context context, boolean hasPermission) {
for (String p : List.of(TEST_BIOMETRIC, MANAGE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {