Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0c596f11 authored by Wenhui Yang's avatar Wenhui Yang
Browse files

[3/n] FRR notifications

Add methods in BiometricNotificationUtils for face and fp enrollment.

Bug: 258872351
Test: atest AuthenticationStatsCollectorTest
Change-Id: Id1100a6cb8113c4c52bfdb5d64b7e8b086d3d559
parent 53bc9fa6
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -2623,6 +2623,8 @@
  <java-symbol type="string" name="fingerprint_recalibrate_notification_name" />
  <java-symbol type="string" name="fingerprint_recalibrate_notification_title" />
  <java-symbol type="string" name="fingerprint_recalibrate_notification_content" />
  <java-symbol type="string" name="fingerprint_setup_notification_title" />
  <java-symbol type="string" name="fingerprint_setup_notification_content" />
  <java-symbol type="string" name="fingerprint_error_power_pressed" />

  <!-- Fingerprint config -->
@@ -2690,6 +2692,7 @@
  <java-symbol type="string" name="face_authenticated_no_confirmation_required" />
  <java-symbol type="string" name="face_authenticated_confirmation_required" />
  <java-symbol type="string" name="face_error_security_update_required" />
  <java-symbol type="string" name="face_setup_notification_title" />

  <java-symbol type="string" name="config_biometric_prompt_ui_package" />
  <java-symbol type="array" name="config_biometric_sensors" />
+43 −7
Original line number Diff line number Diff line
@@ -19,9 +19,13 @@ package com.android.server.biometrics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.BiometricNotification;

