From 1746b038852f63a8c71b75261c6b363fe83d5dcd Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Tue, 23 Jul 2024 06:59:44 +0000 Subject: Notify user with a notification when LOE happens When fingerprint loss of enrollments happens: - Send a notification - Delete the biometric state file Bug: 351036558 Test: atest FingerprintInternalCleanupClientTest Flag: com.android.server.biometrics.Flags.FLAG_NOTIFY_FINGERPINRT_LOE Change-Id: I339bf4f029ca6c82ba5fdf89331778dca6b0e43b --- core/res/res/values/strings.xml | 3 ++ core/res/res/values/symbols.xml | 3 ++ .../android/server/biometrics/biometrics.aconfig | 7 ++++ .../sensors/BiometricNotificationUtils.java | 37 ++++++++++++++++++++++ .../biometrics/sensors/BiometricUserState.java | 29 +++++++++++++++-- .../server/biometrics/sensors/BiometricUtils.java | 10 ++++++ .../biometrics/sensors/InternalCleanupClient.java | 12 ++++++- .../server/biometrics/sensors/face/FaceUtils.java | 16 ++++++++++ .../face/aidl/FaceInternalCleanupClient.java | 6 ++++ .../sensors/fingerprint/FingerprintUtils.java | 16 ++++++++++ .../aidl/FingerprintInternalCleanupClient.java | 16 ++++++++++ .../biometrics/sensors/BiometricSchedulerTest.java | 5 +++ .../aidl/FingerprintInternalCleanupClientTest.java | 24 ++++++++++++++ 13 files changed, 181 insertions(+), 3 deletions(-) diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index ec865f6c376f..e94db2dc7fc4 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6555,4 +6555,7 @@ ul. Maps Applications + + + Your fingerprints can no longer be recognized. Set up Fingerprint Unlock again. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 09688f2f7bec..f4d2462a2b90 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5577,4 +5577,7 @@ + + + diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index 92fd9cbcf14e..15c88500210e 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -14,3 +14,10 @@ flag { description: "This flag controls whether virtual HAL is used for testing instead of TestHal " bug: "294254230" } + +flag { + name: "notify_fingerprint_loe" + namespace: "biometrics_framework" + description: "This flag controls whether a notification should be sent to notify user when loss of enrollment happens" + bug: "351036558" +} 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 53e6bdb2ab5f..27f9cc88e28f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java @@ -150,6 +150,43 @@ public class BiometricNotificationUtils { } + /** + * Shows a fingerprint notification for loss of enrollment + */ + public static void showFingerprintLoeNotification(@NonNull Context context) { + Slog.d(TAG, "Showing fingerprint LOE notification"); + + final String name = + context.getString(R.string.device_unlock_notification_name); + final String title = context.getString(R.string.fingerprint_dangling_notification_title); + final String content = context.getString(R.string.fingerprint_loe_notification_msg); + + // Create "Set up" notification action button. + final Intent setupIntent = + new Intent(BiometricDanglingReceiver.ACTION_FINGERPRINT_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(BiometricDanglingReceiver.ACTION_FINGERPRINT_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(); + + showNotificationHelper(context, name, title, content, setupPendingIntent, setupAction, + notNowAction, Notification.CATEGORY_SYSTEM, FINGERPRINT_RE_ENROLL_CHANNEL, + FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false, + Notification.FLAG_NO_CLEAR); + } + /** * Shows a fingerprint bad calibration notification. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java index 7fb27b6896da..63678aaa16c3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java @@ -57,6 +57,7 @@ public abstract class BiometricUserState { CharSequence getUniqueName(Context context, int userId); void setInvalidationInProgress(Context context, int userId, boolean inProgress); boolean isInvalidationInProgress(Context context, int userId); + + /** + * Return true if the biometric file is correctly read. Otherwise return false. + */ + boolean hasValidBiometricUserState(Context context, int userId); + + /** + * Delete the file of the biometric state. + */ + void deleteStateForUser(int userId); } \ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index 69ad1523118d..3b6aeef92421 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -25,6 +25,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -62,7 +63,7 @@ public abstract class InternalCleanupClient mUnknownHALTemplates = new ArrayList<>(); - private final BiometricUtils mBiometricUtils; + protected final BiometricUtils mBiometricUtils; private final Map mAuthenticatorIds; private final boolean mHasEnrollmentsBeforeStarting; private BaseClientMonitor mCurrentTask; @@ -105,6 +106,11 @@ public abstract class InternalCleanupClient getUnknownHALTemplates() { return mUnknownHALTemplates; } + + protected void handleInvalidBiometricState() {} + + protected abstract int getModality(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java index c5744780cd71..79285cbd9ea5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java @@ -124,6 +124,22 @@ public class FaceUtils implements BiometricUtils { return getStateForUser(context, userId).isInvalidationInProgress(); } + @Override + public boolean hasValidBiometricUserState(Context context, int userId) { + return getStateForUser(context, userId).isInvalidBiometricState(); + } + + @Override + public void deleteStateForUser(int userId) { + synchronized (this) { + FaceUserState state = mUserStates.get(userId); + if (state != null) { + state.deleteBiometricFile(); + mUserStates.delete(userId); + } + } + } + private FaceUserState getStateForUser(Context ctx, int userId) { synchronized (this) { FaceUserState state = mUserStates.get(userId); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index e75c6aba1489..964bf6cad63c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.hardware.face.Face; import android.os.IBinder; @@ -77,4 +78,9 @@ public class FaceInternalCleanupClient extends InternalCleanupClient { return getStateForUser(context, userId).isInvalidationInProgress(); } + @Override + public boolean hasValidBiometricUserState(Context context, int userId) { + return getStateForUser(context, userId).isInvalidBiometricState(); + } + + @Override + public void deleteStateForUser(int userId) { + synchronized (this) { + FingerprintUserState state = mUserStates.get(userId); + if (state != null) { + state.deleteBiometricFile(); + mUserStates.delete(userId); + } + } + } + private FingerprintUserState getStateForUser(Context ctx, int userId) { synchronized (this) { FingerprintUserState state = mUserStates.get(userId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index 5edc2ca080ad..1fc517906c58 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -22,9 +22,11 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; +import android.util.Slog; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalCleanupClient; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -42,6 +44,8 @@ import java.util.function.Supplier; public class FingerprintInternalCleanupClient extends InternalCleanupClient { + private static final String TAG = "FingerprintInternalCleanupClient"; + public FingerprintInternalCleanupClient(@NonNull Context context, @NonNull Supplier lazyDaemon, int userId, @NonNull String owner, int sensorId, @@ -80,4 +84,16 @@ public class FingerprintInternalCleanupClient FingerprintUtils.getInstance(getSensorId()).addBiometricForUser( getContext(), getTargetUserId(), (Fingerprint) identifier); } + + @Override + public void handleInvalidBiometricState() { + Slog.d(TAG, "Invalid fingerprint user state: delete the state."); + mBiometricUtils.deleteStateForUser(getTargetUserId()); + BiometricNotificationUtils.showFingerprintLoeNotification(getContext()); + } + + @Override + protected int getModality() { + return BiometricsProtoEnums.MODALITY_FINGERPRINT; + } } 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 37895315557a..36a7b3dff28d 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 @@ -1296,6 +1296,11 @@ public class BiometricSchedulerTest { mFingerprints.add((Fingerprint) identifier); } + @Override + protected int getModality() { + return 0; + } + public List getFingerprints() { return mFingerprints; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java index c9482ceb00f5..a34e7965ccee 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -30,12 +31,16 @@ import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.Fingerprint; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.TestableContext; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -69,6 +74,10 @@ public class FingerprintInternalCleanupClientTest { public final TestableContext mContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock ISession mSession; @Mock @@ -168,6 +177,21 @@ public class FingerprintInternalCleanupClientTest { assertThat(mClient.getUnknownHALTemplates()).isEmpty(); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINT_LOE) + public void invalidBiometricUserState() throws Exception { + mClient = createClient(); + + final List list = new ArrayList<>(); + doReturn(true).when(mFingerprintUtils) + .hasValidBiometricUserState(mContext, 2); + doReturn(list).when(mFingerprintUtils).getBiometricsForUser(mContext, 2); + + mClient.start(mCallback); + mClient.onEnumerationResult(null, 0); + verify(mFingerprintUtils).deleteStateForUser(2); + } + protected FingerprintInternalCleanupClient createClient() { final Map authenticatorIds = new HashMap<>(); return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */, -- cgit v1.2.3-59-g8ed1b