diff --git a/src/com/android/settings/biometrics2/factory/BiometricsFragmentFactory.java b/src/com/android/settings/biometrics2/factory/BiometricsFragmentFactory.java deleted file mode 100644 index 9a0cab21d5a..00000000000 --- a/src/com/android/settings/biometrics2/factory/BiometricsFragmentFactory.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.settings.biometrics2.factory; - -import android.app.Application; -import android.app.admin.DevicePolicyManager; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentFactory; -import androidx.lifecycle.ViewModelProvider; - -import com.android.settings.biometrics2.ui.view.FingerprintEnrollIntroFragment; - -/** - * Fragment factory for biometrics - */ -public class BiometricsFragmentFactory extends FragmentFactory { - - private final Application mApplication; - private final ViewModelProvider mViewModelProvider; - - public BiometricsFragmentFactory(Application application, - ViewModelProvider viewModelProvider) { - mApplication = application; - mViewModelProvider = viewModelProvider; - } - - @NonNull - @Override - public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { - final Class clazz = loadFragmentClass(classLoader, className); - if (FingerprintEnrollIntroFragment.class.equals(clazz)) { - final DevicePolicyManager devicePolicyManager = - mApplication.getSystemService(DevicePolicyManager.class); - if (devicePolicyManager != null) { - return new FingerprintEnrollIntroFragment(mViewModelProvider, - devicePolicyManager.getResources()); - } - } - return super.instantiate(classLoader, className); - } -} diff --git a/src/com/android/settings/biometrics2/ui/model/CredentialModel.java b/src/com/android/settings/biometrics2/ui/model/CredentialModel.java index 06caf5e6945..b943608be20 100644 --- a/src/com/android/settings/biometrics2/ui/model/CredentialModel.java +++ b/src/com/android/settings/biometrics2/ui/model/CredentialModel.java @@ -22,6 +22,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_C import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE; import android.content.Intent; +import android.os.Bundle; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -80,17 +81,30 @@ public final class CredentialModel { @Nullable private Long mClearGkPwHandleMillis = null; - public CredentialModel(@NonNull Intent intent, @NonNull Clock clock) { - mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); - mSensorId = intent.getIntExtra(EXTRA_KEY_SENSOR_ID, INVALID_SENSOR_ID); - mChallenge = intent.getLongExtra(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE); - mToken = intent.getByteArrayExtra(EXTRA_KEY_CHALLENGE_TOKEN); - mGkPwHandle = intent.getLongExtra(EXTRA_KEY_GK_PW_HANDLE, - INVALID_GK_PW_HANDLE); + public CredentialModel(@NonNull Bundle bundle, @NonNull Clock clock) { + mUserId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId()); + mSensorId = bundle.getInt(EXTRA_KEY_SENSOR_ID, INVALID_SENSOR_ID); + mChallenge = bundle.getLong(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE); + mToken = bundle.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN); + mGkPwHandle = bundle.getLong(EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE); mClock = clock; mInitMillis = mClock.millis(); } + /** + * Get a bundle which can be used to recreate CredentialModel + */ + @NonNull + public Bundle getBundle() { + final Bundle bundle = new Bundle(); + bundle.putInt(Intent.EXTRA_USER_ID, mUserId); + bundle.putInt(EXTRA_KEY_SENSOR_ID, mSensorId); + bundle.putLong(EXTRA_KEY_CHALLENGE, mChallenge); + bundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, mToken); + bundle.putLong(EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle); + return bundle; + } + /** * Get userId for this credential */ diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java index 2308f2ef9d1..14d859dbffc 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java @@ -25,7 +25,7 @@ import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroSt import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT; import android.app.Activity; -import android.app.admin.DevicePolicyResourcesManager; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; @@ -63,9 +63,6 @@ public class FingerprintEnrollIntroFragment extends Fragment { private static final String TAG = "FingerprintEnrollIntroFragment"; - @NonNull private final ViewModelProvider mViewModelProvider; - @Nullable private final DevicePolicyResourcesManager mDevicePolicyMgrRes; - private FingerprintEnrollIntroViewModel mViewModel = null; private View mView = null; @@ -75,12 +72,8 @@ public class FingerprintEnrollIntroFragment extends Fragment { private TextView mFooterMessage6 = null; @Nullable private PorterDuffColorFilter mIconColorFilter; - public FingerprintEnrollIntroFragment( - @NonNull ViewModelProvider viewModelProvider, - @Nullable DevicePolicyResourcesManager devicePolicyMgrRes) { + public FingerprintEnrollIntroFragment() { super(); - mViewModelProvider = viewModelProvider; - mDevicePolicyMgrRes = devicePolicyMgrRes; } @Nullable @@ -197,7 +190,8 @@ public class FingerprintEnrollIntroFragment extends Fragment { @Override public void onAttach(@NonNull Context context) { - mViewModel = mViewModelProvider.get(FingerprintEnrollIntroViewModel.class); + mViewModel = new ViewModelProvider(getActivity()) + .get(FingerprintEnrollIntroViewModel.class); getLifecycle().addObserver(mViewModel); super.onAttach(context); } @@ -232,12 +226,16 @@ public class FingerprintEnrollIntroFragment extends Fragment { private String getDescriptionDisabledByAdmin(@NonNull Context context) { final int defaultStrId = R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled; - if (mDevicePolicyMgrRes == null) { + + final DevicePolicyManager devicePolicyManager = getActivity() + .getSystemService(DevicePolicyManager.class); + if (devicePolicyManager != null) { + return devicePolicyManager.getResources().getString(FINGERPRINT_UNLOCK_DISABLED, + () -> context.getString(defaultStrId)); + } else { Log.w(TAG, "getDescriptionDisabledByAdmin, null device policy manager res"); return ""; } - return mDevicePolicyMgrRes.getString(FINGERPRINT_UNLOCK_DISABLED, - () -> context.getString(defaultStrId)); } private void setHeaderText(@NonNull Activity activity, int resId) { diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java index e9cf6fd49b1..991aeba74d2 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java @@ -19,20 +19,19 @@ package com.android.settings.biometrics2.ui.view; import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY; import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR; -import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK; +import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE; +import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH; import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL; -import android.app.Activity; import android.app.Application; import android.content.Intent; import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Bundle; -import android.os.SystemClock; import android.util.Log; import androidx.activity.result.ActivityResult; @@ -42,7 +41,6 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.viewmodel.CreationExtras; import androidx.lifecycle.viewmodel.MutableCreationExtras; @@ -51,11 +49,9 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor; -import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling; +import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor; import com.android.settings.biometrics2.data.repository.FingerprintRepository; -import com.android.settings.biometrics2.factory.BiometricsFragmentFactory; import com.android.settings.biometrics2.factory.BiometricsViewModelFactory; -import com.android.settings.biometrics2.ui.model.CredentialModel; import com.android.settings.biometrics2.ui.model.EnrollmentRequest; import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel; import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator; @@ -102,12 +98,12 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { getLifecycle().addObserver(mViewModel); mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class); - mAutoCredentialViewModel.setCredentialModel(new CredentialModel(getIntent(), - SystemClock.elapsedRealtimeClock())); - getLifecycle().addObserver(mAutoCredentialViewModel); + mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent()); + mAutoCredentialViewModel.getGenerateChallengeFailLiveData().observe(this, + this::onGenerateChallengeFail); mViewModel.getSetResultLiveData().observe(this, this::onSetActivityResult); - mAutoCredentialViewModel.getActionLiveData().observe(this, this::onCredentialAction); + checkCredential(); // Theme setTheme(mViewModel.getRequest().getTheme()); @@ -116,21 +112,29 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { // fragment setContentView(R.layout.biometric_enrollment_container); - final FragmentManager fragmentManager = getSupportFragmentManager(); - fragmentManager.setFragmentFactory( - new BiometricsFragmentFactory(getApplication(), viewModelProvider)); final FingerprintEnrollIntroViewModel fingerprintEnrollIntroViewModel = viewModelProvider.get(FingerprintEnrollIntroViewModel.class); fingerprintEnrollIntroViewModel.setEnrollmentRequest(mViewModel.getRequest()); fingerprintEnrollIntroViewModel.setUserId(mAutoCredentialViewModel.getUserId()); + + // Clear ActionLiveData in FragmentViewModel to prevent getting previous action when + // recreate + fingerprintEnrollIntroViewModel.clearActionLiveData(); fingerprintEnrollIntroViewModel.getActionLiveData().observe( this, this::observeIntroAction); - final String tag = "FingerprintEnrollIntroFragment"; - fragmentManager.beginTransaction() - .setReorderingAllowed(true) - .add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null, tag) - .commit(); + if (savedInstanceState == null) { + final String tag = "FingerprintEnrollIntroFragment"; + getSupportFragmentManager().beginTransaction() + .setReorderingAllowed(true) + .add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null, + tag) + .commit(); + } + } + + private void onGenerateChallengeFail(@NonNull Boolean isFail) { + onSetActivityResult(new ActivityResult(RESULT_CANCELED, null)); } private void onSetActivityResult(@NonNull ActivityResult result) { @@ -141,8 +145,8 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { finish(); } - private void onCredentialAction(@NonNull Integer action) { - switch (action) { + private void checkCredential() { + switch (mAutoCredentialViewModel.checkCredential()) { case CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK: { final Intent intent = mAutoCredentialViewModel.getChooseLockIntent(this, mViewModel.getRequest().isSuw(), mViewModel.getRequest().getSuwExtras()); @@ -168,12 +172,9 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { } return; } - case CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE: { - Log.w(TAG, "observeCredentialLiveData, finish with action:" + action); - if (mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction()) { - setResult(Activity.RESULT_CANCELED); - } - finish(); + case CREDENTIAL_VALID: + case CREDENTIAL_IS_GENERATING_CHALLENGE: { + // Do nothing } } } @@ -186,10 +187,15 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { if (mAutoCredentialViewModel.checkNewCredentialFromActivityResult( isChooseLock, activityResult)) { overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); + } else { + onSetActivityResult(activityResult); } } - private void observeIntroAction(@NonNull Integer action) { + private void observeIntroAction(@Nullable Integer action) { + if (action == null) { + return; + } switch (action) { case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: { onSetActivityResult( @@ -207,9 +213,9 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag"); } final Intent intent = new Intent(this, isSuw - ? SetupFingerprintEnrollEnrolling.class + ? SetupFingerprintEnrollFindSensor.class : FingerprintEnrollFindSensor.class); - intent.putExtras(mAutoCredentialViewModel.getCredentialBundle()); + intent.putExtras(mAutoCredentialViewModel.getCredentialIntentExtra()); intent.putExtras(mViewModel.getNextActivityBaseIntentExtras()); mNextActivityLauncher.launch(intent); } @@ -272,5 +278,6 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); mViewModel.onSaveInstanceState(outState); + mAutoCredentialViewModel.onSaveInstanceState(outState); } } diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java index b1a7f90e33f..a443e69a855 100644 --- a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java +++ b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java @@ -30,20 +30,21 @@ import android.app.Application; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.SystemClock; import android.util.Log; import androidx.activity.result.ActivityResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.DefaultLifecycleObserver; -import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException; import com.android.settings.biometrics2.data.repository.FingerprintRepository; import com.android.settings.biometrics2.ui.model.CredentialModel; import com.android.settings.password.ChooseLockGeneric; @@ -57,31 +58,40 @@ import java.lang.annotation.RetentionPolicy; * AutoCredentialViewModel which uses CredentialModel to determine next actions for activity, like * start ChooseLockActivity, start ConfirmLockActivity, GenerateCredential, or do nothing. */ -public class AutoCredentialViewModel extends AndroidViewModel implements DefaultLifecycleObserver { +public class AutoCredentialViewModel extends AndroidViewModel { private static final String TAG = "AutoCredentialViewModel"; - private static final boolean DEBUG = true; + + @VisibleForTesting + static final String KEY_CREDENTIAL_MODEL = "credential_model"; + + private static final boolean DEBUG = false; + + /** + * Valid credential, activity doesn't need to do anything. + */ + public static final int CREDENTIAL_VALID = 0; + + /** + * This credential looks good, but still need to run generateChallenge(). + */ + public static final int CREDENTIAL_IS_GENERATING_CHALLENGE = 1; /** * Need activity to run choose lock */ - public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 1; + public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 2; /** * Need activity to run confirm lock */ - public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 2; - - /** - * Fail to use challenge from hardware generateChallenge(), shall finish activity with proper - * error code - */ - public static final int CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE = 3; + public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 3; @IntDef(prefix = { "CREDENTIAL_" }, value = { + CREDENTIAL_VALID, + CREDENTIAL_IS_GENERATING_CHALLENGE, CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK, - CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK, - CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE + CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK }) @Retention(RetentionPolicy.SOURCE) public @interface CredentialAction {} @@ -157,11 +167,10 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default } } - @NonNull private final LockPatternUtils mLockPatternUtils; @NonNull private final ChallengeGenerator mChallengeGenerator; private CredentialModel mCredentialModel = null; - @NonNull private final MutableLiveData mActionLiveData = + @NonNull private final MutableLiveData mGenerateChallengeFailLiveData = new MutableLiveData<>(); public AutoCredentialViewModel( @@ -173,51 +182,63 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default mChallengeGenerator = challengeGenerator; } - public void setCredentialModel(@NonNull CredentialModel credentialModel) { - mCredentialModel = credentialModel; + /** + * Set CredentialModel, the source is coming from savedInstanceState or activity intent + */ + public void setCredentialModel(@Nullable Bundle savedInstanceState, @NonNull Intent intent) { + mCredentialModel = new CredentialModel( + savedInstanceState != null + ? savedInstanceState.getBundle(KEY_CREDENTIAL_MODEL) + : intent.getExtras(), + SystemClock.elapsedRealtimeClock()); + + if (DEBUG) { + Log.d(TAG, "setCredentialModel " + mCredentialModel + ", savedInstanceState exist:" + + (savedInstanceState != null)); + } } /** - * Observe ActionLiveData for actions about choosing lock, confirming lock, or finishing - * activity + * Handle onSaveInstanceState from activity */ - @NonNull - public LiveData getActionLiveData() { - return mActionLiveData; + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBundle(KEY_CREDENTIAL_MODEL, mCredentialModel.getBundle()); } - @Override - public void onCreate(@NonNull LifecycleOwner owner) { - checkCredential(); + @NonNull + public LiveData getGenerateChallengeFailLiveData() { + return mGenerateChallengeFailLiveData; } /** * Check credential status for biometric enrollment. */ - private void checkCredential() { + @CredentialAction + public int checkCredential() { if (isValidCredential()) { - return; + return CREDENTIAL_VALID; } final long gkPwHandle = mCredentialModel.getGkPwHandle(); if (isUnspecifiedPassword()) { - mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK); + return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK; } else if (CredentialModel.isValidGkPwHandle(gkPwHandle)) { generateChallenge(gkPwHandle); + return CREDENTIAL_IS_GENERATING_CHALLENGE; } else { - mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); + return CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK; } } private void generateChallenge(long gkPwHandle) { mChallengeGenerator.setCallback((sensorId, userId, challenge) -> { - mCredentialModel.setSensorId(sensorId); - mCredentialModel.setChallenge(challenge); try { final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId); + mCredentialModel.setSensorId(sensorId); + mCredentialModel.setChallenge(challenge); mCredentialModel.setToken(newToken); } catch (IllegalStateException e) { Log.e(TAG, "generateChallenge, IllegalStateException", e); - mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE); + mGenerateChallengeFailLiveData.postValue(true); return; } @@ -231,7 +252,7 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default // Check credential again if (!isValidCredential()) { Log.w(TAG, "generateChallenge, invalid Credential"); - mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE); + mGenerateChallengeFailLiveData.postValue(true); } }); mChallengeGenerator.generateChallenge(getUserId()); @@ -282,16 +303,16 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default final VerifyCredentialResponse response = mLockPatternUtils .verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId); if (!response.isMatched()) { - throw new IllegalStateException("Unable to request Gatekeeper HAT"); + throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT"); } return response.getGatekeeperHAT(); } /** - * Get Credential bundle which will be used to launch next activity. + * Get Credential intent extra which will be used to launch next activity. */ @NonNull - public Bundle getCredentialBundle() { + public Bundle getCredentialIntentExtra() { final Bundle retBundle = new Bundle(); final long gkPwHandle = mCredentialModel.getGkPwHandle(); if (CredentialModel.isValidGkPwHandle(gkPwHandle)) { diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java index 252a508e630..3274eb29565 100644 --- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java +++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java @@ -144,6 +144,13 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel return mPageStatusLiveData; } + /** + * Clear user's action live data (like clicking Agree, Skip, or Done) + */ + public void clearActionLiveData() { + mActionLiveData.setValue(null); + } + /** * Get user's action live data (like clicking Agree, Skip, or Done) */ diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java new file mode 100644 index 00000000000..1fac8b5b084 --- /dev/null +++ b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java @@ -0,0 +1,164 @@ +/* + * 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.settings.biometrics2.ui.model; + +import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE; +import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID; +import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_CHALLENGE; +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 android.annotation.NonNull; +import android.content.Intent; +import android.os.Bundle; +import android.os.SystemClock; + +import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.password.ChooseLockSettingsHelper; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.Clock; +import java.util.Arrays; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +public class CredentialModelTest { + + private final Clock mClock = SystemClock.elapsedRealtimeClock(); + + public static Bundle newCredentialModelIntentExtras(int userId, long challenge, int sensorId, + @Nullable byte[] token, long gkPwHandle) { + final Bundle bundle = new Bundle(); + bundle.putInt(Intent.EXTRA_USER_ID, userId); + bundle.putInt(EXTRA_KEY_SENSOR_ID, sensorId); + bundle.putLong(EXTRA_KEY_CHALLENGE, challenge); + bundle.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); + bundle.putLong(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); + return bundle; + } + + public static Bundle newValidTokenCredentialIntentExtras(int userId) { + return newCredentialModelIntentExtras(userId, 1L, 1, new byte[] { 0 }, 0L); + } + + public static Bundle newInvalidChallengeCredentialIntentExtras(int userId) { + return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null, 0L); + } + + public static Bundle newGkPwHandleCredentialIntentExtras(int userId, long gkPwHandle) { + return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null, gkPwHandle); + } + + private static void checkBundleLongValue(@NonNull Bundle bundle1, @NonNull Bundle bundle2, + @NonNull String key) { + if (!bundle1.containsKey(key)) { + return; + } + final int value1 = bundle1.getInt(key); + final int value2 = bundle2.getInt(key); + assertWithMessage("bundle not match, key:" + key + ", value1:" + value1 + ", value2:" + + value2).that(value1).isEqualTo(value2); + } + + private static void checkBundleIntValue(@NonNull Bundle bundle1, @NonNull Bundle bundle2, + @NonNull String key) { + if (!bundle1.containsKey(key)) { + return; + } + final long value1 = bundle1.getLong(key); + final long value2 = bundle2.getLong(key); + assertWithMessage("bundle not match, key:" + key + ", value1:" + value1 + ", value2:" + + value2).that(value1).isEqualTo(value2); + } + + private static void checkBundleByteArrayValue(@NonNull Bundle bundle1, @NonNull Bundle bundle2, + @NonNull String key) { + if (!bundle1.containsKey(key)) { + return; + } + final byte[] value1 = bundle1.getByteArray(key); + final byte[] value2 = bundle2.getByteArray(key); + final String errMsg = "bundle not match, key:" + key + ", value1:" + Arrays.toString(value1) + + ", value2:" + Arrays.toString(value2); + if (value1 == null) { + assertWithMessage(errMsg).that(value2).isNull(); + } else { + assertWithMessage(errMsg).that(value1.length).isEqualTo(value2.length); + for (int i = 0; i < value1.length; ++i) { + assertWithMessage(errMsg).that(value1[i]).isEqualTo(value2[i]); + } + } + } + + public static void verifySameCredentialModels(@NonNull CredentialModel model1, + @NonNull CredentialModel model2) { + + assertThat(model1.getUserId()).isEqualTo(model2.getUserId()); + assertThat(model1.getSensorId()).isEqualTo(model2.getSensorId()); + assertThat(model1.getChallenge()).isEqualTo(model2.getChallenge()); + assertThat(model1.getGkPwHandle()).isEqualTo(model2.getGkPwHandle()); + + final byte[] token1 = model1.getToken(); + final byte[] token2 = model2.getToken(); + if (token1 == null) { + assertThat(token2).isNull(); + } else { + assertThat(token2).isNotNull(); + assertThat(token1.length).isEqualTo(token2.length); + for (int i = 0; i < token1.length; ++i) { + assertThat(token1[i]).isEqualTo(token2[i]); + } + } + + final Bundle bundle1 = model1.getBundle(); + final Bundle bundle2 = model2.getBundle(); + final Set keySet1 = bundle1.keySet(); + assertThat(keySet1.equals(bundle2.keySet())).isTrue(); + checkBundleIntValue(bundle1, bundle2, Intent.EXTRA_USER_ID); + checkBundleIntValue(bundle1, bundle2, EXTRA_KEY_SENSOR_ID); + checkBundleLongValue(bundle1, bundle2, EXTRA_KEY_CHALLENGE); + checkBundleByteArrayValue(bundle1, bundle2, EXTRA_KEY_CHALLENGE); + checkBundleLongValue(bundle1, bundle2, EXTRA_KEY_GK_PW_HANDLE); + } + + @Test + public void sameValueFromBundle() { + final Bundle bundle = newCredentialModelIntentExtras(1234, 6677L, 1, + new byte[] { 33, 44, 55 }, 987654321); + + final CredentialModel model1 = new CredentialModel(bundle, mClock); + final CredentialModel model2 = new CredentialModel(model1.getBundle(), mClock); + + verifySameCredentialModels(model1, model2); + } + + @Test + public void sameValueFromBundle_nullToken() { + final Bundle bundle = newCredentialModelIntentExtras(22, 33L, 1, null, 21L); + + final CredentialModel model1 = new CredentialModel(bundle, mClock); + final CredentialModel model2 = new CredentialModel(model1.getBundle(), mClock); + + verifySameCredentialModels(model1, model2); + } +} diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java index 7a13875a8fa..a11650ebe84 100644 --- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java +++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java @@ -24,12 +24,22 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE; import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID; import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_CHALLENGE; +import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_GK_PW_HANDLE; import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_SENSOR_ID; +import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newCredentialModelIntentExtras; +import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newGkPwHandleCredentialIntentExtras; +import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newInvalidChallengeCredentialIntentExtras; +import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newValidTokenCredentialIntentExtras; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK; +import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE; +import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CredentialAction; import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.GenerateChallengeCallback; +import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.KEY_CREDENTIAL_MODEL; +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; @@ -38,12 +48,11 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.Activity; import android.content.Intent; -import android.os.SystemClock; +import android.os.Bundle; import android.os.UserHandle; import androidx.activity.result.ActivityResult; import androidx.annotation.Nullable; -import androidx.lifecycle.LifecycleOwner; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -68,7 +77,6 @@ public class AutoCredentialViewModelTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule(); - @Mock private LifecycleOwner mLifecycleOwner; @Mock private LockPatternUtils mLockPatternUtils; private TestChallengeGenerator mChallengeGenerator = null; private AutoCredentialViewModel mAutoCredentialViewModel; @@ -82,142 +90,222 @@ public class AutoCredentialViewModelTest { mChallengeGenerator); } - private CredentialModel newCredentialModel(int userId, long challenge, - @Nullable byte[] token, long gkPwHandle) { - final Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_USER_ID, userId); - intent.putExtra(EXTRA_KEY_SENSOR_ID, 1); - intent.putExtra(EXTRA_KEY_CHALLENGE, challenge); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); - return new CredentialModel(intent, SystemClock.elapsedRealtimeClock()); - } - - private CredentialModel newValidTokenCredentialModel(int userId) { - return newCredentialModel(userId, 1L, new byte[] { 0 }, 0L); - } - - private CredentialModel newInvalidChallengeCredentialModel(int userId) { - return newCredentialModel(userId, INVALID_CHALLENGE, null, 0L); - } - - private CredentialModel newGkPwHandleCredentialModel(int userId, long gkPwHandle) { - return newCredentialModel(userId, INVALID_CHALLENGE, null, gkPwHandle); - } - - private void verifyNothingHappen() { - assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull(); - } - - private void verifyOnlyActionLiveData(@CredentialAction int action) { - final Integer value = mAutoCredentialViewModel.getActionLiveData().getValue(); - assertThat(value).isEqualTo(action); - } - - private void setupGenerateTokenFlow(long gkPwHandle, int userId, int newSensorId, - long newChallenge) { + private void setupGenerateChallenge(int userId, int newSensorId, long newChallenge) { when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); mChallengeGenerator.mUserId = userId; mChallengeGenerator.mSensorId = newSensorId; mChallengeGenerator.mChallenge = newChallenge; - when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) - .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 })); } @Test - public void checkCredential_validCredentialCase() { + public void testGetCredentialIntentExtra_sameResultFromSavedInstanceOrIntent() { + final Bundle extras = newCredentialModelIntentExtras(12, 33, 1, new byte[] { 2, 3 }, 3L); + + AutoCredentialViewModel autoCredentialViewModel2 = new AutoCredentialViewModel( + ApplicationProvider.getApplicationContext(), + mLockPatternUtils, + mChallengeGenerator); + + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(extras)); + final Bundle savedInstance = new Bundle(); + savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras); + autoCredentialViewModel2.setCredentialModel(savedInstance, new Intent()); + + final Bundle bundle1 = mAutoCredentialViewModel.getCredentialIntentExtra(); + final Bundle bundle2 = autoCredentialViewModel2.getCredentialIntentExtra(); + assertThat(bundle1.getLong(EXTRA_KEY_GK_PW_HANDLE)) + .isEqualTo(bundle2.getLong(EXTRA_KEY_GK_PW_HANDLE)); + assertThat(bundle1.getLong(Intent.EXTRA_USER_ID)) + .isEqualTo(bundle2.getLong(Intent.EXTRA_USER_ID)); + assertThat(bundle1.getLong(EXTRA_KEY_CHALLENGE)) + .isEqualTo(bundle2.getLong(EXTRA_KEY_CHALLENGE)); + assertThat(bundle1.getInt(EXTRA_KEY_SENSOR_ID)) + .isEqualTo(bundle2.getInt(EXTRA_KEY_SENSOR_ID)); + final byte[] token1 = bundle1.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN); + final byte[] token2 = bundle2.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN); + assertThat(token1).isNotNull(); + assertThat(token2).isNotNull(); + assertThat(token1.length).isEqualTo(token2.length); + for (int i = 0; i < token2.length; ++i) { + assertThat(token1[i]).isEqualTo(token2[i]); + } + } + + @Test + public void testGetCredentialIntentExtra_sameResultFromSavedInstanceOrIntent_invalidValues() { + final Bundle extras = newCredentialModelIntentExtras(UserHandle.USER_NULL, + INVALID_CHALLENGE, INVALID_SENSOR_ID, null, INVALID_GK_PW_HANDLE); + + AutoCredentialViewModel autoCredentialViewModel2 = new AutoCredentialViewModel( + ApplicationProvider.getApplicationContext(), + mLockPatternUtils, + mChallengeGenerator); + + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(extras)); + final Bundle savedInstance = new Bundle(); + savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras); + autoCredentialViewModel2.setCredentialModel(savedInstance, new Intent()); + + final Bundle bundle1 = mAutoCredentialViewModel.getCredentialIntentExtra(); + final Bundle bundle2 = autoCredentialViewModel2.getCredentialIntentExtra(); + assertThat(bundle1.containsKey(EXTRA_KEY_GK_PW_HANDLE)).isFalse(); + assertThat(bundle2.containsKey(EXTRA_KEY_GK_PW_HANDLE)).isFalse(); + assertThat(bundle1.containsKey(EXTRA_KEY_CHALLENGE_TOKEN)).isFalse(); + assertThat(bundle2.containsKey(EXTRA_KEY_CHALLENGE_TOKEN)).isFalse(); + assertThat(bundle1.containsKey(EXTRA_KEY_SENSOR_ID)).isTrue(); + assertThat(bundle2.containsKey(EXTRA_KEY_SENSOR_ID)).isTrue(); + assertThat(bundle1.containsKey(Intent.EXTRA_USER_ID)).isFalse(); + assertThat(bundle2.containsKey(Intent.EXTRA_USER_ID)).isFalse(); + } + + @Test + public void testCheckCredential_validCredentialCase() { final int userId = 99; - mAutoCredentialViewModel.setCredentialModel(newValidTokenCredentialModel(userId)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newValidTokenCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); // Run credential check - mAutoCredentialViewModel.onCreate(mLifecycleOwner); + @CredentialAction final int action = mAutoCredentialViewModel.checkCredential(); - verifyNothingHappen(); + assertThat(action).isEqualTo(CREDENTIAL_VALID); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test - public void checkCredential_needToChooseLock() { + public void testCheckCredential_needToChooseLock() { final int userId = 100; - mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_UNSPECIFIED); // Run credential check - mAutoCredentialViewModel.onCreate(mLifecycleOwner); + @CredentialAction final int action = mAutoCredentialViewModel.checkCredential(); - verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK); + assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test - public void checkCredential_needToConfirmLockFoSomething() { + public void testCheckCredential_needToConfirmLockFoSomething() { final int userId = 101; - mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); // Run credential check - mAutoCredentialViewModel.onCreate(mLifecycleOwner); + @CredentialAction final int action = mAutoCredentialViewModel.checkCredential(); - verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); + assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test - public void checkCredential_needToConfirmLockForNumeric() { + public void testCheckCredential_needToConfirmLockForNumeric() { final int userId = 102; - mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_NUMERIC); // Run credential check - mAutoCredentialViewModel.onCreate(mLifecycleOwner); + @CredentialAction final int action = mAutoCredentialViewModel.checkCredential(); - verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); + assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test - public void checkCredential_needToConfirmLockForAlphabetic() { + public void testCheckCredential_needToConfirmLockForAlphabetic() { final int userId = 103; - mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_ALPHABETIC); // Run credential check - mAutoCredentialViewModel.onCreate(mLifecycleOwner); + @CredentialAction final int action = mAutoCredentialViewModel.checkCredential(); - verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); + assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test - public void checkCredential_generateChallenge() { + public void testCheckCredential_generateChallenge() { final int userId = 104; final long gkPwHandle = 1111L; - final CredentialModel credentialModel = newGkPwHandleCredentialModel(userId, gkPwHandle); - mAutoCredentialViewModel.setCredentialModel(credentialModel); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); final int newSensorId = 10; final long newChallenge = 20L; - setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge); + setupGenerateChallenge(userId, newSensorId, newChallenge); + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) + .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 })); // Run credential check - mAutoCredentialViewModel.onCreate(mLifecycleOwner); + @CredentialAction final int action = mAutoCredentialViewModel.checkCredential(); - assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull(); - assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId); - assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge); - assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue(); - assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse(); + assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); + final Bundle extras = mAutoCredentialViewModel.getCredentialIntentExtra(); + assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); + assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); + assertThat(CredentialModel.isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))) + .isTrue(); + assertThat(CredentialModel.isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))) + .isFalse(); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); } @Test - public void testGetUserId() { + public void testCheckCredential_generateChallengeFail() { + final int userId = 104; + final long gkPwHandle = 1111L; + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))); + when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( + PASSWORD_QUALITY_SOMETHING); + + final int newSensorId = 10; + final long newChallenge = 20L; + setupGenerateChallenge(userId, newSensorId, newChallenge); + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) + .thenReturn(newBadCredential(0)); + + // Run credential check + @CredentialAction final int action = mAutoCredentialViewModel.checkCredential(); + + assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isTrue(); + assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); + } + + @Test + public void testGetUserId_fromIntent() { final int userId = 106; - mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + + // Get userId + assertThat(mAutoCredentialViewModel.getUserId()).isEqualTo(userId); + } + + @Test + public void testGetUserId_fromSavedInstance() { + final int userId = 106; + final Bundle savedInstance = new Bundle(); + savedInstance.putBundle(KEY_CREDENTIAL_MODEL, + newInvalidChallengeCredentialIntentExtras(userId)); + mAutoCredentialViewModel.setCredentialModel(savedInstance, new Intent()); // Get userId assertThat(mAutoCredentialViewModel.getUserId()).isEqualTo(userId); @@ -227,8 +315,8 @@ public class AutoCredentialViewModelTest { public void testCheckNewCredentialFromActivityResult_invalidChooseLock() { final int userId = 107; final long gkPwHandle = 3333L; - mAutoCredentialViewModel.setCredentialModel( - newGkPwHandleCredentialModel(userId, gkPwHandle)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))); final Intent intent = new Intent(); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); @@ -237,15 +325,15 @@ public class AutoCredentialViewModelTest { new ActivityResult(ChooseLockPattern.RESULT_FINISHED + 1, intent)); assertThat(ret).isFalse(); - verifyNothingHappen(); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test public void testCheckNewCredentialFromActivityResult_invalidConfirmLock() { final int userId = 107; final long gkPwHandle = 3333L; - mAutoCredentialViewModel.setCredentialModel( - newGkPwHandleCredentialModel(userId, gkPwHandle)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))); final Intent intent = new Intent(); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); @@ -254,62 +342,68 @@ public class AutoCredentialViewModelTest { new ActivityResult(Activity.RESULT_OK + 1, intent)); assertThat(ret).isFalse(); - verifyNothingHappen(); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test public void testCheckNewCredentialFromActivityResult_nullDataChooseLock() { final int userId = 108; final long gkPwHandle = 4444L; - mAutoCredentialViewModel.setCredentialModel( - newGkPwHandleCredentialModel(userId, gkPwHandle)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))); // run checkNewCredentialFromActivityResult() final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(true, new ActivityResult(ChooseLockPattern.RESULT_FINISHED, null)); assertThat(ret).isFalse(); - verifyNothingHappen(); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test public void testCheckNewCredentialFromActivityResult_nullDataConfirmLock() { final int userId = 109; - mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId)); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); // run checkNewCredentialFromActivityResult() final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(false, new ActivityResult(Activity.RESULT_OK, null)); assertThat(ret).isFalse(); - verifyNothingHappen(); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); } @Test public void testCheckNewCredentialFromActivityResult_validChooseLock() { final int userId = 108; - final CredentialModel credentialModel = newInvalidChallengeCredentialModel(userId); - mAutoCredentialViewModel.setCredentialModel(credentialModel); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); final long gkPwHandle = 6666L; final int newSensorId = 50; final long newChallenge = 60L; - setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge); - final Intent intent = new Intent(); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); + setupGenerateChallenge(userId, newSensorId, newChallenge); + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) + .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 })); // Run checkNewCredentialFromActivityResult() + final Intent intent = new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + gkPwHandle); final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(true, new ActivityResult(ChooseLockPattern.RESULT_FINISHED, intent)); assertThat(ret).isTrue(); - assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull(); - assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId); - assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge); - assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue(); - assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse(); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); + final Bundle extras = mAutoCredentialViewModel.getCredentialIntentExtra(); + assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); + assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); + assertThat(CredentialModel.isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))) + .isTrue(); + assertThat(CredentialModel.isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))) + .isFalse(); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); } @@ -317,28 +411,33 @@ public class AutoCredentialViewModelTest { @Test public void testCheckNewCredentialFromActivityResult_validConfirmLock() { final int userId = 109; - final CredentialModel credentialModel = newInvalidChallengeCredentialModel(userId); - mAutoCredentialViewModel.setCredentialModel(credentialModel); + mAutoCredentialViewModel.setCredentialModel(null, + new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); final long gkPwHandle = 5555L; final int newSensorId = 80; final long newChallenge = 90L; - setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge); - final Intent intent = new Intent(); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); + setupGenerateChallenge(userId, newSensorId, newChallenge); + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId)) + .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 })); // Run checkNewCredentialFromActivityResult() + final Intent intent = new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + gkPwHandle); final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(false, new ActivityResult(Activity.RESULT_OK, intent)); assertThat(ret).isTrue(); - assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull(); - assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId); - assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge); - assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue(); - assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse(); + assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull(); + final Bundle extras = mAutoCredentialViewModel.getCredentialIntentExtra(); + assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); + assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); + assertThat(CredentialModel.isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))) + .isTrue(); + assertThat(CredentialModel.isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))) + .isFalse(); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); } @@ -377,4 +476,12 @@ public class AutoCredentialViewModelTest { .setGatekeeperHAT(hat) .build(); } + + private VerifyCredentialResponse newBadCredential(int timeout) { + if (timeout > 0) { + return VerifyCredentialResponse.fromTimeout(timeout); + } else { + return VerifyCredentialResponse.fromError(); + } + } } diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java index 5069ea1962a..921f1822809 100644 --- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java +++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java @@ -46,6 +46,7 @@ import android.hardware.fingerprint.FingerprintManager; import androidx.annotation.NonNull; import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -92,6 +93,18 @@ public class FingerprintEnrollIntroViewModelTest { assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_UNKNOWN); } + @Test + public void testClearActionLiveData() { + final MutableLiveData actionLiveData = + (MutableLiveData) mViewModel.getActionLiveData(); + actionLiveData.postValue(1); + assertThat(actionLiveData.getValue()).isEqualTo(1); + + mViewModel.clearActionLiveData(); + + assertThat(actionLiveData.getValue()).isNull(); + } + @Test public void testGetEnrollmentRequest() { final EnrollmentRequest request = newAllFalseRequest(mApplication);