import java.util.HashMap;
import java.util.Map;
@@ -50,13 +54,16 @@ public class AuthenticationStatsCollector {
    @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;

    @NonNull private AuthenticationStatsPersister mAuthenticationStatsPersister;
    @NonNull private BiometricNotification mBiometricNotification;

    public AuthenticationStatsCollector(@NonNull Context context, int modality) {
    public AuthenticationStatsCollector(@NonNull Context context, int modality,
            @NonNull BiometricNotification biometricNotification) {
        mContext = context;
        mThreshold = context.getResources()
                .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
        mUserAuthenticationStatsMap = new HashMap<>();
        mModality = modality;
        mBiometricNotification = biometricNotification;
    }

    private void initializeUserAuthenticationStatsMap() {
@@ -86,16 +93,45 @@ public class AuthenticationStatsCollector {
        sendNotificationIfNeeded(userId);
    }

    /** Check if a notification should be sent after a calculation cycle. */
    private void sendNotificationIfNeeded(int userId) {
        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
        if (authenticationStats.getTotalAttempts() >= MINIMUM_ATTEMPTS) {
            // Send notification if FRR exceeds the threshold
            if (authenticationStats.getEnrollmentNotifications() < MAXIMUM_ENROLLMENT_NOTIFICATIONS
                    && authenticationStats.getFrr() >= mThreshold) {
                // TODO(wenhuiy): Send notifications.
        if (authenticationStats.getTotalAttempts() < MINIMUM_ATTEMPTS) {
            return;
        }

        // Don't send notification if FRR below the threshold.
        if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS
                || authenticationStats.getFrr() < mThreshold) {
            authenticationStats.resetData();
            return;
        }

        authenticationStats.resetData();

        final PackageManager packageManager = mContext.getPackageManager();

        // Don't send notification to single-modality devices.
        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
                || !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
            return;
        }

        final FaceManager faceManager = mContext.getSystemService(FaceManager.class);
        final boolean hasEnrolledFace = faceManager.hasEnrolledTemplates(userId);

        final FingerprintManager fingerprintManager = mContext
                .getSystemService(FingerprintManager.class);
        final boolean hasEnrolledFingerprint = fingerprintManager.hasEnrolledTemplates(userId);

        // Don't send notification when both face and fingerprint are enrolled.
        if (hasEnrolledFace && hasEnrolledFingerprint) {
            return;
        }
        if (hasEnrolledFace && !hasEnrolledFingerprint) {
            mBiometricNotification.sendFpEnrollNotification(mContext);
        } else if (!hasEnrolledFace && hasEnrolledFingerprint) {
            mBiometricNotification.sendFaceEnrollNotification(mContext);
        }
    }

+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.biometrics.sensors;

import android.annotation.NonNull;
import android.content.Context;

/**
 * Interface for biometrics to send notifications.
 */
public interface BiometricNotification {

    /**
     * Sends a face enrollment notification.
     */
    void sendFaceEnrollNotification(@NonNull Context context);

    /**
     * Sends a fingerprint enrollment notification.
     */
    void sendFpEnrollNotification(@NonNull Context context);
}
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.biometrics.sensors;

import android.annotation.NonNull;
import android.content.Context;

import com.android.server.biometrics.AuthenticationStatsCollector;

/**
 * Implementation to send biometric notifications for {@link AuthenticationStatsCollector}.
 */
public class BiometricNotificationImpl implements BiometricNotification {

    @Override
    public void sendFaceEnrollNotification(@NonNull Context context) {
        BiometricNotificationUtils.showFaceEnrollNotification(context);
    }

    @Override
    public void sendFpEnrollNotification(@NonNull Context context) {
        BiometricNotificationUtils.showFingerprintEnrollNotification(context);
    }
}
+98 −17
Original line number Diff line number Diff line
@@ -35,9 +35,22 @@ import com.android.internal.R;
public class BiometricNotificationUtils {

    private static final String TAG = "BiometricNotificationUtils";
    private static final String RE_ENROLL_NOTIFICATION_TAG = "FaceService";
    private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintService";
    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 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;
@@ -56,18 +69,67 @@ public class BiometricNotificationUtils {
        final String content =
                context.getString(R.string.face_recalibrate_notification_content);

        final Intent intent = new Intent("android.settings.FACE_SETTINGS");
        intent.setPackage("com.android.settings");
        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 */,
                null /* options */, UserHandle.CURRENT);

        final String channelName = "FaceEnrollNotificationChannel";
        showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
                FACE_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
    }

    /**
     * Shows a face enrollment notification.
     */
    public static void showFaceEnrollNotification(@NonNull Context context) {

        final String name =
                context.getString(R.string.face_recalibrate_notification_name);
        final String title =
                context.getString(R.string.fingerprint_setup_notification_title);
        final String content =
                context.getString(R.string.face_setup_notification_title);

        final Intent intent = new Intent(FACE_ENROLL_ACTION);
        intent.setPackage(SETTINGS_PACKAGE);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

        final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
                0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
                null /* options */, UserHandle.CURRENT);

        showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
                FACE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
    }

    /**
     * Shows a fingerprint enrollment notification.
     */
    public static void showFingerprintEnrollNotification(@NonNull Context context) {

        final String name =
                context.getString(R.string.fingerprint_recalibrate_notification_name);
        final String title =
                context.getString(R.string.fingerprint_setup_notification_title);
        final String content =
                context.getString(R.string.fingerprint_setup_notification_content);

        final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
        intent.setPackage(SETTINGS_PACKAGE);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

        showNotificationHelper(context, name, title, content, pendingIntent, channelName,
                RE_ENROLL_NOTIFICATION_TAG);
        final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
                0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
                null /* options */, UserHandle.CURRENT);

        showNotificationHelper(context, name, title, content, pendingIntent,
                FINGERPRINT_ENROLL_CHANNEL, FINGERPRINT_ENROLL_NOTIFICATION_TAG,
                Notification.VISIBILITY_PUBLIC);
    }

    /**
@@ -93,22 +155,21 @@ public class BiometricNotificationUtils {
        final String content =
                context.getString(R.string.fingerprint_recalibrate_notification_content);

        final Intent intent = new Intent("android.settings.FINGERPRINT_SETTINGS");
        intent.setPackage("com.android.settings");
        final Intent intent = new Intent(FINGERPRINT_SETTINGS_ACTION);
        intent.setPackage(SETTINGS_PACKAGE);

        final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
                0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
                null /* options */, UserHandle.CURRENT);

        final String channelName = "FingerprintBadCalibrationNotificationChannel";

        showNotificationHelper(context, name, title, content, pendingIntent, channelName,
                BAD_CALIBRATION_NOTIFICATION_TAG);
        showNotificationHelper(context, name, title, content, pendingIntent,
                FINGERPRINT_BAD_CALIBRATION_CHANNEL, BAD_CALIBRATION_NOTIFICATION_TAG,
                Notification.VISIBILITY_SECRET);
    }

    private static void showNotificationHelper(Context context, String name, String title,
                String content, PendingIntent pendingIntent, String channelName,
                String notificationTag) {
                String notificationTag, int visibility) {
        final NotificationManager notificationManager =
                context.getSystemService(NotificationManager.class);
        final NotificationChannel channel = new NotificationChannel(channelName, name,
@@ -123,7 +184,7 @@ public class BiometricNotificationUtils {
                .setAutoCancel(true)
                .setCategory(Notification.CATEGORY_SYSTEM)
                .setContentIntent(pendingIntent)
                .setVisibility(Notification.VISIBILITY_SECRET)
                .setVisibility(visibility)
                .build();

        notificationManager.createNotificationChannel(channel);
@@ -134,10 +195,30 @@ public class BiometricNotificationUtils {
    /**
     * Cancels a face re-enrollment notification
     */
    public static void cancelReEnrollNotification(@NonNull Context context) {
    public static void cancelFaceReEnrollNotification(@NonNull Context context) {
        final NotificationManager notificationManager =
                context.getSystemService(NotificationManager.class);
        notificationManager.cancelAsUser(FACE_RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
                UserHandle.CURRENT);
    }

    /**
     * Cancels a face enrollment notification
     */
    public static void cancelFaceEnrollNotification(@NonNull Context context) {
        final NotificationManager notificationManager =
                context.getSystemService(NotificationManager.class);
        notificationManager.cancelAsUser(FACE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
                UserHandle.CURRENT);
    }

    /**
     * Cancels a fingerprint enrollment notification
     */
    public static void cancelFingerprintEnrollNotification(@NonNull Context context) {
        final NotificationManager notificationManager =
                context.getSystemService(NotificationManager.class);
        notificationManager.cancelAsUser(RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
        notificationManager.cancelAsUser(FINGERPRINT_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
                UserHandle.CURRENT);
    }

Loading