diff options
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();  |