blob: a806277ed45e23918bc94378a5a8da5deb33b788 [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.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.EnrollmentType;
import android.hardware.biometrics.face.Feature;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.common.NativeHandle;
import android.hardware.face.Face;
import android.hardware.face.FaceEnrollFrame;
import android.hardware.face.FaceManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.Surface;
import com.android.internal.R;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
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.FaceService;
import com.android.server.biometrics.sensors.face.FaceUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 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;
@NonNull private final int[] mDisabledFeatures;
@Nullable private final Surface mPreviewSurface;
@Nullable private android.os.NativeHandle mOsPreviewHandle;
@Nullable private NativeHandle mHwPreviewHandle;
@Nullable private ICancellationSignal mCancellationSignal;
private final int mMaxTemplatesPerUser;
private final boolean mDebugConsent;
private final BaseClientMonitor.Callback mPreviewHandleDeleterCallback =
new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
releaseSurfaceHandlesIfNeeded();
}
};
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 Surface previewSurface, int sensorId, int maxTemplatesPerUser,
boolean debugConsent) {
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;
mDebugConsent = debugConsent;
mDisabledFeatures = disabledFeatures;
mPreviewSurface = previewSurface;
}
@Override
public void start(@NonNull Callback callback) {
super.start(callback);
BiometricNotificationUtils.cancelReEnrollNotification(getContext());
}
@NonNull
@Override
protected Callback wrapCallbackForStart(@NonNull Callback callback) {
return new CompositeCallback(mPreviewHandleDeleterCallback,
createALSCallback(true /* startWithClient */), callback);
}
@Override
protected boolean hasReachedEnrollmentLimit() {
return FaceUtils.getInstance(getSensorId()).getBiometricsForUser(getContext(),
getTargetUserId()).size() >= mMaxTemplatesPerUser;
}
private boolean shouldSendAcquiredMessage(int acquireInfo, int vendorCode) {
return acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR
? !Utils.listContains(mEnrollIgnoreListVendor, vendorCode)
: !Utils.listContains(mEnrollIgnoreList, acquireInfo);
}
@Override
public void onAcquired(int acquireInfo, int vendorCode) {
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
}
/**
* Called each time a new frame is received during face enrollment.
*
* @param frame Information about the current frame.
*/
public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) {
// Log acquisition but don't send it to the client yet, since that's handled below.
final int acquireInfo = frame.getData().getAcquiredInfo();
final int vendorCode = frame.getData().getVendorCode();
onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
if (shouldSend && getListener() != null) {
try {
getListener().onEnrollmentFrame(frame);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to send enrollment frame", e);
mCallback.onClientFinished(this, false /* success */);
}
}
}
@Override
protected void startHalOperation() {
obtainSurfaceHandlesIfNeeded();
try {
List<Byte> featureList = new ArrayList<Byte>();
if (mDebugConsent) {
featureList.add(Feature.DEBUG);
}
boolean shouldAddDiversePoses = true;
for (int disabledFeature : mDisabledFeatures) {
if (AidlConversionUtils.convertFrameworkToAidlFeature(disabledFeature)
== Feature.REQUIRE_DIVERSE_POSES) {
shouldAddDiversePoses = false;
}
}
if (shouldAddDiversePoses) {
featureList.add(Feature.REQUIRE_DIVERSE_POSES);
}
byte[] features = new byte[featureList.size()];
for (int i = 0; i < featureList.size(); i++) {
features[i] = featureList.get(i);
}
mCancellationSignal = getFreshDaemon().enroll(
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
EnrollmentType.DEFAULT, features, mHwPreviewHandle);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "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 */);
}
}
}
private void obtainSurfaceHandlesIfNeeded() {
if (mPreviewSurface != null) {
// There is no direct way to convert Surface to android.hardware.common.NativeHandle. We
// first convert Surface to android.os.NativeHandle, and then android.os.NativeHandle to
// android.hardware.common.NativeHandle, which can be passed to the HAL.
// The resources for both handles must be explicitly freed to avoid memory leaks.
mOsPreviewHandle = FaceService.acquireSurfaceHandle(mPreviewSurface);
try {
// We must manually free up the resources for both handles after they are no longer
// needed. mHwPreviewHandle must be closed, but mOsPreviewHandle must be released
// through FaceService.
mHwPreviewHandle = AidlNativeHandleUtils.dup(mOsPreviewHandle);
Slog.v(TAG, "Obtained handles for the preview surface.");
} catch (IOException e) {
mHwPreviewHandle = null;
Slog.e(TAG, "Failed to dup mOsPreviewHandle", e);
}
}
}
private void releaseSurfaceHandlesIfNeeded() {
if (mPreviewSurface != null && mHwPreviewHandle == null) {
Slog.w(TAG, "mHwPreviewHandle is null even though mPreviewSurface is not null.");
}
if (mHwPreviewHandle != null) {
try {
Slog.v(TAG, "Closing mHwPreviewHandle");
AidlNativeHandleUtils.close(mHwPreviewHandle);
} catch (IOException e) {
Slog.e(TAG, "Failed to close mPreviewSurface", e);
}
mHwPreviewHandle = null;
}
if (mOsPreviewHandle != null) {
Slog.v(TAG, "Releasing mOsPreviewHandle");
FaceService.releaseSurfaceHandle(mOsPreviewHandle);
mOsPreviewHandle = null;
}
if (mPreviewSurface != null) {
Slog.v(TAG, "Releasing mPreviewSurface");
// We need to manually release this surface because it's a copy of the original surface
// that was sent to us by an app (e.g. Settings). The app cleans up its own surface (as
// part of the SurfaceView lifecycle, for example), but there is no mechanism in place
// that will clean up this copy.
// If this copy isn't cleaned up, it will eventually be garbage collected. However, this
// surface could be holding onto the native buffers that the GC is not aware of,
// exhausting the native memory before the GC feels the need to garbage collect.
mPreviewSurface.release();
}
}
}