blob: 32f12e6b2cf46b45f435ced8aabfbd8efb84f1b0 [file] [log] [blame]
/*
* 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.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ITestSession;
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.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.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 {
private boolean mTestHalEnabled;
@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;
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);
}
}
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(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(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(long newAuthenticatorId) {
mHandler.post(() -> {
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof FaceInvalidationClient)) {
Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
+ Utils.getClientName(client));
return;
}
final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client;
invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
});
}
}
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 = () -> {
if (mTestHalEnabled) {
return new TestSession(mCurrentSession.mHalSessionCallback);
} else {
return 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;
}
}
@NonNull ITestSession createTestSession() {
return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this);
}
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;
}
void setTestHalEnabled(boolean enabled) {
mTestHalEnabled = enabled;
}
void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
for (UserInfo user : UserManager.get(mContext).getUsers()) {
final int userId = user.getUserHandle().getIdentifier();
final long userToken = proto.start(SensorStateProto.USER_STATES);
proto.write(UserStateProto.USER_ID, userId);
proto.write(UserStateProto.NUM_ENROLLED,
FaceUtils.getInstance(mSensorProperties.sensorId)
.getBiometricsForUser(mContext, userId).size());
proto.end(userToken);
}
proto.end(sensorToken);
}
@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;
}
});
}
}