blob: 206b8f0779e8e1f7e284d0e400e79041b3fb8ce8 [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.ITestSessionCallback;
import android.hardware.biometrics.face.AuthenticationFrame;
import android.hardware.biometrics.face.EnrollmentFrame;
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.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
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.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
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.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
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 {
private boolean mTestHalEnabled;
@NonNull private final String mTag;
@NonNull private final FaceProvider mProvider;
@NonNull private final Context mContext;
@NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
@NonNull private final UserAwareBiometricScheduler mScheduler;
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@NonNull private final HalClientMonitor.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 UserAwareBiometricScheduler mScheduler;
private final int mSensorId;
private final int mUserId;
@NonNull
private final LockoutCache mLockoutCache;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull
private final Callback mCallback;
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
@NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
@NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Callback callback) {
mContext = context;
mHandler = handler;
mTag = tag;
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mCallback = callback;
}
@Override
public int getInterfaceVersion() {
return this.VERSION;
}
@Override
public String getInterfaceHash() {
return this.HASH;
}
@Override
public void onChallengeGenerated(long challenge) {
mHandler.post(() -> {
final BaseClientMonitor 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 BaseClientMonitor 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 onAuthenticationFrame(AuthenticationFrame frame) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FaceAuthenticationClient)) {
Slog.e(mTag, "onAuthenticationFrame for incompatible client: "
+ Utils.getClientName(client));
return;
}
if (frame == null) {
Slog.e(mTag, "Received null authentication frame for client: "
+ Utils.getClientName(client));
return;
}
((FaceAuthenticationClient) client).onAuthenticationFrame(
AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
});
}
@Override
public void onEnrollmentFrame(EnrollmentFrame frame) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FaceEnrollClient)) {
Slog.e(mTag, "onEnrollmentFrame for incompatible client: "
+ Utils.getClientName(client));
return;
}
if (frame == null) {
Slog.e(mTag, "Received null enrollment frame for client: "
+ Utils.getClientName(client));
return;
}
((FaceEnrollClient) client).onEnrollmentFrame(
AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
});
}
@Override
public void onError(byte error, int vendorCode) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
Slog.d(mTag, "onError"
+ ", client: " + Utils.getClientName(client)
+ ", error: " + error
+ ", vendorCode: " + vendorCode);
if (!(client instanceof ErrorConsumer)) {
Slog.e(mTag, "onError for non-error consumer: "
+ Utils.getClientName(client));
return;
}
final ErrorConsumer errorConsumer = (ErrorConsumer) client;
errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
if (error == Error.HW_UNAVAILABLE) {
mCallback.onHardwareUnavailable();
}
});
}
@Override
public void onEnrollmentProgress(int enrollmentId, int remaining) {
mHandler.post(() -> {
final BaseClientMonitor 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 BaseClientMonitor 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 BaseClientMonitor 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 BaseClientMonitor 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 BaseClientMonitor 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 BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FaceResetLockoutClient)) {
Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
mLockoutCache, mLockoutResetDispatcher);
} else {
Slog.d(mTag, "onLockoutCleared after resetLockout");
final FaceResetLockoutClient resetLockoutClient =
(FaceResetLockoutClient) client;
resetLockoutClient.onLockoutCleared();
}
});
}
@Override
public void onInteractionDetected() {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FaceDetectClient)) {
Slog.e(mTag, "onInteractionDetected for wrong client: "
+ Utils.getClientName(client));
return;
}
final FaceDetectClient detectClient = (FaceDetectClient) client;
detectClient.onInteractionDetected();
});
}
@Override
public void onEnrollmentsEnumerated(int[] enrollmentIds) {
mHandler.post(() -> {
final BaseClientMonitor 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 onFeaturesRetrieved(byte[] features) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FaceGetFeatureClient)) {
Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: "
+ Utils.getClientName(client));
return;
}
final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client;
faceGetFeatureClient.onFeatureGet(true /* success */, features);
});
}
@Override
public void onFeatureSet(byte feature) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FaceSetFeatureClient)) {
Slog.e(mTag, "onFeatureSet for non-set consumer: "
+ Utils.getClientName(client));
return;
}
final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client;
faceSetFeatureClient.onFeatureSet(true /* success */);
});
}
@Override
public void onEnrollmentsRemoved(int[] enrollmentIds) {
mHandler.post(() -> {
final BaseClientMonitor 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 BaseClientMonitor 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 BaseClientMonitor 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);
});
}
@Override
public void onSessionClosed() {
mHandler.post(mScheduler::onUserStopped);
}
}
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
mTag = tag;
mProvider = provider;
mContext = context;
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
mScheduler = new UserAwareBiometricScheduler(tag,
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@NonNull
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
mSensorProperties.sensorId, () -> mCurrentSession = null);
}
@NonNull
@Override
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final HalSessionCallback.Callback callback = () -> {
Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
mCurrentSession = null;
};
final int sensorId = mSensorProperties.sensorId;
final HalSessionCallback resultController = new HalSessionCallback(mContext,
mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
lockoutResetDispatcher, callback);
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession) -> {
mCurrentSession = new Session(mTag, newSession, userIdStarted,
resultController);
if (FaceUtils.getLegacyInstance(sensorId)
.isInvalidationInProgress(mContext, userIdStarted)) {
Slog.w(mTag,
"Scheduling unfinished invalidation request for "
+ "sensor: "
+ sensorId
+ ", user: " + userIdStarted);
provider.scheduleInvalidationRequest(sensorId,
userIdStarted);
}
};
return new FaceStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
resultController, userStartedCallback);
}
});
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
}
@NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
return mLazySession;
}
@NonNull FaceSensorPropertiesInternal getSensorProperties() {
return mSensorProperties;
}
@Nullable Session getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
return mCurrentSession;
} else {
return null;
}
}
@NonNull ITestSession createTestSession(@NonNull ITestSessionCallback callback) {
return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
mProvider, this);
}
@NonNull BiometricScheduler getScheduler() {
return mScheduler;
}
@NonNull LockoutCache getLockoutCache() {
return mLockoutCache;
}
@NonNull Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
void setTestHalEnabled(boolean enabled) {
Slog.w(mTag, "setTestHalEnabled: " + enabled);
if (enabled != mTestHalEnabled) {
// The framework should retrieve a new session from the HAL.
try {
if (mCurrentSession != null && mCurrentSession.mSession != null) {
// TODO(181984005): This should be scheduled instead of directly invoked
Slog.d(mTag, "Closing old session");
mCurrentSession.mSession.close();
}
} catch (RemoteException e) {
Slog.e(mTag, "RemoteException", e);
}
mCurrentSession = null;
}
mTestHalEnabled = enabled;
}
void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE);
proto.write(SensorStateProto.CURRENT_STRENGTH,
Utils.getCurrentStrength(mSensorProperties.sensorId));
proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
for (UserInfo user : UserManager.get(mContext).getUsers()) {
final int userId = user.getUserHandle().getIdentifier();
final long userToken = proto.start(SensorStateProto.USER_STATES);
proto.write(UserStateProto.USER_ID, userId);
proto.write(UserStateProto.NUM_ENROLLED,
FaceUtils.getInstance(mSensorProperties.sensorId)
.getBiometricsForUser(mContext, userId).size());
proto.end(userToken);
}
proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
mSensorProperties.resetLockoutRequiresHardwareAuthToken);
proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
mSensorProperties.resetLockoutRequiresChallenge);
proto.end(sensorToken);
}
public void onBinderDied() {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (client instanceof Interruptable) {
Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
final ErrorConsumer errorConsumer = (ErrorConsumer) client;
errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
BiometricsProtoEnums.MODALITY_FACE,
BiometricsProtoEnums.ISSUE_HAL_DEATH,
-1 /* sensorId */);
}
mScheduler.recordCrashState();
mScheduler.reset();
mCurrentSession = null;
}
}