Merge changes from topic "framework-face-aidl"
* changes: Load AIDL HALs in FaceService Implement FaceProvider and Sensor for face AIDL Implement client monitors for face AIDL Make LockoutCache accessible to both fingerprint and face
This commit is contained in:
committed by
Android (Google) Code Review
commit
d41342eb09
@@ -110,6 +110,7 @@ java_library_static {
|
||||
"android.hardware.tv.cec-V1.0-java",
|
||||
"android.hardware.weaver-V1.0-java",
|
||||
"android.hardware.biometrics.face-V1.1-java",
|
||||
"android.hardware.biometrics.face-java",
|
||||
"android.hardware.biometrics.fingerprint-V2.3-java",
|
||||
"android.hardware.biometrics.fingerprint-java",
|
||||
"android.hardware.oemlock-V1.0-java",
|
||||
|
||||
@@ -14,21 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.fingerprint.aidl;
|
||||
package com.android.server.biometrics.sensors;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.server.biometrics.sensors.LockoutTracker;
|
||||
|
||||
/**
|
||||
* For a single sensor, caches lockout states for all users.
|
||||
*/
|
||||
class LockoutCache implements LockoutTracker {
|
||||
public class LockoutCache implements LockoutTracker {
|
||||
|
||||
// Map of userId to LockoutMode
|
||||
private final SparseIntArray mUserLockoutStates;
|
||||
|
||||
LockoutCache() {
|
||||
public LockoutCache() {
|
||||
mUserLockoutStates = new SparseIntArray();
|
||||
}
|
||||
|
||||
@@ -27,13 +27,19 @@ import android.hardware.biometrics.BiometricManager;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.IBiometricSensorReceiver;
|
||||
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.SensorProps;
|
||||
import android.hardware.face.Face;
|
||||
import android.hardware.face.FaceSensorPropertiesInternal;
|
||||
import android.hardware.face.IFaceService;
|
||||
import android.hardware.face.IFaceServiceReceiver;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.NativeHandle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
@@ -42,11 +48,13 @@ import android.view.Surface;
|
||||
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.biometrics.Utils;
|
||||
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 java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -508,6 +516,38 @@ public class FaceService extends SystemService {
|
||||
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
|
||||
mLockPatternUtils = new LockPatternUtils(context);
|
||||
mServiceProviders = new ArrayList<>();
|
||||
|
||||
initializeAidlHals();
|
||||
}
|
||||
|
||||
private void initializeAidlHals() {
|
||||
final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
|
||||
if (instances == null || instances.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If for some reason the HAL is not started before the system service, do not block
|
||||
// the rest of system server. Put this on a background thread.
|
||||
final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
|
||||
true /* allowIo */);
|
||||
thread.start();
|
||||
final Handler handler = new Handler(thread.getLooper());
|
||||
|
||||
handler.post(() -> {
|
||||
for (String instance : instances) {
|
||||
final String fqName = IFace.DESCRIPTOR + "/" + instance;
|
||||
final IFace face = IFace.Stub.asInterface(
|
||||
ServiceManager.waitForDeclaredService(fqName));
|
||||
try {
|
||||
final SensorProps[] props = face.getSensorProps();
|
||||
final FaceProvider provider = new FaceProvider(getContext(), props, instance,
|
||||
mLockoutResetDispatcher);
|
||||
mServiceProviders.add(provider);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception when initializing instance: " + fqName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricFaceConstants;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.common.ICancellationSignal;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.biometrics.Utils;
|
||||
import com.android.server.biometrics.sensors.AuthenticationClient;
|
||||
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
|
||||
import com.android.server.biometrics.sensors.LockoutCache;
|
||||
import com.android.server.biometrics.sensors.LockoutConsumer;
|
||||
import com.android.server.biometrics.sensors.LockoutTracker;
|
||||
import com.android.server.biometrics.sensors.face.UsageStats;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Face-specific authentication client for the {@link IFace} AIDL HAL interface.
|
||||
*/
|
||||
class FaceAuthenticationClient extends AuthenticationClient<ISession> implements LockoutConsumer {
|
||||
private static final String TAG = "FaceAuthenticationClient";
|
||||
|
||||
@NonNull private final UsageStats mUsageStats;
|
||||
@NonNull private final LockoutCache mLockoutCache;
|
||||
@Nullable private final NotificationManager mNotificationManager;
|
||||
@Nullable private ICancellationSignal mCancellationSignal;
|
||||
|
||||
private final int[] mBiometricPromptIgnoreList;
|
||||
private final int[] mBiometricPromptIgnoreListVendor;
|
||||
private final int[] mKeyguardIgnoreList;
|
||||
private final int[] mKeyguardIgnoreListVendor;
|
||||
|
||||
private int mLastAcquire;
|
||||
|
||||
FaceAuthenticationClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
|
||||
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
|
||||
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
|
||||
boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
|
||||
@NonNull LockoutCache lockoutCache) {
|
||||
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
|
||||
owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
|
||||
BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
|
||||
lockoutCache);
|
||||
mUsageStats = usageStats;
|
||||
mLockoutCache = lockoutCache;
|
||||
mNotificationManager = context.getSystemService(NotificationManager.class);
|
||||
|
||||
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
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, 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() {
|
||||
if (mCancellationSignal != null) {
|
||||
try {
|
||||
mCancellationSignal.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 */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean wasUserDetected() {
|
||||
// Do not provide haptic feedback if the user was not detected, and an error (usually
|
||||
// ERROR_TIMEOUT) is received.
|
||||
return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
|
||||
&& mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
|
||||
boolean authenticated, ArrayList<Byte> token) {
|
||||
super.onAuthenticated(identifier, authenticated, token);
|
||||
|
||||
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
|
||||
getStartTimeMs(),
|
||||
System.currentTimeMillis() - getStartTimeMs() /* latency */,
|
||||
authenticated,
|
||||
0 /* error */,
|
||||
0 /* vendorError */,
|
||||
getTargetUserId()));
|
||||
|
||||
// For face, the authentication lifecycle ends either when
|
||||
// 1) Authenticated == true
|
||||
// 2) Error occurred
|
||||
// 3) Authenticated == false
|
||||
mCallback.onClientFinished(this, true /* success */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int error, int vendorCode) {
|
||||
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
|
||||
getStartTimeMs(),
|
||||
System.currentTimeMillis() - getStartTimeMs() /* latency */,
|
||||
false /* authenticated */,
|
||||
error,
|
||||
vendorCode,
|
||||
getTargetUserId()));
|
||||
|
||||
switch (error) {
|
||||
case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
|
||||
if (!wasUserDetected() && !isBiometricPrompt()) {
|
||||
// No vibration if user was not detected on keyguard
|
||||
break;
|
||||
}
|
||||
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
|
||||
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
|
||||
if (mAuthAttempted) {
|
||||
// Only vibrate if auth was attempted. If the user was already locked out prior
|
||||
// to starting authentication, do not vibrate.
|
||||
vibrateError();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
super.onError(error, vendorCode);
|
||||
}
|
||||
|
||||
private int[] getAcquireIgnorelist() {
|
||||
return isBiometricPrompt() ? mBiometricPromptIgnoreList : mKeyguardIgnoreList;
|
||||
}
|
||||
|
||||
private int[] getAcquireVendorIgnorelist() {
|
||||
return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
|
||||
}
|
||||
|
||||
private boolean shouldSend(int acquireInfo, int vendorCode) {
|
||||
if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
|
||||
return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
|
||||
} else {
|
||||
return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcquired(int acquireInfo, int vendorCode) {
|
||||
mLastAcquire = acquireInfo;
|
||||
|
||||
final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
|
||||
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
|
||||
}
|
||||
|
||||
@Override public void onLockoutTimed(long durationMillis) {
|
||||
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
|
||||
// Lockout metrics are logged as an error code.
|
||||
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
|
||||
logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId());
|
||||
|
||||
try {
|
||||
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onLockoutPermanent() {
|
||||
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
|
||||
// Lockout metrics are logged as an error code.
|
||||
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
|
||||
logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId());
|
||||
|
||||
try {
|
||||
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricFaceConstants;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.common.ICancellationSignal;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.hardware.face.Face;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.NativeHandle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.biometrics.HardwareAuthTokenUtils;
|
||||
import com.android.server.biometrics.Utils;
|
||||
import com.android.server.biometrics.sensors.BiometricUtils;
|
||||
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
|
||||
import com.android.server.biometrics.sensors.EnrollClient;
|
||||
import com.android.server.biometrics.sensors.face.FaceUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Face-specific enroll client for the {@link IFace} AIDL HAL interface.
|
||||
*/
|
||||
public class FaceEnrollClient extends EnrollClient<ISession> {
|
||||
|
||||
private static final String TAG = "FaceEnrollClient";
|
||||
|
||||
@NonNull private final int[] mEnrollIgnoreList;
|
||||
@NonNull private final int[] mEnrollIgnoreListVendor;
|
||||
@Nullable private ICancellationSignal mCancellationSignal;
|
||||
private final int mMaxTemplatesPerUser;
|
||||
|
||||
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
|
||||
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
||||
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName,
|
||||
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
|
||||
@Nullable NativeHandle previewSurface, int sensorId, int maxTemplatesPerUser) {
|
||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
|
||||
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
|
||||
false /* shouldVibrate */);
|
||||
mEnrollIgnoreList = getContext().getResources()
|
||||
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
|
||||
mEnrollIgnoreListVendor = getContext().getResources()
|
||||
.getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
|
||||
mMaxTemplatesPerUser = maxTemplatesPerUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasReachedEnrollmentLimit() {
|
||||
return FaceUtils.getInstance(getSensorId()).getBiometricsForUser(getContext(),
|
||||
getTargetUserId()).size() >= mMaxTemplatesPerUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcquired(int acquireInfo, int vendorCode) {
|
||||
final boolean shouldSend;
|
||||
if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
|
||||
shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
|
||||
} else {
|
||||
shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
|
||||
}
|
||||
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
final ArrayList<Byte> token = new ArrayList<>();
|
||||
for (byte b : mHardwareAuthToken) {
|
||||
token.add(b);
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO(b/172593978): Pass features.
|
||||
// TODO(b/172593521): Pass mPreviewSurface as android.hardware.common.NativeHandle.
|
||||
mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
|
||||
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
|
||||
null /* mPreviewSurface */);
|
||||
} 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() {
|
||||
if (mCancellationSignal != null) {
|
||||
try {
|
||||
mCancellationSignal.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 */);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
|
||||
import com.android.server.biometrics.sensors.GenerateChallengeClient;
|
||||
|
||||
/**
|
||||
* Face-specific generateChallenge client for the {@link IFace} AIDL HAL interface.
|
||||
*/
|
||||
public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISession> {
|
||||
private static final String TAG = "FaceGenerateChallengeClient";
|
||||
private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
|
||||
|
||||
FaceGenerateChallengeClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
|
||||
@NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId) {
|
||||
super(context, lazyDaemon, token, listener, owner, sensorId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
getFreshDaemon().generateChallenge(mSequentialId, CHALLENGE_TIMEOUT_SEC);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to generateChallenge", e);
|
||||
}
|
||||
}
|
||||
|
||||
void onChallengeGenerated(int sensorId, int userId, long challenge) {
|
||||
try {
|
||||
getListener().onChallengeGenerated(sensorId, challenge);
|
||||
mCallback.onClientFinished(this, true /* success */);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to send challenge", e);
|
||||
mCallback.onClientFinished(this, false /* success */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.sensors.ClientMonitor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class FaceGetAuthenticatorIdClient extends ClientMonitor<ISession> {
|
||||
|
||||
private static final String TAG = "FaceGetAuthenticatorIdClient";
|
||||
|
||||
private final Map<Integer, Long> mAuthenticatorIds;
|
||||
|
||||
FaceGetAuthenticatorIdClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
|
||||
int userId, @NonNull String opPackageName, int sensorId,
|
||||
Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FACE,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unableToStart() {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
getFreshDaemon().getAuthenticatorId(mSequentialId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
void onAuthenticatorIdRetrieved(long authenticatorId) {
|
||||
mAuthenticatorIds.put(getTargetUserId(), authenticatorId);
|
||||
mCallback.onClientFinished(this, true /* success */);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.hardware.face.Face;
|
||||
import android.os.IBinder;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.
|
||||
*/
|
||||
class FaceInternalCleanupClient extends InternalCleanupClient<Face, ISession> {
|
||||
|
||||
FaceInternalCleanupClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, int userId, @NonNull String owner,
|
||||
int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
|
||||
@NonNull Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
|
||||
enrolledList, utils, authenticatorIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InternalEnumerateClient<ISession> getEnumerateClient(Context context,
|
||||
LazyDaemon<ISession> lazyDaemon, IBinder token, int userId, String owner,
|
||||
List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
|
||||
return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
|
||||
enrolledList, utils, sensorId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RemovalClient<Face, ISession> getRemovalClient(Context context,
|
||||
LazyDaemon<ISession> lazyDaemon, IBinder token,
|
||||
int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
|
||||
Map<Integer, Long> authenticatorIds) {
|
||||
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
|
||||
// is all done internally.
|
||||
return new FaceRemovalClient(context, lazyDaemon, token,
|
||||
null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
|
||||
sensorId, authenticatorIds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.hardware.face.Face;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.sensors.BiometricUtils;
|
||||
import com.android.server.biometrics.sensors.InternalEnumerateClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Face-specific internal enumerate client for the {@link IFace} AIDL HAL interface.
|
||||
*/
|
||||
class FaceInternalEnumerateClient extends InternalEnumerateClient<ISession> {
|
||||
private static final String TAG = "FaceInternalEnumerateClient";
|
||||
|
||||
FaceInternalEnumerateClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, int userId,
|
||||
@NonNull String owner, @NonNull List<Face> enrolledList,
|
||||
@NonNull BiometricUtils<Face> utils, int sensorId) {
|
||||
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_FACE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
getFreshDaemon().enumerateEnrollments(mSequentialId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception when requesting enumerate", e);
|
||||
mCallback.onClientFinished(this, false /* success */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.IActivityTaskManager;
|
||||
import android.app.TaskStackListener;
|
||||
import android.content.Context;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.SensorProps;
|
||||
import android.hardware.face.Face;
|
||||
import android.hardware.face.FaceSensorPropertiesInternal;
|
||||
import android.hardware.face.IFaceServiceReceiver;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.NativeHandle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserManager;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.server.biometrics.Utils;
|
||||
import com.android.server.biometrics.sensors.AuthenticationClient;
|
||||
import com.android.server.biometrics.sensors.ClientMonitor;
|
||||
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
|
||||
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
|
||||
import com.android.server.biometrics.sensors.PerformanceTracker;
|
||||
import com.android.server.biometrics.sensors.face.FaceUtils;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provider for a single instance of the {@link IFace} HAL.
|
||||
*/
|
||||
public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
|
||||
private static final String TAG = "FaceProvider";
|
||||
private static final int ENROLL_TIMEOUT_SEC = 75;
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final String mHalInstanceName;
|
||||
@NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
|
||||
@NonNull private final ClientMonitor.LazyDaemon<IFace> mLazyDaemon;
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
|
||||
@NonNull private final UsageStats mUsageStats;
|
||||
@NonNull private final IActivityTaskManager mActivityTaskManager;
|
||||
@NonNull private final BiometricTaskStackListener mTaskStackListener;
|
||||
|
||||
@Nullable private IFace mDaemon;
|
||||
|
||||
private final class BiometricTaskStackListener extends TaskStackListener {
|
||||
@Override
|
||||
public void onTaskStackChanged() {
|
||||
mHandler.post(() -> {
|
||||
for (int i = 0; i < mSensors.size(); i++) {
|
||||
final ClientMonitor<?> client = mSensors.get(i).getScheduler()
|
||||
.getCurrentClient();
|
||||
if (!(client instanceof AuthenticationClient)) {
|
||||
Slog.e(getTag(), "Task stack changed for client: " + client);
|
||||
continue;
|
||||
}
|
||||
if (Utils.isKeyguard(mContext, client.getOwnerString())) {
|
||||
continue; // Keyguard is always allowed
|
||||
}
|
||||
|
||||
try {
|
||||
final List<ActivityManager.RunningTaskInfo> runningTasks =
|
||||
mActivityTaskManager.getTasks(1);
|
||||
if (!runningTasks.isEmpty()) {
|
||||
final String topPackage =
|
||||
runningTasks.get(0).topActivity.getPackageName();
|
||||
if (!topPackage.contentEquals(client.getOwnerString())
|
||||
&& !client.isAlreadyDone()) {
|
||||
Slog.e(getTag(), "Stopping background authentication, top: "
|
||||
+ topPackage + " currentClient: " + client);
|
||||
mSensors.get(i).getScheduler()
|
||||
.cancelAuthentication(client.getToken());
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Unable to get running tasks", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props,
|
||||
@NonNull String halInstanceName,
|
||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
|
||||
mContext = context;
|
||||
mHalInstanceName = halInstanceName;
|
||||
mSensors = new SparseArray<>();
|
||||
mLazyDaemon = this::getHalInstance;
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mUsageStats = new UsageStats(context);
|
||||
mLockoutResetDispatcher = lockoutResetDispatcher;
|
||||
mActivityTaskManager = ActivityTaskManager.getService();
|
||||
mTaskStackListener = new BiometricTaskStackListener();
|
||||
|
||||
for (SensorProps prop : props) {
|
||||
final int sensorId = prop.commonProps.sensorId;
|
||||
|
||||
final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
|
||||
prop.commonProps.sensorId, prop.commonProps.sensorStrength,
|
||||
prop.commonProps.maxEnrollmentsPerUser, false /* supportsFaceDetection */,
|
||||
prop.halControlsPreview);
|
||||
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
|
||||
internalProp);
|
||||
|
||||
mSensors.put(sensorId, sensor);
|
||||
Slog.d(getTag(), "Added: " + internalProp);
|
||||
}
|
||||
}
|
||||
|
||||
private String getTag() {
|
||||
return "FaceProvider/" + mHalInstanceName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private synchronized IFace getHalInstance() {
|
||||
if (mDaemon != null) {
|
||||
return mDaemon;
|
||||
}
|
||||
|
||||
Slog.d(getTag(), "Daemon was null, reconnecting");
|
||||
|
||||
mDaemon = IFace.Stub.asInterface(
|
||||
ServiceManager.waitForDeclaredService(IFace.DESCRIPTOR + "/" + mHalInstanceName));
|
||||
if (mDaemon == null) {
|
||||
Slog.e(getTag(), "Unable to get daemon");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Unable to linkToDeath", e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mSensors.size(); i++) {
|
||||
final int sensorId = mSensors.keyAt(i);
|
||||
scheduleLoadAuthenticatorIds(sensorId);
|
||||
scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser());
|
||||
}
|
||||
|
||||
return mDaemon;
|
||||
}
|
||||
|
||||
private void scheduleForSensor(int sensorId, @NonNull ClientMonitor<?> client) {
|
||||
if (!mSensors.contains(sensorId)) {
|
||||
throw new IllegalStateException("Unable to schedule client: " + client
|
||||
+ " for sensor: " + sensorId);
|
||||
}
|
||||
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
|
||||
}
|
||||
|
||||
private void scheduleForSensor(int sensorId, @NonNull ClientMonitor<?> client,
|
||||
ClientMonitor.Callback callback) {
|
||||
if (!mSensors.contains(sensorId)) {
|
||||
throw new IllegalStateException("Unable to schedule client: " + client
|
||||
+ " for sensor: " + sensorId);
|
||||
}
|
||||
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
|
||||
}
|
||||
|
||||
private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId,
|
||||
int userId) throws RemoteException {
|
||||
// Note that per IFingerprint createSession contract, this method will block until all
|
||||
// existing operations are canceled/finished. However, also note that this is fine, since
|
||||
// this method "withoutHandler" means it should only ever be invoked from the worker thread,
|
||||
// so callers will never be blocked.
|
||||
mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
|
||||
private void scheduleLoadAuthenticatorIds(int sensorId) {
|
||||
for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
|
||||
scheduleLoadAuthenticatorIdsForUser(sensorId, user.id);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
|
||||
mContext, mSensors.get(sensorId).getLazySession(), userId,
|
||||
mContext.getOpPackageName(), sensorId,
|
||||
mSensors.get(sensorId).getAuthenticatorIds());
|
||||
|
||||
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
|
||||
+ ", sensorId: " + sensorId
|
||||
+ ", userId: " + userId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSensor(int sensorId) {
|
||||
return mSensors.contains(sensorId);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<FaceSensorPropertiesInternal> getSensorProperties() {
|
||||
final List<FaceSensorPropertiesInternal> props = new ArrayList<>();
|
||||
for (int i = 0; i < mSensors.size(); ++i) {
|
||||
props.add(mSensors.valueAt(i).getSensorProperties());
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Face> getEnrolledFaces(int sensorId, int userId) {
|
||||
return FaceUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLockoutModeForUser(int sensorId, int userId) {
|
||||
return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAuthenticatorId(int sensorId, int userId) {
|
||||
return mSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHardwareDetected(int sensorId) {
|
||||
return getHalInstance() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
|
||||
@NonNull IFaceServiceReceiver receiver, String opPackageName) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
|
||||
mSensors.get(sensorId).getLazySession(), token,
|
||||
new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
|
||||
|
||||
scheduleForSensor(sensorId, client);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
|
||||
@NonNull String opPackageName, long challenge) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
|
||||
mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId,
|
||||
challenge);
|
||||
|
||||
scheduleForSensor(sensorId, client);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
|
||||
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
|
||||
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
|
||||
@Nullable NativeHandle previewSurface) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
|
||||
// If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
|
||||
// this operation. We should not send the callback yet, since the scheduler may
|
||||
// be processing something else.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final int maxTemplatesPerUser = mSensors.get(
|
||||
sensorId).getSensorProperties().maxEnrollmentsPerUser;
|
||||
final FaceEnrollClient client = new FaceEnrollClient(mContext,
|
||||
mSensors.get(sensorId).getLazySession(), token,
|
||||
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
|
||||
opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
|
||||
ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser);
|
||||
scheduleForSensor(sensorId, client, new ClientMonitor.Callback() {
|
||||
@Override
|
||||
public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
|
||||
boolean success) {
|
||||
if (success) {
|
||||
scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling enroll", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
|
||||
mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
|
||||
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
|
||||
@NonNull String opPackageName, boolean restricted, int statsClient,
|
||||
boolean isKeyguard) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
|
||||
// If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
|
||||
// this operation. We should not send the callback yet, since the scheduler may
|
||||
// be processing something else.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
|
||||
final FaceAuthenticationClient client = new FaceAuthenticationClient(
|
||||
mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
|
||||
operationId, restricted, opPackageName, cookie,
|
||||
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
|
||||
mUsageStats, mSensors.get(sensorId).getLockoutCache());
|
||||
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
|
||||
mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelAuthentication(token));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
|
||||
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId);
|
||||
// If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
|
||||
// this operation. We should not send the callback yet, since the scheduler may
|
||||
// be processing something else.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final FaceRemovalClient client = new FaceRemovalClient(mContext,
|
||||
mSensors.get(sensorId).getLazySession(), token,
|
||||
new ClientMonitorCallbackConverter(receiver), faceId, userId,
|
||||
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
|
||||
mSensors.get(sensorId).getAuthenticatorIds());
|
||||
|
||||
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling remove", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final FaceResetLockoutClient client = new FaceResetLockoutClient(
|
||||
mContext, mSensors.get(sensorId).getLazySession(), userId,
|
||||
mContext.getOpPackageName(), sensorId, hardwareAuthToken,
|
||||
mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
|
||||
|
||||
scheduleForSensor(sensorId, client);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
|
||||
boolean enabled, @NonNull byte[] hardwareAuthToken,
|
||||
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
|
||||
// TODO(b/171335732): implement this.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
|
||||
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
|
||||
// TODO(b/171335732): implement this.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreparedClient(int sensorId, int cookie) {
|
||||
mHandler.post(() -> {
|
||||
mSensors.get(sensorId).getScheduler().startPreparedClient(cookie);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleInternalCleanup(int sensorId, int userId) {
|
||||
mHandler.post(() -> {
|
||||
final IFace daemon = getHalInstance();
|
||||
if (daemon == null) {
|
||||
Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
|
||||
createNewSessionWithoutHandler(daemon, sensorId, userId);
|
||||
}
|
||||
|
||||
final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
|
||||
final FaceInternalCleanupClient client =
|
||||
new FaceInternalCleanupClient(mContext,
|
||||
mSensors.get(sensorId).getLazySession(), userId,
|
||||
mContext.getOpPackageName(), sensorId, enrolledList,
|
||||
FaceUtils.getInstance(sensorId),
|
||||
mSensors.get(sensorId).getAuthenticatorIds());
|
||||
|
||||
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
|
||||
PerformanceTracker performanceTracker =
|
||||
PerformanceTracker.getInstanceForSensorId(sensorId);
|
||||
|
||||
JSONObject dump = new JSONObject();
|
||||
try {
|
||||
dump.put("service", "Face Manager");
|
||||
|
||||
JSONArray sets = new JSONArray();
|
||||
for (UserInfo user : UserManager.get(mContext).getUsers()) {
|
||||
final int userId = user.getUserHandle().getIdentifier();
|
||||
final int c = FaceUtils.getInstance().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());
|
||||
|
||||
mSensors.get(sensorId).getScheduler().dump(pw);
|
||||
mUsageStats.print(pw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Slog.e(getTag(), "HAL died");
|
||||
mHandler.post(() -> {
|
||||
mDaemon = null;
|
||||
for (int i = 0; i < mSensors.size(); i++) {
|
||||
final int sensorId = mSensors.keyAt(i);
|
||||
PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.hardware.face.Face;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Face-specific removal client for the {@link IFace} AIDL HAL interface.
|
||||
*/
|
||||
class FaceRemovalClient extends RemovalClient<Face, ISession> {
|
||||
private static final String TAG = "FaceRemovalClient";
|
||||
|
||||
FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
|
||||
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
|
||||
int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
|
||||
int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
|
||||
authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
final int[] ids = new int[]{mBiometricId};
|
||||
getFreshDaemon().removeEnrollments(mSequentialId, ids);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception when requesting remove", e);
|
||||
mCallback.onClientFinished(this, false /* success */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.hardware.keymaster.HardwareAuthToken;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.HardwareAuthTokenUtils;
|
||||
import com.android.server.biometrics.sensors.ClientMonitor;
|
||||
import com.android.server.biometrics.sensors.LockoutCache;
|
||||
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
|
||||
import com.android.server.biometrics.sensors.LockoutTracker;
|
||||
|
||||
/**
|
||||
* Face-specific resetLockout client for the {@link IFace} AIDL HAL interface.
|
||||
* Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
|
||||
* cleared.
|
||||
*/
|
||||
public class FaceResetLockoutClient extends ClientMonitor<ISession> {
|
||||
|
||||
private static final String TAG = "FaceResetLockoutClient";
|
||||
|
||||
private final HardwareAuthToken mHardwareAuthToken;
|
||||
private final LockoutCache mLockoutCache;
|
||||
private final LockoutResetDispatcher mLockoutResetDispatcher;
|
||||
|
||||
FaceResetLockoutClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, int userId, String owner, int sensorId,
|
||||
@NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
|
||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
|
||||
mLockoutCache = lockoutTracker;
|
||||
mLockoutResetDispatcher = lockoutResetDispatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unableToStart() {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to reset lockout", e);
|
||||
mCallback.onClientFinished(this, false /* success */);
|
||||
}
|
||||
}
|
||||
|
||||
void onLockoutCleared() {
|
||||
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_NONE);
|
||||
mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
|
||||
mCallback.onClientFinished(this, true /* success */);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.sensors.RevokeChallengeClient;
|
||||
|
||||
/**
|
||||
* Face-specific revokeChallenge client for the {@link IFace} AIDL HAL interface.
|
||||
*/
|
||||
public class FaceRevokeChallengeClient extends RevokeChallengeClient<ISession> {
|
||||
|
||||
private static final String TAG = "FaceRevokeChallengeClient";
|
||||
|
||||
private final long mChallenge;
|
||||
|
||||
FaceRevokeChallengeClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
|
||||
@NonNull String owner, int sensorId, long challenge) {
|
||||
super(context, lazyDaemon, token, owner, sensorId);
|
||||
mChallenge = challenge;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
getFreshDaemon().revokeChallenge(mSequentialId, mChallenge);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to revokeChallenge", e);
|
||||
}
|
||||
}
|
||||
|
||||
void onChallengeRevoked(int sensorId, int userId, long challenge) {
|
||||
final boolean success = challenge == mChallenge;
|
||||
mCallback.onClientFinished(this, success);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.sensors.face.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.face.Error;
|
||||
import android.hardware.biometrics.face.IFace;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
import android.hardware.biometrics.face.ISessionCallback;
|
||||
import android.hardware.face.Face;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.face.FaceSensorPropertiesInternal;
|
||||
import android.hardware.keymaster.HardwareAuthToken;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.server.biometrics.HardwareAuthTokenUtils;
|
||||
import com.android.server.biometrics.Utils;
|
||||
import com.android.server.biometrics.sensors.AcquisitionClient;
|
||||
import com.android.server.biometrics.sensors.AuthenticationConsumer;
|
||||
import com.android.server.biometrics.sensors.BiometricScheduler;
|
||||
import com.android.server.biometrics.sensors.ClientMonitor;
|
||||
import com.android.server.biometrics.sensors.EnumerateConsumer;
|
||||
import com.android.server.biometrics.sensors.Interruptable;
|
||||
import com.android.server.biometrics.sensors.LockoutCache;
|
||||
import com.android.server.biometrics.sensors.LockoutConsumer;
|
||||
import com.android.server.biometrics.sensors.RemovalConsumer;
|
||||
import com.android.server.biometrics.sensors.face.FaceUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Maintains the state of a single sensor within an instance of the {@link IFace} HAL.
|
||||
*/
|
||||
public class Sensor implements IBinder.DeathRecipient {
|
||||
|
||||
@NonNull private final String mTag;
|
||||
@NonNull private final FaceProvider mProvider;
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
|
||||
@NonNull private final BiometricScheduler mScheduler;
|
||||
@NonNull private final LockoutCache mLockoutCache;
|
||||
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
|
||||
@NonNull private final ClientMonitor.LazyDaemon<ISession> mLazySession;
|
||||
@Nullable private Session mCurrentSession;
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Slog.e(mTag, "Binder died");
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (client instanceof Interruptable) {
|
||||
Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
|
||||
final Interruptable interruptable = (Interruptable) client;
|
||||
interruptable.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE,
|
||||
0 /* vendorCode */);
|
||||
|
||||
mScheduler.recordCrashState();
|
||||
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
|
||||
BiometricsProtoEnums.MODALITY_FACE,
|
||||
BiometricsProtoEnums.ISSUE_HAL_DEATH);
|
||||
mCurrentSession = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class Session {
|
||||
@NonNull final HalSessionCallback mHalSessionCallback;
|
||||
@NonNull private final String mTag;
|
||||
@NonNull private final ISession mSession;
|
||||
private final int mUserId;
|
||||
|
||||
Session(@NonNull String tag, @NonNull ISession session, int userId,
|
||||
@NonNull HalSessionCallback halSessionCallback) {
|
||||
mTag = tag;
|
||||
mSession = session;
|
||||
mUserId = userId;
|
||||
mHalSessionCallback = halSessionCallback;
|
||||
Slog.d(mTag, "New session created for user: " + userId);
|
||||
}
|
||||
}
|
||||
|
||||
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
|
||||
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties) {
|
||||
mTag = tag;
|
||||
mProvider = provider;
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
mSensorProperties = sensorProperties;
|
||||
mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
|
||||
mLockoutCache = new LockoutCache();
|
||||
mAuthenticatorIds = new HashMap<>();
|
||||
mLazySession = () -> (mCurrentSession != null) ? mCurrentSession.mSession : null;
|
||||
}
|
||||
|
||||
@NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() {
|
||||
return mLazySession;
|
||||
}
|
||||
|
||||
@NonNull FaceSensorPropertiesInternal getSensorProperties() {
|
||||
return mSensorProperties;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean hasSessionForUser(int userId) {
|
||||
return mCurrentSession != null && mCurrentSession.mUserId == userId;
|
||||
}
|
||||
|
||||
@Nullable Session getSessionForUser(int userId) {
|
||||
if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
|
||||
return mCurrentSession;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void createNewSession(@NonNull IFace daemon, int sensorId, int userId)
|
||||
throws RemoteException {
|
||||
|
||||
final HalSessionCallback.Callback callback = () -> {
|
||||
Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
|
||||
mCurrentSession = null;
|
||||
};
|
||||
final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
|
||||
mTag, mScheduler, sensorId, userId, callback);
|
||||
|
||||
final ISession newSession = daemon.createSession(sensorId, userId, resultController);
|
||||
newSession.asBinder().linkToDeath(this, 0 /* flags */);
|
||||
mCurrentSession = new Session(mTag, newSession, userId, resultController);
|
||||
}
|
||||
|
||||
@NonNull BiometricScheduler getScheduler() {
|
||||
return mScheduler;
|
||||
}
|
||||
|
||||
@NonNull LockoutCache getLockoutCache() {
|
||||
return mLockoutCache;
|
||||
}
|
||||
|
||||
@NonNull Map<Integer, Long> getAuthenticatorIds() {
|
||||
return mAuthenticatorIds;
|
||||
}
|
||||
|
||||
static class HalSessionCallback extends ISessionCallback.Stub {
|
||||
/**
|
||||
* Interface to sends results to the HalSessionCallback's owner.
|
||||
*/
|
||||
public interface Callback {
|
||||
/**
|
||||
* Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
|
||||
*/
|
||||
void onHardwareUnavailable();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
@NonNull
|
||||
private final String mTag;
|
||||
@NonNull
|
||||
private final BiometricScheduler mScheduler;
|
||||
private final int mSensorId;
|
||||
private final int mUserId;
|
||||
@NonNull
|
||||
private final Callback mCallback;
|
||||
|
||||
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
|
||||
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
|
||||
@NonNull Callback callback) {
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
mTag = tag;
|
||||
mScheduler = scheduler;
|
||||
mSensorId = sensorId;
|
||||
mUserId = userId;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(int cookie, byte state) {
|
||||
// TODO(b/162973174)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof FaceGenerateChallengeClient)) {
|
||||
Slog.e(mTag, "onChallengeGenerated for wrong client: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final FaceGenerateChallengeClient generateChallengeClient =
|
||||
(FaceGenerateChallengeClient) client;
|
||||
generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChallengeRevoked(int sensorId, int userId, long challenge) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof FaceRevokeChallengeClient)) {
|
||||
Slog.e(mTag, "onChallengeRevoked for wrong client: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final FaceRevokeChallengeClient revokeChallengeClient =
|
||||
(FaceRevokeChallengeClient) client;
|
||||
revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcquired(byte info, int vendorCode) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof AcquisitionClient)) {
|
||||
Slog.e(mTag, "onAcquired for non-acquisition client: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
|
||||
acquisitionClient.onAcquired(info, vendorCode);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(byte error, int vendorCode) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
Slog.d(mTag, "onError"
|
||||
+ ", client: " + Utils.getClientName(client)
|
||||
+ ", error: " + error
|
||||
+ ", vendorCode: " + vendorCode);
|
||||
if (!(client instanceof Interruptable)) {
|
||||
Slog.e(mTag, "onError for non-error consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final Interruptable interruptable = (Interruptable) client;
|
||||
interruptable.onError(error, vendorCode);
|
||||
|
||||
if (error == Error.HW_UNAVAILABLE) {
|
||||
mCallback.onHardwareUnavailable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentProgress(int enrollmentId, int remaining) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof FaceEnrollClient)) {
|
||||
Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final int currentUserId = client.getTargetUserId();
|
||||
final CharSequence name = FaceUtils.getInstance(mSensorId)
|
||||
.getUniqueName(mContext, currentUserId);
|
||||
final Face face = new Face(name, enrollmentId, mSensorId);
|
||||
|
||||
final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
|
||||
enrollClient.onEnrollResult(face, remaining);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof AuthenticationConsumer)) {
|
||||
Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final AuthenticationConsumer authenticationConsumer =
|
||||
(AuthenticationConsumer) client;
|
||||
final Face face = new Face("" /* name */, enrollmentId, mSensorId);
|
||||
final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
|
||||
final ArrayList<Byte> byteList = new ArrayList<>();
|
||||
for (byte b : byteArray) {
|
||||
byteList.add(b);
|
||||
}
|
||||
authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof AuthenticationConsumer)) {
|
||||
Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final AuthenticationConsumer authenticationConsumer =
|
||||
(AuthenticationConsumer) client;
|
||||
final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
|
||||
authenticationConsumer.onAuthenticated(face, false /* authenticated */,
|
||||
null /* hat */);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockoutTimed(long durationMillis) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof LockoutConsumer)) {
|
||||
Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
|
||||
lockoutConsumer.onLockoutTimed(durationMillis);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockoutPermanent() {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof LockoutConsumer)) {
|
||||
Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
|
||||
lockoutConsumer.onLockoutPermanent();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockoutCleared() {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof FaceResetLockoutClient)) {
|
||||
Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final FaceResetLockoutClient resetLockoutClient = (FaceResetLockoutClient) client;
|
||||
resetLockoutClient.onLockoutCleared();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteractionDetected() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentsEnumerated(int[] enrollmentIds) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof EnumerateConsumer)) {
|
||||
Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final EnumerateConsumer enumerateConsumer =
|
||||
(EnumerateConsumer) client;
|
||||
if (enrollmentIds.length > 0) {
|
||||
for (int i = 0; i < enrollmentIds.length; ++i) {
|
||||
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
|
||||
enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1);
|
||||
}
|
||||
} else {
|
||||
enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentsRemoved(int[] enrollmentIds) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof RemovalConsumer)) {
|
||||
Slog.e(mTag, "onRemoved for non-removal consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final RemovalConsumer removalConsumer = (RemovalConsumer) client;
|
||||
if (enrollmentIds.length > 0) {
|
||||
for (int i = 0; i < enrollmentIds.length; i++) {
|
||||
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
|
||||
removalConsumer.onRemoved(face, enrollmentIds.length - i - 1);
|
||||
}
|
||||
} else {
|
||||
removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticatorIdRetrieved(long authenticatorId) {
|
||||
mHandler.post(() -> {
|
||||
final ClientMonitor<?> client = mScheduler.getCurrentClient();
|
||||
if (!(client instanceof FaceGetAuthenticatorIdClient)) {
|
||||
Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
|
||||
+ Utils.getClientName(client));
|
||||
return;
|
||||
}
|
||||
|
||||
final FaceGetAuthenticatorIdClient getAuthenticatorIdClient =
|
||||
(FaceGetAuthenticatorIdClient) client;
|
||||
getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticatorIdInvalidated() {
|
||||
// TODO(b/159667191)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.sensors.AuthenticationClient;
|
||||
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
|
||||
import com.android.server.biometrics.sensors.LockoutCache;
|
||||
import com.android.server.biometrics.sensors.LockoutConsumer;
|
||||
import com.android.server.biometrics.sensors.LockoutTracker;
|
||||
import com.android.server.biometrics.sensors.fingerprint.Udfps;
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.HardwareAuthTokenUtils;
|
||||
import com.android.server.biometrics.sensors.ClientMonitor;
|
||||
import com.android.server.biometrics.sensors.LockoutCache;
|
||||
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
|
||||
import com.android.server.biometrics.sensors.LockoutTracker;
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.android.server.biometrics.sensors.BiometricScheduler;
|
||||
import com.android.server.biometrics.sensors.ClientMonitor;
|
||||
import com.android.server.biometrics.sensors.EnumerateConsumer;
|
||||
import com.android.server.biometrics.sensors.Interruptable;
|
||||
import com.android.server.biometrics.sensors.LockoutCache;
|
||||
import com.android.server.biometrics.sensors.LockoutConsumer;
|
||||
import com.android.server.biometrics.sensors.RemovalConsumer;
|
||||
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
|
||||
|
||||
Reference in New Issue
Block a user