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

Commit 30b0a909 authored by Bill Lin's avatar Bill Lin Committed by Android (Google) Code Review
Browse files

Merge "[DO NOT MERGE] Support biometric re-enrollment for dangling" into 24D1-dev

parents 9de892ce 5b515d8b
Loading
Loading
Loading
Loading
+19 −0
Original line number Original line Diff line number Diff line
@@ -6438,4 +6438,23 @@ ul.</string>
    <string name="satellite_notification_open_message">Open Messages</string>
    <string name="satellite_notification_open_message">Open Messages</string>
    <!-- Invoke Satellite setting activity of Settings -->
    <!-- Invoke Satellite setting activity of Settings -->
    <string name="satellite_notification_how_it_works">How it works</string>
    <string name="satellite_notification_how_it_works">How it works</string>

    <!-- Fingerprint dangling notification title -->
    <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string>
    <!-- Fingerprint dangling notification content for only 1 fingerprint deleted -->
    <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted to improve performance</string>
    <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted -->
    <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted to improve performance</string>
    <!-- Fingerprint dangling notification content for only 1 fingerprint deleted and no fingerprint left-->
    <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint.</string>
    <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted and no fingerprint left  -->
    <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint.</string>
    <!-- Face dangling notification title -->
    <string name="face_dangling_notification_title">Set up Face Unlock again</string>
    <!-- Face dangling notification content -->
    <string name="face_dangling_notification_msg">Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face.</string>
    <!-- Biometric dangling notification "set up" action button -->
    <string name="biometric_dangling_notification_action_set_up">Set up</string>
    <!-- Biometric dangling notification "Not now" action button -->
    <string name="biometric_dangling_notification_action_not_now">Not now</string>
</resources>
</resources>
+11 −0
Original line number Original line Diff line number Diff line
@@ -5381,4 +5381,15 @@
  <java-symbol type="string" name="config_defaultContextualSearchKey" />
  <java-symbol type="string" name="config_defaultContextualSearchKey" />
  <java-symbol type="string" name="config_defaultContextualSearchEnabled" />
  <java-symbol type="string" name="config_defaultContextualSearchEnabled" />
  <java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
  <java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />

  <!-- Biometric dangling notification strings -->
  <java-symbol type="string" name="fingerprint_dangling_notification_title" />
  <java-symbol type="string" name="fingerprint_dangling_notification_msg_1" />
  <java-symbol type="string" name="fingerprint_dangling_notification_msg_2" />
  <java-symbol type="string" name="fingerprint_dangling_notification_msg_all_deleted_1" />
  <java-symbol type="string" name="fingerprint_dangling_notification_msg_all_deleted_2" />
  <java-symbol type="string" name="face_dangling_notification_title" />
  <java-symbol type="string" name="face_dangling_notification_msg" />
  <java-symbol type="string" name="biometric_dangling_notification_action_set_up" />
  <java-symbol type="string" name="biometric_dangling_notification_action_not_now" />
</resources>
</resources>
+93 −0
Original line number Original line Diff line number Diff line
/*
 * 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 static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;

import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.provider.Settings;
import android.util.Slog;

import com.android.server.biometrics.sensors.BiometricNotificationUtils;

/**
 * Receives broadcast to biometrics dangling notification.
 */
public class BiometricDanglingReceiver extends BroadcastReceiver {
    private static final String TAG = "BiometricDanglingReceiver";

    public static final String ACTION_FINGERPRINT_RE_ENROLL_LAUNCH =
            "action_fingerprint_re_enroll_launch";
    public static final String ACTION_FINGERPRINT_RE_ENROLL_DISMISS =
            "action_fingerprint_re_enroll_dismiss";

    public static final String ACTION_FACE_RE_ENROLL_LAUNCH =
            "action_face_re_enroll_launch";
    public static final String ACTION_FACE_RE_ENROLL_DISMISS =
            "action_face_re_enroll_dismiss";

    public static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";

    private static final String SETTINGS_PACKAGE = "com.android.settings";

