diff options
6 files changed, 184 insertions, 9 deletions
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 378d9ebdaaa8..bad484fa3807 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -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, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 7cece1f5cecc..f9ac02271a27 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -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(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index 65d6f45b5c6c..fcd98e0742ea 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -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 diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 7354ad4b9ac3..5818133aa2a4 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -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"; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java index 8e0d7be5f44f..2a169b775ca3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java @@ -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() { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java index b33253264317..2b9a05c3ef63 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java @@ -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,12 +71,21 @@ 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. } |