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