    /**
     * Constructor for BiometricDanglingReceiver.
     *
     * @param context context
     * @param modality the value from BiometricsProtoEnums.MODALITY_*
     */
    public BiometricDanglingReceiver(@NonNull Context context, int modality) {
        final IntentFilter intentFilter = new IntentFilter();
        if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
            intentFilter.addAction(ACTION_FINGERPRINT_RE_ENROLL_LAUNCH);
            intentFilter.addAction(ACTION_FINGERPRINT_RE_ENROLL_DISMISS);
        } else if (modality == BiometricsProtoEnums.MODALITY_FACE) {
            intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH);
            intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS);
        }
        context.registerReceiver(this, intentFilter);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Slog.d(TAG, "Received: " + intent.getAction());
        if (ACTION_FINGERPRINT_RE_ENROLL_LAUNCH.equals(intent.getAction())) {
            launchBiometricEnrollActivity(context, Settings.ACTION_FINGERPRINT_ENROLL);
            BiometricNotificationUtils.cancelFingerprintReEnrollNotification(context);
        } else if (ACTION_FINGERPRINT_RE_ENROLL_DISMISS.equals(intent.getAction())) {
            BiometricNotificationUtils.cancelFingerprintReEnrollNotification(context);
        } else if (ACTION_FACE_RE_ENROLL_LAUNCH.equals(intent.getAction())) {
            launchBiometricEnrollActivity(context, FACE_SETTINGS_ACTION);
            BiometricNotificationUtils.cancelFaceReEnrollNotification(context);
        } else if (ACTION_FACE_RE_ENROLL_DISMISS.equals(intent.getAction())) {
            BiometricNotificationUtils.cancelFaceReEnrollNotification(context);
        }
        context.unregisterReceiver(this);
    }

    private void launchBiometricEnrollActivity(Context context, String action) {
        context.sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS));
        final Intent intent = new Intent(action);
        intent.setPackage(SETTINGS_PACKAGE);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}
+141 −3
Original line number Original line Diff line number Diff line
@@ -24,13 +24,18 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceEnrollOptions;
import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserHandle;
import android.text.BidiFormatter;
import android.util.Slog;
import android.util.Slog;


import com.android.internal.R;
import com.android.internal.R;
import com.android.server.biometrics.BiometricDanglingReceiver;

import java.util.List;


