From 7ca8d4cb604c5d8eac9250cc5dc3f06b06e68653 Mon Sep 17 00:00:00 2001 From: harvey186 Date: Fri, 13 Dec 2024 11:44:31 +0100 Subject: [PATCH] face unlock Change-Id: Icbdcbb9725de2dadb6f5a6ccf07c144f89aa6ee6 --- .../privacy/AppOpsPrivacyItemMonitor.kt | 8 + .../phone/BiometricUnlockController.java | 2 +- .../phone/KeyguardBypassController.kt | 11 +- services/core/Android.bp | 1 + .../server/biometrics/AuthService.java | 4 +- .../sensors/BiometricScheduler.java | 4 + .../biometrics/sensors/face/FaceService.java | 26 +- .../face/sense/BiometricTestSessionImpl.java | 234 ++++ .../face/sense/FaceAuthenticationClient.java | 233 ++++ .../sensors/face/sense/FaceEnrollClient.java | 134 ++ .../sense/FaceGenerateChallengeClient.java | 111 ++ .../face/sense/FaceGetFeatureClient.java | 101 ++ .../face/sense/FaceInternalCleanupClient.java | 76 ++ .../sense/FaceInternalEnumerateClient.java | 64 + .../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 | 1111 +++++++++++++++++ .../sensors/face/sense/SenseUtils.java | 61 + .../sensors/face/sense/TestHal.java | 144 +++ 22 files changed, 2695 insertions(+), 7 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 fedbdec11..cc4ce05a4 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 6f29f618e..d5c05297e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -661,7 +661,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 316e1f13b..86084cd1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -119,6 +119,8 @@ class KeyguardBypassController @Inject constructor( notifyListeners() } + var bypassEnabledBiometric: Boolean = false + var bouncerShowing: Boolean = false var launchingAffordance: Boolean = false var qsExpanded = false @@ -148,7 +150,7 @@ class KeyguardBypassController @Inject constructor( val dismissByDefault = if (resources.getBoolean( 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( object : NotificationLockscreenUserManager.UserChangedListener { @@ -184,8 +186,8 @@ class KeyguardBypassController @Inject constructor( 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) } @@ -209,7 +211,7 @@ class KeyguardBypassController @Inject constructor( * If keyguard can be dismissed because of bypass. */ fun canBypass(): Boolean { - if (bypassEnabled) { + if (bypassEnabledBiometric) { return when { bouncerShowing -> true keyguardTransitionInteractor.getCurrentState() == KeyguardState.ALTERNATE_BOUNCER -> @@ -243,6 +245,7 @@ class KeyguardBypassController @Inject constructor( pw.println(" mPendingUnlock: $pendingUnlock") } pw.println(" bypassEnabled: $bypassEnabled") + pw.println(" bypassEnabledBiometric: $bypassEnabledBiometric") pw.println(" canBypass: ${canBypass()}") pw.println(" bouncerShowing: $bouncerShowing") pw.println(" altBouncerShowing:" + diff --git a/services/core/Android.bp b/services/core/Android.bp index a56adcf51..9cc6933c9 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -239,6 +239,7 @@ java_library_static { "aconfig_new_storage_flags_lib", "powerstats_flags_lib", "locksettings_flags_lib", + "vendor.aospa.biometrics.face", // HIDL "vendor.mediatek.hardware.mtkpower-V1.1-java", "vendor.samsung.hardware.sysinput-V1.2-java", diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index a5187cd22..14d91e37d 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -74,6 +74,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.SystemService; import com.android.server.biometrics.sensors.fingerprint.FingerprintService; +import com.android.server.biometrics.sensors.face.sense.SenseUtils; import com.android.server.biometrics.sensors.face.FaceService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; @@ -1052,7 +1053,8 @@ public class AuthService extends SystemService { final String[] hidlConfigStrings, final Context context, final IFaceService faceService, final BiometricHandlerProvider handlerProvider) { if ((hidlConfigStrings == null || hidlConfigStrings.length == 0) - && (faceAidlInstances == null || faceAidlInstances.length == 0)) { + && (faceAidlInstances == null || faceAidlInstances.length == 0) + && !SenseUtils.canUseProvider()) { Slog.d(TAG, "No face sensors."); return; } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 82d5d4d81..8c66768f2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -311,6 +311,10 @@ public class BiometricScheduler { Slog.d(TAG, "No operations, returning to idle"); return; } + if (mCurrentUserRetriever == null) { + startNextOperationIfIdle(); + return; + } final int currentUserId = mCurrentUserRetriever.get(); final int nextUserId = mPendingOperations.getFirst().getTargetUserId(); 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 8c988729a..3853d4197 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 @@ -27,6 +27,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; @@ -40,6 +41,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; @@ -74,6 +76,8 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; 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.sense.SenseProvider; +import com.android.server.biometrics.sensors.face.sense.SenseUtils; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -664,12 +668,28 @@ public class FaceService extends SystemService { new ClientMonitorCallbackConverter(receiver), opPackageName); } + private List getSenseProviders() { + final List providers = new ArrayList<>(); + 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( FaceSensorConfigurations faceSensorConfigurations) { super.registerAuthenticators_enforcePermission(); - if (!faceSensorConfigurations.hasSensorConfigurations()) { + if (!faceSensorConfigurations.hasSensorConfigurations() && !SenseUtils.canUseProvider()) { Slog.d(TAG, "No face sensors to register."); return; } @@ -679,6 +699,10 @@ public class FaceService extends SystemService { private List getProviders( FaceSensorConfigurations faceSensorConfigurations) { final List providers = new ArrayList<>(); + if (SenseUtils.canUseProvider()) { + providers.addAll(getSenseProviders()); + return providers; + } final Pair filteredSensorProps = filterAvailableHalInstances( faceSensorConfigurations); providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps, 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 000000000..90dc55a7e --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/BiometricTestSessionImpl.java @@ -0,0 +1,234 @@ +/* + * 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.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 */, null); + } + + @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 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); + } + } + }); + } + + @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) + @Override + public int getSensorId() { + super.getSensorId_enforcePermission(); + return mSensorId; + } +} 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 000000000..a6e27cf0f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceAuthenticationClient.java @@ -0,0 +1,233 @@ +/* + * 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 static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_RECALIBRATE; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_SENSOR_DIRTY; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR; + +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.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 { + + 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 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 != FACE_ACQUIRED_NOT_DETECTED + && mLastAcquire != 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 token) { + super.onAuthenticated(identifier, authenticated, token); + vibrateSuccess(); + + 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 == 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 == 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 000000000..875aab5a2 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceEnrollClient.java @@ -0,0 +1,134 @@ +/* + * 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 static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.face.Face; +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 { + + 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 lazyDaemon, + @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, + @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId, + @NonNull BiometricUtils utils, @NonNull int[] disabledFeatures, int timeoutSec, + @Nullable Surface previewSurface, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + int enrollReason) { + super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, + timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext, enrollReason); + 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); + } + + @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 == FACE_ACQUIRED_VENDOR) { + shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode); + } else { + shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo); + } + onAcquiredInternal(acquireInfo, vendorCode, shouldSend); + } + + @Override + protected void startHalOperation() { + final ArrayList token = new ArrayList<>(); + for (byte b : mHardwareAuthToken) { + token.add(Byte.valueOf(b)); + } + final ArrayList 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 000000000..9a9414ff9 --- /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 { + + 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 mWaiting; + private Long mChallengeResult; + + FaceGenerateChallengeClient(@NonNull Context context, + @NonNull Supplier 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 000000000..73f99f168 --- /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 { + + private static final String TAG = "FaceGetFeatureClient"; + + private final int mFeature; + private final int mFaceId; + private boolean mValue; + + FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier 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, false /* isMandatoryBiometrics */); + 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 000000000..c2b21f55a --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalCleanupClient.java @@ -0,0 +1,76 @@ +/* + * 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.BiometricsProtoEnums; +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 { + + FaceInternalCleanupClient(@NonNull Context context, + @NonNull Supplier lazyDaemon, int userId, @NonNull String owner, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, + @NonNull BiometricUtils utils, @NonNull Map authenticatorIds) { + super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, + utils, authenticatorIds); + } + + @Override + protected InternalEnumerateClient getEnumerateClient(Context context, + Supplier lazyDaemon, IBinder token, int userId, String owner, + List enrolledList, BiometricUtils 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 getRemovalClient(Context context, + Supplier lazyDaemon, IBinder token, + int biometricId, int userId, String owner, BiometricUtils utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + Map 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); + } + + @Override + protected int getModality() { + return BiometricsProtoEnums.MODALITY_FACE; + } +} 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 000000000..c86d0361f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/FaceInternalEnumerateClient.java @@ -0,0 +1,64 @@ +/* + * 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.BiometricsProtoEnums; +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 { + private static final String TAG = "FaceInternalEnumerateClient"; + + FaceInternalEnumerateClient(@NonNull Context context, + @NonNull Supplier lazyDaemon, @NonNull IBinder token, int userId, + @NonNull String owner, @NonNull List enrolledList, + @NonNull BiometricUtils 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 */); + } + } + + @Override + protected int getModality() { + return BiometricsProtoEnums.MODALITY_FACE; + } +} 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 000000000..93e72863f --- /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 { + private static final String TAG = "FaceRemovalClient"; + + private final int mBiometricId; + + FaceRemovalClient(@NonNull Context context, @NonNull Supplier lazyDaemon, + @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, + int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils utils, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, + @NonNull Map 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 000000000..83c68b3c3 --- /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 { + + private static final String TAG = "FaceResetLockoutClient"; + + private final byte[] mHardwareAuthToken; + + FaceResetLockoutClient(@NonNull Context context, + @NonNull Supplier 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, false /* isMandatoryBiometrics */); + + 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 000000000..7f42df49d --- /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 { + + private static final String TAG = "FaceRevokeChallengeClient"; + + FaceRevokeChallengeClient(@NonNull Context context, + @NonNull Supplier 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 000000000..0b8d3ffff --- /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 { + + 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 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, false /* isMandatoryBiometrics */); + 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 000000000..338e6d293 --- /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 { + private static final String TAG = "FaceUpdateActiveUserClient"; + private static final String FACE_DATA_DIR = "facedata"; + + private final boolean mHasEnrolledBiometrics; + @NonNull private final Map mAuthenticatorIds; + + FaceUpdateActiveUserClient(@NonNull Context context, + @NonNull Supplier lazyDaemon, int userId, @NonNull String owner, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics, + @NonNull Map authenticatorIds) { + super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, + 0 /* cookie */, sensorId, logger, biometricContext, false /* isMandatoryBiometrics */); + 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 000000000..8dff4ac49 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/sense/SenseProvider.java @@ -0,0 +1,1111 @@ +/* + * 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 mLazyDaemon; + @NonNull private final LockoutHalImpl mLockoutTracker; + @NonNull private final UsageStats mUsageStats; + @NonNull private final Map mAuthenticatorIds; + @Nullable private IBiometricsFace mDaemon; + @NonNull private final HalResultController mHalResultController; + @NonNull private final BiometricContext mBiometricContext; + @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector; + SparseArray 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 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 getSensorProperties() { + final List properties = new ArrayList<>(); + properties.add(mSensorProperties); + return properties; + } + + @NonNull + @Override + public FaceSensorPropertiesInternal getSensorProperties(int sensorId) { + return mSensorProperties; + } + + @Override + @NonNull + public List 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, + mContext.getResources().getInteger( + com.android.internal.R.integer.config_faceMaxTemplatesPerUser)); + + 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 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 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 000000000..9e49fa1e7 --- /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 toByteArrayList(byte[] in) { + if (in == null) { + return null; + } + ArrayList out = new ArrayList<>(in.length); + for (byte c : in) { + out.add(Byte.valueOf(c)); + } + return out; + } + + public static byte[] toByteArray(ArrayList 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 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 000000000..0499bc51e --- /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 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