Merge "Add enrollment sync command for virtual sensors."
This commit is contained in:
@@ -9658,6 +9658,13 @@ public final class Settings {
|
||||
public static final String BIOMETRIC_DEBUG_ENABLED =
|
||||
"biometric_debug_enabled";
|
||||
|
||||
/**
|
||||
* Whether or not virtual sensors are enabled.
|
||||
* @hide
|
||||
*/
|
||||
@Readable
|
||||
public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled";
|
||||
|
||||
/**
|
||||
* Whether or not biometric is allowed on Keyguard.
|
||||
* @hide
|
||||
|
||||
@@ -53,6 +53,7 @@ import android.hardware.biometrics.SensorProperties;
|
||||
import android.hardware.biometrics.SensorPropertiesInternal;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
@@ -87,6 +88,13 @@ public class Utils {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** If virtualized biometrics are supported (requires debug build). */
|
||||
public static boolean isVirtualEnabled(Context context) {
|
||||
return Build.isDebuggable()
|
||||
&& Settings.Secure.getIntForUser(context.getContentResolver(),
|
||||
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines {@link PromptInfo#setDeviceCredentialAllowed(boolean)} with
|
||||
* {@link PromptInfo#setAuthenticators(int)}, as the former is not flexible enough.
|
||||
@@ -374,6 +382,15 @@ public class Utils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Same as checkPermission but also allows shell. */
|
||||
public static void checkPermissionOrShell(Context context, String permission) {
|
||||
if (Binder.getCallingUid() == Process.SHELL_UID) {
|
||||
return;
|
||||
}
|
||||
checkPermission(context, permission);
|
||||
}
|
||||
|
||||
|
||||
public static void checkPermission(Context context, String permission) {
|
||||
context.enforceCallingOrSelfPermission(permission,
|
||||
"Must have " + permission + " permission.");
|
||||
|
||||
@@ -19,9 +19,11 @@ package com.android.server.biometrics.sensors;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.os.Build;
|
||||
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;
|
||||
@@ -65,6 +67,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
|
||||
private final List<S> mEnrolledList;
|
||||
private final boolean mHasEnrollmentsBeforeStarting;
|
||||
private BaseClientMonitor mCurrentTask;
|
||||
private boolean mFavorHalEnrollments = false;
|
||||
|
||||
private final ClientMonitorCallback mEnumerateCallback = new ClientMonitorCallback() {
|
||||
@Override
|
||||
@@ -87,7 +90,21 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
|
||||
// InternalEnumerateClient. Finish this client.
|
||||
mCallback.onClientFinished(InternalCleanupClient.this, success);
|
||||
} else {
|
||||
startCleanupUnknownHalTemplates();
|
||||
if (mFavorHalEnrollments && Build.isDebuggable()) {
|
||||
// on debug builds, optionally allow the HAL be the source of
|
||||
// truth for enrollments
|
||||
try {
|
||||
for (UserTemplate template : mUnknownHALTemplates) {
|
||||
Slog.i(TAG, "Adding unknown HAL template: "
|
||||
+ template.mIdentifier.getBiometricId());
|
||||
onAddUnknownTemplate(template.mUserId, template.mIdentifier);
|
||||
}
|
||||
} finally {
|
||||
mCallback.onClientFinished(InternalCleanupClient.this, success);
|
||||
}
|
||||
} else {
|
||||
startCleanupUnknownHalTemplates();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -197,8 +214,27 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
|
||||
((EnumerateConsumer) mCurrentTask).onEnumerationResult(identifier, remaining);
|
||||
}
|
||||
|
||||
/** When set unknown templates in the HAL will be added instead of deleted. */
|
||||
public void setFavorHalEnrollments() {
|
||||
mFavorHalEnrollments = true;
|
||||
}
|
||||
|
||||
/** Called when an unknown template is found and setFavorHalEnrollments was requested. */
|
||||
protected void onAddUnknownTemplate(int userId,
|
||||
@NonNull BiometricAuthenticator.Identifier identifier) {}
|
||||
|
||||
@Override
|
||||
public int getProtoEnum() {
|
||||
return BiometricsProto.CM_INTERNAL_CLEANUP;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public InternalEnumerateClient<T> getCurrentEnumerateClient() {
|
||||
return (InternalEnumerateClient<T>) mCurrentTask;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public RemovalClient<S, T> getCurrentRemoveClient() {
|
||||
return (RemovalClient<S, T>) mCurrentTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
|
||||
@NonNull Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
|
||||
logger, biometricContext);
|
||||
//, BiometricsProtoEnums.ACTION_REMOVE,
|
||||
// BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mBiometricUtils = utils;
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
|
||||
|
||||
@@ -18,6 +18,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.face.IFace;
|
||||
import android.hardware.face.Face;
|
||||
import android.os.IBinder;
|
||||
@@ -28,6 +29,7 @@ import com.android.server.biometrics.sensors.BiometricUtils;
|
||||
import com.android.server.biometrics.sensors.InternalCleanupClient;
|
||||
import com.android.server.biometrics.sensors.InternalEnumerateClient;
|
||||
import com.android.server.biometrics.sensors.RemovalClient;
|
||||
import com.android.server.biometrics.sensors.face.FaceUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -68,4 +70,11 @@ class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession>
|
||||
null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
|
||||
utils, sensorId, logger, biometricContext, authenticatorIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddUnknownTemplate(int userId,
|
||||
@NonNull BiometricAuthenticator.Identifier identifier) {
|
||||
FaceUtils.getInstance(getSensorId()).addBiometricForUser(
|
||||
getContext(), getTargetUserId(), (Face) identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -67,7 +68,9 @@ import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ShellCallback;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
@@ -620,6 +623,15 @@ public class FingerprintService extends SystemService {
|
||||
mLockoutResetDispatcher.addCallback(callback, opPackageName);
|
||||
}
|
||||
|
||||
@Override // Binder call
|
||||
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
|
||||
@Nullable FileDescriptor err, @NonNull String[] args,
|
||||
@Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver)
|
||||
throws RemoteException {
|
||||
(new FingerprintShellCommand(getContext(), FingerprintService.this))
|
||||
.exec(this, in, out, err, args, callback, resultReceiver);
|
||||
}
|
||||
|
||||
@Override // Binder call
|
||||
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
|
||||
@@ -1172,4 +1184,18 @@ public class FingerprintService extends SystemService {
|
||||
}
|
||||
return appOpsOk;
|
||||
}
|
||||
|
||||
void syncEnrollmentsNow() {
|
||||
Utils.checkPermissionOrShell(getContext(), MANAGE_FINGERPRINT);
|
||||
if (Utils.isVirtualEnabled(getContext())) {
|
||||
Slog.i(TAG, "Sync virtual enrollments");
|
||||
final int userId = ActivityManager.getCurrentUser();
|
||||
for (ServiceProvider provider : mServiceProviders) {
|
||||
for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) {
|
||||
provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
|
||||
true /* favorHalEnrollments */);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.fingerprint;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.ShellCommand;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/** Handles shell commands for {@link FingerprintService}. */
|
||||
public class FingerprintShellCommand extends ShellCommand {
|
||||
|
||||
private final Context mContext;
|
||||
private final FingerprintService mService;
|
||||
|
||||
public FingerprintShellCommand(Context context, FingerprintService service) {
|
||||
mContext = context;
|
||||
mService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(String cmd) {
|
||||
if (cmd == null) {
|
||||
onHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (cmd) {
|
||||
case "help":
|
||||
return doHelp();
|
||||
case "sync":
|
||||
return doSync();
|
||||
default:
|
||||
getOutPrintWriter().println("Unrecognized command: " + cmd);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getOutPrintWriter().println("Exception: " + e);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHelp() {
|
||||
PrintWriter pw = getOutPrintWriter();
|
||||
pw.println("Fingerprint Service commands:");
|
||||
pw.println(" help");
|
||||
pw.println(" Print this help text.");
|
||||
pw.println(" sync");
|
||||
pw.println(" Sync enrollments now (virtualized sensors only).");
|
||||
}
|
||||
|
||||
private int doHelp() {
|
||||
onHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int doSync() {
|
||||
mService.syncEnrollmentsNow();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,9 @@ public interface ServiceProvider {
|
||||
void scheduleInternalCleanup(int sensorId, int userId,
|
||||
@Nullable ClientMonitorCallback callback);
|
||||
|
||||
void scheduleInternalCleanup(int sensorId, int userId,
|
||||
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
|
||||
|
||||
boolean isHardwareDetected(int sensorId);
|
||||
|
||||
void rename(int sensorId, int fingerId, int userId, @NonNull String name);
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.os.IBinder;
|
||||
@@ -72,4 +73,11 @@ class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint
|
||||
utils, sensorId, logger.swapAction(context, BiometricsProtoEnums.ACTION_REMOVE),
|
||||
biometricContext, authenticatorIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddUnknownTemplate(int userId,
|
||||
@NonNull BiometricAuthenticator.Identifier identifier) {
|
||||
FingerprintUtils.getInstance(getSensorId()).addBiometricForUser(
|
||||
getContext(), getTargetUserId(), (Fingerprint) identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +516,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
|
||||
@Override
|
||||
public void scheduleInternalCleanup(int sensorId, int userId,
|
||||
@Nullable ClientMonitorCallback callback) {
|
||||
scheduleInternalCleanup(sensorId, userId, callback, false /* favorHalEnrollments */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleInternalCleanup(int sensorId, int userId,
|
||||
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
|
||||
mHandler.post(() -> {
|
||||
final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
|
||||
final FingerprintInternalCleanupClient client =
|
||||
@@ -527,6 +533,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
|
||||
mBiometricContext,
|
||||
enrolledList, FingerprintUtils.getInstance(sensorId),
|
||||
mSensors.get(sensorId).getAuthenticatorIds());
|
||||
if (favorHalEnrollments) {
|
||||
client.setFavorHalEnrollments();
|
||||
}
|
||||
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
|
||||
mFingerprintStateCallback));
|
||||
});
|
||||
|
||||
@@ -757,6 +757,13 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
|
||||
mFingerprintStateCallback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleInternalCleanup(int sensorId, int userId,
|
||||
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
|
||||
scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
|
||||
mFingerprintStateCallback));
|
||||
}
|
||||
|
||||
private BiometricLogger createLogger(int statsAction, int statsClient) {
|
||||
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
|
||||
statsAction, statsClient);
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.fingerprint.aidl;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.biometrics.fingerprint.ISession;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.testing.TestableContext;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.biometrics.log.BiometricContext;
|
||||
import com.android.server.biometrics.log.BiometricLogger;
|
||||
import com.android.server.biometrics.sensors.ClientMonitorCallback;
|
||||
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Presubmit
|
||||
@SmallTest
|
||||
public class FingerprintInternalCleanupClientTest {
|
||||
|
||||
private static final int SENSOR_ID = 22;
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
@Rule
|
||||
public final TestableContext mContext = new TestableContext(
|
||||
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
|
||||
|
||||
@Mock
|
||||
private AidlSession mAidlSession;
|
||||
@Mock
|
||||
private ISession mSession;
|
||||
@Mock
|
||||
private BiometricLogger mLogger;
|
||||
@Mock
|
||||
private BiometricContext mBiometricContext;
|
||||
@Mock
|
||||
private FingerprintUtils mFingerprintUtils;
|
||||
@Mock
|
||||
private ClientMonitorCallback mCallback;
|
||||
|
||||
private FingerprintInternalCleanupClient mClient;
|
||||
private List<Integer> mAddedIds;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(mAidlSession.getSession()).thenReturn(mSession);
|
||||
mAddedIds = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Ignore("TODO(b/229015801): verify cleanup behavior")
|
||||
@Test
|
||||
public void removesUnknownTemplate() throws Exception {
|
||||
mClient = createClient();
|
||||
|
||||
final List<Fingerprint> templates = List.of(
|
||||
new Fingerprint("one", 1, 1),
|
||||
new Fingerprint("two", 2, 1)
|
||||
);
|
||||
mClient.start(mCallback);
|
||||
for (int i = templates.size() - 1; i >= 0; i--) {
|
||||
mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
|
||||
}
|
||||
for (int i = templates.size() - 1; i >= 0; i--) {
|
||||
mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
|
||||
}
|
||||
|
||||
assertThat(mAddedIds).isEmpty();
|
||||
final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
|
||||
verify(mSession, times(2)).removeEnrollments(captor.capture());
|
||||
assertThat(captor.getAllValues().stream()
|
||||
.flatMap(x -> Arrays.stream(x).boxed())
|
||||
.collect(Collectors.toList()))
|
||||
.containsExactly(1, 2);
|
||||
verify(mCallback).onClientFinished(eq(mClient), eq(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception {
|
||||
mClient = createClient();
|
||||
mClient.setFavorHalEnrollments();
|
||||
|
||||
final List<Fingerprint> templates = List.of(
|
||||
new Fingerprint("one", 1, 1),
|
||||
new Fingerprint("two", 2, 1)
|
||||
);
|
||||
mClient.start(mCallback);
|
||||
for (int i = templates.size() - 1; i >= 0; i--) {
|
||||
mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
|
||||
}
|
||||
|
||||
assertThat(mAddedIds).containsExactly(1, 2);
|
||||
verify(mSession, never()).removeEnrollments(any());
|
||||
verify(mCallback).onClientFinished(eq(mClient), eq(true));
|
||||
}
|
||||
|
||||
protected FingerprintInternalCleanupClient createClient() {
|
||||
final List<Fingerprint> enrollments = new ArrayList<>();
|
||||
final Map<Integer, Long> authenticatorIds = new HashMap<>();
|
||||
return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */,
|
||||
"the.test.owner", SENSOR_ID, mLogger, mBiometricContext, enrollments,
|
||||
mFingerprintUtils, authenticatorIds) {
|
||||
@Override
|
||||
protected void onAddUnknownTemplate(int userId,
|
||||
@NonNull BiometricAuthenticator.Identifier identifier) {
|
||||
mAddedIds.add(identifier.getBiometricId());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user