/**
/**
 * Biometric notification helper class.
 * Biometric notification helper class.
@@ -39,6 +44,7 @@ public class BiometricNotificationUtils {


    private static final String TAG = "BiometricNotificationUtils";
    private static final String TAG = "BiometricNotificationUtils";
    private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll";
    private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll";
    private static final String FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG = "FingerprintReEnroll";
    private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration";
    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 KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
    private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
    private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
@@ -50,6 +56,8 @@ public class BiometricNotificationUtils {
    private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel";
    private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel";
    private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
    private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
    private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel";
    private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel";
    private static final String FINGERPRINT_RE_ENROLL_CHANNEL =
            "FingerprintReEnrollNotificationChannel";
    private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL =
    private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL =
            "FingerprintBadCalibrationNotificationChannel";
            "FingerprintBadCalibrationNotificationChannel";
    private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
    private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
@@ -177,10 +185,124 @@ public class BiometricNotificationUtils {
                BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false);
                BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false);
    }
    }


    /**
     * Shows a biometric re-enroll notification.
     */
    public static void showBiometricReEnrollNotification(@NonNull Context context,
            @NonNull List<String> identifiers, boolean allIdentifiersDeleted, int modality) {
        final boolean isFingerprint = modality == BiometricsProtoEnums.MODALITY_FINGERPRINT;
        final String reEnrollName = isFingerprint ? FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG
                : FACE_RE_ENROLL_NOTIFICATION_TAG;
        if (identifiers.isEmpty()) {
            Slog.v(TAG, "Skipping " + reEnrollName + " notification : empty list");
            return;
        }
        Slog.d(TAG, "Showing " + reEnrollName + " notification :[" + identifiers.size()
                + " identifier(s) deleted, allIdentifiersDeleted=" + allIdentifiersDeleted + "]");

        final String name =
                context.getString(R.string.device_unlock_notification_name);
        final String title = context.getString(isFingerprint
                ? R.string.fingerprint_dangling_notification_title
                : R.string.face_dangling_notification_title);
        final String content = isFingerprint
                ? getFingerprintDanglingContentString(context, identifiers, allIdentifiersDeleted)
                : context.getString(R.string.face_dangling_notification_msg);

        // Create "Set up" notification action button.
        final Intent setupIntent = new Intent(
                isFingerprint ? BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH
                : BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_LAUNCH);
        final PendingIntent setupPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
                setupIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
        final String setupText =
                context.getString(R.string.biometric_dangling_notification_action_set_up);
        final Notification.Action setupAction = new Notification.Action.Builder(
                null, setupText, setupPendingIntent).build();

        // Create "Not now" notification action button.
        final Intent notNowIntent = new Intent(
                isFingerprint ? BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_DISMISS
                : BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_DISMISS);
        final PendingIntent notNowPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
                notNowIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
        final String notNowText = context.getString(
                R.string.biometric_dangling_notification_action_not_now);
        final Notification.Action notNowAction = new Notification.Action.Builder(
                null, notNowText, notNowPendingIntent).build();

        final String channel = isFingerprint ? FINGERPRINT_RE_ENROLL_CHANNEL
                : FACE_RE_ENROLL_CHANNEL;
        final String tag = isFingerprint ? FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG
                : FACE_RE_ENROLL_NOTIFICATION_TAG;

        showNotificationHelper(context, name, title, content, setupPendingIntent, setupAction,
                notNowAction, Notification.CATEGORY_SYSTEM, channel, tag,
                Notification.VISIBILITY_SECRET, false);
    }

    private static String getFingerprintDanglingContentString(Context context,
            @NonNull List<String> fingerprints, boolean allFingerprintDeleted) {
        if (fingerprints.isEmpty()) {
            return null;
        }

        final int resId;
        final int size = fingerprints.size();
        final StringBuilder first = new StringBuilder();
        final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
        if (size > 1) {
            // If there are more than 1 fingerprint deleted, the "second" will be the last
            // fingerprint and set the others to "first".
            // For example, if we have 3 fingerprints deleted(fp1, fp2 and fp3):
            //   first  = "fp1, fp2"
            //   second = "fp3"
            final String separator = ", ";
            String second = null;
            for (int i = 0; i < size; i++) {
                if (i == size - 1) {
                    second = bidiFormatter.unicodeWrap("\"" + fingerprints.get(i) + "\"");
                } else {
                    first.append(bidiFormatter.unicodeWrap("\""));
                    first.append(bidiFormatter.unicodeWrap(fingerprints.get(i)));
                    first.append(bidiFormatter.unicodeWrap("\""));
                    if (i < size - 2) {
                        first.append(bidiFormatter.unicodeWrap(separator));
                    }
                }
            }
            if (allFingerprintDeleted) {
                resId = R.string.fingerprint_dangling_notification_msg_all_deleted_2;
            } else {
                resId = R.string.fingerprint_dangling_notification_msg_2;
            }

            return String.format(context.getString(resId), first, second);
        } else {
            if (allFingerprintDeleted) {
                resId = R.string.fingerprint_dangling_notification_msg_all_deleted_1;
            } else {
                resId = R.string.fingerprint_dangling_notification_msg_1;
            }
            first.append(bidiFormatter.unicodeWrap("\""));
            first.append(bidiFormatter.unicodeWrap(fingerprints.get(0)));
            first.append(bidiFormatter.unicodeWrap("\""));
            return String.format(context.getString(resId), first);
        }
    }

    private static void showNotificationHelper(Context context, String name, String title,
    private static void showNotificationHelper(Context context, String name, String title,
                String content, PendingIntent pendingIntent, String category,
            String content, PendingIntent pendingIntent, String category, String channelName,
                String channelName, String notificationTag, int visibility,
            String notificationTag, int visibility, boolean listenToDismissEvent) {
                boolean listenToDismissEvent) {
        showNotificationHelper(context, name, title, content, pendingIntent,
                null /* positiveAction */, null /* negativeAction */, category, channelName,
                notificationTag, visibility, listenToDismissEvent);
    }

    private static void showNotificationHelper(Context context, String name, String title,
            String content, PendingIntent pendingIntent, Notification.Action positiveAction,
            Notification.Action negativeAction, String category, String channelName,
            String notificationTag, int visibility, boolean listenToDismissEvent) {
        Slog.v(TAG," listenToDismissEvent = " + listenToDismissEvent);
        Slog.v(TAG," listenToDismissEvent = " + listenToDismissEvent);
        final PendingIntent dismissIntent = PendingIntent.getActivityAsUser(context,
        final PendingIntent dismissIntent = PendingIntent.getActivityAsUser(context,
                0 /* requestCode */, DISMISS_FRR_INTENT, PendingIntent.FLAG_IMMUTABLE /* flags */,
                0 /* requestCode */, DISMISS_FRR_INTENT, PendingIntent.FLAG_IMMUTABLE /* flags */,
@@ -202,6 +324,12 @@ public class BiometricNotificationUtils {
                .setContentIntent(pendingIntent)
                .setContentIntent(pendingIntent)
                .setVisibility(visibility);
                .setVisibility(visibility);


        if (positiveAction != null) {
            builder.addAction(positiveAction);
        }
        if (negativeAction != null) {
            builder.addAction(negativeAction);
        }
        if (listenToDismissEvent) {
        if (listenToDismissEvent) {
            builder.setDeleteIntent(dismissIntent);
            builder.setDeleteIntent(dismissIntent);
        }
        }
@@ -253,4 +381,14 @@ public class BiometricNotificationUtils {
                UserHandle.CURRENT);
                UserHandle.CURRENT);
    }
    }


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

}
}
+26 −0
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.os.IBinder;
import android.os.IBinder;
import android.util.Slog;
import android.util.Slog;


