Remove all biometrics data of a user when password is cleared.
Previously, the biometrics were only cleared if the password was cleared from the Settings.
Moved the logic from the Settings app to the system server side.
Now, the biometrics will be removed no matter how the password is cleared (Settings, adb, TestDPC).
Bug: 130653263
Test: Atest LockSettingsServiceTests
manual testing from Settings, adb and TestDPC
Change-Id: I840856b2e2db5e595d33583fd79359fde6bccb6f
This commit is contained in:
@@ -61,7 +61,10 @@ import android.database.ContentObserver;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.hardware.authsecret.V1_0.IAuthSecret;
|
||||
import android.hardware.biometrics.BiometricManager;
|
||||
import android.hardware.face.Face;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
@@ -117,6 +120,7 @@ import com.android.internal.widget.LockPatternUtils.CredentialType;
|
||||
import com.android.internal.widget.LockSettingsInternal;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
|
||||
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
|
||||
@@ -387,8 +391,15 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return new Handler();
|
||||
public ServiceThread getServiceThread() {
|
||||
ServiceThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
|
||||
true /*allowIo*/);
|
||||
handlerThread.start();
|
||||
return handlerThread;
|
||||
}
|
||||
|
||||
public Handler getHandler(ServiceThread handlerThread) {
|
||||
return new Handler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
public LockSettingsStorage getStorage() {
|
||||
@@ -483,6 +494,23 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
public boolean isGsiRunning() {
|
||||
return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
|
||||
}
|
||||
|
||||
public FingerprintManager getFingerprintManager() {
|
||||
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
||||
return (FingerprintManager) mContext.getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public FaceManager getFaceManager() {
|
||||
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
|
||||
return (FaceManager) mContext.getSystemService(Context.FACE_SERVICE);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public LockSettingsService(Context context) {
|
||||
@@ -495,7 +523,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
mContext = injector.getContext();
|
||||
mKeyStore = injector.getKeyStore();
|
||||
mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(mKeyStore);
|
||||
mHandler = injector.getHandler();
|
||||
mHandler = injector.getHandler(injector.getServiceThread());
|
||||
mStrongAuth = injector.getStrongAuth();
|
||||
mActivityManager = injector.getActivityManager();
|
||||
|
||||
@@ -2713,6 +2741,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
fixateNewestUserKeyAuth(userId);
|
||||
unlockKeystore(auth.deriveKeyStorePassword(), userId);
|
||||
setKeystorePassword(null, userId);
|
||||
removeBiometricsForUser(userId);
|
||||
}
|
||||
setSyntheticPasswordHandleLocked(newHandle, userId);
|
||||
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
|
||||
@@ -2728,6 +2757,85 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
return newHandle;
|
||||
}
|
||||
|
||||
private void removeBiometricsForUser(int userId) {
|
||||
removeAllFingerprintForUser(userId);
|
||||
removeAllFaceForUser(userId);
|
||||
}
|
||||
|
||||
private void removeAllFingerprintForUser(final int userId) {
|
||||
FingerprintManager mFingerprintManager = mInjector.getFingerprintManager();
|
||||
if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
|
||||
if (mFingerprintManager.hasEnrolledFingerprints(userId)) {
|
||||
mFingerprintManager.setActiveUser(userId);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
// For the purposes of M and N, groupId is the same as userId.
|
||||
Fingerprint finger = new Fingerprint(null, userId, 0, 0);
|
||||
mFingerprintManager.remove(finger, userId,
|
||||
fingerprintManagerRemovalCallback(latch));
|
||||
try {
|
||||
latch.await(10000, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Slog.e(TAG, "Latch interrupted when removing fingerprint", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeAllFaceForUser(final int userId) {
|
||||
FaceManager mFaceManager = mInjector.getFaceManager();
|
||||
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
|
||||
if (mFaceManager.hasEnrolledTemplates(userId)) {
|
||||
mFaceManager.setActiveUser(userId);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Face face = new Face(null, 0, 0);
|
||||
mFaceManager.remove(face, userId, faceManagerRemovalCallback(latch));
|
||||
try {
|
||||
latch.await(10000, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Slog.e(TAG, "Latch interrupted when removing face", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FingerprintManager.RemovalCallback fingerprintManagerRemovalCallback(
|
||||
CountDownLatch latch) {
|
||||
return new FingerprintManager.RemovalCallback() {
|
||||
@Override
|
||||
public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence err) {
|
||||
Slog.e(TAG, String.format(
|
||||
"Can't remove fingerprint %d in group %d. Reason: %s",
|
||||
fp.getBiometricId(), fp.getGroupId(), err));
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemovalSucceeded(Fingerprint fp, int remaining) {
|
||||
if (remaining == 0) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private FaceManager.RemovalCallback faceManagerRemovalCallback(CountDownLatch latch) {
|
||||
return new FaceManager.RemovalCallback() {
|
||||
@Override
|
||||
public void onRemovalError(Face face, int errMsgId, CharSequence err) {
|
||||
Slog.e(TAG, String.format("Can't remove face %d. Reason: %s",
|
||||
face.getBiometricId(), err));
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemovalSucceeded(Face face, int remaining) {
|
||||
if (remaining == 0) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@GuardedBy("mSpManager")
|
||||
private boolean spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
|
||||
byte[] savedCredential, int requestedQuality, int userId,
|
||||
|
||||
@@ -32,8 +32,11 @@ import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.app.admin.DeviceStateCache;
|
||||
import android.app.trust.TrustManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.hardware.authsecret.V1_0.IAuthSecret;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.FileUtils;
|
||||
import android.os.IProgressListener;
|
||||
import android.os.RemoteException;
|
||||
@@ -95,6 +98,9 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
|
||||
RecoverableKeyStoreManager mRecoverableKeyStoreManager;
|
||||
UserManagerInternal mUserManagerInternal;
|
||||
DeviceStateCache mDeviceStateCache;
|
||||
FingerprintManager mFingerprintManager;
|
||||
FaceManager mFaceManager;
|
||||
PackageManager mPackageManager;
|
||||
protected boolean mHasSecureLockScreen;
|
||||
|
||||
@Override
|
||||
@@ -114,6 +120,9 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
|
||||
mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class);
|
||||
mUserManagerInternal = mock(UserManagerInternal.class);
|
||||
mDeviceStateCache = mock(DeviceStateCache.class);
|
||||
mFingerprintManager = mock(FingerprintManager.class);
|
||||
mFaceManager = mock(FaceManager.class);
|
||||
mPackageManager = mock(PackageManager.class);
|
||||
|
||||
LocalServices.removeServiceForTest(LockSettingsInternal.class);
|
||||
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
|
||||
@@ -123,7 +132,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
|
||||
|
||||
mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
|
||||
mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
|
||||
mock(KeyguardManager.class));
|
||||
mock(KeyguardManager.class), mFingerprintManager, mFaceManager, mPackageManager);
|
||||
mStorage = new LockSettingsStorageTestable(mContext,
|
||||
new File(getContext().getFilesDir(), "locksettings"));
|
||||
File storageDir = mStorage.mStorageDir;
|
||||
@@ -181,6 +190,8 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
|
||||
new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
|
||||
when(mUserManagerInternal.isDeviceManaged()).thenReturn(true);
|
||||
when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
|
||||
mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
|
||||
mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
|
||||
|
||||
mLocalService = LocalServices.getService(LockSettingsInternal.class);
|
||||
}
|
||||
@@ -233,6 +244,18 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
|
||||
return sm;
|
||||
}
|
||||
|
||||
private void mockBiometricsHardwareFingerprintsAndTemplates(int userId) {
|
||||
// Hardware must be detected and fingerprints must be enrolled
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
|
||||
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
|
||||
when(mFingerprintManager.hasEnrolledFingerprints(userId)).thenReturn(true);
|
||||
|
||||
// Hardware must be detected and templates must be enrolled
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
|
||||
when(mFaceManager.isHardwareDetected()).thenReturn(true);
|
||||
when(mFaceManager.hasEnrolledTemplates(userId)).thenReturn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.app.admin.DeviceStateCache;
|
||||
import android.content.Context;
|
||||
import android.hardware.authsecret.V1_0.IAuthSecret;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserManagerInternal;
|
||||
@@ -32,6 +31,7 @@ import android.security.KeyStore;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -70,8 +70,8 @@ public class LockSettingsServiceTestable extends LockSettingsService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Handler getHandler() {
|
||||
return new Handler(Looper.getMainLooper());
|
||||
public Handler getHandler(ServiceThread handlerThread) {
|
||||
return new Handler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -330,6 +330,27 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
|
||||
.lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, MANAGED_PROFILE_USER_ID);
|
||||
}
|
||||
|
||||
public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
|
||||
final String oldCredential = "oldPassword";
|
||||
|
||||
initializeStorageWithCredential(
|
||||
PRIMARY_USER_ID,
|
||||
oldCredential,
|
||||
CREDENTIAL_TYPE_PATTERN,
|
||||
PASSWORD_QUALITY_SOMETHING);
|
||||
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
|
||||
|
||||
mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, oldCredential.getBytes(),
|
||||
PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
|
||||
|
||||
// Verify fingerprint is removed
|
||||
verify(mFingerprintManager).remove(any(), eq(PRIMARY_USER_ID), any());
|
||||
verify(mFaceManager).remove(any(), eq(PRIMARY_USER_ID), any());
|
||||
|
||||
verify(mFingerprintManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
|
||||
verify(mFaceManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
|
||||
}
|
||||
|
||||
public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
|
||||
throws Exception {
|
||||
final String parentPassword = "parentPassword";
|
||||
|
||||
@@ -24,8 +24,11 @@ import android.app.KeyguardManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.trust.TrustManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.FileUtils;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
@@ -86,7 +89,9 @@ public class LockSettingsStorageTests extends AndroidTestCase {
|
||||
|
||||
MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
|
||||
mock(NotificationManager.class), mock(DevicePolicyManager.class),
|
||||
mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class));
|
||||
mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class),
|
||||
mock(FingerprintManager.class), mock(FaceManager.class),
|
||||
mock(PackageManager.class));
|
||||
mStorage = new LockSettingsStorageTestable(context,
|
||||
new File(getContext().getFilesDir(), "locksettings"));
|
||||
mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
|
||||
|
||||
@@ -23,6 +23,8 @@ import android.app.trust.TrustManager;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.UserManager;
|
||||
import android.os.storage.StorageManager;
|
||||
|
||||
@@ -34,11 +36,15 @@ public class MockLockSettingsContext extends ContextWrapper {
|
||||
private StorageManager mStorageManager;
|
||||
private TrustManager mTrustManager;
|
||||
private KeyguardManager mKeyguardManager;
|
||||
private FingerprintManager mFingerprintManager;
|
||||
private FaceManager mFaceManager;
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
public MockLockSettingsContext(Context base, UserManager userManager,
|
||||
NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
|
||||
StorageManager storageManager, TrustManager trustManager,
|
||||
KeyguardManager keyguardManager) {
|
||||
KeyguardManager keyguardManager, FingerprintManager fingerprintManager,
|
||||
FaceManager faceManager, PackageManager packageManager) {
|
||||
super(base);
|
||||
mUserManager = userManager;
|
||||
mNotificationManager = notificationManager;
|
||||
@@ -46,6 +52,9 @@ public class MockLockSettingsContext extends ContextWrapper {
|
||||
mStorageManager = storageManager;
|
||||
mTrustManager = trustManager;
|
||||
mKeyguardManager = keyguardManager;
|
||||
mFingerprintManager = fingerprintManager;
|
||||
mFaceManager = faceManager;
|
||||
mPackageManager = packageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,11 +71,20 @@ public class MockLockSettingsContext extends ContextWrapper {
|
||||
return mTrustManager;
|
||||
} else if (KEYGUARD_SERVICE.equals(name)) {
|
||||
return mKeyguardManager;
|
||||
} else if (FINGERPRINT_SERVICE.equals(name)) {
|
||||
return mFingerprintManager;
|
||||
} else if (FACE_SERVICE.equals(name)) {
|
||||
return mFaceManager;
|
||||
} else {
|
||||
throw new RuntimeException("System service not mocked: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageManager getPackageManager() {
|
||||
return mPackageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enforceCallingOrSelfPermission(String permission, String message) {
|
||||
// Skip permission checks for unit tests.
|
||||
|
||||
Reference in New Issue
Block a user