mirror of
https://github.com/ponces/treble_aosp.git
synced 2024-11-28 01:14:52 +00:00
2944 lines
123 KiB
Diff
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
|
|
|