import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.BiometricLogger;
@@ -44,6 +45,7 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
    private List<? extends BiometricAuthenticator.Identifier> mEnrolledList;
    private List<? extends BiometricAuthenticator.Identifier> mEnrolledList;
    // List of templates to remove from the HAL
    // List of templates to remove from the HAL
    private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>();
    private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>();
    private final int mInitialEnrolledSize;


    protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
    protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
            @NonNull IBinder token, int userId, @NonNull String owner,
            @NonNull IBinder token, int userId, @NonNull String owner,
@@ -55,6 +57,7 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
        super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
        super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
                0 /* cookie */, sensorId, logger, biometricContext);
                0 /* cookie */, sensorId, logger, biometricContext);
        mEnrolledList = enrolledList;
        mEnrolledList = enrolledList;
        mInitialEnrolledSize = mEnrolledList.size();
        mUtils = utils;
        mUtils = utils;
    }
    }


@@ -111,8 +114,10 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>


        // At this point, mEnrolledList only contains templates known to the framework and
        // At this point, mEnrolledList only contains templates known to the framework and
        // not the HAL.
        // not the HAL.
        final List<String> names = new ArrayList<>();
        for (int i = 0; i < mEnrolledList.size(); i++) {
        for (int i = 0; i < mEnrolledList.size(); i++) {
            BiometricAuthenticator.Identifier identifier = mEnrolledList.get(i);
            BiometricAuthenticator.Identifier identifier = mEnrolledList.get(i);
            names.add(identifier.getName().toString());
            Slog.e(TAG, "doTemplateCleanup(): Removing dangling template from framework: "
            Slog.e(TAG, "doTemplateCleanup(): Removing dangling template from framework: "
                    + identifier.getBiometricId() + " " + identifier.getName());
                    + identifier.getBiometricId() + " " + identifier.getName());
            mUtils.removeBiometricForUser(getContext(),
            mUtils.removeBiometricForUser(getContext(),
@@ -120,6 +125,11 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>


            getLogger().logUnknownEnrollmentInFramework();
            getLogger().logUnknownEnrollmentInFramework();
        }
        }

        // Send dangling notification.
        if (!names.isEmpty()) {
            sendDanglingNotification(names);
        }
        mEnrolledList.clear();
        mEnrolledList.clear();
    }
    }


@@ -127,8 +137,24 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
        return mUnknownHALTemplates;
        return mUnknownHALTemplates;
    }
    }


    /**
     * Send the dangling notification.
     */
    @VisibleForTesting
    public void sendDanglingNotification(@NonNull List<String> identifierNames) {
        if (!identifierNames.isEmpty()) {
            Slog.e(TAG, "sendDanglingNotification(): initial enrolledSize="
                    + mInitialEnrolledSize + ", after clean up size=" + mEnrolledList.size());
            final boolean allIdentifiersDeleted = mEnrolledList.size() == mInitialEnrolledSize;
            BiometricNotificationUtils.showBiometricReEnrollNotification(
                    getContext(), identifierNames, allIdentifiersDeleted, getModality());
        }
    }

    @Override
    @Override
    public int getProtoEnum() {
    public int getProtoEnum() {
        return BiometricsProto.CM_ENUMERATE;
        return BiometricsProto.CM_ENUMERATE;
    }
    }

    protected abstract int getModality();
}
}
Loading