Launch settings for clicking fingerprint unlock

Launch FingerprintSettings directly when user clicks "Fingerprint
Unlock". Let Fingerprint settings peform confirmLock() or chooseLock().
And to have smoothly animation, instead of generating challenge in
FingerprintSettings::onActivityResult(), challenge is generated in next
visible activity, and pass it back through next activity result.

Bug: 197717071
Test: atest GatekeeperPasswordProviderTest CredentialModelTest
Test: atest AutoCredentialViewModelTest FingerprintStatusUtilsTest
Test: RunSettingsRoboTests2 FingerprintEnrollIntroductionTest
Test: Manually test fingerprint enroll in settings or suw
Change-Id: Ie27c3c493ea475f6b53cb6bb3f2d45d555f47cb3
This commit is contained in:
Milton Wu
2022-12-05 03:09:58 +00:00
parent 58c3318e19
commit 160661dc6d
14 changed files with 899 additions and 131 deletions

View File

@@ -16,9 +16,20 @@
package com.android.settings.biometrics.fingerprint;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.content.Intent.EXTRA_USER_ID;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -32,8 +43,15 @@ import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.UserManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.settings.R;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -42,11 +60,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@@ -54,17 +72,25 @@ import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class FingerprintEnrollIntroductionTest {
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private FingerprintManager mFingerprintManager;
@Mock private UserManager mUserManager;
private GatekeeperPasswordProvider mGatekeeperPasswordProvider;
private Context mContext;
private FingerprintEnrollIntroduction mFingerprintEnrollIntroduction;
private TestFingerprintEnrollIntroduction mFingerprintEnrollIntroduction;
private static final int MAX_ENROLLMENTS = 5;
private static final byte[] EXPECTED_TOKEN = new byte[] { 10, 20, 30, 40 };
private static final long EXPECTED_CHALLENGE = 9876L;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mGatekeeperPasswordProvider = new GatekeeperPasswordProvider(mLockPatternUtils);
mContext = spy(RuntimeEnvironment.application.getApplicationContext());
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -79,14 +105,34 @@ public class FingerprintEnrollIntroductionTest {
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
props.add(prop);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
when(mUserManager.getCredentialOwnerProfile(anyInt())).thenAnswer(
(Answer<Integer>) invocation -> (int) invocation.getArgument(0));
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), anyInt()))
.thenAnswer((Answer<VerifyCredentialResponse>) invocation ->
newGoodCredential(invocation.getArgument(0), EXPECTED_TOKEN));
doNothing().when(mLockPatternUtils).removeGatekeeperPasswordHandle(anyLong());
}
void setupFingerprintEnrollIntroWith(Intent intent) {
ActivityController<FingerprintEnrollIntroduction> controller =
Robolectric.buildActivity(FingerprintEnrollIntroduction.class, intent);
mFingerprintEnrollIntroduction = spy(controller.get());
ReflectionHelpers.setField(
mFingerprintEnrollIntroduction, "mFingerprintManager", mFingerprintManager);
void setupFingerprintEnrollIntroWith(@NonNull Intent intent) {
final ActivityController<TestFingerprintEnrollIntroduction> controller =
Robolectric.buildActivity(TestFingerprintEnrollIntroduction.class, intent);
mFingerprintEnrollIntroduction = controller.get();
mFingerprintEnrollIntroduction.mMockedFingerprintManager = mFingerprintManager;
mFingerprintEnrollIntroduction.mMockedGatekeeperPasswordProvider =
mGatekeeperPasswordProvider;
mFingerprintEnrollIntroduction.mMockedLockPatternUtils = mLockPatternUtils;
mFingerprintEnrollIntroduction.mMockedUserManager = mUserManager;
mFingerprintEnrollIntroduction.mNewSensorId = 1;
mFingerprintEnrollIntroduction.mNewChallenge = EXPECTED_CHALLENGE;
final int userId = intent.getIntExtra(EXTRA_USER_ID, 0);
when(mLockPatternUtils.getActivePasswordQuality(userId))
.thenReturn(PASSWORD_QUALITY_SOMETHING);
controller.create();
}
@@ -102,7 +148,7 @@ public class FingerprintEnrollIntroductionTest {
@Test
public void intro_CheckCanEnrollNormal() {
setupFingerprintEnrollIntroWith(new Intent());
setupFingerprintEnrollIntroWith(newTokenOnlyIntent());
setFingerprintManagerToHave(3 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -111,7 +157,7 @@ public class FingerprintEnrollIntroductionTest {
@Test
public void intro_CheckMaxEnrolledNormal() {
setupFingerprintEnrollIntroWith(new Intent());
setupFingerprintEnrollIntroWith(newTokenOnlyIntent());
setFingerprintManagerToHave(7 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -126,10 +172,7 @@ public class FingerprintEnrollIntroductionTest {
when(resources.getInteger(anyInt())).thenReturn(5);
when(mContext.getResources()).thenReturn(resources);
setupFingerprintEnrollIntroWith(
new Intent()
.putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true)
.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true));
setupFingerprintEnrollIntroWith(newFirstSuwIntent());
setFingerprintManagerToHave(0 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -144,10 +187,7 @@ public class FingerprintEnrollIntroductionTest {
when(mContext.getResources()).thenReturn(resources);
when(resources.getInteger(anyInt())).thenReturn(1);
setupFingerprintEnrollIntroWith(
new Intent()
.putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true)
.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true));
setupFingerprintEnrollIntroWith(newFirstSuwIntent());
setFingerprintManagerToHave(1 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -156,8 +196,7 @@ public class FingerprintEnrollIntroductionTest {
@Test
public void intro_CheckCanEnrollDuringDeferred() {
setupFingerprintEnrollIntroWith(
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true));
setupFingerprintEnrollIntroWith(newDeferredSuwIntent());
setFingerprintManagerToHave(2 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -166,8 +205,7 @@ public class FingerprintEnrollIntroductionTest {
@Test
public void intro_CheckMaxEnrolledDuringDeferred() {
setupFingerprintEnrollIntroWith(
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true));
setupFingerprintEnrollIntroWith(newDeferredSuwIntent());
setFingerprintManagerToHave(6 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -176,8 +214,7 @@ public class FingerprintEnrollIntroductionTest {
@Test
public void intro_CheckCanEnrollDuringPortal() {
setupFingerprintEnrollIntroWith(
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, true));
setupFingerprintEnrollIntroWith(newPortalSuwIntent());
setFingerprintManagerToHave(2 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -186,11 +223,124 @@ public class FingerprintEnrollIntroductionTest {
@Test
public void intro_CheckMaxEnrolledDuringPortal() {
setupFingerprintEnrollIntroWith(
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, true));
setupFingerprintEnrollIntroWith(newPortalSuwIntent());
setFingerprintManagerToHave(6 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
assertThat(result).isEqualTo(R.string.fingerprint_intro_error_max);
}
@Test
public void intro_CheckGenerateChallenge() {
setupFingerprintEnrollIntroWith(newGkPwHandleAndFromSettingsIntent());
final long challengeField = mFingerprintEnrollIntroduction.getChallengeField();
assertThat(challengeField).isEqualTo(EXPECTED_CHALLENGE);
final byte[] token = mFingerprintEnrollIntroduction.getTokenField();
assertThat(token).isNotNull();
assertThat(token.length).isEqualTo(EXPECTED_TOKEN.length);
for (int i = 0; i < token.length; ++i) {
assertWithMessage("token[" + i + "] is " + token[i] + " not " + EXPECTED_TOKEN[i])
.that(token[i]).isEqualTo(EXPECTED_TOKEN[i]);
}
final Intent resultIntent = mFingerprintEnrollIntroduction.getSetResultIntentExtra(null);
assertThat(resultIntent).isNotNull();
assertThat(resultIntent.getLongExtra(EXTRA_KEY_CHALLENGE, -1L)).isEqualTo(challengeField);
final byte[] token2 = resultIntent.getByteArrayExtra(EXTRA_KEY_CHALLENGE_TOKEN);
assertThat(token2).isNotNull();
assertThat(token2.length).isEqualTo(EXPECTED_TOKEN.length);
for (int i = 0; i < token2.length; ++i) {
assertWithMessage("token2[" + i + "] is " + token2[i] + " not " + EXPECTED_TOKEN[i])
.that(token2[i]).isEqualTo(EXPECTED_TOKEN[i]);
}
}
private Intent newTokenOnlyIntent() {
return new Intent()
.putExtra(EXTRA_KEY_CHALLENGE_TOKEN, new byte[] { 1 });
}
private Intent newFirstSuwIntent() {
return newTokenOnlyIntent()
.putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true)
.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true);
}
private Intent newDeferredSuwIntent() {
return newTokenOnlyIntent()
.putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true);
}
private Intent newPortalSuwIntent() {
return newTokenOnlyIntent()
.putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, true);
}
private Intent newGkPwHandleAndFromSettingsIntent() {
return new Intent()
.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true)
.putExtra(EXTRA_KEY_GK_PW_HANDLE, 1L);
}
private VerifyCredentialResponse newGoodCredential(long gkPwHandle, @NonNull byte[] hat) {
return new VerifyCredentialResponse.Builder()
.setGatekeeperPasswordHandle(gkPwHandle)
.setGatekeeperHAT(hat)
.build();
}
public static class TestFingerprintEnrollIntroduction
extends FingerprintEnrollIntroduction {
public FingerprintManager mMockedFingerprintManager;
public GatekeeperPasswordProvider mMockedGatekeeperPasswordProvider;
public LockPatternUtils mMockedLockPatternUtils;
public UserManager mMockedUserManager;
public int mNewSensorId;
public long mNewChallenge;
@Nullable
public byte[] getTokenField() {
return mToken;
}
public long getChallengeField() {
return mChallenge;
}
@Override
protected boolean isDisabledByAdmin() {
return false;
}
@Nullable
@Override
protected FingerprintManager getFingerprintManager() {
return mMockedFingerprintManager;
}
@Override
protected UserManager getUserManager() {
return mMockedUserManager;
}
@NonNull
@Override
protected GatekeeperPasswordProvider getGatekeeperPasswordProvider() {
return mMockedGatekeeperPasswordProvider;
}
@NonNull
@Override
protected LockPatternUtils getLockPatternUtils() {
return mMockedLockPatternUtils;
}
@Override
protected void getChallenge(GenerateChallengeCallback callback) {
callback.onChallengeGenerated(mNewSensorId, mUserId, mNewChallenge);
}
}
}