Implement FaceProvider and Sensor for face AIDL
FaceProvider.java corresponds to IFace.aidl and Sensor.java corresponds to Session.aidl Bug: 171335732 Test: build Change-Id: I044ea61bf0e28bef17ecd0cfdc748c85daa52d6d
This commit is contained in:
@@ -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,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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user