diff options
| author | 2024-02-22 01:04:06 +0000 | |
|---|---|---|
| committer | 2024-02-22 01:04:06 +0000 | |
| commit | 91ba7efbe3caafd99f298e895f4eea2d6ba858d5 (patch) | |
| tree | 4627891fa599a93cd69feedf2b05fc5e008b6064 | |
| parent | 090f31e3e2fb2201cb6593b770bfa34b17cc1c17 (diff) | |
| parent | fc2b6f3918bee422fc21d4daf20050aad7496449 (diff) | |
Merge "Adding biometric FRR Notification atom." into main
46 files changed, 1196 insertions, 87 deletions
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index 2b62b98529a9..2ba1d897fe34 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -19,6 +19,8 @@ package android.hardware.biometrics; import android.annotation.IntDef; import android.app.KeyguardManager; import android.hardware.biometrics.BiometricManager.Authenticators; +import android.hardware.face.FaceEnrollOptions; +import android.hardware.face.FaceEnrollOptions.EnrollReason; import android.hardware.face.FaceManager; import java.lang.annotation.Retention; @@ -432,4 +434,22 @@ public interface BiometricFaceConstants { * vendor code. */ int FACE_ACQUIRED_VENDOR_BASE = 1000; + + + /** + * Converts FaceEnrollOptions.reason into BiometricsProtoEnums.enrollReason + */ + public static int reasonToMetric(@EnrollReason int reason) { + switch (reason) { + case FaceEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_FRR_NOTIFICATION; + case FaceEnrollOptions.ENROLL_REASON_SETTINGS: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_SETTINGS; + case FaceEnrollOptions.ENROLL_REASON_SUW: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW; + default: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_UNKNOWN; + } + + } } diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index 5b24fb6860a2..770448bd594b 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -20,6 +20,8 @@ import android.annotation.IntDef; import android.app.KeyguardManager; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.biometrics.BiometricManager.Authenticators; +import android.hardware.fingerprint.FingerprintEnrollOptions; +import android.hardware.fingerprint.FingerprintEnrollOptions.EnrollReason; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; @@ -350,4 +352,22 @@ public interface BiometricFingerprintConstants { return false; } } + + + /** + * Converts FaceEnrollOptions.reason into BiometricsProtoEnums.enrollReason + */ + static int reasonToMetric(@EnrollReason int reason) { + switch(reason) { + case FingerprintEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_FRR_NOTIFICATION; + case FingerprintEnrollOptions.ENROLL_REASON_SETTINGS: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_SETTINGS; + case FingerprintEnrollOptions.ENROLL_REASON_SUW: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW; + default: + return BiometricsProtoEnums.ENROLLMENT_SOURCE_UNKNOWN; + } + + } } diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index d7d1d1a7c677..fdd8b04921f5 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -100,6 +100,13 @@ public class BiometricManager { Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG; /** + * Enroll reason extra that can be used by settings to understand where this request came + * from. + * @hide + */ + public static final String EXTRA_ENROLL_REASON = "enroll_reason"; + + /** * @hide */ @IntDef({BIOMETRIC_SUCCESS, diff --git a/core/java/android/hardware/face/FaceEnrollOptions.aidl b/core/java/android/hardware/face/FaceEnrollOptions.aidl new file mode 100644 index 000000000000..336de9db899c --- /dev/null +++ b/core/java/android/hardware/face/FaceEnrollOptions.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 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 android.hardware.face; + +parcelable FaceEnrollOptions; diff --git a/core/java/android/hardware/face/FaceEnrollOptions.java b/core/java/android/hardware/face/FaceEnrollOptions.java new file mode 100644 index 000000000000..440105584f74 --- /dev/null +++ b/core/java/android/hardware/face/FaceEnrollOptions.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2024 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 android.hardware.face; + +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * Additional options when requesting Face enrollment. + * + * @hide + */ +@DataClass( + genParcelable = true, + genAidl = true, + genBuilder = true, + genSetters = true, + genEqualsHashCode = true +) +public class FaceEnrollOptions implements Parcelable { + public static final int ENROLL_REASON_UNKNOWN = 0; + public static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION = 1; + public static final int ENROLL_REASON_SETTINGS = 2; + public static final int ENROLL_REASON_SUW = 3; + + /** + * The reason for enrollment. + */ + @EnrollReason + private final int mEnrollReason; + private static int defaultEnrollReason() { + return ENROLL_REASON_UNKNOWN; + } + + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/face/FaceEnrollOptions.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @android.annotation.IntDef(prefix = "ENROLL_REASON_", value = { + ENROLL_REASON_UNKNOWN, + ENROLL_REASON_RE_ENROLL_NOTIFICATION, + ENROLL_REASON_SETTINGS, + ENROLL_REASON_SUW + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface EnrollReason {} + + @DataClass.Generated.Member + public static String enrollReasonToString(@EnrollReason int value) { + switch (value) { + case ENROLL_REASON_UNKNOWN: + return "ENROLL_REASON_UNKNOWN"; + case ENROLL_REASON_RE_ENROLL_NOTIFICATION: + return "ENROLL_REASON_RE_ENROLL_NOTIFICATION"; + case ENROLL_REASON_SETTINGS: + return "ENROLL_REASON_SETTINGS"; + case ENROLL_REASON_SUW: + return "ENROLL_REASON_SUW"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + /* package-private */ FaceEnrollOptions( + @EnrollReason int enrollReason) { + this.mEnrollReason = enrollReason; + + if (!(mEnrollReason == ENROLL_REASON_UNKNOWN) + && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION) + && !(mEnrollReason == ENROLL_REASON_SETTINGS) + && !(mEnrollReason == ENROLL_REASON_SUW)) { + throw new java.lang.IllegalArgumentException( + "enrollReason was " + mEnrollReason + " but must be one of: " + + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), " + + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), " + + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), " + + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The reason for enrollment. + */ + @DataClass.Generated.Member + public @EnrollReason int getEnrollReason() { + return mEnrollReason; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(FaceEnrollOptions other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + FaceEnrollOptions that = (FaceEnrollOptions) o; + //noinspection PointlessBooleanExpression + return true + && mEnrollReason == that.mEnrollReason; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mEnrollReason; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mEnrollReason); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected FaceEnrollOptions(@android.annotation.NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int enrollReason = in.readInt(); + + this.mEnrollReason = enrollReason; + + if (!(mEnrollReason == ENROLL_REASON_UNKNOWN) + && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION) + && !(mEnrollReason == ENROLL_REASON_SETTINGS) + && !(mEnrollReason == ENROLL_REASON_SUW)) { + throw new java.lang.IllegalArgumentException( + "enrollReason was " + mEnrollReason + " but must be one of: " + + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), " + + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), " + + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), " + + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @android.annotation.NonNull Parcelable.Creator<FaceEnrollOptions> CREATOR + = new Parcelable.Creator<FaceEnrollOptions>() { + @Override + public FaceEnrollOptions[] newArray(int size) { + return new FaceEnrollOptions[size]; + } + + @Override + public FaceEnrollOptions createFromParcel(@android.annotation.NonNull android.os.Parcel in) { + return new FaceEnrollOptions(in); + } + }; + + /** + * A builder for {@link FaceEnrollOptions} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static class Builder { + + private @EnrollReason int mEnrollReason; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * The reason for enrollment. + */ + @DataClass.Generated.Member + public @android.annotation.NonNull Builder setEnrollReason(@EnrollReason int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mEnrollReason = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @android.annotation.NonNull FaceEnrollOptions build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEnrollReason = defaultEnrollReason(); + } + FaceEnrollOptions o = new FaceEnrollOptions( + mEnrollReason); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x2) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1707949032303L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/hardware/face/FaceEnrollOptions.java", + inputSignatures = "public static final int ENROLL_REASON_UNKNOWN\npublic static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION\npublic static final int ENROLL_REASON_SETTINGS\npublic static final int ENROLL_REASON_SUW\nprivate final @android.hardware.face.FaceEnrollOptions.EnrollReason int mEnrollReason\nprivate static int defaultEnrollReason()\nclass FaceEnrollOptions extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index bae5e7f83569..066c45f03ec4 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -393,7 +393,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel, EnrollmentCallback callback, int[] disabledFeatures) { enroll(userId, hardwareAuthToken, cancel, callback, disabledFeatures, - null /* previewSurface */, false /* debugConsent */); + null /* previewSurface */, false /* debugConsent */, + (new FaceEnrollOptions.Builder()).build()); + } /** @@ -418,7 +420,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan @RequiresPermission(MANAGE_BIOMETRIC) public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel, EnrollmentCallback callback, int[] disabledFeatures, @Nullable Surface previewSurface, - boolean debugConsent) { + boolean debugConsent, FaceEnrollOptions options) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); } @@ -449,7 +451,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan Trace.beginSection("FaceManager#enroll"); final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver, mContext.getOpPackageName(), disabledFeatures, - previewSurface, debugConsent); + previewSurface, debugConsent, options); if (cancel != null) { cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 8e234fa11866..b98c0cb41ac9 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -26,6 +26,7 @@ import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticateOptions; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.FaceSensorConfigurations; import android.view.Surface; @@ -100,7 +101,7 @@ interface IFaceService { @EnforcePermission("MANAGE_BIOMETRIC") long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, String opPackageName, in int [] disabledFeatures, - in Surface previewSurface, boolean debugConsent); + in Surface previewSurface, boolean debugConsent, in FaceEnrollOptions options); // Start remote face enrollment @EnforcePermission("MANAGE_BIOMETRIC") diff --git a/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.aidl b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.aidl new file mode 100644 index 000000000000..77803f3e3a9b --- /dev/null +++ b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 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 android.hardware.fingerprint; + +parcelable FingerprintEnrollOptions;
\ No newline at end of file diff --git a/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java new file mode 100644 index 000000000000..9f9f322a8876 --- /dev/null +++ b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2024 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 android.hardware.fingerprint; + +import android.os.Parcelable; + + +import com.android.internal.util.DataClass; + +/** + * Additional options when requesting Fingerprint enrollment. + * + * @hide + */ +@DataClass( + genParcelable = true, + genAidl = true, + genBuilder = true, + genSetters = true, + genEqualsHashCode = true +) +public class FingerprintEnrollOptions implements Parcelable { + public static final int ENROLL_REASON_UNKNOWN = 0; + public static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION = 1; + public static final int ENROLL_REASON_SETTINGS = 2; + public static final int ENROLL_REASON_SUW = 3; + + /** + * The reason for enrollment. + */ + @EnrollReason + private final int mEnrollReason; + + private static int defaultEnrollReason() { + return ENROLL_REASON_UNKNOWN; + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @android.annotation.IntDef(prefix = "ENROLL_REASON_", value = { + ENROLL_REASON_UNKNOWN, + ENROLL_REASON_RE_ENROLL_NOTIFICATION, + ENROLL_REASON_SETTINGS, + ENROLL_REASON_SUW + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface EnrollReason {} + + @DataClass.Generated.Member + public static String enrollReasonToString(@EnrollReason int value) { + switch (value) { + case ENROLL_REASON_UNKNOWN: + return "ENROLL_REASON_UNKNOWN"; + case ENROLL_REASON_RE_ENROLL_NOTIFICATION: + return "ENROLL_REASON_RE_ENROLL_NOTIFICATION"; + case ENROLL_REASON_SETTINGS: + return "ENROLL_REASON_SETTINGS"; + case ENROLL_REASON_SUW: + return "ENROLL_REASON_SUW"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + /* package-private */ FingerprintEnrollOptions( + @EnrollReason int enrollReason) { + this.mEnrollReason = enrollReason; + + if (!(mEnrollReason == ENROLL_REASON_UNKNOWN) + && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION) + && !(mEnrollReason == ENROLL_REASON_SETTINGS) + && !(mEnrollReason == ENROLL_REASON_SUW)) { + throw new java.lang.IllegalArgumentException( + "enrollReason was " + mEnrollReason + " but must be one of: " + + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), " + + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), " + + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), " + + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The reason for enrollment. + */ + @DataClass.Generated.Member + public @EnrollReason int getEnrollReason() { + return mEnrollReason; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(FingerprintEnrollOptions other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + FingerprintEnrollOptions that = (FingerprintEnrollOptions) o; + //noinspection PointlessBooleanExpression + return true + && mEnrollReason == that.mEnrollReason; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mEnrollReason; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mEnrollReason); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected FingerprintEnrollOptions(@android.annotation.NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int enrollReason = in.readInt(); + + this.mEnrollReason = enrollReason; + + if (!(mEnrollReason == ENROLL_REASON_UNKNOWN) + && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION) + && !(mEnrollReason == ENROLL_REASON_SETTINGS) + && !(mEnrollReason == ENROLL_REASON_SUW)) { + throw new java.lang.IllegalArgumentException( + "enrollReason was " + mEnrollReason + " but must be one of: " + + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), " + + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), " + + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), " + + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @android.annotation.NonNull Parcelable.Creator<FingerprintEnrollOptions> CREATOR + = new Parcelable.Creator<FingerprintEnrollOptions>() { + @Override + public FingerprintEnrollOptions[] newArray(int size) { + return new FingerprintEnrollOptions[size]; + } + + @Override + public FingerprintEnrollOptions createFromParcel(@android.annotation.NonNull android.os.Parcel in) { + return new FingerprintEnrollOptions(in); + } + }; + + /** + * A builder for {@link FingerprintEnrollOptions} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static class Builder { + + private @EnrollReason int mEnrollReason; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * The reason for enrollment. + */ + @DataClass.Generated.Member + public @android.annotation.NonNull Builder setEnrollReason(@EnrollReason int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mEnrollReason = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @android.annotation.NonNull FingerprintEnrollOptions build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEnrollReason = defaultEnrollReason(); + } + FingerprintEnrollOptions o = new FingerprintEnrollOptions( + mEnrollReason); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x2) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1704407629121L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java", + inputSignatures = "public static final int ENROLL_REASON_UNKNOWN\npublic static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION\npublic static final int ENROLL_REASON_SETTINGS\npublic static final int ENROLL_REASON_SUW\nprivate final @android.hardware.fingerprint.FingerprintEnrollOptions.EnrollReason int mEnrollReason\nprivate static int defaultEnrollReason()\nclass FingerprintEnrollOptions extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 5a38a34fde11..b0f69f56cba7 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -755,7 +755,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @RequiresPermission(MANAGE_FINGERPRINT) public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId, - EnrollmentCallback callback, @EnrollReason int enrollReason) { + EnrollmentCallback callback, @EnrollReason int enrollReason, + FingerprintEnrollOptions options) { if (userId == UserHandle.USER_CURRENT) { userId = getCurrentUserId(); } @@ -779,7 +780,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing try { mEnrollmentCallback = callback; final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId, - mServiceReceiver, mContext.getOpPackageName(), enrollReason); + mServiceReceiver, mContext.getOpPackageName(), enrollReason, options); if (cancel != null) { cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 606b171f36ba..8b37c24527d7 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -30,6 +30,7 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorConfigurations; import java.util.List; @@ -98,7 +99,7 @@ interface IFingerprintService { // Start fingerprint enrollment @EnforcePermission("MANAGE_FINGERPRINT") long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver, - String opPackageName, int enrollReason); + String opPackageName, int enrollReason, in FingerprintEnrollOptions options); // Cancel enrollment in progress @EnforcePermission("MANAGE_FINGERPRINT") diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index d816d0853ab6..34f5841aef72 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -194,13 +194,13 @@ public class FaceManagerTest { new CancellationSignal(), mEnrollmentCallback, null /* disabledFeatures */); verify(mService).enroll(eq(USER_ID), any(), any(), any(), anyString(), any(), any(), - anyBoolean()); + anyBoolean(), any()); mFaceManager.enroll(USER_ID, new byte[]{}, new CancellationSignal(), mEnrollmentCallback, null /* disabledFeatures */); verify(mService, atMost(1 /* maxNumberOfInvocations */)).enroll(eq(USER_ID), any(), any(), - any(), anyString(), any(), any(), anyBoolean()); + any(), anyString(), any(), any(), anyBoolean(), any()); verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_HW_UNAVAILABLE), anyString()); } @@ -213,7 +213,7 @@ public class FaceManagerTest { verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_UNABLE_TO_PROCESS), anyString()); verify(mService, never()).enroll(eq(USER_ID), any(), any(), - any(), anyString(), any(), any(), anyBoolean()); + any(), anyString(), any(), any(), anyBoolean(), any()); } @Test diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java index 70313b8c9ea7..ce7d6a95c2f4 100644 --- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java +++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java @@ -162,11 +162,13 @@ public class FingerprintManagerTest { mCaptor.getValue().onAllAuthenticatorsRegistered(mProps); mFingerprintManager.enroll(null, new CancellationSignal(), USER_ID, - mEnrollCallback, FingerprintManager.ENROLL_ENROLL); + mEnrollCallback, FingerprintManager.ENROLL_ENROLL, + (new FingerprintEnrollOptions.Builder()).build()); verify(mEnrollCallback).onEnrollmentError(eq(FINGERPRINT_ERROR_UNABLE_TO_PROCESS), anyString()); - verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt()); + verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt(), + any()); } @Test diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java index 5d609bca334c..526264d67318 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java @@ -153,7 +153,6 @@ public class AuthenticationStatsCollector { if (authenticationStats.getTotalAttempts() < MINIMUM_ATTEMPTS) { return; } - // Don't send notification if FRR below the threshold. if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS || authenticationStats.getFrr() < mThreshold) { @@ -161,6 +160,7 @@ public class AuthenticationStatsCollector { return; } + authenticationStats.resetData(); final boolean hasEnrolledFace = hasEnrolledFace(userId); @@ -186,6 +186,28 @@ public class AuthenticationStatsCollector { } } + /** + * This is meant for debug purposes only, this will bypass many checks. + * The origination of this call should be from an adb shell command sent from + * FaceService. + * + * adb shell cmd face notification + */ + public void sendFaceReEnrollNotification() { + mBiometricNotification.sendFaceEnrollNotification(mContext); + } + + /** + * This is meant for debug purposes only, this will bypass many checks. + * The origination of this call should be from an adb shell command sent from + * FingerprintService. + * + * adb shell cmd fingerprint notification + */ + public void sendFingerprintReEnrollNotification() { + mBiometricNotification.sendFpEnrollNotification(mContext); + } + private void onUserRemoved(final int userId) { mUserAuthenticationStatsMap.remove(userId); mAuthenticationStatsPersister.removeFrrStats(userId); diff --git a/services/core/java/com/android/server/biometrics/BiometricNotificationLogger.java b/services/core/java/com/android/server/biometrics/BiometricNotificationLogger.java new file mode 100644 index 000000000000..51a2b1ac2dfc --- /dev/null +++ b/services/core/java/com/android/server/biometrics/BiometricNotificationLogger.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 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; + +import android.hardware.biometrics.BiometricsProtoEnums; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.sensors.BiometricNotificationUtils; +import com.android.server.biometrics.log.BiometricFrameworkStatsLogger; + +/** + * A class that logs metric info related to the posting/dismissal of Biometric FRR notifications. + */ +public class BiometricNotificationLogger extends NotificationListenerService { + private static final String TAG = "FRRNotificationListener"; + private BiometricFrameworkStatsLogger mLogger; + + BiometricNotificationLogger() { + this(BiometricFrameworkStatsLogger.getInstance()); + } + + @VisibleForTesting + BiometricNotificationLogger(BiometricFrameworkStatsLogger logger) { + mLogger = logger; + } + + @Override + public void onNotificationPosted(StatusBarNotification sbn, RankingMap map) { + if (sbn == null || sbn.getTag() == null) { + return; + } + switch (sbn.getTag()) { + case BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG: + case BiometricNotificationUtils.FINGERPRINT_ENROLL_NOTIFICATION_TAG: + final int modality = + sbn.getTag() == BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG + ? BiometricsProtoEnums.MODALITY_FACE + : BiometricsProtoEnums.MODALITY_FINGERPRINT; + Slog.d(TAG, "onNotificationPosted, tag=(" + sbn.getTag() + ")"); + mLogger.logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_SHOWN, + modality + ); + break; + default: + break; + } + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, + int reason) { + if (sbn == null || sbn.getTag() == null) { + return; + } + switch (sbn.getTag()) { + case BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG: + case BiometricNotificationUtils.FINGERPRINT_ENROLL_NOTIFICATION_TAG: + Slog.d(TAG, "onNotificationRemoved, tag=(" + + sbn.getTag() + "), reason=(" + reason + ")"); + final int modality = + sbn.getTag() == BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG + ? BiometricsProtoEnums.MODALITY_FACE + : BiometricsProtoEnums.MODALITY_FINGERPRINT; + switch (reason) { + // REASON_CLICK = 1 + case NotificationListenerService.REASON_CLICK: + mLogger.logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_CLICKED, + modality); + break; + // REASON_CANCEL = 2 + case NotificationListenerService.REASON_CANCEL: + mLogger.logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_DISMISSED, + modality); + break; + default: + Slog.d(TAG, "unhandled reason, ignoring logging"); + break; + } + break; + default: + break; + } + } +} diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index fc948da260e6..894b4d5d0b36 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -32,6 +32,7 @@ import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; import android.content.ContentResolver; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; @@ -143,6 +144,8 @@ public class BiometricService extends SystemService { private final BiometricCameraManager mBiometricCameraManager; + private final BiometricNotificationLogger mBiometricNotificationLogger; + /** * Tracks authenticatorId invalidation. For more details, see * {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}. @@ -1100,6 +1103,10 @@ public class BiometricService extends SystemService { return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class), context.getSystemService(SensorPrivacyManager.class)); } + + public BiometricNotificationLogger getNotificationLogger() { + return new BiometricNotificationLogger(); + } } /** @@ -1133,6 +1140,7 @@ public class BiometricService extends SystemService { mBiometricCameraManager = injector.getBiometricCameraManager(context); mKeystoreAuthorization = injector.getKeystoreAuthorizationService(); mGateKeeper = injector.getGateKeeperService(); + mBiometricNotificationLogger = injector.getNotificationLogger(); try { injector.getActivityManagerService().registerUserSwitchObserver( @@ -1157,6 +1165,20 @@ public class BiometricService extends SystemService { mInjector.publishBinderService(this, mImpl); mBiometricStrengthController = mInjector.getBiometricStrengthController(this); mBiometricStrengthController.startListening(); + + mHandler.post(new Runnable(){ + @Override + public void run() { + try { + mBiometricNotificationLogger.registerAsSystemService(getContext(), + new ComponentName(getContext(), BiometricNotificationLogger.class), + UserHandle.USER_ALL); + } catch (RemoteException e) { + // Intra-process call, should never happen. + } + } + + }); } private boolean isStrongBiometric(int id) { @@ -1508,4 +1530,5 @@ public class BiometricService extends SystemService { pw.println("CurrentSession: " + mAuthSession); pw.println(); } + } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java index 6bd48802baf5..f31b2e11b021 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -113,14 +113,16 @@ public class BiometricFrameworkStatsLogger { /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ public void enroll(int statsModality, int statsAction, int statsClient, - int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) { + int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux, + int source) { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, statsModality, targetUserId, sanitizeLatency(latency), enrollSuccessful, -1, /* sensorId */ - ambientLightLux); + ambientLightLux, + source); } /** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */ @@ -239,6 +241,12 @@ public class BiometricFrameworkStatsLogger { -1 /* sensorId */); } + /** {@see FrameworkStatsLog.BIOMETRIC_FRR_NOTIFICATION}. */ + public void logFrameworkNotification(int action, int modality) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_FRR_NOTIFICATION, + action, modality); + } + private long sanitizeLatency(long latency) { if (latency < 0) { Slog.w(TAG, "found a negative latency : " + latency); diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java index dbef7178efd0..cd5d0c8dc3f2 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -252,7 +252,8 @@ public class BiometricLogger { } /** Log enrollment outcome. */ - public void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) { + public void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful, + int source) { if (!mShouldLogMetrics) { return; } @@ -273,7 +274,7 @@ public class BiometricLogger { } mSink.enroll(mStatsModality, mStatsAction, mStatsClient, - targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux()); + targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux(), source); } /** Report unexpected enrollment reported by the HAL. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java index 2aec9aefa8d1..0e22f7511af9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java @@ -23,6 +23,9 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.hardware.biometrics.BiometricManager; +import android.hardware.face.FaceEnrollOptions; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.os.SystemClock; import android.os.UserHandle; import android.util.Slog; @@ -36,25 +39,28 @@ public class BiometricNotificationUtils { private static final String TAG = "BiometricNotificationUtils"; private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll"; - private static final String FACE_ENROLL_NOTIFICATION_TAG = "FaceEnroll"; - private static final String FINGERPRINT_ENROLL_NOTIFICATION_TAG = "FingerprintEnroll"; private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration"; private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock"; private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS"; - private static final String FINGERPRINT_SETTINGS_ACTION = - "android.settings.FINGERPRINT_SETTINGS"; private static final String FACE_ENROLL_ACTION = "android.settings.FACE_ENROLL"; private static final String FINGERPRINT_ENROLL_ACTION = "android.settings.FINGERPRINT_ENROLL"; + private static final String FINGERPRINT_SETTINGS_ACTION = + "android.settings.FINGERPRINT_SETTINGS"; private static final String SETTINGS_PACKAGE = "com.android.settings"; private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel"; private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel"; private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel"; private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL = "FingerprintBadCalibrationNotificationChannel"; - private static final int NOTIFICATION_ID = 1; private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000; private static long sLastAlertTime = 0; + private static final String ACTION_BIOMETRIC_FRR_DISMISS = "action_biometric_frr_dismiss"; + // Dismissal action for FRR notification. + private static final Intent DISMISS_FRR_INTENT = new Intent(ACTION_BIOMETRIC_FRR_DISMISS); + public static final int NOTIFICATION_ID = 1; + public static final String FACE_ENROLL_NOTIFICATION_TAG = "FaceEnroll"; + public static final String FINGERPRINT_ENROLL_NOTIFICATION_TAG = "FingerprintEnroll"; /** * Shows a face re-enrollment notification. */ @@ -71,7 +77,6 @@ public class BiometricNotificationUtils { final Intent intent = new Intent(FACE_SETTINGS_ACTION); intent.setPackage(SETTINGS_PACKAGE); - intent.putExtra(KEY_RE_ENROLL_FACE, true); final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */, @@ -79,13 +84,14 @@ public class BiometricNotificationUtils { showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL, Notification.CATEGORY_SYSTEM, FACE_RE_ENROLL_NOTIFICATION_TAG, - Notification.VISIBILITY_SECRET); + Notification.VISIBILITY_SECRET, false); } /** * Shows a face enrollment notification. */ public static void showFaceEnrollNotification(@NonNull Context context) { + Slog.d(TAG, "Showing Face Enroll Notification"); final String name = context.getString(R.string.device_unlock_notification_name); @@ -96,6 +102,8 @@ public class BiometricNotificationUtils { final Intent intent = new Intent(FACE_ENROLL_ACTION); intent.setPackage(SETTINGS_PACKAGE); + intent.putExtra(BiometricManager.EXTRA_ENROLL_REASON, + FaceEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION); final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */, @@ -103,14 +111,15 @@ public class BiometricNotificationUtils { showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL, Notification.CATEGORY_RECOMMENDATION, FACE_ENROLL_NOTIFICATION_TAG, - Notification.VISIBILITY_PUBLIC); + Notification.VISIBILITY_PUBLIC, true); + } /** * Shows a fingerprint enrollment notification. */ public static void showFingerprintEnrollNotification(@NonNull Context context) { - + Slog.d(TAG, "Showing Fingerprint Enroll Notification"); final String name = context.getString(R.string.device_unlock_notification_name); final String title = @@ -120,6 +129,8 @@ public class BiometricNotificationUtils { final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION); intent.setPackage(SETTINGS_PACKAGE); + intent.putExtra(BiometricManager.EXTRA_ENROLL_REASON, + FingerprintEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION); final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */, @@ -127,7 +138,8 @@ public class BiometricNotificationUtils { showNotificationHelper(context, name, title, content, pendingIntent, Notification.CATEGORY_RECOMMENDATION, FINGERPRINT_ENROLL_CHANNEL, - FINGERPRINT_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC); + FINGERPRINT_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC, true); + } /** @@ -162,17 +174,22 @@ public class BiometricNotificationUtils { showNotificationHelper(context, name, title, content, pendingIntent, Notification.CATEGORY_SYSTEM, FINGERPRINT_BAD_CALIBRATION_CHANNEL, - BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET); + BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false); } private static void showNotificationHelper(Context context, String name, String title, String content, PendingIntent pendingIntent, String category, - String channelName, String notificationTag, int visibility) { + String channelName, String notificationTag, int visibility, + boolean listenToDismissEvent) { + Slog.v(TAG," listenToDismissEvent = " + listenToDismissEvent); + final PendingIntent dismissIntent = PendingIntent.getActivityAsUser(context, + 0 /* requestCode */, DISMISS_FRR_INTENT, PendingIntent.FLAG_IMMUTABLE /* flags */, + null /* options */, UserHandle.CURRENT); final NotificationManager notificationManager = context.getSystemService(NotificationManager.class); final NotificationChannel channel = new NotificationChannel(channelName, name, NotificationManager.IMPORTANCE_HIGH); - final Notification notification = new Notification.Builder(context, channelName) + final Notification.Builder builder = new Notification.Builder(context, channelName) .setSmallIcon(R.drawable.ic_lock) .setContentTitle(title) .setContentText(content) @@ -183,12 +200,17 @@ public class BiometricNotificationUtils { .setAutoCancel(true) .setCategory(category) .setContentIntent(pendingIntent) - .setVisibility(visibility) - .build(); + .setVisibility(visibility); + + if (listenToDismissEvent) { + builder.setDeleteIntent(dismissIntent); + } + final Notification notification = builder.build(); notificationManager.createNotificationChannel(channel); notificationManager.notifyAsUser(notificationTag, NOTIFICATION_ID, notification, UserHandle.CURRENT); + } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 8e7004d8fde5..af6de5c7316a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -45,6 +45,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En private long mEnrollmentStartTimeMs; private final boolean mHasEnrollmentsBeforeStarting; + private final int mEnrollReason; /** * @return true if the user has already enrolled the maximum number of templates. @@ -55,13 +56,15 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils, int timeoutSec, int sensorId, boolean shouldVibrate, - @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + int enrollReason) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, shouldVibrate, logger, biometricContext); mBiometricUtils = utils; mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length); mTimeoutSec = timeoutSec; mHasEnrollmentsBeforeStarting = hasEnrollments(); + mEnrollReason = enrollReason; } @Override @@ -91,7 +94,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier); getLogger().logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs, - true /* enrollSuccessful */); + true /* enrollSuccessful */, mEnrollReason); mCallback.onClientFinished(this, true /* success */); } notifyUserActivity(); @@ -119,7 +122,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En public void onError(int error, int vendorCode) { getLogger().logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs, - false /* enrollSuccessful */); + false /* enrollSuccessful */, mEnrollReason); super.onError(error, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 321e951ec09b..68b4e3fb51ba 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -37,6 +37,7 @@ import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.SensorProps; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticateOptions; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.FaceSensorConfigurations; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.FaceServiceReceiver; @@ -44,6 +45,7 @@ import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.NativeHandle; import android.os.RemoteException; @@ -210,7 +212,8 @@ public class FaceService extends SystemService { @Override // Binder call public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, - final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) { + final int[] disabledFeatures, Surface previewSurface, boolean debugConsent, + FaceEnrollOptions options) { super.enroll_enforcePermission(); final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); @@ -220,7 +223,8 @@ public class FaceService extends SystemService { } return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, - receiver, opPackageName, disabledFeatures, previewSurface, debugConsent); + receiver, opPackageName, disabledFeatures, previewSurface, debugConsent, + options); } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @@ -958,4 +962,25 @@ public class FaceService extends SystemService { } } } + + /** + * This should only be called from FaceShellCommand class. + */ + void sendFaceReEnrollNotification() { + Utils.checkPermissionOrShell(getContext(), MANAGE_FACE); + if (Build.IS_DEBUGGABLE) { + final long identity = Binder.clearCallingIdentity(); + try { + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); + if (provider != null) { + FaceProvider faceProvider = (FaceProvider) provider.second; + faceProvider.sendFaceReEnrollNotification(); + } else { + Slog.w(TAG, "Null provider for notification"); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java index 187575dcd664..e167bbafc02e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java @@ -42,6 +42,8 @@ public class FaceShellCommand extends ShellCommand { return doHelp(); case "sync": return doSync(); + case "notification": + return doNotify(); default: getOutPrintWriter().println("Unrecognized command: " + cmd); } @@ -59,6 +61,8 @@ public class FaceShellCommand extends ShellCommand { pw.println(" Print this help text."); pw.println(" sync"); pw.println(" Sync enrollments now (virtualized sensors only)."); + pw.println(" notification"); + pw.println(" Sends a Face re-enrollment notification"); } private int doHelp() { @@ -70,4 +74,9 @@ public class FaceShellCommand extends ShellCommand { mService.syncEnrollmentsNow(); return 0; } + + private int doNotify() { + mService.sendFaceReEnrollNotification(); + return 0; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index 2cf64b72d01f..6f76cdae4539 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticateOptions; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; @@ -79,7 +80,7 @@ public interface ServiceProvider extends BiometricServiceProvider<FaceSensorProp long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface, - boolean debugConsent); + boolean debugConsent, FaceEnrollOptions options); void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); 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 d11f0991a5d4..0fdd57d64d8d 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 @@ -26,6 +26,7 @@ import android.hardware.biometrics.face.EnrollmentFrame; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceEnrollFrame; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; import android.os.RemoteException; @@ -155,7 +156,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, mContext.getOpPackageName(), new int[0] /* disabledFeatures */, - null /* previewSurface */, false /* debugConsent */); + null /* previewSurface */, false /* debugConsent */, + (new FaceEnrollOptions.Builder()).build()); } @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index 5f370f23134c..781e3f491ec8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -93,9 +93,11 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @Nullable Surface previewSurface, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - int maxTemplatesPerUser, boolean debugConsent) { + int maxTemplatesPerUser, boolean debugConsent, + android.hardware.face.FaceEnrollOptions options) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils, - timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext); + timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext, + BiometricFaceConstants.reasonToMetric(options.getEnrollReason())); setRequestId(requestId); mEnrollIgnoreList = getContext().getResources() .getIntArray(R.array.config_face_acquire_enroll_ignorelist); @@ -105,6 +107,9 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { mDebugConsent = debugConsent; mDisabledFeatures = disabledFeatures; mPreviewSurface = previewSurface; + Slog.w(TAG, "EnrollOptions " + + android.hardware.face.FaceEnrollOptions.enrollReasonToString( + options.getEnrollReason())); } @Override 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 f469f6224e4c..007b7462f637 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 @@ -35,6 +35,7 @@ import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.SensorProps; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticateOptions; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; @@ -519,7 +520,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, - @Nullable Surface previewSurface, boolean debugConsent) { + @Nullable Surface previewSurface, boolean debugConsent, FaceEnrollOptions options) { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId); @@ -533,7 +534,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, maxTemplatesPerUser, debugConsent); + mBiometricContext, maxTemplatesPerUser, debugConsent, options); if (Flags.deHidl()) { scheduleForSensor(sensorId, client, mBiometricStateCallback); } else { @@ -903,4 +904,11 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public boolean getTestHalEnabled() { return mTestHalEnabled; } + + /** + * Sends a face re enroll notification. + */ + public void sendFaceReEnrollNotification() { + mAuthenticationStatsCollector.sendFaceReEnrollNotification(); + } } 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 151ffaa1cb28..0e2367a599a5 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 @@ -23,6 +23,7 @@ import android.hardware.biometrics.ITestSessionCallback; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceEnrollFrame; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; import android.os.RemoteException; @@ -143,7 +144,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, mContext.getOpPackageName(), new int[0] /* disabledFeatures */, - null /* previewSurface */, false /* debugConsent */); + null /* previewSurface */, false /* debugConsent */, + (new FaceEnrollOptions.Builder()).build()); } @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 48a676ce4937..306ddfa1e083 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -32,6 +32,7 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticateOptions; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; @@ -711,16 +712,17 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, - @Nullable Surface previewSurface, boolean debugConsent) { + @Nullable Surface previewSurface, boolean debugConsent, + @NonNull FaceEnrollOptions options) { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); if (Flags.deHidl()) { scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, - opPackageName, disabledFeatures, previewSurface, id); + opPackageName, disabledFeatures, previewSurface, id, options); } else { scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, - opPackageName, disabledFeatures, previewSurface, id); + opPackageName, disabledFeatures, previewSurface, id, options); } }); return id; @@ -729,7 +731,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private void scheduleEnrollAidl(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, - @Nullable Surface previewSurface, long id) { + @Nullable Surface previewSurface, long id, + @NonNull FaceEnrollOptions options) { final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client = new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient( mContext, this::getSession, token, @@ -742,7 +745,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mAuthenticationStatsCollector), mBiometricContext, mContext.getResources().getInteger( com.android.internal.R.integer.config_faceMaxTemplatesPerUser), - false); + false, options); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -770,14 +773,14 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private void scheduleEnrollHidl(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, - @Nullable Surface previewSurface, long id) { + @Nullable Surface previewSurface, long id, FaceEnrollOptions options) { final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext); + mBiometricContext, options); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java index 27b9c79516af..815cf9180130 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.Status; import android.hardware.face.Face; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; @@ -61,15 +62,20 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @Nullable Surface previewSurface, int sensorId, - @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull FaceEnrollOptions options) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, - timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext); + timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext, + BiometricFaceConstants.reasonToMetric(options.getEnrollReason())); setRequestId(requestId); mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); mEnrollIgnoreList = getContext().getResources() .getIntArray(R.array.config_face_acquire_enroll_ignorelist); mEnrollIgnoreListVendor = getContext().getResources() .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist); + + Slog.w(TAG, "EnrollOptions " + + FaceEnrollOptions.enrollReasonToString(options.getEnrollReason())); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index e01d672d7e34..1ba12134ab29 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -48,6 +48,7 @@ import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorConfigurations; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -239,7 +240,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, final int userId, final IFingerprintServiceReceiver receiver, - final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { + final String opPackageName, @FingerprintManager.EnrollReason int enrollReason, + FingerprintEnrollOptions options) { super.enroll_enforcePermission(); final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); @@ -249,7 +251,7 @@ public class FingerprintService extends SystemService { } return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, - receiver, opPackageName, enrollReason); + receiver, opPackageName, enrollReason, options); } @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT) @@ -1322,4 +1324,25 @@ public class FingerprintService extends SystemService { } } } + + /** + * This should only be called from FingerprintShellCommand + */ + void sendFingerprintReEnrollNotification() { + Utils.checkPermissionOrShell(getContext(), MANAGE_FINGERPRINT); + if (Build.IS_DEBUGGABLE) { + final long identity = Binder.clearCallingIdentity(); + try { + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); + if (provider != null) { + FingerprintProvider fingerprintProvider = (FingerprintProvider) provider.second; + fingerprintProvider.sendFingerprintReEnrollNotification(); + } else { + Slog.w(TAG, "Null provider for notification"); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java index dc6a63f82bb1..766b3a1f1fbf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java @@ -47,6 +47,8 @@ public class FingerprintShellCommand extends ShellCommand { return doSync(); case "fingerdown": return doSimulateVhalFingerDown(); + case "notification": + return doNotify(); default: getOutPrintWriter().println("Unrecognized command: " + cmd); } @@ -66,6 +68,8 @@ public class FingerprintShellCommand extends ShellCommand { pw.println(" Sync enrollments now (virtualized sensors only)."); pw.println(" fingerdown"); pw.println(" Simulate finger down event (virtualized sensors only)."); + pw.println(" notification"); + pw.println(" Sends a Fingerprint re-enrollment notification"); } private int doHelp() { @@ -82,4 +86,9 @@ public class FingerprintShellCommand extends ShellCommand { mService.simulateVhalFingerDown(); return 0; } + + private int doNotify() { + mService.sendFingerprintReEnrollNotification(); + return 0; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index fc37d7020a21..c2d11699b91f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -24,6 +24,7 @@ import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -74,7 +75,8 @@ public interface ServiceProvider extends */ long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, - @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason); + @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason, + @NonNull FingerprintEnrollOptions options); void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index ec1eeb138505..d64b6c29c840 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -16,13 +16,12 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; -import static android.Manifest.permission.TEST_BIOMETRIC; - import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.os.Binder; @@ -30,7 +29,6 @@ import android.os.RemoteException; import android.util.Slog; 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.BiometricStateCallback; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -153,7 +151,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { super.startEnroll_enforcePermission(); mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, - mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL); + mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL, + (new FingerprintEnrollOptions.Builder()).build()); } @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 79975e515c70..a24ab1d022c6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -29,6 +29,7 @@ import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.OperationState; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; @@ -98,11 +99,13 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, @NonNull AuthenticationStateListeners authenticationStateListeners, - int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { + int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason, + @NonNull FingerprintEnrollOptions options) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), - logger, biometricContext); + logger, biometricContext, + BiometricFingerprintConstants.reasonToMetric(options.getEnrollReason())); setRequestId(requestId); mSensorProps = sensorProps; if (sidefpsControllerRefactor()) { @@ -120,6 +123,8 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { getLogger().disableMetrics(); } + Slog.w(TAG, "EnrollOptions " + + FingerprintEnrollOptions.enrollReasonToString(options.getEnrollReason())); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index fd938ed9c389..a104cf4e1726 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -39,6 +39,7 @@ import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -515,7 +516,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, - @FingerprintManager.EnrollReason int enrollReason) { + @FingerprintManager.EnrollReason int enrollReason, + @NonNull FingerprintEnrollOptions options) { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { final int maxTemplatesPerUser = mFingerprintSensors.get(sensorId).getSensorProperties() @@ -529,7 +531,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, mFingerprintSensors.get(sensorId).getSensorProperties(), mUdfpsOverlayController, mSidefpsController, - mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason); + mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason, options); if (Flags.deHidl()) { scheduleForSensor(sensorId, client, mBiometricStateCallback); } else { @@ -1021,4 +1023,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi Slog.e(getTag(), "failed hal operation ", e); } } + + /** + * Sends a fingerprint enroll notification. + */ + public void sendFingerprintReEnrollNotification() { + mAuthenticationStatsCollector.sendFingerprintReEnrollNotification(); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java index c20a9eb958c4..fc037aeb7e17 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -16,20 +16,18 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; -import static android.Manifest.permission.TEST_BIOMETRIC; - import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.os.Binder; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -153,7 +151,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { super.startEnroll_enforcePermission(); mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, - mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL); + mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL, + (new FingerprintEnrollOptions.Builder()).build()); } @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 4accf8f7ff30..33e448bbe612 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -35,6 +35,7 @@ import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -700,17 +701,18 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, - @FingerprintManager.EnrollReason int enrollReason) { + @FingerprintManager.EnrollReason int enrollReason, + @NonNull FingerprintEnrollOptions options) { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); if (Flags.deHidl()) { scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, - opPackageName, enrollReason, id); + opPackageName, enrollReason, id, options); } else { scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, - opPackageName, enrollReason, id); + opPackageName, enrollReason, id, options); } }); return id; @@ -719,7 +721,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private void scheduleEnrollHidl(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, - @FingerprintManager.EnrollReason int enrollReason, long id) { + @FingerprintManager.EnrollReason int enrollReason, long id, + @NonNull FingerprintEnrollOptions options) { final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, @@ -730,7 +733,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mBiometricContext, mUdfpsOverlayController, // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR mSidefpsController, - mAuthenticationStateListeners, enrollReason); + mAuthenticationStateListeners, enrollReason, options); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -758,7 +761,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private void scheduleEnrollAidl(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, - @FingerprintManager.EnrollReason int enrollReason, long id) { + @FingerprintManager.EnrollReason int enrollReason, long id, + @NonNull FingerprintEnrollOptions options) { final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient client = new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient( @@ -778,8 +782,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mAuthenticationStateListeners, mContext.getResources().getInteger( com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser), - enrollReason); - + enrollReason, options); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index 26332ff6893c..8f937fc65cbb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -27,6 +27,7 @@ import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -75,10 +76,12 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR @Nullable ISidefpsController sidefpsController, @NonNull AuthenticationStateListeners authenticationStateListeners, - @FingerprintManager.EnrollReason int enrollReason) { + @FingerprintManager.EnrollReason int enrollReason, + @NonNull FingerprintEnrollOptions options) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger, - biometricContext); + biometricContext, + BiometricFingerprintConstants.reasonToMetric(options.getEnrollReason())); setRequestId(requestId); if (sidefpsControllerRefactor()) { mSensorOverlays = new SensorOverlays(udfpsOverlayController); @@ -91,6 +94,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { getLogger().disableMetrics(); } + Slog.w(TAG, "EnrollOptions " + + FingerprintEnrollOptions.enrollReasonToString(options.getEnrollReason())); } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricNotificationLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricNotificationLoggerTest.java new file mode 100644 index 000000000000..8319623eec0a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricNotificationLoggerTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 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; + +import android.hardware.biometrics.BiometricsProtoEnums; +import android.platform.test.annotations.Presubmit; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; + +import com.android.server.biometrics.log.BiometricFrameworkStatsLogger; +import com.android.server.biometrics.sensors.BiometricNotificationUtils; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class BiometricNotificationLoggerTest { + @Rule + public MockitoRule mockitorule = MockitoJUnit.rule(); + + @Mock + private BiometricFrameworkStatsLogger mLogger; + private BiometricNotificationLogger mNotificationLogger; + + @Before + public void setUp() { + mNotificationLogger = new BiometricNotificationLogger( + mLogger); + } + + @Test + public void testNotification_nullNotification_doesNothing() { + mNotificationLogger.onNotificationPosted(null, null); + + verify(mLogger, never()).logFrameworkNotification(anyInt(), anyInt()); + } + + @Test + public void testNotification_emptyStringTag_doesNothing() { + final StatusBarNotification noti = createNotificationWithNullTag(); + mNotificationLogger.onNotificationPosted(noti, null); + + verify(mLogger, never()).logFrameworkNotification(anyInt(), anyInt()); + } + + @Test + public void testFaceNotification_posted() { + final StatusBarNotification noti = createFaceNotification(); + mNotificationLogger.onNotificationPosted(noti, null); + + verify(mLogger).logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_SHOWN, + BiometricsProtoEnums.MODALITY_FACE); + } + + @Test + public void testFingerprintNotification_posted() { + final StatusBarNotification noti = createFingerprintNotification(); + mNotificationLogger.onNotificationPosted(noti, null); + + verify(mLogger).logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_SHOWN, + BiometricsProtoEnums.MODALITY_FINGERPRINT); + } + + @Test + public void testFaceNotification_clicked() { + final StatusBarNotification noti = createFaceNotification(); + mNotificationLogger.onNotificationRemoved(noti, null, + NotificationListenerService.REASON_CLICK); + + verify(mLogger).logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_CLICKED, + BiometricsProtoEnums.MODALITY_FACE); + } + + @Test + public void testFingerprintNotification_clicked() { + final StatusBarNotification noti = createFingerprintNotification(); + mNotificationLogger.onNotificationRemoved(noti, null, + NotificationListenerService.REASON_CLICK); + + verify(mLogger).logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_CLICKED, + BiometricsProtoEnums.MODALITY_FINGERPRINT); + } + + @Test + public void testFaceNotification_dismissed() { + final StatusBarNotification noti = createFaceNotification(); + mNotificationLogger.onNotificationRemoved(noti, null, + NotificationListenerService.REASON_CANCEL); + + verify(mLogger).logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_DISMISSED, + BiometricsProtoEnums.MODALITY_FACE); + } + + @Test + public void testFingerprintNotification_dismissed() { + final StatusBarNotification noti = createFingerprintNotification(); + mNotificationLogger.onNotificationRemoved(noti, null, + NotificationListenerService.REASON_CANCEL); + + verify(mLogger).logFrameworkNotification( + BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_DISMISSED, + BiometricsProtoEnums.MODALITY_FINGERPRINT); + } + + private StatusBarNotification createNotificationWithNullTag() { + final StatusBarNotification notification = mock(StatusBarNotification.class); + return notification; + } + + private StatusBarNotification createFaceNotification() { + final StatusBarNotification notification = mock(StatusBarNotification.class); + when(notification.getTag()) + .thenReturn(BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG); + return notification; + } + + private StatusBarNotification createFingerprintNotification() { + final StatusBarNotification notification = mock(StatusBarNotification.class); + when(notification.getTag()) + .thenReturn(BiometricNotificationUtils.FINGERPRINT_ENROLL_NOTIFICATION_TAG); + return notification; + } + +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 35ad55c0938e..49583ef5194b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -187,6 +187,9 @@ public class BiometricServiceTest { @Mock private IGateKeeperService mGateKeeperService; + @Mock + private BiometricNotificationLogger mNotificationLogger; + BiometricContextProvider mBiometricContextProvider; @Before @@ -242,6 +245,7 @@ public class BiometricServiceTest { when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider); when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService); when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService); + when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger); when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L); if (com.android.server.biometrics.Flags.deHidl()) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java index b69b55448bcd..238a9289c05b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java @@ -137,11 +137,11 @@ public class BiometricLoggerTest { final long latency = 44; final boolean enrollSuccessful = true; - mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful); + mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful, -1); verify(mSink).enroll( eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), - eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat()); + eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat(), anyInt()); } @Test @@ -192,7 +192,8 @@ public class BiometricLoggerTest { true/* isBiometricPrompt */); mLogger.logOnEnrolled(2 /* targetUserId */, 10 /* latency */, - true /* enrollSuccessful */); + true /* enrollSuccessful */, + 30 /* source */); mLogger.logOnError(mContext, mOpContext, 4 /* error */, 0 /* vendorCode */, @@ -205,7 +206,8 @@ public class BiometricLoggerTest { anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat()); verify(mSink, never()).enroll( - anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat()); + anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat(), + anyInt()); verify(mSink, never()).error(eq(mOpContext), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(), anyInt(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index f7480dea780b..981eba55a430 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -1139,7 +1139,7 @@ public class BiometricSchedulerTest { super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69], "test" /* owner */, mock(BiometricUtils.class), 5 /* timeoutSec */, TEST_SENSOR_ID, true /* shouldVibrate */, mock(BiometricLogger.class), - mock(BiometricContext.class)); + mock(BiometricContext.class), 0 /* enrollReason */); } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java index 43ed07a0bf76..02363cd66349 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java @@ -21,15 +21,20 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyByte; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.face.ISession; import android.hardware.face.Face; +import android.hardware.face.FaceEnrollOptions; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -68,6 +73,7 @@ public class FaceEnrollClientTest { private static final byte[] HAT = new byte[69]; private static final int USER_ID = 12; + private static final int ENROLL_SOURCE = FaceEnrollOptions.ENROLL_REASON_SUW; @Rule public final TestableContext mContext = new TestableContext( @@ -208,6 +214,16 @@ public class FaceEnrollClientTest { verify(mHal).enrollWithOptions(any()); } + @Test + public void testEnrollWithReasonLogsMetric() throws RemoteException { + final FaceEnrollClient client = createClient(4); + client.start(mCallback); + client.onEnrollResult(new Face("face", 1 /* faceId */, 20 /* deviceId */), 0); + + verify(mBiometricLogger).logOnEnrolled(anyInt(), anyLong(), anyBoolean(), + eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW)); + } + private FaceEnrollClient createClient() throws RemoteException { return createClient(200 /* version */); } @@ -221,6 +237,7 @@ public class FaceEnrollClientTest { mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */, null /* previewSurface */, 8 /* sensorId */, mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */, - true /* debugConsent */); + true /* debugConsent */, + (new FaceEnrollOptions.Builder()).setEnrollReason(ENROLL_SOURCE).build()); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java index 940fe69925b5..7a778d5dd211 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java @@ -34,6 +34,7 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.OptionalUint64; import android.hardware.biometrics.face.V1_0.Status; import android.hardware.face.Face; +import android.hardware.face.FaceEnrollOptions; import android.hardware.face.HidlFaceSensorConfig; import android.os.Handler; import android.os.RemoteException; @@ -201,7 +202,7 @@ public class HidlToAidlSensorAdapterTest { USER_ID, HAT, TAG, 1 /* requestId */, mBiometricUtils, new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */, SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */, - false /* debugConsent */)); + false /* debugConsent */, (new FaceEnrollOptions.Builder()).build())); mLooper.dispatchAll(); verify(mAidlResponseHandlerCallback).onEnrollSuccess(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index 3ee54f53d44f..916f6960efc9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -21,6 +21,7 @@ import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.inOrder; @@ -30,10 +31,12 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; @@ -78,6 +81,8 @@ import java.util.function.Consumer; @SmallTest public class FingerprintEnrollClientTest { + private static final int ENROLL_SOURCE = FingerprintEnrollOptions.ENROLL_REASON_SUW; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule public final CheckFlagsRule mCheckFlagsRule = @@ -353,6 +358,16 @@ public class FingerprintEnrollClientTest { c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0)); } + @Test + public void testEnrollWithReasonLogsMetric() throws RemoteException { + final FingerprintEnrollClient client = createClient(4); + client.start(mCallback); + client.onEnrollResult(new Fingerprint("fingerprint", 1 /* faceId */, 20 /* deviceId */), 0); + + verify(mBiometricLogger).logOnEnrolled(anyInt(), anyLong(), anyBoolean(), + eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW)); + } + private void showHideOverlay_sidefpsControllerRemovalRefactor( Consumer<FingerprintEnrollClient> block) throws RemoteException { mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); @@ -382,6 +397,8 @@ public class FingerprintEnrollClientTest { HAT, "owner", mBiometricUtils, 8 /* sensorId */, mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController, mSideFpsController, mAuthenticationStateListeners, 6 /* maxTemplatesPerUser */, - FingerprintManager.ENROLL_ENROLL); + FingerprintManager.ENROLL_ENROLL, (new FingerprintEnrollOptions.Builder()) + .setEnrollReason(ENROLL_SOURCE).build() + ); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java index cbbc54547bf6..6c3bfe80510e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java @@ -34,6 +34,7 @@ import android.app.AlarmManager; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.HidlFingerprintSensorConfig; import android.os.Handler; import android.os.HandlerThread; @@ -217,7 +218,8 @@ public class HidlToAidlSensorAdapterTest { 1 /* requestId */, null /* listener */, USER_ID, HAT, TAG, mBiometricUtils, SENSOR_ID, mLogger, mBiometricContext, mHidlToAidlSensorAdapter.getSensorProperties(), null, null, - mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL)); + mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL, + (new FingerprintEnrollOptions.Builder()).build())); mLooper.dispatchAll(); verify(mAidlResponseHandlerCallback).onEnrollSuccess(); |