Skip to content
Snippets Groups Projects
Commit a5f83382 authored by Yong Zhang's avatar Yong Zhang Committed by Android (Google) Code Review
Browse files

Merge "Support biometric re-enrollment for dangling" into main

parents f1908787 4e636370
No related branches found
No related tags found
No related merge requests found
Showing
with 487 additions and 10 deletions
......@@ -6484,4 +6484,23 @@ ul.</string>
<string name="satellite_notification_how_it_works">How it works</string>
<!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
<string name="unarchival_session_app_label">Pending...</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>
......@@ -5432,4 +5432,15 @@
<!-- For PowerManagerService to determine whether to use auto-suspend mode -->
<java-symbol type="bool" name="config_useAutoSuspend" />
<!-- 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>
/*
* 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);
}
}
......@@ -24,13 +24,18 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.face.FaceEnrollOptions;
import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.BidiFormatter;
import android.util.Slog;
import com.android.internal.R;
import com.android.server.biometrics.BiometricDanglingReceiver;
import java.util.List;
/**
* Biometric notification helper class.
......@@ -39,6 +44,7 @@ public class BiometricNotificationUtils {
private static final String TAG = "BiometricNotificationUtils";
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 KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
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_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
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 =
"FingerprintBadCalibrationNotificationChannel";
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);
}
/**
* 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,
String content, PendingIntent pendingIntent, String category, String channelName,
String notificationTag, int visibility, 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, String category,
String channelName, String notificationTag, int visibility,
boolean listenToDismissEvent) {
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);
final PendingIntent dismissIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, DISMISS_FRR_INTENT, PendingIntent.FLAG_IMMUTABLE /* flags */,
......@@ -202,6 +324,12 @@ public class BiometricNotificationUtils {
.setContentIntent(pendingIntent)
.setVisibility(visibility);
if (positiveAction != null) {
builder.addAction(positiveAction);
}
if (negativeAction != null) {
builder.addAction(negativeAction);
}
if (listenToDismissEvent) {
builder.setDeleteIntent(dismissIntent);
}
......@@ -253,4 +381,14 @@ public class BiometricNotificationUtils {
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);
}
}
......@@ -22,6 +22,7 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.os.IBinder;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.log.BiometricContext;
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;
// List of templates to remove from the HAL
private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>();
private final int mInitialEnrolledSize;
protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@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,
0 /* cookie */, sensorId, logger, biometricContext);
mEnrolledList = enrolledList;
mInitialEnrolledSize = mEnrolledList.size();
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
// not the HAL.
final List<String> names = new ArrayList<>();
for (int i = 0; i < mEnrolledList.size(); i++) {
BiometricAuthenticator.Identifier identifier = mEnrolledList.get(i);
names.add(identifier.getName().toString());
Slog.e(TAG, "doTemplateCleanup(): Removing dangling template from framework: "
+ identifier.getBiometricId() + " " + identifier.getName());
mUtils.removeBiometricForUser(getContext(),
......@@ -120,6 +125,11 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
getLogger().logUnknownEnrollmentInFramework();
}
// Send dangling notification.
if (!names.isEmpty()) {
sendDanglingNotification(names);
}
mEnrolledList.clear();
}
......@@ -127,8 +137,24 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T>
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
public int getProtoEnum() {
return BiometricsProto.CM_ENUMERATE;
}
protected abstract int getModality();
}
......@@ -18,12 +18,14 @@ package com.android.server.biometrics.sensors.face.aidl;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
......@@ -35,7 +37,8 @@ import java.util.function.Supplier;
/**
* Face-specific internal enumerate client for the {@link IFace} AIDL HAL interface.
*/
class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
@VisibleForTesting
public class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
private static final String TAG = "FaceInternalEnumerateClient";
FaceInternalEnumerateClient(@NonNull Context context,
......@@ -56,4 +59,9 @@ class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected int getModality() {
return BiometricsProtoEnums.MODALITY_FACE;
}
}
......@@ -52,6 +52,7 @@ import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricDanglingReceiver;
import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
......@@ -201,6 +202,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mBiometricHandlerProvider = biometricHandlerProvider;
initAuthenticationBroadcastReceiver();
initFaceDanglingBroadcastReceiver();
initSensors(resetLockoutRequiresChallenge, props);
}
......@@ -214,6 +216,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
});
}
private void initFaceDanglingBroadcastReceiver() {
new BiometricDanglingReceiver(mContext, BiometricsProtoEnums.MODALITY_FACE);
}
private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
if (resetLockoutRequiresChallenge) {
Slog.d(getTag(), "Adding HIDL configs");
......
......@@ -18,11 +18,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
......@@ -35,7 +37,8 @@ import java.util.function.Supplier;
* Fingerprint-specific internal client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
@VisibleForTesting
public class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
private static final String TAG = "FingerprintInternalEnumerateClient";
protected FingerprintInternalEnumerateClient(@NonNull Context context,
......@@ -56,4 +59,9 @@ class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSes
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected int getModality() {
return BiometricsProtoEnums.MODALITY_FINGERPRINT;
}
}
......@@ -58,6 +58,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricDanglingReceiver;
import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
......@@ -205,6 +206,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mBiometricHandlerProvider = biometricHandlerProvider;
initAuthenticationBroadcastReceiver();
initFingerprintDanglingBroadcastReceiver();
initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
}
......@@ -218,6 +220,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
});
}
private void initFingerprintDanglingBroadcastReceiver() {
new BiometricDanglingReceiver(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT);
}
private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
if (!resetLockoutRequiresHardwareAuthToken) {
......
/*
* 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 com.android.server.biometrics.sensors.BiometricNotificationUtils.NOTIFICATION_ID;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.NotificationManager;
import android.content.Intent;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BiometricDanglingReceiverTest {
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
private BiometricDanglingReceiver mBiometricDanglingReceiver;
@Rule
public final TestableContext mContext = spy(new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null));
@Mock
NotificationManager mNotificationManager;
@Mock
Intent mIntent;
@Captor
private ArgumentCaptor<Intent> mArgumentCaptor;
@Before
public void setUp() {
mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
}
@Test
public void testFingerprintRegisterReceiver() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT);
verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
}
@Test
public void testFaceRegisterReceiver() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE);
verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
}
@Test
public void testOnReceive_fingerprintReEnrollLaunch() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT);
when(mIntent.getAction()).thenReturn(
BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH);
mBiometricDanglingReceiver.onReceive(mContext, mIntent);
// Verify fingerprint enroll process is launched.
verify(mContext).startActivity(mArgumentCaptor.capture());
assertThat(mArgumentCaptor.getValue().getAction())
.isEqualTo(Settings.ACTION_FINGERPRINT_ENROLL);
// Verify notification is canceled
verify(mNotificationManager).cancelAsUser("FingerprintReEnroll", NOTIFICATION_ID,
UserHandle.CURRENT);
// Verify receiver is unregistered after receiving the broadcast
verify(mContext).unregisterReceiver(mBiometricDanglingReceiver);
}
@Test
public void testOnReceive_faceReEnrollLaunch() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE);
when(mIntent.getAction()).thenReturn(
BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_LAUNCH);
mBiometricDanglingReceiver.onReceive(mContext, mIntent);
// Verify face enroll process is launched.
verify(mContext).startActivity(mArgumentCaptor.capture());
assertThat(mArgumentCaptor.getValue().getAction())
.isEqualTo(BiometricDanglingReceiver.FACE_SETTINGS_ACTION);
// Verify notification is canceled
verify(mNotificationManager).cancelAsUser("FaceReEnroll", NOTIFICATION_ID,
UserHandle.CURRENT);
// Verify receiver is unregistered after receiving the broadcast.
verify(mContext).unregisterReceiver(mBiometricDanglingReceiver);
}
private void initBroadcastReceiver(int modality) {
mBiometricDanglingReceiver = new BiometricDanglingReceiver(mContext, modality);
}
}
......@@ -1228,6 +1228,11 @@ public class BiometricSchedulerTest {
Slog.d(TAG, "TestInternalEnumerateClient#startHalOperation");
onEnumerationResult(TEST_FINGERPRINT, 0 /* remaining */);
}
@Override
protected int getModality() {
return BiometricsProtoEnums.MODALITY_FINGERPRINT;
}
}
private static class TestRemovalClient extends RemovalClient<Fingerprint, Object> {
......
......@@ -20,8 +20,10 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
......@@ -79,15 +81,21 @@ public class FaceInternalEnumerateClientTest {
private final int mBiometricId = 1;
private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
private FaceInternalEnumerateClient mClient;
private boolean mNotificationSent;
@Before
public void setUp() {
when(mAidlSession.getSession()).thenReturn(mSession);
final List<Face> enrolled = new ArrayList<>();
enrolled.add(mFace);
mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext);
mClient = spy(new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext));
mNotificationSent = false;
doAnswer(invocation -> {
mNotificationSent = true;
return null;
}).when(mClient).sendDanglingNotification(anyList());
}
@Test
......@@ -101,6 +109,7 @@ public class FaceInternalEnumerateClientTest {
verify(mSession).enumerateEnrollments();
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
assertThat(mNotificationSent).isFalse();
verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
verify(mCallback).onClientFinished(mClient, true);
}
......@@ -116,6 +125,7 @@ public class FaceInternalEnumerateClientTest {
verify(mSession).enumerateEnrollments();
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
assertThat(mNotificationSent).isFalse();
verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
verify(mCallback, never()).onClientFinished(mClient, true);
}
......@@ -131,6 +141,7 @@ public class FaceInternalEnumerateClientTest {
verify(mSession).enumerateEnrollments();
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
assertThat(mNotificationSent).isTrue();
verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
verify(mCallback).onClientFinished(mClient, true);
}
......@@ -147,6 +158,7 @@ public class FaceInternalEnumerateClientTest {
verify(mSession).enumerateEnrollments();
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
assertThat(mNotificationSent).isFalse();
verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
verify(mCallback, never()).onClientFinished(mClient, true);
}
......@@ -164,6 +176,7 @@ public class FaceInternalEnumerateClientTest {
verify(mSession).enumerateEnrollments();
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
assertThat(mNotificationSent).isTrue();
verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
verify(mCallback).onClientFinished(mClient, true);
}
......
......@@ -20,8 +20,10 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
......@@ -80,15 +82,23 @@ public class FingerprintInternalEnumerateClientTest {
private FingerprintInternalEnumerateClient mClient;
private boolean mNotificationSent;
@Before
public void setUp() {
when(mAidlSession.getSession()).thenReturn(mSession);
List<Fingerprint> enrolled = new ArrayList<>();
enrolled.add(new Fingerprint("one", 1, 1));
mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
mClient = spy(new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger,
mBiometricContext);
mBiometricContext));
mNotificationSent = false;
doAnswer(invocation -> {
mNotificationSent = true;
return null;
}).when(mClient).sendDanglingNotification(anyList());
}
@Test
......@@ -104,6 +114,7 @@ public class FingerprintInternalEnumerateClientTest {
assertThat(mClient.getUnknownHALTemplates().stream()
.flatMap(x -> Stream.of(x.getBiometricId()))
.collect(Collectors.toList())).containsExactly(2, 3);
assertThat(mNotificationSent).isTrue();
verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1);
verify(mCallback).onClientFinished(mClient, true);
}
......@@ -118,6 +129,7 @@ public class FingerprintInternalEnumerateClientTest {
verify(mSession).enumerateEnrollments();
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
assertThat(mNotificationSent).isFalse();
verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
verify(mCallback).onClientFinished(mClient, true);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment