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:
Ilya Matyukhin
2020-11-07 23:11:56 +00:00
committed by Android (Google) Code Review
17 changed files with 1868 additions and 5 deletions

View File

@@ -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",

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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 */);
}
}
}
}

View File

@@ -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 */);
}
}
}

View File

@@ -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 */);
}
}

View File

@@ -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);
}
}

View File

@@ -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 */);
}
}
}

View File

@@ -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();
}
});
}
}

View File

@@ -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 */);
}
}
}

View File

@@ -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 */);
}
}

View File

@@ -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);
}
}

View File

@@ -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)
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;