1
0
mirror of https://github.com/ponces/treble_aosp.git synced 2024-11-28 01:14:52 +00:00
treble_aosp/patches/personal/platform_frameworks_base/0009-feat-Add-Face-Unlock-with-ParanoidSense-1-2.patch

2944 lines
123 KiB
Diff

From bf2cd94c2f900ea898d8531694a7b326ef9fe089 Mon Sep 17 00:00:00 2001
From: Chris Crump <chriscrawford893@gmail.com>
Date: Tue, 1 Nov 2022 17:27:48 -0400
Subject: [PATCH 09/10] feat: Add Face Unlock with ParanoidSense (1/2)
Based on AOSPA's implementation and adapted by @ghostrider-reborn
Co-authored-by: Chris Crump <chriscrawford893@gmail.com>
Co-authored-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
Change-Id: I05fa784d9f7f978be9f5944900a97ad7df19f59e
---
.../privacy/AppOpsPrivacyItemMonitor.kt | 8 +
.../phone/BiometricUnlockController.java | 2 +-
.../phone/KeyguardBypassController.kt | 11 +-
services/core/Android.bp | 1 +
.../biometrics/sensors/face/FaceService.java | 34 +-
.../face/sense/BiometricTestSessionImpl.java | 229 ++++
.../face/sense/FaceAuthenticationClient.java | 228 ++++
.../sensors/face/sense/FaceEnrollClient.java | 138 ++
.../sense/FaceGenerateChallengeClient.java | 111 ++
.../face/sense/FaceGetFeatureClient.java | 101 ++
.../face/sense/FaceInternalCleanupClient.java | 70 ++
.../sense/FaceInternalEnumerateClient.java | 58 +
.../sensors/face/sense/FaceRemovalClient.java | 63 +
.../face/sense/FaceResetLockoutClient.java | 82 ++
.../face/sense/FaceRevokeChallengeClient.java | 55 +
.../face/sense/FaceSetFeatureClient.java | 93 ++
.../sense/FaceUpdateActiveUserClient.java | 84 ++
.../sensors/face/sense/SenseProvider.java | 1110 +++++++++++++++++
.../sensors/face/sense/SenseUtils.java | 61 +
.../sensors/face/sense/TestHal.java | 144 +++
20 files changed, 2667 insertions(+), 16 deletions(-)
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/BiometricTestSessionImpl.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceAuthenticationClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceEnrollClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGenerateChallengeClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGetFeatureClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalCleanupClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalEnumerateClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRemovalClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceResetLockoutClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRevokeChallengeClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceSetFeatureClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/FaceUpdateActiveUserClient.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/SenseProvider.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/SenseUtils.java
create mode 100644 services/core/java/com/android/server/biometrics/sensors/face/sense/TestHal.java
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
index fedbdec1..cc4ce05a 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
@@ -95,6 +95,10 @@ class AppOpsPrivacyItemMonitor @Inject constructor(
if (code in OPS_LOCATION && !locationAvailable) {
return
}
+ // Hide incoming chip from sense caller package
+ if (packageName == "co.aospa.sense") {
+ return
+ }
if (userTracker.userProfiles.any { it.id == UserHandle.getUserId(uid) } ||
code in USER_INDEPENDENT_OPS) {
logger.logUpdatedItemFromAppOps(code, uid, packageName, active)
@@ -219,6 +223,10 @@ class AppOpsPrivacyItemMonitor @Inject constructor(
AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
else -> return null
}
+ // Hide incoming chip from sense caller package
+ if (appOpItem.packageName == "co.aospa.sense") {
+ return null
+ }
val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
return PrivacyItem(type, app, appOpItem.timeStartedElapsed, appOpItem.isDisabled)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 97fc35a0..1d4069ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -618,7 +618,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
final boolean unlockingAllowed =
mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
final boolean deviceDreaming = mUpdateMonitor.isDreaming();
- final boolean bypass = mKeyguardBypassController.getBypassEnabled()
+ final boolean bypass = mKeyguardBypassController.getBypassEnabledBiometric()
|| mAuthController.isUdfpsFingerDown();
logCalculateModeForPassiveAuth(unlockingAllowed, deviceInteractive, isKeyguardShowing,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index a3d316b6..866bc590 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -107,6 +107,8 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
notifyListeners()
}
+ var bypassEnabledBiometric: Boolean = false
+
var bouncerShowing: Boolean = false
var altBouncerShowing: Boolean = false
var launchingAffordance: Boolean = false
@@ -163,7 +165,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
tunerService.addTunable({ key, _ ->
- bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
+ bypassEnabledBiometric = tunerService.getValue(key, dismissByDefault) != 0
}, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
lockscreenUserManager.addUserChangedListener(
@@ -200,8 +202,8 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
biometricSourceType: BiometricSourceType,
isStrongBiometric: Boolean
): Boolean {
- if (biometricSourceType == BiometricSourceType.FACE && bypassEnabled) {
- val can = canBypass()
+ if (bypassEnabledBiometric) {
+ val can = biometricSourceType != BiometricSourceType.FACE || canBypass()
if (!can && (isPulseExpanding || qsExpanded)) {
pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
}
@@ -225,7 +227,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
* If keyguard can be dismissed because of bypass.
*/
fun canBypass(): Boolean {
- if (bypassEnabled) {
+ if (bypassEnabledBiometric) {
return when {
bouncerShowing -> true
altBouncerShowing -> true
@@ -258,6 +260,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
pw.println(" mPendingUnlock: $pendingUnlock")
}
pw.println(" bypassEnabled: $bypassEnabled")
+ pw.println(" bypassEnabledBiometric: $bypassEnabledBiometric")
pw.println(" canBypass: ${canBypass()}")
pw.println(" bouncerShowing: $bouncerShowing")
pw.println(" altBouncerShowing: $altBouncerShowing")
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 69089d3a..680669ff 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -263,6 +263,7 @@ java_library_static {
"vendor.oplus.hardware.biometrics.fingerprint-V2.1-java",
"vendor.oppo.hardware.biometrics.fingerprint-V2.1-java",
"vendor.xiaomi.hardware.fingerprintextension-V1.0-java",
+ "vendor.aospa.biometrics.face",
//AIDL
"vendor.samsung.hardware.sysinput-V1-java",
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7ee2a7ab..6ada92b5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -26,6 +26,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
@@ -39,6 +40,7 @@ import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceSensorConfigurations;
+import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -73,6 +75,8 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
import com.android.server.biometrics.sensors.face.hidl.Face10;
+import com.android.server.biometrics.sensors.face.sense.SenseProvider;
+import com.android.server.biometrics.sensors.face.sense.SenseUtils;
import com.google.android.collect.Lists;
@@ -692,24 +696,32 @@ public class FaceService extends SystemService {
return providers;
}
+ private List<ServiceProvider> getSenseProviders() {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ if (SenseUtils.canUseProvider()) {
+ FaceSensorPropertiesInternal props = new FaceSensorPropertiesInternal(
+ SenseProvider.DEVICE_ID,
+ SensorProperties.STRENGTH_WEAK,
+ 1, /** maxEnrollmentsPerUser **/
+ new ArrayList(),
+ FaceSensorProperties.TYPE_RGB,
+ false, /** supportsFaceDetection **/
+ false, /** supportsSelfIllumination **/
+ false); /** resetLockoutRequiresChallenge **/
+ SenseProvider provider = new SenseProvider(getContext(), mBiometricStateCallback, props, mLockoutResetDispatcher);
+ providers.add(provider);
+ }
+ return providers;
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public void registerAuthenticators(
@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
super.registerAuthenticators_enforcePermission();
mRegistry.registerAll(() -> {
- List<String> aidlSensors = new ArrayList<>();
- final String[] instances = mAidlInstanceNameSupplier.get();
- if (instances != null) {
- aidlSensors.addAll(Lists.newArrayList(instances));
- }
-
- final Pair<List<FaceSensorPropertiesInternal>, List<String>>
- filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
-
final List<ServiceProvider> providers = new ArrayList<>();
- providers.addAll(getHidlProviders(filteredInstances.first));
- providers.addAll(getAidlProviders(filteredInstances.second));
+ providers.addAll(getSenseProviders());
return providers;
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/BiometricTestSessionImpl.java
new file mode 100644
index 00000000..c1122a20
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/BiometricTestSessionImpl.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
+import android.hardware.face.FaceEnrollOptions;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * A test session implementation for {@link SenseProvider}. See
+ * {@link android.hardware.biometrics.BiometricTestSession}.
+ */
+public class BiometricTestSessionImpl extends ITestSession.Stub {
+
+ private static final String TAG = "face/sense/BiometricTestSessionImpl";
+
+ @NonNull private final Context mContext;
+ private final int mSensorId;
+ @NonNull private final ITestSessionCallback mCallback;
+ @NonNull private final Random mRandom;
+
+ @NonNull private final SenseProvider.HalResultController mHalResultController;
+ @NonNull private final SenseProvider mSenseProvider;
+
+ /**
+ * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
+ * test, since enrollment is a platform-only API. The authentication path is tested through
+ * the public BiometricPrompt APIs and does not use this receiver.
+ */
+ private final IFaceServiceReceiver mReceiver = new IFaceServiceReceiver.Stub() {
+ @Override
+ public void onEnrollResult(Face face, int remaining) {
+
+ }
+
+ @Override
+ public void onAcquired(int acquireInfo, int vendorCode) {
+
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+
+ }
+
+ @Override
+ public void onError(int error, int vendorCode) {
+
+ }
+
+ @Override
+ public void onRemoved(Face face, int remaining) {
+
+ }
+
+ @Override
+ public void onFeatureSet(boolean success, int feature) {
+
+ }
+
+ @Override
+ public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
+
+ }
+
+ @Override
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
+
+ }
+
+ @Override
+ public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+
+ }
+
+ @Override
+ public void onEnrollmentFrame(FaceEnrollFrame frame) {
+
+ }
+ };
+
+ BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+ @NonNull ITestSessionCallback callback, @NonNull SenseProvider provider,
+ @NonNull SenseProvider.HalResultController halResultController) {
+ mContext = context;
+ mSensorId = sensorId;
+ mCallback = callback;
+ mSenseProvider = provider;
+ mHalResultController = halResultController;
+ mRandom = new Random();
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void setTestHalEnabled(boolean enabled) {
+ super.setTestHalEnabled_enforcePermission();
+
+ mSenseProvider.setTestHalEnabled(enabled);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void startEnroll(int userId) {
+ super.startEnroll_enforcePermission();
+
+ mSenseProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
+ mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
+ null /* previewSurface */, false /* debugConsent */,
+ (new FaceEnrollOptions.Builder()).build());
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void finishEnroll(int userId) {
+ super.finishEnroll_enforcePermission();
+
+ mHalResultController.onEnrollResult(1, userId, 0);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void acceptAuthentication(int userId) {
+ super.acceptAuthentication_enforcePermission();
+
+ // Fake authentication with any of the existing faces
+ List<Face> faces = FaceUtils.getInstance(mSensorId)
+ .getBiometricsForUser(mContext, userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "No faces, returning");
+ return;
+ }
+ final int fid = faces.get(0).getBiometricId();
+ byte[] hat = {0};
+ mHalResultController.onAuthenticated(0 /* deviceId */, userId, hat);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void rejectAuthentication(int userId) {
+ super.rejectAuthentication_enforcePermission();
+
+ mHalResultController.onAuthenticated(0 /* deviceId */, userId, null);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void notifyAcquired(int userId, int acquireInfo) {
+ super.notifyAcquired_enforcePermission();
+
+ mHalResultController.onAcquired(userId, 0 /* deviceId */, 0 /* vendorCode */);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void notifyError(int userId, int errorCode) {
+ super.notifyError_enforcePermission();
+
+ mHalResultController.onError(0 /* deviceId */, 0 /* vendorCode */);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public void cleanupInternalState(int userId) {
+ super.cleanupInternalState_enforcePermission();
+
+ mSenseProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ try {
+ mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ try {
+ mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceAuthenticationClient.java
new file mode 100644
index 00000000..118970dc
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceAuthenticationClient.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.face.UsageStats;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+/**
+ * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
+ * HIDL interface.
+ */
+class FaceAuthenticationClient
+ extends AuthenticationClient<ISenseService, FaceAuthenticateOptions> {
+
+ private static final String TAG = "FaceAuthenticationClient";
+
+ private final UsageStats mUsageStats;
+
+ private final int[] mBiometricPromptIgnoreList;
+ private final int[] mBiometricPromptIgnoreListVendor;
+ private final int[] mKeyguardIgnoreList;
+ private final int[] mKeyguardIgnoreListVendor;
+
+ private int mLastAcquire;
+
+ FaceAuthenticationClient(@NonNull Context context,
+ @NonNull Supplier<ISenseService> lazyDaemon,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, long operationId,
+ boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+ boolean requireConfirmation,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
+ @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
+ @Authenticators.Types int sensorStrength) {
+ super(context, lazyDaemon, token, listener, operationId, restricted,
+ options, cookie, requireConfirmation, logger, biometricContext,
+ isStrongBiometric, null /* taskStackListener */,
+ lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
+ sensorStrength);
+ setRequestId(requestId);
+ mUsageStats = usageStats;
+
+ final Resources resources = getContext().getResources();
+ mBiometricPromptIgnoreList = resources.getIntArray(
+ R.array.config_face_acquire_biometricprompt_ignorelist);
+ mBiometricPromptIgnoreListVendor = resources.getIntArray(
+ R.array.config_face_acquire_vendor_biometricprompt_ignorelist);
+ mKeyguardIgnoreList = resources.getIntArray(
+ R.array.config_face_acquire_keyguard_ignorelist);
+ mKeyguardIgnoreListVendor = resources.getIntArray(
+ R.array.config_face_acquire_vendor_keyguard_ignorelist);
+ }
+
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ mState = STATE_STARTED;
+ }
+
+ @NonNull
+ @Override
+ protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+ return new ClientMonitorCompositeCallback(
+ getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().authenticate(mOperationId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting auth", e);
+ onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ protected void stopHalOperation() {
+ try {
+ getFreshDaemon().cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public boolean wasUserDetected() {
+ // Do not provide haptic feedback if the user was not detected, and an error (usually
+ // ERROR_TIMEOUT) is received.
+ return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
+ && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
+ }
+
+ @Override
+ protected void handleLifecycleAfterAuth(boolean authenticated) {
+ // For face, the authentication lifecycle ends either when
+ // 1) Authenticated == true
+ // 2) Error occurred
+ // 3) Authenticated == false
+ mCallback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
+ @LockoutTracker.LockoutMode final int lockoutMode =
+ getLockoutTracker().getLockoutModeForUser(userId);
+ final PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(getSensorId());
+ if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+ performanceTracker.incrementPermanentLockoutForUser(userId);
+ } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+ performanceTracker.incrementTimedLockoutForUser(userId);
+ }
+
+ return lockoutMode;
+ }
+
+ @Override
+ public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
+ boolean authenticated, ArrayList<Byte> token) {
+ super.onAuthenticated(identifier, authenticated, token);
+
+ mState = STATE_STOPPED;
+ mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
+ getStartTimeMs(),
+ System.currentTimeMillis() - getStartTimeMs() /* latency */,
+ authenticated,
+ 0 /* error */,
+ 0 /* vendorError */,
+ getTargetUserId()));
+ }
+
+ @Override
+ public void onError(@BiometricConstants.Errors int error, int vendorCode) {
+ mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
+ getStartTimeMs(),
+ System.currentTimeMillis() - getStartTimeMs() /* latency */,
+ false /* authenticated */,
+ error,
+ vendorCode,
+ getTargetUserId()));
+
+ super.onError(error, vendorCode);
+ }
+
+ private int[] getAcquireIgnorelist() {
+ return isBiometricPrompt() ? mBiometricPromptIgnoreList : mKeyguardIgnoreList;
+ }
+
+ private int[] getAcquireVendorIgnorelist() {
+ return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
+ }
+
+ private boolean shouldSend(int acquireInfo, int vendorCode) {
+ if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
+ return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
+ } else {
+ return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
+ }
+ }
+
+ @Override
+ public void onAcquired(int acquireInfo, int vendorCode) {
+ mLastAcquire = acquireInfo;
+
+ if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) {
+ BiometricNotificationUtils.showReEnrollmentNotification(getContext());
+ }
+ @LockoutTracker.LockoutMode final int lockoutMode =
+ getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+ if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
+ PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+ pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+ }
+
+ final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
+ onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceEnrollClient.java
new file mode 100644
index 00000000..3b4e21f7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceEnrollClient.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.face.Face;
+import android.hardware.face.FaceEnrollOptions;
+import android.hardware.face.FaceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.Surface;
+
+import com.android.internal.R;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
+import com.android.server.biometrics.sensors.EnrollClient;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+public class FaceEnrollClient extends EnrollClient<ISenseService> {
+
+ private static final String TAG = "FaceEnrollClient";
+
+ @NonNull private final int[] mDisabledFeatures;
+ @NonNull private final int[] mEnrollIgnoreList;
+ @NonNull private final int[] mEnrollIgnoreListVendor;
+
+ FaceEnrollClient(@NonNull Context context, @NonNull Supplier<ISenseService> lazyDaemon,
+ @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
+ @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
+ @Nullable Surface previewSurface, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull FaceEnrollOptions options) {
+ super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
+ timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
+ BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
+ setRequestId(requestId);
+ mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
+ mEnrollIgnoreList = getContext().getResources()
+ .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
+ mEnrollIgnoreListVendor = getContext().getResources()
+ .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
+
+ Slog.w(TAG, "EnrollOptions "
+ + FaceEnrollOptions.enrollReasonToString(options.getEnrollReason()));
+ }
+
+ @NonNull
+ @Override
+ protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+ return new ClientMonitorCompositeCallback(
+ getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
+ }
+
+ @Override
+ protected boolean hasReachedEnrollmentLimit() {
+ final int limit = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
+ final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
+ .size();
+ if (enrolled >= limit) {
+ Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onAcquired(int acquireInfo, int vendorCode) {
+ final boolean shouldSend;
+ if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
+ shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
+ } else {
+ shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
+ }
+ onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
+ }
+
+ @Override
+ protected void startHalOperation() {
+ final ArrayList<Byte> token = new ArrayList<>();
+ for (byte b : mHardwareAuthToken) {
+ token.add(Byte.valueOf(b));
+ }
+ final ArrayList<Integer> disabledFeatures = new ArrayList<>();
+ for (int disabledFeature : mDisabledFeatures) {
+ disabledFeatures.add(disabledFeature);
+ }
+
+ try {
+ getFreshDaemon().enroll(SenseUtils.toByteArray(token), mTimeoutSec, SenseUtils.toIntArray(disabledFeatures));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting enroll", e);
+ onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ protected void stopHalOperation() {
+ try {
+ getFreshDaemon().cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGenerateChallengeClient.java
new file mode 100644
index 00000000..9a9414ff
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGenerateChallengeClient.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.GenerateChallengeClient;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISenseService> {
+
+ private static final String TAG = "FaceGenerateChallengeClient";
+ static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
+ private static final ClientMonitorCallback EMPTY_CALLBACK = new ClientMonitorCallback() {
+ };
+
+ private final long mCreatedAt;
+ private List<IFaceServiceReceiver> mWaiting;
+ private Long mChallengeResult;
+
+ FaceGenerateChallengeClient(@NonNull Context context,
+ @NonNull Supplier<ISenseService> lazyDaemon, @NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, long now) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+ biometricContext);
+ mCreatedAt = now;
+ mWaiting = new ArrayList<>();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ mChallengeResult = null;
+ try {
+ mChallengeResult = Long.valueOf(getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC));
+ // send the result to the original caller via mCallback and any waiting callers
+ // that called reuseResult
+ sendChallengeResult(getListener(), mCallback);
+ for (IFaceServiceReceiver receiver : mWaiting) {
+ sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "generateChallenge failed", e);
+ mCallback.onClientFinished(this, false /* success */);
+ } finally {
+ mWaiting = null;
+ }
+ }
+
+ /** @return An arbitrary time value for caching provided to the constructor. */
+ public long getCreatedAt() {
+ return mCreatedAt;
+ }
+
+ /**
+ * Reuse the result of this operation when it is available. The receiver will be notified
+ * immediately if a challenge has already been generated.
+ *
+ * @param receiver receiver to be notified of challenge result
+ */
+ public void reuseResult(@NonNull IFaceServiceReceiver receiver) {
+ if (mWaiting != null) {
+ mWaiting.add(receiver);
+ } else {
+ sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
+ }
+ }
+
+ private void sendChallengeResult(@NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull ClientMonitorCallback ownerCallback) {
+ Preconditions.checkState(mChallengeResult != null, "result not available");
+ try {
+ receiver.onChallengeGenerated(getSensorId(), getTargetUserId(), mChallengeResult);
+ ownerCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ ownerCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGetFeatureClient.java
new file mode 100644
index 00000000..fc3b8223
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceGetFeatureClient.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+public class FaceGetFeatureClient extends HalClientMonitor<ISenseService> {
+
+ private static final String TAG = "FaceGetFeatureClient";
+
+ private final int mFeature;
+ private final int mFaceId;
+ private boolean mValue;
+
+ FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<ISenseService> lazyDaemon,
+ @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, int faceId) {
+ super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+ logger, biometricContext);
+ mFeature = feature;
+ mFaceId = faceId;
+ }
+
+ @Override
+ public void unableToStart() {
+ try {
+ if (getListener() != null) {
+ getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to send error", e);
+ }
+ }
+
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ final boolean result = getFreshDaemon().getFeature(mFeature, mFaceId);
+ int[] features = new int[1];
+ features[0] = mFeature;
+ boolean[] featureState = {result};
+ mValue = result;
+
+ if (getListener() != null) {
+ getListener().onFeatureGet(result, features, featureState);
+ }
+ mCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to getFeature", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ boolean getValue() {
+ return mValue;
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_GET_FEATURE;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalCleanupClient.java
new file mode 100644
index 00000000..bd696fd6
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalCleanupClient.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.face.Face;
+import android.os.IBinder;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.InternalCleanupClient;
+import com.android.server.biometrics.sensors.InternalEnumerateClient;
+import com.android.server.biometrics.sensors.RemovalClient;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+class FaceInternalCleanupClient extends InternalCleanupClient<Face, ISenseService> {
+
+ FaceInternalCleanupClient(@NonNull Context context,
+ @NonNull Supplier<ISenseService> lazyDaemon, int userId, @NonNull String owner,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+ utils, authenticatorIds);
+ }
+
+ @Override
+ protected InternalEnumerateClient<ISenseService> getEnumerateClient(Context context,
+ Supplier<ISenseService> lazyDaemon, IBinder token, int userId, String owner,
+ List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
+ enrolledList, utils, sensorId, logger, biometricContext);
+ }
+
+ @Override
+ protected RemovalClient<Face, ISenseService> getRemovalClient(Context context,
+ Supplier<ISenseService> lazyDaemon, IBinder token,
+ int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ Map<Integer, Long> authenticatorIds) {
+ // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
+ // is all done internally.
+ return new FaceRemovalClient(context, lazyDaemon, token,
+ null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
+ sensorId, logger, biometricContext, authenticatorIds);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalEnumerateClient.java
new file mode 100644
index 00000000..c9f0a028
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalEnumerateClient.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.InternalEnumerateClient;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+class FaceInternalEnumerateClient extends InternalEnumerateClient<ISenseService> {
+ private static final String TAG = "FaceInternalEnumerateClient";
+
+ FaceInternalEnumerateClient(@NonNull Context context,
+ @NonNull Supplier<ISenseService> lazyDaemon, @NonNull IBinder token, int userId,
+ @NonNull String owner, @NonNull List<Face> enrolledList,
+ @NonNull BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
+ logger, biometricContext);
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().enumerate();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting enumerate", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRemovalClient.java
new file mode 100644
index 00000000..93e72863
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRemovalClient.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.RemovalClient;
+
+import java.util.Map;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+class FaceRemovalClient extends RemovalClient<Face, ISenseService> {
+ private static final String TAG = "FaceRemovalClient";
+
+ private final int mBiometricId;
+
+ FaceRemovalClient(@NonNull Context context, @NonNull Supplier<ISenseService> lazyDaemon,
+ @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
+ int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
+ biometricContext, authenticatorIds);
+ mBiometricId = biometricId;
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().remove(mBiometricId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting remove", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceResetLockoutClient.java
new file mode 100644
index 00000000..ab791d0b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceResetLockoutClient.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+public class FaceResetLockoutClient extends HalClientMonitor<ISenseService> {
+
+ private static final String TAG = "FaceResetLockoutClient";
+
+ private final byte[] mHardwareAuthToken;
+
+ FaceResetLockoutClient(@NonNull Context context,
+ @NonNull Supplier<ISenseService> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull byte[] hardwareAuthToken) {
+ super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+ 0 /* cookie */, sensorId, logger, biometricContext);
+
+ mHardwareAuthToken = (byte[]) hardwareAuthToken.clone();
+ }
+
+ @Override
+ public void unableToStart() {
+ // Nothing to do here
+ }
+
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().resetLockout(mHardwareAuthToken);
+ mCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to reset lockout", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_RESET_LOCKOUT;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRevokeChallengeClient.java
new file mode 100644
index 00000000..7f42df49
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceRevokeChallengeClient.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.RevokeChallengeClient;
+
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+public class FaceRevokeChallengeClient extends RevokeChallengeClient<ISenseService> {
+
+ private static final String TAG = "FaceRevokeChallengeClient";
+
+ FaceRevokeChallengeClient(@NonNull Context context,
+ @NonNull Supplier<ISenseService> lazyDaemon, @NonNull IBinder token,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().revokeChallenge();
+ mCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "revokeChallenge failed", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceSetFeatureClient.java
new file mode 100644
index 00000000..65f17d5a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceSetFeatureClient.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+public class FaceSetFeatureClient extends HalClientMonitor<ISenseService> {
+
+ private static final String TAG = "FaceSetFeatureClient";
+
+ private final int mFeature;
+ private final boolean mEnabled;
+ private final byte[] mHardwareAuthToken;
+ private final int mFaceId;
+
+ FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<ISenseService> lazyDaemon,
+ @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
+ super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+ logger, biometricContext);
+ mFeature = feature;
+ mEnabled = enabled;
+ mFaceId = faceId;
+
+ mHardwareAuthToken = (byte[]) hardwareAuthToken.clone();
+ }
+
+ @Override
+ public void unableToStart() {
+ try {
+ getListener().onFeatureSet(false /* success */, mFeature);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to send error", e);
+ }
+ }
+
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().setFeature(mFeature, mEnabled, mHardwareAuthToken, mFaceId);
+ getListener().onFeatureSet(true, mFeature);
+ mCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_SET_FEATURE;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceUpdateActiveUserClient.java
new file mode 100644
index 00000000..08c07506
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceUpdateActiveUserClient.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+import java.io.File;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+
+public class FaceUpdateActiveUserClient extends HalClientMonitor<ISenseService> {
+ private static final String TAG = "FaceUpdateActiveUserClient";
+ private static final String FACE_DATA_DIR = "facedata";
+
+ private final boolean mHasEnrolledBiometrics;
+ @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+
+ FaceUpdateActiveUserClient(@NonNull Context context,
+ @NonNull Supplier<ISenseService> lazyDaemon, int userId, @NonNull String owner,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+ 0 /* cookie */, sensorId, logger, biometricContext);
+ mHasEnrolledBiometrics = hasEnrolledBiometrics;
+ mAuthenticatorIds = authenticatorIds;
+ }
+
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ public void unableToStart() {
+ // Nothing to do here
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ final ISenseService daemon = getFreshDaemon();
+ mAuthenticatorIds.put(getTargetUserId(),
+ mHasEnrolledBiometrics ? daemon.getAuthenticatorId() : 0L);
+ mCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setActiveUser: " + e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/SenseProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/SenseProvider.java
new file mode 100644
index 00000000..3f899c69
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/SenseProvider.java
@@ -0,0 +1,1110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.UserSwitchObserver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceEnrollOptions;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
+import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.SensorServiceStateProto;
+import com.android.server.biometrics.SensorStateProto;
+import com.android.server.biometrics.UserStateProto;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+import com.android.server.biometrics.sensors.face.LockoutHalImpl;
+import com.android.server.biometrics.sensors.face.ServiceProvider;
+import com.android.server.biometrics.sensors.face.UsageStats;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+
+import vendor.aospa.biometrics.face.ISenseService;
+import vendor.aospa.biometrics.face.ISenseServiceReceiver;
+
+public class SenseProvider implements ServiceProvider {
+
+ private static final String TAG = "SenseProvider";
+
+ private static final String BIND_SENSE_ACTION = "co.aospa.sense.BIND";
+ private static final String PACKAGE_NAME = "co.aospa.sense";
+ private static final String SERVICE_NAME = "co.aospa.sense.SenseService";
+
+ public static final int DEVICE_ID = 1008;
+ private static final int ENROLL_TIMEOUT_SEC = 75;
+ private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
+ private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS =
+ FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC * 1000;
+ @VisibleForTesting
+ public static Clock sSystemClock = Clock.systemUTC();
+
+ private boolean mIsBinding;
+ private boolean mTestHalEnabled;
+
+ @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
+ @NonNull private final BiometricStateCallback mBiometricStateCallback;
+ @NonNull private final Context mContext;
+ @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final Handler mHandler;
+ @NonNull private final Supplier<ISenseService> mLazyDaemon;
+ @NonNull private final LockoutHalImpl mLockoutTracker;
+ @NonNull private final UsageStats mUsageStats;
+ @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+ @Nullable private IBiometricsFace mDaemon;
+ @NonNull private final HalResultController mHalResultController;
+ @NonNull private final BiometricContext mBiometricContext;
+ @Nullable
+ private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ SparseArray<ISenseService> mServices;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private final int mSensorId;
+ private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+ private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
+
+ private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ mCurrentUserId = newUserId;
+ ISenseService service = getDaemon();
+ if (service == null) {
+ bindService(mCurrentUserId);
+ }
+ }
+ };
+
+ public static class HalResultController extends ISenseServiceReceiver.Stub {
+ /**
+ * Interface to sends results to the HalResultController's owner.
+ */
+ public interface Callback {
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
+ private final int mSensorId;
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final BiometricScheduler mScheduler;
+ @Nullable private Callback mCallback;
+ @NonNull private final LockoutHalImpl mLockoutTracker;
+ @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+
+
+ HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
+ @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ mSensorId = sensorId;
+ mContext = context;
+ mHandler = handler;
+ mScheduler = scheduler;
+ mLockoutTracker = lockoutTracker;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ }
+
+ public void setCallback(@Nullable Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onEnrollResult(int faceId, int userId, int remaining) {
+ mHandler.post(() -> {
+ final CharSequence name = FaceUtils.getLegacyInstance(mSensorId)
+ .getUniqueName(mContext, userId);
+ final Face face = new Face(name, faceId, Long.valueOf(DEVICE_ID));
+
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (!(client instanceof FaceEnrollClient)) {
+ Slog.e(TAG, "onEnrollResult for non-enroll client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
+ enrollClient.onEnrollResult(face, remaining);
+ });
+ }
+
+ @Override
+ public void onAuthenticated(int faceId, int userId, byte[] token) {
+ mHandler.post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationConsumer)) {
+ Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AuthenticationConsumer authenticationConsumer =
+ (AuthenticationConsumer) client;
+ final boolean authenticated = faceId != 0;
+ final Face face = new Face("", faceId, DEVICE_ID);
+ authenticationConsumer.onAuthenticated(face, authenticated, SenseUtils.toByteArrayList(token));
+ });
+ }
+
+ @Override
+ public void onAcquired(int userId, int acquiredInfo, int vendorCode) {
+ mHandler.post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (!(client instanceof AcquisitionClient)) {
+ Slog.e(TAG, "onAcquired for non-acquire client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AcquisitionClient<?> acquisitionClient =
+ (AcquisitionClient<?>) client;
+ acquisitionClient.onAcquired(acquiredInfo, vendorCode);
+ });
+ }
+
+ @Override
+ public void onError(int error, int vendorCode) {
+ mHandler.post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ Slog.d(TAG, "handleError"
+ + ", client: " + (client != null ? client.getOwnerString() : null)
+ + ", error: " + error
+ + ", vendorCode: " + vendorCode);
+ if (!(client instanceof ErrorConsumer)) {
+ Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(
+ client));
+ return;
+ }
+
+ final ErrorConsumer errorConsumer = (ErrorConsumer) client;
+ errorConsumer.onError(error, vendorCode);
+
+ if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+ Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
+ if (mCallback != null) {
+ mCallback.onHardwareUnavailable();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onRemoved(int[] faceIds, int userId) {
+ mHandler.post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (!(client instanceof RemovalConsumer)) {
+ Slog.e(TAG, "onRemoved for non-removal consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final RemovalConsumer removalConsumer = (RemovalConsumer) client;
+
+ if (faceIds.length > 0) {
+ // Convert to old fingerprint-like behavior, where remove() receives
+ // one removal at a time. This way, remove can share some more common code.
+ for (int i = 0; i < faceIds.length; i++) {
+ final int id = faceIds[i];
+ final Face face = new Face("", id, Long.valueOf(DEVICE_ID));
+ final int remaining = (faceIds.length - i) - 1;
+ Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
+ removalConsumer.onRemoved(face, remaining);
+ }
+ } else {
+ removalConsumer.onRemoved(null, 0 /* remaining */);
+ }
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
+ });
+ }
+
+ @Override
+ public void onEnumerate(int[] faceIds, int userId) {
+ mHandler.post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (!(client instanceof EnumerateConsumer)) {
+ Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
+
+ if (faceIds.length > 0) {
+ // Convert to old fingerprint-like behavior, where enumerate() receives one
+ // template at a time. This way, enumerate can share some more common code.
+ for (int i = 0; i < faceIds.length; i++) {
+ final Face face = new Face("", faceIds[i], Long.valueOf(DEVICE_ID));
+ enumerateConsumer.onEnumerationResult(face, (faceIds.length - i) - 1);
+ }
+ } else {
+ // For face, the HIDL contract is to receive an empty list when there are no
+ // templates enrolled. Send a null identifier since we don't consume them
+ // anywhere, and send remaining == 0 so this code can be shared with Face@1.1
+ enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
+ }
+ });
+ }
+
+ @Override
+ public void onLockoutChanged(long duration) {
+ mHandler.post(() -> {
+ Slog.d(TAG, "onLockoutChanged: " + duration);
+ final @LockoutTracker.LockoutMode int lockoutMode;
+ if (duration == 0) {
+ lockoutMode = LockoutTracker.LOCKOUT_NONE;
+ } else if (duration == -1 || duration == Long.MAX_VALUE) {
+ lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+ } else {
+ lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+ }
+
+ mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
+
+ if (duration == 0) {
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+ }
+ });
+ }
+ }
+
+ @VisibleForTesting
+ public SenseProvider(@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull FaceSensorPropertiesInternal sensorProps,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricScheduler scheduler) {
+ mServices = new SparseArray<>();
+ mIsBinding = false;
+ mSensorProperties = sensorProps;
+ mContext = context;
+ mBiometricStateCallback = biometricStateCallback;
+ mSensorId = sensorProps.sensorId;
+ mScheduler = scheduler;
+ mHandler = new Handler(Looper.getMainLooper());
+ mBiometricContext = BiometricContext.getInstance(context);
+ mUsageStats = new UsageStats(context);
+ mAuthenticatorIds = new HashMap<>();
+ mLazyDaemon = SenseProvider.this::getDaemon;
+ mLockoutTracker = new LockoutHalImpl();
+ mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
+ mScheduler, mLockoutTracker, lockoutResetDispatcher);
+ mHalResultController.setCallback(() -> {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ });
+ mCurrentUserId = ActivityManager.getCurrentUser();
+
+ AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
+ new AuthenticationStatsBroadcastReceiver(
+ mContext,
+ BiometricsProtoEnums.MODALITY_FACE,
+ (AuthenticationStatsCollector collector) -> {
+ Slog.d(TAG, "Initializing AuthenticationStatsCollector");
+ mAuthenticationStatsCollector = collector;
+ });
+
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register user switch observer");
+ }
+ }
+
+ public SenseProvider(Context context, BiometricStateCallback biometricStateCallback, FaceSensorPropertiesInternal sensorProps, LockoutResetDispatcher lockoutResetDispatcher) {
+ this(context, biometricStateCallback, sensorProps, lockoutResetDispatcher, new BiometricScheduler(0, null));
+ }
+
+ private synchronized ISenseService getDaemon() {
+ if (mTestHalEnabled) {
+ final TestHal testHal = new TestHal(mContext, mSensorId);
+ testHal.setCallback(mHalResultController);
+ return testHal;
+ }
+
+ ISenseService service = getService(mCurrentUserId);
+ if (service == null) {
+ bindService(mCurrentUserId);
+ }
+ return service;
+ }
+
+ @Override
+ public boolean containsSensor(int sensorId) {
+ return mSensorId == sensorId;
+ }
+
+ @Override
+ @NonNull
+ public List<FaceSensorPropertiesInternal> getSensorProperties() {
+ final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
+ properties.add(mSensorProperties);
+ return properties;
+ }
+
+ @NonNull
+ @Override
+ public FaceSensorPropertiesInternal getSensorProperties(int sensorId) {
+ return mSensorProperties;
+ }
+
+ @Override
+ @NonNull
+ public List<Face> getEnrolledFaces(int sensorId, int userId) {
+ return FaceUtils.getLegacyInstance(mSensorId).getBiometricsForUser(mContext, userId);
+ }
+
+ @Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFaces(sensorId, userId).isEmpty();
+ }
+
+ @Override
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int sensorId, int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public long getAuthenticatorId(int sensorId, int userId) {
+ return mAuthenticatorIds.getOrDefault(userId, 0L);
+ }
+
+ @Override
+ public boolean isHardwareDetected(int sensorId) {
+ return getDaemon() != null;
+ }
+
+ private boolean isGeneratedChallengeCacheValid() {
+ return mGeneratedChallengeCache != null
+ && sSystemClock.millis() - mGeneratedChallengeCache.getCreatedAt()
+ < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+ }
+
+ private void incrementChallengeCount() {
+ mGeneratedChallengeCount.add(0, sSystemClock.millis());
+ }
+
+ private int decrementChallengeCount() {
+ final long now = sSystemClock.millis();
+ // ignore values that are old in case generate/revoke calls are not matched
+ // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+ mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+ if (!mGeneratedChallengeCount.isEmpty()) {
+ mGeneratedChallengeCount.remove(0);
+ }
+ return mGeneratedChallengeCount.size();
+ }
+
+ /**
+ * {@link IBiometricsFace} only supports a single in-flight challenge but there are cases where
+ * two callers both need challenges (e.g. resetLockout right before enrollment).
+ */
+ @Override
+ public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ try {
+ receiver.onChallengeGenerated(sensorId, userId, 0L);
+ return;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ incrementChallengeCount();
+
+ if (isGeneratedChallengeCacheValid()) {
+ Slog.d(TAG, "Current challenge is cached and will be reused");
+ mGeneratedChallengeCache.reuseResult(receiver);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, sSystemClock.millis());
+ mGeneratedChallengeCache = client;
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ if (client != clientMonitor) {
+ Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client."
+ + " Expecting: " + client + ", received: " + clientMonitor);
+ }
+ }
+ });
+ });
+ }
+
+ @Override
+ public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
+ @NonNull String opPackageName, long challenge) {
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ return;
+ }
+ final boolean shouldRevoke = decrementChallengeCount() == 0;
+ if (!shouldRevoke) {
+ Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+ + mGeneratedChallengeCount);
+ return;
+ }
+
+ Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
+ mGeneratedChallengeCache = null;
+
+ final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+ mLazyDaemon, token, userId, opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (client != clientMonitor) {
+ Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
+ + "Expecting: " + client + ", received: " + clientMonitor);
+ }
+ }
+ });
+ });
+ }
+
+ @Override
+ public long scheduleEnroll(int sensorId, @NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+ @Nullable Surface previewSurface, boolean debugConsent,
+ @NonNull FaceEnrollOptions options) {
+ final long id = mRequestCounter.incrementAndGet();
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ try {
+ receiver.onError(2, 0);
+ return;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ BiometricNotificationUtils.cancelFaceReEnrollNotification(mContext);
+
+ final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+ ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, options);
+
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+ }
+ }
+ });
+ });
+ return id;
+ }
+
+ @Override
+ public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
+ }
+
+ @Override
+ public long scheduleFaceDetect(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FaceAuthenticateOptions options, int statsClient) {
+ throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
+ + "forget to check the supportsFaceDetection flag?");
+ }
+
+ @Override
+ public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
+ throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
+ + "forget to check the supportsFaceDetection flag?");
+ }
+
+ @Override
+ public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+ int statsClient, boolean allowBackgroundAuthentication) {
+ mHandler.post(() -> {
+ final int userId = options.getUserId();
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ try {
+ receiver.onError(1008, 0, 1, 0);
+ return;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
+ final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+ mLazyDaemon, token, requestId, receiver, operationId, restricted,
+ options, cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric, mLockoutTracker,
+ mUsageStats, allowBackgroundAuthentication,
+ Utils.getCurrentStrength(mSensorId));
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ @Override
+ public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull FaceAuthenticateOptions options, boolean restricted,
+ int statsClient, boolean allowBackgroundAuthentication) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(token, operationId, cookie, receiver,
+ options, id, restricted, statsClient, allowBackgroundAuthentication);
+
+ return id;
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
+ }
+
+ @Override
+ public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ try {
+ receiver.onError(1, 0);
+ return;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
+ FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ });
+ }
+
+ @Override
+ public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ try {
+ receiver.onError(1, 0);
+ return;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ // For IBiometricsFace@1.0, remove(0) means remove all enrollments
+ final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
+ opPackageName,
+ FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ });
+ }
+
+ @Override
+ public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ }
+ if (getEnrolledFaces(sensorId, userId).isEmpty()) {
+ Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hardwareAuthToken);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ });
+ }
+
+ @Override
+ public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
+ boolean enabled, @NonNull byte[] hardwareAuthToken,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ return;
+ }
+ final List<Face> faces = getEnrolledFaces(sensorId, userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
+ mBiometricContext,
+ feature, enabled, hardwareAuthToken, faceId);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ });
+ }
+
+ @Override
+ public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
+ @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ if (getDaemon() == null) {
+ bindService(mCurrentUserId);
+ if (listener != null) {
+ try {
+ listener.onError(1008, 0, 1, 0);
+ return;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ return;
+ }
+ final List<Face> faces = getEnrolledFaces(sensorId, userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
+ token, listener, userId, opPackageName, mSensorId,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature, faceId);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(
+ @NonNull BaseClientMonitor clientMonitor, boolean success) {
+ if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
+ final int settingsValue = client.getValue() ? 1 : 0;
+ Slog.d(TAG, "Updating attention value for user: " + userId
+ + " to value: " + settingsValue);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
+ settingsValue, userId);
+ }
+ }
+ });
+ });
+ }
+
+ private void scheduleInternalCleanup(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
+ FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
+ mBiometricStateCallback));
+ });
+ }
+
+ @Override
+ public void scheduleInternalCleanup(int sensorId, int userId,
+ @Nullable ClientMonitorCallback callback) {
+ scheduleInternalCleanup(userId, mBiometricStateCallback);
+ }
+
+ @Override
+ public void scheduleInternalCleanup(int sensorId, int userId,
+ @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
+ scheduleInternalCleanup(userId, mBiometricStateCallback);
+ }
+
+ @Override
+ public void startPreparedClient(int sensorId, int cookie) {
+ mHandler.post(() -> {
+ mScheduler.startPreparedClient(cookie);
+ });
+ }
+
+ @Override
+ public void dumpProtoState(int sensorId, ProtoOutputStream proto,
+ boolean clearSchedulerBuffer) {
+ final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
+
+ proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
+ proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE);
+ proto.write(SensorStateProto.CURRENT_STRENGTH,
+ Utils.getCurrentStrength(mSensorProperties.sensorId));
+ proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
+
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+
+ final long userToken = proto.start(SensorStateProto.USER_STATES);
+ proto.write(UserStateProto.USER_ID, userId);
+ proto.write(UserStateProto.NUM_ENROLLED, FaceUtils.getLegacyInstance(mSensorId)
+ .getBiometricsForUser(mContext, userId).size());
+ proto.end(userToken);
+ }
+
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
+ mSensorProperties.resetLockoutRequiresHardwareAuthToken);
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
+ mSensorProperties.resetLockoutRequiresChallenge);
+
+ proto.end(sensorToken);
+ }
+
+ @Override
+ public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
+ }
+
+ @Override
+ public void dumpInternal(int sensorId, PrintWriter pw) {
+ PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(mSensorId);
+
+ JSONObject dump = new JSONObject();
+ try {
+ dump.put("service", TAG);
+
+ JSONArray sets = new JSONArray();
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+ final int c = FaceUtils.getLegacyInstance(mSensorId)
+ .getBiometricsForUser(mContext, userId).size();
+ JSONObject set = new JSONObject();
+ set.put("id", userId);
+ set.put("count", c);
+ set.put("accept", performanceTracker.getAcceptForUser(userId));
+ set.put("reject", performanceTracker.getRejectForUser(userId));
+ set.put("acquire", performanceTracker.getAcquireForUser(userId));
+ set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
+ set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
+ // cryptoStats measures statistics about secure face transactions
+ // (e.g. to unlock password storage, make secure purchases, etc.)
+ set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
+ set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
+ set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
+ sets.put(set);
+ }
+
+ dump.put("prints", sets);
+ } catch (JSONException e) {
+ Slog.e(TAG, "dump formatting failure", e);
+ }
+ pw.println(dump);
+ pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
+
+ mScheduler.dump(pw);
+ mUsageStats.print(pw);
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ // Note that this can be performed on the scheduler (as opposed to being done immediately
+ // when the HAL is (re)loaded, since
+ // 1) If this is truly the first time it's being performed (e.g. system has just started),
+ // this will be run very early and way before any applications need to generate keys.
+ // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+ // just been reloaded), the framework already has a cache of the authenticatorIds. This
+ // is safe because authenticatorIds only change when A) new template has been enrolled,
+ // or B) all templates are removed.
+ mHandler.post(() -> {
+ for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
+ final int targetUserId = user.id;
+ if (!mAuthenticatorIds.containsKey(targetUserId)) {
+ scheduleUpdateActiveUserWithoutHandler(targetUserId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Schedules the {@link FaceUpdateActiveUserClient} without posting the work onto the handler.
+ * Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
+ * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
+ * this operation on the same lambda/runnable as those operations so that the ordering is
+ * correct.
+ */
+ private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+ final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
+ final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
+ mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hasEnrolled, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (success) {
+ mCurrentUserId = targetUserId;
+ } else {
+ Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
+ }
+ }
+ });
+ }
+
+ public class SenseServiceConnection implements ServiceConnection {
+ private int mUserId;
+
+ public SenseServiceConnection(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Slog.d(TAG, "Service connected : " + mUserId);
+ ISenseService senseService = ISenseService.Stub.asInterface(service);
+ if (senseService != null) {
+ synchronized (mServices) {
+ try {
+ senseService.setCallback(mHalResultController);
+ mServices.put(mUserId, senseService);
+ mHandler.post(() -> {
+ updateSchedule();
+ });
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mIsBinding = false;
+ }
+ }
+ }
+
+ public void updateSchedule() {
+ scheduleInternalCleanup(mUserId, null);
+ scheduleGetFeature(mSensorId, new Binder(), mUserId, 1, null, mContext.getOpPackageName());
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Slog.d(TAG, "Service disconnected : " + mUserId);
+ mServices.remove(mUserId);
+ mIsBinding = false;
+ if (mUserId == mCurrentUserId) {
+ mHandler.post(() -> {
+ updateResetSchedule();
+ });
+ }
+ mContext.unbindService(this);
+ }
+
+ public void updateResetSchedule() {
+ BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (client != null && (client instanceof ErrorConsumer)) {
+ ErrorConsumer errorConsumer = (ErrorConsumer) client;
+ errorConsumer.onError(5, 0);
+ }
+ bindService(mUserId);
+ mScheduler.recordCrashState();
+ mScheduler.reset();
+ }
+ }
+
+ private boolean isServiceEnabled() {
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent(BIND_SENSE_ACTION);
+ intent.setClassName(PACKAGE_NAME, SERVICE_NAME);
+ ResolveInfo info = pm.resolveService(intent, 131072);
+ if (info != null && info.serviceInfo.isEnabled()) {
+ return true;
+ }
+ return false;
+ }
+
+ private ISenseService getService(int userId) {
+ if (userId == -10000) {
+ scheduleUpdateActiveUserWithoutHandler(ActivityManager.getCurrentUser());
+ }
+ return mServices.get(mCurrentUserId);
+ }
+
+ public boolean bindService(int userId) {
+ Slog.d(TAG, "bindService " + userId);
+ if (!isServiceEnabled()) {
+ Slog.d(TAG, "Service disabled");
+ return false;
+ } else if (mIsBinding) {
+ Slog.d(TAG, "Service is binding");
+ return true;
+ } else {
+ if (userId != -10000 && getService(userId) == null) {
+ try {
+ Intent intent = new Intent(BIND_SENSE_ACTION);
+ intent.setClassName(PACKAGE_NAME, SERVICE_NAME);
+ boolean result = mContext.bindServiceAsUser(intent, new SenseServiceConnection(userId), 1, UserHandle.of(userId));
+ if (result) {
+ mIsBinding = true;
+ }
+ return result;
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ }
+ }
+ return false;
+ }
+ }
+
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+ statsAction, statsClient, mAuthenticationStatsCollector);
+ }
+
+ /**
+ * Sends a debug message to the HAL with the provided FileDescriptor and arguments.
+ */
+ public void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args) { }
+
+ void setTestHalEnabled(boolean enabled) {
+ mTestHalEnabled = enabled;
+ }
+
+ @NonNull
+ @Override
+ public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+ @NonNull String opPackageName) {
+ return new BiometricTestSessionImpl(mContext, mSensorId, callback, this,
+ mHalResultController);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/SenseUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/SenseUtils.java
new file mode 100644
index 00000000..9e49fa1e
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/SenseUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.os.SystemProperties;
+
+import java.util.ArrayList;
+
+public class SenseUtils {
+
+ public static boolean canUseProvider() {
+ return SystemProperties.getBoolean("ro.face.sense_service", false);
+ }
+
+ public static ArrayList<Byte> toByteArrayList(byte[] in) {
+ if (in == null) {
+ return null;
+ }
+ ArrayList<Byte> out = new ArrayList<>(in.length);
+ for (byte c : in) {
+ out.add(Byte.valueOf(c));
+ }
+ return out;
+ }
+
+ public static byte[] toByteArray(ArrayList<Byte> in) {
+ if (in == null) {
+ return null;
+ }
+ byte[] out = new byte[in.size()];
+ for (int i = 0; i < in.size(); i++) {
+ out[i] = in.get(i).byteValue();
+ }
+ return out;
+ }
+
+ public static int[] toIntArray(ArrayList<Integer> in) {
+ if (in == null) {
+ return null;
+ }
+ int[] out = new int[in.size()];
+ for (int i = 0; i < in.size(); i++) {
+ out[i] = in.get(i).intValue();
+ }
+ return out;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/sense/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/sense/TestHal.java
new file mode 100644
index 00000000..0499bc51
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/TestHal.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * 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 com.android.server.biometrics.sensors.face.sense;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.FaceError;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.face.FaceUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import vendor.aospa.biometrics.face.ISenseService;
+import vendor.aospa.biometrics.face.ISenseServiceReceiver;
+
+public class TestHal extends ISenseService.Stub {
+ private static final String TAG = "face.hidl.TestHal";
+
+ @NonNull
+ private final Context mContext;
+ private final int mSensorId;
+
+ @Nullable
+ private ISenseServiceReceiver mCallback;
+ private int mUserId;
+
+ TestHal(@NonNull Context context, int sensorId) {
+ mContext = context;
+ mSensorId = sensorId;
+ }
+
+ @Override
+ public void setCallback(ISenseServiceReceiver clientCallback) {
+ mCallback = clientCallback;
+ }
+
+ @Override
+ public long generateChallenge(int challengeTimeoutSec) {
+ Slog.w(TAG, "generateChallenge");
+ return 0L;
+ }
+
+ @Override
+ public void enroll(byte[] hat, int timeoutSec, int[] disabledFeatures) {
+ Slog.w(TAG, "enroll");
+ }
+
+ @Override
+ public int revokeChallenge() {
+ return 0;
+ }
+
+ @Override
+ public void setFeature(int feature, boolean enabled, byte[] token, int faceId) { }
+
+ @Override
+ public boolean getFeature(int feature, int faceId) {
+ return false;
+ }
+
+ @Override
+ public int getFeatureCount() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int getAuthenticatorId() {
+ return 0;
+ }
+
+ @Override
+ public void cancel() throws RemoteException {
+ if (mCallback != null) {
+ mCallback.onError(0 /* deviceId */, 0 /* vendorCode */);
+ }
+ }
+
+ @Override
+ public int enumerate() throws RemoteException {
+ Slog.w(TAG, "enumerate");
+ if (mCallback != null) {
+ mCallback.onEnumerate(new int[0], 0 /* userId */);
+ }
+ return 0;
+ }
+
+ @Override
+ public void remove(int faceId) throws RemoteException {
+ Slog.w(TAG, "remove");
+ if (mCallback != null) {
+ if (faceId == 0) {
+ List<Face> faces = FaceUtils.getInstance(mSensorId).getBiometricsForUser(mContext, mUserId);
+ if (faces.size() <= 0) {
+ mCallback.onError(6, 0);
+ return;
+ }
+ int[] faceIds = new int[faces.size()];
+ for (int i = 0; i < faces.size(); i++) {
+ Face face = faces.get(i);
+ faceIds[i] = face.getBiometricId();
+ }
+
+ mCallback.onRemoved(faceIds, mUserId);
+ } else {
+ mCallback.onRemoved(new int[]{faceId}, mUserId);
+ }
+ }
+ }
+
+ @Override
+ public void authenticate(long operationId) {
+ Slog.w(TAG, "authenticate");
+ }
+
+ @Override
+ public void resetLockout(byte[] hat) {
+ Slog.w(TAG, "resetLockout");
+ }
+
+}
--
2.34.1