diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 13e2700b3f54d..5f8789993a5e8 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -133,11 +133,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } @Override - public void onFeatureGet(boolean success, int feature, boolean value) { + public void onFeatureGet(boolean success, int[] features, boolean[] featureState) { SomeArgs args = SomeArgs.obtain(); args.arg1 = success; - args.argi1 = feature; - args.arg2 = value; + args.arg2 = features; + args.arg3 = featureState; mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget(); } @@ -1088,7 +1088,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ public abstract static class GetFeatureCallback { - public abstract void onCompleted(boolean success, int feature, boolean value); + public abstract void onCompleted(boolean success, int[] features, boolean[] featureState); } /** @@ -1179,8 +1179,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan case MSG_GET_FEATURE_COMPLETED: SomeArgs args = (SomeArgs) msg.obj; sendGetFeatureCompleted((boolean) args.arg1 /* success */, - args.argi1 /* feature */, - (boolean) args.arg2 /* value */); + (int[]) args.arg2 /* features */, + (boolean[]) args.arg3 /* featureState */); args.recycle(); break; case MSG_CHALLENGE_GENERATED: @@ -1216,11 +1216,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan mSetFeatureCallback.onCompleted(success, feature); } - private void sendGetFeatureCompleted(boolean success, int feature, boolean value) { + private void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) { if (mGetFeatureCallback == null) { return; } - mGetFeatureCallback.onCompleted(success, feature, value); + mGetFeatureCallback.onCompleted(success, features, featureState); } private void sendChallengeGenerated(int sensorId, long challenge) { diff --git a/core/java/android/hardware/face/FaceServiceReceiver.java b/core/java/android/hardware/face/FaceServiceReceiver.java index f0f975dcea570..9e62ca5e466bf 100644 --- a/core/java/android/hardware/face/FaceServiceReceiver.java +++ b/core/java/android/hardware/face/FaceServiceReceiver.java @@ -66,7 +66,8 @@ public class FaceServiceReceiver extends IFaceServiceReceiver.Stub { } @Override - public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException { + public void onFeatureGet(boolean success, int[] features, boolean[] featureState) + throws RemoteException { } diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl index 2ef1430a2f997..0ccb395835541 100644 --- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl +++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl @@ -32,7 +32,7 @@ oneway interface IFaceServiceReceiver { void onError(int error, int vendorCode); void onRemoved(in Face face, int remaining); void onFeatureSet(boolean success, int feature); - void onFeatureGet(boolean success, int feature, boolean value); + void onFeatureGet(boolean success, in int[] features, in boolean[] featureState); void onChallengeGenerated(int sensorId, long challenge); void onChallengeInterrupted(int sensorId); void onChallengeInterruptFinished(int sensorId); diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java index 25b7add0a7d8d..d82847c95dce0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java @@ -146,9 +146,10 @@ public class ClientMonitorCallbackConverter { } } - public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException { + public void onFeatureGet(boolean success, int[] features, boolean[] featureState) + throws RemoteException { if (mFaceServiceReceiver != null) { - mFaceServiceReceiver.onFeatureGet(success, feature, value); + mFaceServiceReceiver.onFeatureGet(success, features, featureState); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java index ca9be67914e3d..87269237bc853 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java @@ -105,7 +105,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { } @Override - public void onFeatureGet(boolean success, int feature, boolean value) { + public void onFeatureGet(boolean success, int[] features, boolean[] featureState) { } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java new file mode 100644 index 0000000000000..12f3e87dd37e4 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2021 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.face.Feature; +import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.Slog; + +import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.HalClientMonitor; + +import java.util.HashMap; +import java.util.Map; + +/** + * Face-specific get feature client for the {@link IFace} AIDL HAL interface. + */ +public class FaceGetFeatureClient extends HalClientMonitor implements ErrorConsumer { + + private static final String TAG = "FaceGetFeatureClient"; + + private final int mUserId; + + FaceGetFeatureClient(@NonNull Context context, @NonNull LazyDaemon lazyDaemon, + @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, + @NonNull String owner, int sensorId) { + super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, + BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN); + mUserId = userId; + } + + @Override + public void unableToStart() { + mCallback.onClientFinished(this, false /* success */); + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override + protected void startHalOperation() { + try { + getFreshDaemon().getFeatures(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to getFeature", e); + mCallback.onClientFinished(this, false /* success */); + } + } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_GET_FEATURE; + } + + public void onFeatureGet(boolean success, byte[] features) { + HashMap featureMap = getFeatureMap(); + int[] featuresToSend = new int[featureMap.size()]; + boolean[] featureState = new boolean[featureMap.size()]; + + // The AIDL get feature api states that the presence of a feature means + // it is enabled, while the lack thereof means its disabled. + for (int i = 0; i < features.length; i++) { + Integer feature = convertAidlToFrameworkFeature(features[i]); + if (feature != null) { + featureMap.put(feature, true); + } + } + + int i = 0; + for (Map.Entry entry : featureMap.entrySet()) { + featuresToSend[i] = entry.getKey(); + featureState[i] = entry.getValue(); + i++; + } + + boolean attentionEnabled = featureMap.get(BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION); + Slog.d(TAG, "Updating attention value for user: " + mUserId + + " to value: " + attentionEnabled); + Settings.Secure.putIntForUser(getContext().getContentResolver(), + Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, + attentionEnabled ? 1 : 0, mUserId); + try { + getListener().onFeatureGet(success, featuresToSend, featureState); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + + mCallback.onClientFinished(this, true /* success */); + } + + private @NonNull HashMap getFeatureMap() { + HashMap featureMap = new HashMap<>(); + featureMap.put(BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, false); + return featureMap; + } + + private Integer convertAidlToFrameworkFeature(byte feature) { + switch (feature) { + case Feature.REQUIRE_ATTENTION: + return new Integer(BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION); + default: + return null; + } + } + + @Override + public void onError(int errorCode, int vendorCode) { + try { + getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + + mCallback.onClientFinished(this, false /* success */); + } + +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index b8bac402f4301..23be50e5925ae 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -435,13 +435,36 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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. + mHandler.post(() -> { + final List faces = FaceUtils.getInstance(sensorId) + .getBiometricsForUser(mContext, userId); + if (faces.isEmpty()) { + Slog.w(getTag(), "Ignoring setFeature, no templates enrolled for user: " + userId); + return; + } + final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + new ClientMonitorCallbackConverter(receiver), userId, + mContext.getOpPackageName(), sensorId, feature, enabled, hardwareAuthToken); + scheduleForSensor(sensorId, client); + }); } @Override public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) { - // TODO(b/171335732): implement this. + mHandler.post(() -> { + final List faces = FaceUtils.getInstance(sensorId) + .getBiometricsForUser(mContext, userId); + if (faces.isEmpty()) { + Slog.w(getTag(), "Ignoring getFeature, no templates enrolled for user: " + userId); + return; + } + final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, + mSensors.get(sensorId).getLazySession(), token, callback, userId, + mContext.getOpPackageName(), sensorId); + scheduleForSensor(sensorId, client); + }); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java new file mode 100644 index 0000000000000..c3abfc2ddd0bf --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 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.content.Context; +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.face.Feature; +import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.HalClientMonitor; + +/** + * Face-specific get feature client for the {@link IFace} AIDL HAL interface. + */ +public class FaceSetFeatureClient extends HalClientMonitor implements ErrorConsumer { + + private static final String TAG = "FaceSetFeatureClient"; + + private final int mFeature; + private final boolean mEnabled; + private final HardwareAuthToken mHardwareAuthToken; + + FaceSetFeatureClient(@NonNull Context context, @NonNull LazyDaemon lazyDaemon, + @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, + @NonNull String owner, int sensorId, int feature, boolean enabled, + byte[] hardwareAuthToken) { + super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, + BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN); + mFeature = feature; + mEnabled = enabled; + mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken); + } + + @Override + public void unableToStart() { + try { + getListener().onFeatureSet(false /* success */, mFeature); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to send error", e); + } + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override + protected void startHalOperation() { + try { + getFreshDaemon() + .setFeature(mHardwareAuthToken, + convertFrameworkToAidlFeature(mFeature), mEnabled); + } catch (RemoteException | IllegalArgumentException e) { + Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e); + mCallback.onClientFinished(this, false /* success */); + } + } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_SET_FEATURE; + } + + public void onFeatureSet(boolean success) { + try { + getListener().onFeatureSet(success, mFeature); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + + mCallback.onClientFinished(this, true /* success */); + } + + private byte convertFrameworkToAidlFeature(int feature) throws IllegalArgumentException { + switch (feature) { + case BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION: + return Feature.REQUIRE_ATTENTION; + default: + Slog.e(TAG, "Unsupported feature : " + feature); + throw new IllegalArgumentException(); + } + } + + @Override + public void onError(int errorCode, int vendorCode) { + try { + getListener().onFeatureSet(false /* success */, mFeature); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + + mCallback.onClientFinished(this, false /* success */); + } + +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 4dcaf52b4b5c4..c560f149d1f16 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -376,12 +376,32 @@ public class Sensor { @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 diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java index e8668ed1b6c57..f8067670f61f7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java @@ -94,7 +94,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { } @Override - public void onFeatureGet(boolean success, int feature, boolean value) { + public void onFeatureGet(boolean success, int[] features, boolean[] featureState) { } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java index b1083d410fec9..7821601c84334 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java @@ -58,7 +58,7 @@ public class FaceGetFeatureClient extends HalClientMonitor { public void unableToStart() { try { if (getListener() != null) { - getListener().onFeatureGet(false /* success */, mFeature, false /* value */); + getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]); } } catch (RemoteException e) { Slog.e(TAG, "Unable to send error", e); @@ -75,9 +75,14 @@ public class FaceGetFeatureClient extends HalClientMonitor { protected void startHalOperation() { try { final OptionalBool result = getFreshDaemon().getFeature(mFeature, mFaceId); + int[] features = new int[1]; + boolean[] featureState = new boolean[1]; + features[0] = mFeature; + featureState[0] = result.value; mValue = result.value; + if (getListener() != null) { - getListener().onFeatureGet(result.status == Status.OK, mFeature, mValue); + getListener().onFeatureGet(result.status == Status.OK, features, featureState); } mCallback.onClientFinished(this, true /* success */); } catch (RemoteException e) {