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

Commit b1305404 authored by Wenhui Yang's avatar Wenhui Yang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "High FRR - notification" into udc-qpr-dev

* changes:
  [4/n] FRR data cleanup
  [3/n] FRR notifications
parents 04079089 f37c7ec7
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -2624,6 +2624,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 -->
@@ -2691,6 +2693,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" />
+65 −7
Original line number Diff line number Diff line
@@ -18,10 +18,18 @@ package com.android.server.biometrics;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.UserHandle;

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 +58,29 @@ public class AuthenticationStatsCollector {
    @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;

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

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(@NonNull Context context, @NonNull Intent intent) {
            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
            if (userId != UserHandle.USER_NULL
                    && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
                onUserRemoved(userId);
            }
        }
    };

    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;

        context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
    }

    private void initializeUserAuthenticationStatsMap() {
@@ -86,16 +110,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);
        }
    }

@@ -110,6 +163,11 @@ public class AuthenticationStatsCollector {
        }
    }

    private void onUserRemoved(final int userId) {
        mUserAuthenticationStatsMap.remove(userId);
        mAuthenticationStatsPersister.removeFrrStats(userId);
    }

    /**
     * Only being used in tests. Callers should not make any changes to the returned
     * authentication stats.
+23 −0
Original line number Diff line number Diff line
@@ -93,6 +93,29 @@ public class AuthenticationStatsPersister {
        return authenticationStatsList;
    }

    /**
     * Remove frr data for a specific user.
     */
    public void removeFrrStats(int userId) {
        try {
            // Copy into a new HashSet to avoid iterator exception.
            Set<String> frrStatsSet = new HashSet<>(readFrrStats());

            // Remove the old authentication stat for the user if it exists.
            for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) {
                String frrStats = iterator.next();
                JSONObject frrStatJson = new JSONObject(frrStats);
                if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) {
                    iterator.remove();
                    break;
                }
            }

            mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
        } catch (JSONException ignored) {
        }
    }

    /**
     * Persist frr data for a specific user.
     */
+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);
    }
}
Loading