diff options
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.</string> <string name="keyboard_shortcut_group_applications_maps">Maps</string> <!-- User visible title for the keyboard shortcut group containing system-wide application launch shortcuts. [CHAR-LIMIT=70] --> <string name="keyboard_shortcut_group_applications">Applications</string> + + <!-- Fingerprint loe notification string --> + <string name="fingerprint_loe_notification_msg">Your fingerprints can no longer be recognized. Set up Fingerprint Unlock again.</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6b583962e5e8..cbf3fe7b0a1b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5584,4 +5584,7 @@ <java-symbol type="string" name="keyboard_shortcut_group_applications_music" /> <java-symbol type="string" name="keyboard_shortcut_group_applications_sms" /> <java-symbol type="string" name="keyboard_shortcut_group_applications" /> + + <!-- Fingerprint loe notification string --> + <java-symbol type="string" name="fingerprint_loe_notification_msg" /> </resources> 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 @@ -151,6 +151,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. */ public static void showBadCalibrationNotification(@NonNull Context context) { 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<T extends BiometricAuthenticator.Identi protected boolean mInvalidationInProgress; protected final Context mContext; protected final File mFile; + private boolean mIsInvalidBiometricState = false; private final Runnable mWriteStateRunnable = this::doWriteStateInternal; @@ -102,7 +103,7 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi serializer.endDocument(); destination.finishWrite(out); } catch (Throwable t) { - Slog.wtf(TAG, "Failed to write settings, restoring backup", t); + Slog.e(TAG, "Failed to write settings, restoring backup", t); destination.failWrite(out); throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t); } finally { @@ -192,6 +193,29 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi } } + /** + * Return true if the biometric file is correctly read. Otherwise return false. + */ + public boolean isInvalidBiometricState() { + return mIsInvalidBiometricState; + } + + /** + * Delete the file of the biometric state. + */ + public void deleteBiometricFile() { + synchronized (this) { + if (!mFile.exists()) { + return; + } + if (mFile.delete()) { + Slog.i(TAG, mFile + " is deleted successfully"); + } else { + Slog.i(TAG, "Failed to delete " + mFile); + } + } + } + private boolean isUnique(String name) { for (T identifier : mBiometrics) { if (identifier.getName().equals(name)) { @@ -218,7 +242,8 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi try { in = new FileInputStream(mFile); } catch (FileNotFoundException fnfe) { - Slog.i(TAG, "No fingerprint state"); + Slog.i(TAG, "No fingerprint state", fnfe); + mIsInvalidBiometricState = true; return; } try { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java index ebe467942790..0b4f64042055 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java @@ -33,4 +33,14 @@ public interface BiometricUtils<T extends BiometricAuthenticator.Identifier> { 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<S extends BiometricAuthenticator.Ide } private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>(); - private final BiometricUtils<S> mBiometricUtils; + protected final BiometricUtils<S> mBiometricUtils; private final Map<Integer, Long> mAuthenticatorIds; private final boolean mHasEnrollmentsBeforeStarting; private BaseClientMonitor mCurrentTask; @@ -105,6 +106,11 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide startCleanupUnknownHalTemplates(); } } + + if (mBiometricUtils.hasValidBiometricUserState(getContext(), getTargetUserId()) + && Flags.notifyFingerprintLoe()) { + handleInvalidBiometricState(); + } } }; @@ -248,4 +254,8 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide public ArrayList<UserTemplate> 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<Face> { 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<Face, AidlS FaceUtils.getInstance(getSensorId()).addBiometricForUser( getContext(), getTargetUserId(), (Face) identifier); } + + @Override + protected int getModality() { + return BiometricsProtoEnums.MODALITY_FACE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java index 0062d31962a9..b8c06c730edc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java @@ -140,6 +140,22 @@ public class FingerprintUtils implements BiometricUtils<Fingerprint> { 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<Fingerprint, AidlSession> { + private static final String TAG = "FingerprintInternalCleanupClient"; + public FingerprintInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> 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<Fingerprint> 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<Fingerprint> 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<Integer, Long> authenticatorIds = new HashMap<>(); return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */, |