diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index acfe5a10255..883a19e0f68 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -31,6 +31,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; @@ -58,6 +59,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase private static final String KEY_CONFIRMING_CREDENTIALS = "confirming_credentials"; private static final String KEY_SCROLLED_TO_BOTTOM = "scrolled"; + private GatekeeperPasswordProvider mGatekeeperPasswordProvider; private UserManager mUserManager; private boolean mHasPassword; private boolean mBiometricUnlockDisabledByAdmin; @@ -178,7 +180,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase mErrorText = getErrorTextView(); - mUserManager = UserManager.get(this); + mUserManager = getUserManager(); updatePasswordQuality(); if (!mConfirmingCredentials) { @@ -253,8 +255,28 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase return super.shouldFinishWhenBackgrounded() && !mConfirmingCredentials && !mNextClicked; } + @VisibleForTesting + @NonNull + protected GatekeeperPasswordProvider getGatekeeperPasswordProvider() { + if (mGatekeeperPasswordProvider == null) { + mGatekeeperPasswordProvider = new GatekeeperPasswordProvider(getLockPatternUtils()); + } + return mGatekeeperPasswordProvider; + } + + @VisibleForTesting + protected UserManager getUserManager() { + return UserManager.get(this); + } + + @VisibleForTesting + @NonNull + protected LockPatternUtils getLockPatternUtils() { + return new LockPatternUtils(this); + } + private void updatePasswordQuality() { - final int passwordQuality = new LockPatternUtils(this) + final int passwordQuality = getLockPatternUtils() .getActivePasswordQuality(mUserManager.getCredentialOwnerProfile(mUserId)); mHasPassword = passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; } @@ -301,6 +323,14 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST); } + /** + * Returns the intent extra data for setResult(), null means nothing need to been sent back + */ + @Nullable + protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { + return activityResultIntent; + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, @@ -310,7 +340,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase && BiometricUtils.isMultiBiometricFingerprintEnrollmentFlow(this); if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) { if (isResultFinished(resultCode)) { - handleBiometricResultSkipOrFinished(resultCode, data); + handleBiometricResultSkipOrFinished(resultCode, getSetResultIntentExtra(data)); } else if (isResultSkipped(resultCode)) { if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, "BIOMETRIC_FIND_SENSOR_SKIPPED")) { diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java index 4d3162785b6..cd3a2a3227f 100644 --- a/src/com/android/settings/biometrics/BiometricUtils.java +++ b/src/com/android/settings/biometrics/BiometricUtils.java @@ -72,6 +72,8 @@ public class BiometricUtils { }; /** + * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. + * * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * @@ -83,6 +85,7 @@ public class BiometricUtils { * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match * @throws IllegalStateException if Gatekeeper Password is missing */ + @Deprecated public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result, int userId, long challenge) { if (!containsGatekeeperPasswordHandle(result)) { @@ -93,6 +96,10 @@ public class BiometricUtils { return requestGatekeeperHat(context, gatekeeperPasswordHandle, userId, challenge); } + /** + * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. + */ + @Deprecated public static byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId, long challenge) { final LockPatternUtils utils = new LockPatternUtils(context); @@ -104,15 +111,25 @@ public class BiometricUtils { return response.getGatekeeperHAT(); } + /** + * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. + */ + @Deprecated public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); } + /** + * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. + */ + @Deprecated public static long getGatekeeperPasswordHandle(@NonNull Intent data) { return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); } /** + * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. + * * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the * gatekeeper password associated with a previous * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} @@ -120,6 +137,7 @@ public class BiometricUtils { * @param context Caller's context * @param data The onActivityResult intent from ChooseLock* or ConfirmLock* */ + @Deprecated public static void removeGatekeeperPasswordHandle(@NonNull Context context, @Nullable Intent data) { if (data == null) { @@ -131,6 +149,10 @@ public class BiometricUtils { removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data)); } + /** + * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. + */ + @Deprecated public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) { final LockPatternUtils utils = new LockPatternUtils(context); utils.removeGatekeeperPasswordHandle(handle); diff --git a/src/com/android/settings/biometrics/GatekeeperPasswordProvider.java b/src/com/android/settings/biometrics/GatekeeperPasswordProvider.java new file mode 100644 index 00000000000..3320c8dc6af --- /dev/null +++ b/src/com/android/settings/biometrics/GatekeeperPasswordProvider.java @@ -0,0 +1,128 @@ +/* + * 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.biometrics; + +import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException; + +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.VerifyCredentialResponse; +import com.android.settings.password.ChooseLockSettingsHelper; + +/** + * Gatekeeper hat related methods + */ +public class GatekeeperPasswordProvider { + + private static final String TAG = "GatekeeperPasswordProvider"; + + private final LockPatternUtils mLockPatternUtils; + + public GatekeeperPasswordProvider(LockPatternUtils lockPatternUtils) { + mLockPatternUtils = lockPatternUtils; + } + + /** + * Given the result from confirming or choosing a credential, request Gatekeeper to generate + * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. + * + * @param result The onActivityResult intent from ChooseLock* or ConfirmLock* + * @param challenge Unique biometric challenge from FingerprintManager/FaceManager + * @param userId User ID that the credential/biometric operation applies to + * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match + * @throws IllegalStateException if Gatekeeper Password is missing + */ + public byte[] requestGatekeeperHat(@NonNull Intent result, long challenge, int userId) { + if (!containsGatekeeperPasswordHandle(result)) { + throw new IllegalStateException("Gatekeeper Password is missing!!"); + } + final long gatekeeperPasswordHandle = result.getLongExtra( + ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); + return requestGatekeeperHat(gatekeeperPasswordHandle, challenge, userId); + } + + /** + * Given the result from confirming or choosing a credential, request Gatekeeper to generate + * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. + * + * @param gkPwHandle The Gatekeeper password handle from ChooseLock* or ConfirmLock* + * @param challenge Unique biometric challenge from FingerprintManager/FaceManager + * @param userId User ID that the credential/biometric operation applies to + * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match + */ + public byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId) { + final VerifyCredentialResponse response = mLockPatternUtils.verifyGatekeeperPasswordHandle( + gkPwHandle, challenge, userId); + if (!response.isMatched()) { + throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT"); + } + return response.getGatekeeperHAT(); + } + + /** + * Intent data contains gatekeeper password handle or not + */ + public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { + return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); + } + + /** + * Returns the gatekeeper password handle from intent + */ + public static long getGatekeeperPasswordHandle(@NonNull Intent data) { + return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); + } + + /** + * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the + * gatekeeper password associated with a previous + * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} + * + * @param data The onActivityResult intent from ChooseLock* or ConfirmLock* + * @param alsoRemoveItFromIntent set it to true if gkPwHandle needs to be removed from intent + */ + public void removeGatekeeperPasswordHandle(@Nullable Intent data, + boolean alsoRemoveItFromIntent) { + if (data == null) { + return; + } + if (!containsGatekeeperPasswordHandle(data)) { + return; + } + removeGatekeeperPasswordHandle(getGatekeeperPasswordHandle(data)); + if (alsoRemoveItFromIntent) { + data.removeExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); + } + } + + /** + * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the + * gatekeeper password associated with a previous + * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} + * + * @param handle The Gatekeeper password handle from ChooseLock* or ConfirmLock* + */ + public void removeGatekeeperPasswordHandle(long handle) { + mLockPatternUtils.removeGatekeeperPasswordHandle(handle); + Log.d(TAG, "Removed handle"); + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index 08fb5c0c5e9..eb647491f99 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -43,6 +43,7 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.biometrics.GatekeeperPasswordProvider; import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.HelpUtils; @@ -68,7 +69,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void onCreate(Bundle savedInstanceState) { - mFingerprintManager = Utils.getFingerprintManagerOrNull(this); + mFingerprintManager = getFingerprintManager(); if (mFingerprintManager == null) { Log.e(TAG, "Null FingerprintManager"); finish(); @@ -127,11 +128,50 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { final ScrollView scrollView = findViewById(R.id.sud_scroll_view); scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + + final Intent intent = getIntent(); + if (mFromSettingsSummary + && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)) { + overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); + getNextButton().setEnabled(false); + getChallenge(((sensorId, userId, challenge) -> { + if (isFinishing()) { + // Do nothing if activity is finishing + Log.w(TAG, "activity finished before challenge callback launched."); + return; + } + + mSensorId = sensorId; + mChallenge = challenge; + final GatekeeperPasswordProvider provider = getGatekeeperPasswordProvider(); + mToken = provider.requestGatekeeperHat(intent, challenge, mUserId); + provider.removeGatekeeperPasswordHandle(intent, true); + getNextButton().setEnabled(true); + })); + } } + @VisibleForTesting + @Nullable + protected FingerprintManager getFingerprintManager() { + return Utils.getFingerprintManagerOrNull(this); + } + + /** + * Returns the intent extra data for setResult(), null means nothing need to been sent back + */ + @Nullable @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); + protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { + Intent intent = super.getSetResultIntentExtra(activityResultIntent); + if (mFromSettingsSummary && mToken != null && mChallenge != -1L) { + if (intent == null) { + intent = new Intent(); + } + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); + intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); + } + return intent; } @Override @@ -295,11 +335,6 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void getChallenge(GenerateChallengeCallback callback) { - mFingerprintManager = Utils.getFingerprintManagerOrNull(this); - if (mFingerprintManager == null) { - callback.onChallengeGenerated(0, 0, 0L); - return; - } mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated); } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 4cab05bb170..9c244e5155b 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.UNDEFINED; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY; +import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE; import android.app.Activity; import android.app.Dialog; @@ -60,11 +61,13 @@ import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; +import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.biometrics.GatekeeperPasswordProvider; import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity; import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; @@ -410,7 +413,7 @@ public class FingerprintSettings extends SubSettings { launchChooseOrConfirmLock(); } else if (!mHasFirstEnrolled) { mIsEnrolling = true; - addFirstFingerprint(); + addFirstFingerprint(null); } } updateFooterColumns(activity); @@ -776,7 +779,7 @@ public class FingerprintSettings extends SubSettings { if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) { mLaunchedConfirm = false; if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { - if (data != null && BiometricUtils.containsGatekeeperPasswordHandle(data)) { + if (BiometricUtils.containsGatekeeperPasswordHandle(data)) { if (!mHasFirstEnrolled && !mIsEnrolling) { final Activity activity = getActivity(); if (activity != null) { @@ -784,21 +787,34 @@ public class FingerprintSettings extends SubSettings { activity.overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); } + + // To have smoother animation, change flow to let next visible activity + // to generateChallenge, then pass it back through activity result. + // Token and challenge will be updated later through the activity result + // of AUTO_ADD_FIRST_FINGERPRINT_REQUEST. + mIsEnrolling = true; + addFirstFingerprint( + BiometricUtils.getGatekeeperPasswordHandle(data)); + } else { + mFingerprintManager.generateChallenge(mUserId, + (sensorId, userId, challenge) -> { + final Activity activity = getActivity(); + if (activity == null || activity.isFinishing()) { + // Stop everything + Log.w(TAG, "activity detach or finishing"); + return; + } + + final GatekeeperPasswordProvider provider = + new GatekeeperPasswordProvider( + new LockPatternUtils(activity)); + mToken = provider.requestGatekeeperHat(data, challenge, + mUserId); + mChallenge = challenge; + provider.removeGatekeeperPasswordHandle(data, false); + updateAddPreference(); + }); } - mFingerprintManager.generateChallenge(mUserId, - (sensorId, userId, challenge) -> { - mToken = BiometricUtils.requestGatekeeperHat(getActivity(), - data, - mUserId, challenge); - mChallenge = challenge; - BiometricUtils.removeGatekeeperPasswordHandle(getActivity(), - data); - updateAddPreference(); - if (!mHasFirstEnrolled && !mIsEnrolling) { - mIsEnrolling = true; - addFirstFingerprint(); - } - }); } else { Log.d(TAG, "Data null or GK PW missing"); finish(); @@ -815,12 +831,29 @@ public class FingerprintSettings extends SubSettings { activity.finish(); } } else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) { + if (resultCode != RESULT_FINISHED || data == null) { + Log.d(TAG, "Add first fingerprint, fail or null data, result:" + resultCode); + finish(); + return; + } + + mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); + if (mToken == null) { + Log.w(TAG, "Add first fingerprint, null token"); + finish(); + return; + } + + mChallenge = data.getLongExtra(EXTRA_KEY_CHALLENGE, -1L); + if (mChallenge == -1L) { + Log.w(TAG, "Add first fingerprint, invalid challenge"); + finish(); + return; + } + mIsEnrolling = false; mHasFirstEnrolled = true; - if (resultCode != RESULT_FINISHED) { - Log.d(TAG, "Add first fingerprint fail, result:" + resultCode); - finish(); - } + updateAddPreference(); } } @@ -892,7 +925,7 @@ public class FingerprintSettings extends SubSettings { } } - private void addFirstFingerprint() { + private void addFirstFingerprint(@Nullable Long gkPwHandle) { Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, FeatureFlagUtils.isEnabled(getActivity(), @@ -906,7 +939,13 @@ public class FingerprintSettings extends SubSettings { SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); intent.putExtra(Intent.EXTRA_USER_ID, mUserId); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); + if (gkPwHandle != null) { + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + gkPwHandle.longValue()); + } else { + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); + intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, mChallenge); + } startActivityForResult(intent, AUTO_ADD_FIRST_FINGERPRINT_REQUEST); } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java index b82e8b615f6..bb689945420 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java @@ -16,17 +16,13 @@ package com.android.settings.biometrics.fingerprint; -import static android.util.FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT; - import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.fingerprint.FingerprintManager; -import android.util.FeatureFlagUtils; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.ParentalControlsUtils; -import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.utils.StringUtil; @@ -82,11 +78,7 @@ public class FingerprintStatusUtils { * Returns the class name of the Settings page corresponding to fingerprint settings. */ public String getSettingsClassName() { - return !hasEnrolled() && isAvailable() - ? (FeatureFlagUtils.isEnabled(mContext, SETTINGS_BIOMETRICS2_ENROLLMENT) - ? FingerprintEnrollmentActivity.class.getName() - : FingerprintEnrollIntroductionInternal.class.getName()) - : FingerprintSettings.class.getName(); + return FingerprintSettings.class.getName(); } /** diff --git a/src/com/android/settings/biometrics2/ui/model/CredentialModel.java b/src/com/android/settings/biometrics2/ui/model/CredentialModel.java index b943608be20..8f2f45b8831 100644 --- a/src/com/android/settings/biometrics2/ui/model/CredentialModel.java +++ b/src/com/android/settings/biometrics2/ui/model/CredentialModel.java @@ -50,6 +50,7 @@ public final class CredentialModel { /** * Default value if GkPwHandle is invalid. */ + @VisibleForTesting public static final long INVALID_GK_PW_HANDLE = 0L; /** @@ -115,8 +116,8 @@ public final class CredentialModel { /** * Check user id is valid or not */ - public static boolean isValidUserId(int userId) { - return userId != UserHandle.USER_NULL; + public boolean isValidUserId() { + return mUserId != UserHandle.USER_NULL; } /** @@ -134,6 +135,13 @@ public final class CredentialModel { mChallenge = value; } + /** + * Check challenge is valid or not + */ + public boolean isValidChallenge() { + return mChallenge != INVALID_CHALLENGE; + } + /** * Get challenge token */ @@ -153,8 +161,8 @@ public final class CredentialModel { /** * Check challengeToken is valid or not */ - public static boolean isValidToken(@Nullable byte[] token) { - return token != null; + public boolean isValidToken() { + return mToken != null; } /** @@ -175,8 +183,8 @@ public final class CredentialModel { /** * Check gkPwHandle is valid or not */ - public static boolean isValidGkPwHandle(long gkPwHandle) { - return gkPwHandle != INVALID_GK_PW_HANDLE; + public boolean isValidGkPwHandle() { + return mGkPwHandle != INVALID_GK_PW_HANDLE; } /** @@ -206,10 +214,10 @@ public final class CredentialModel { + ", userId:" + mUserId + ", challenge:{len:" + challengeLen + ", updateMillis:" + mUpdateChallengeMillis + "}" - + ", token:{len:" + tokenLen + ", isValid:" + isValidToken(mToken) + + ", token:{len:" + tokenLen + ", isValid:" + isValidToken() + ", updateMillis:" + mUpdateTokenMillis + "}" - + ", gkPwHandle:{len:" + gkPwHandleLen + ", isValid:" - + isValidGkPwHandle(mGkPwHandle) + ", clearMillis:" + mClearGkPwHandleMillis + "}" + + ", gkPwHandle:{len:" + gkPwHandleLen + ", isValid:" + isValidGkPwHandle() + + ", clearMillis:" + mClearGkPwHandleMillis + "}" + ", mSensorId:{id:" + mSensorId + ", updateMillis:" + mUpdateSensorIdMillis + "}" + " }"; } diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java index 1f67100afcf..cd4c539b361 100644 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java @@ -98,7 +98,6 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class); mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent()); - checkCredential(); // Theme setTheme(mViewModel.getRequest().getTheme()); @@ -107,12 +106,14 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { // fragment setContentView(R.layout.biometric_enrollment_container); - final FingerprintEnrollIntroViewModel introViewModel = viewModelProvider.get(FingerprintEnrollIntroViewModel.class); introViewModel.setEnrollmentRequest(mViewModel.getRequest()); introViewModel.setUserId(mAutoCredentialViewModel.getUserId()); + if (savedInstanceState == null) { + checkCredential(); + final String tag = "FingerprintEnrollIntroFragment"; getSupportFragmentManager().beginTransaction() .setReorderingAllowed(true) @@ -138,11 +139,34 @@ public class FingerprintEnrollmentActivity extends FragmentActivity { onSetActivityResult(new ActivityResult(RESULT_CANCELED, null)); } + /** + * Get intent which passing back to FingerprintSettings for late generateChallenge() + */ + @Nullable + private Intent createSetResultIntentWithGeneratingChallengeExtra( + @Nullable Intent activityResultIntent) { + if (!mViewModel.getRequest().isFromSettingsSummery()) { + return activityResultIntent; + } + + final Bundle extra = mAutoCredentialViewModel.createGeneratingChallengeExtras(); + if (extra != null) { + if (activityResultIntent == null) { + activityResultIntent = new Intent(); + } + activityResultIntent.putExtras(extra); + } + return activityResultIntent; + } + private void onSetActivityResult(@NonNull ActivityResult result) { - setResult(mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction() - ? RESULT_CANCELED - : result.getResultCode(), - result.getData()); + final int resultCode = mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction() + ? RESULT_CANCELED + : result.getResultCode(); + final Intent intent = resultCode == BiometricEnrollBase.RESULT_FINISHED + ? createSetResultIntentWithGeneratingChallengeExtra(result.getData()) + : result.getData(); + setResult(resultCode, intent); finish(); } diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java index 3b7a436fe5c..a4f94b5771f 100644 --- a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java +++ b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java @@ -65,6 +65,10 @@ public class AutoCredentialViewModel extends AndroidViewModel { @VisibleForTesting static final String KEY_CREDENTIAL_MODEL = "credential_model"; + @VisibleForTesting + static final String KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL = + "is_generating_challenge_during_checking_credential"; + private static final boolean DEBUG = false; /** @@ -173,6 +177,9 @@ public class AutoCredentialViewModel extends AndroidViewModel { @NonNull private final MutableLiveData mGenerateChallengeFailedLiveData = new MutableLiveData<>(); + // flag if token is generating through checkCredential()'s generateChallenge() + private boolean mIsGeneratingChallengeDuringCheckingCredential; + public AutoCredentialViewModel( @NonNull Application application, @NonNull LockPatternUtils lockPatternUtils, @@ -189,10 +196,13 @@ public class AutoCredentialViewModel extends AndroidViewModel { final Bundle bundle; if (savedInstanceState != null) { bundle = savedInstanceState.getBundle(KEY_CREDENTIAL_MODEL); + mIsGeneratingChallengeDuringCheckingCredential = savedInstanceState.getBoolean( + KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL); } else { bundle = intent.getExtras(); } - mCredentialModel = new CredentialModel(bundle, SystemClock.elapsedRealtimeClock()); + mCredentialModel = new CredentialModel(bundle != null ? bundle : new Bundle(), + SystemClock.elapsedRealtimeClock()); if (DEBUG) { Log.d(TAG, "setCredentialModel " + mCredentialModel + ", savedInstanceState exist:" @@ -204,6 +214,8 @@ public class AutoCredentialViewModel extends AndroidViewModel { * Handle onSaveInstanceState from activity */ public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL, + mIsGeneratingChallengeDuringCheckingCredential); outState.putBundle(KEY_CREDENTIAL_MODEL, mCredentialModel.getBundle()); } @@ -212,6 +224,24 @@ public class AutoCredentialViewModel extends AndroidViewModel { return mGenerateChallengeFailedLiveData; } + /** + * Get bundle which passing back to FingerprintSettings for late generateChallenge() + */ + @Nullable + public Bundle createGeneratingChallengeExtras() { + if (!mIsGeneratingChallengeDuringCheckingCredential + || !mCredentialModel.isValidToken() + || !mCredentialModel.isValidChallenge()) { + return null; + } + + Bundle bundle = new Bundle(); + bundle.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, + mCredentialModel.getToken()); + bundle.putLong(EXTRA_KEY_CHALLENGE, mCredentialModel.getChallenge()); + return bundle; + } + /** * Check credential status for biometric enrollment. */ @@ -220,11 +250,11 @@ public class AutoCredentialViewModel extends AndroidViewModel { if (isValidCredential()) { return CREDENTIAL_VALID; } - final long gkPwHandle = mCredentialModel.getGkPwHandle(); if (isUnspecifiedPassword()) { return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK; - } else if (CredentialModel.isValidGkPwHandle(gkPwHandle)) { - generateChallenge(gkPwHandle); + } else if (mCredentialModel.isValidGkPwHandle()) { + generateChallenge(mCredentialModel.getGkPwHandle()); + mIsGeneratingChallengeDuringCheckingCredential = true; return CREDENTIAL_IS_GENERATING_CHALLENGE; } else { return CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK; @@ -261,8 +291,7 @@ public class AutoCredentialViewModel extends AndroidViewModel { } private boolean isValidCredential() { - return !isUnspecifiedPassword() - && CredentialModel.isValidToken(mCredentialModel.getToken()); + return !isUnspecifiedPassword() && mCredentialModel.isValidToken(); } private boolean isUnspecifiedPassword() { @@ -316,17 +345,14 @@ public class AutoCredentialViewModel extends AndroidViewModel { @NonNull public Bundle createCredentialIntentExtra() { final Bundle retBundle = new Bundle(); - final long gkPwHandle = mCredentialModel.getGkPwHandle(); - if (CredentialModel.isValidGkPwHandle(gkPwHandle)) { - retBundle.putLong(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); + if (mCredentialModel.isValidGkPwHandle()) { + retBundle.putLong(EXTRA_KEY_GK_PW_HANDLE, mCredentialModel.getGkPwHandle()); } - final byte[] token = mCredentialModel.getToken(); - if (CredentialModel.isValidToken(token)) { - retBundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, token); + if (mCredentialModel.isValidToken()) { + retBundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, mCredentialModel.getToken()); } - final int userId = getUserId(); - if (CredentialModel.isValidUserId(userId)) { - retBundle.putInt(Intent.EXTRA_USER_ID, userId); + if (mCredentialModel.isValidUserId()) { + retBundle.putInt(Intent.EXTRA_USER_ID, mCredentialModel.getUserId()); } retBundle.putLong(EXTRA_KEY_CHALLENGE, mCredentialModel.getChallenge()); retBundle.putInt(EXTRA_KEY_SENSOR_ID, mCredentialModel.getSensorId()); @@ -346,9 +372,8 @@ public class AutoCredentialViewModel extends AndroidViewModel { intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true); - final int userId = getUserId(); - if (CredentialModel.isValidUserId(userId)) { - intent.putExtra(Intent.EXTRA_USER_ID, userId); + if (mCredentialModel.isValidUserId()) { + intent.putExtra(Intent.EXTRA_USER_ID, mCredentialModel.getUserId()); } return intent; } @@ -367,9 +392,8 @@ public class AutoCredentialViewModel extends AndroidViewModel { .setForegroundOnly(true) .setReturnCredentials(true); - final int userId = mCredentialModel.getUserId(); - if (CredentialModel.isValidUserId(userId)) { - builder.setUserId(userId); + if (mCredentialModel.isValidUserId()) { + builder.setUserId(mCredentialModel.getUserId()); } return builder.build(); } diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java index 468e132555d..4b862731da5 100644 --- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java +++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java @@ -104,7 +104,7 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements * Handle activity result from FingerprintFindSensor */ public void onContinueEnrollActivityResult(@NonNull ActivityResult result, int userId) { - if (mIsWaitingActivityResult.compareAndSet(true, false)) { + if (!mIsWaitingActivityResult.compareAndSet(true, false)) { Log.w(TAG, "fail to reset isWaiting flag for enrollment"); } if (result.getResultCode() == RESULT_FINISHED diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java index 1096f4085a4..4eb0a89699c 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java @@ -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 componentInfo = new ArrayList<>(); @@ -79,14 +105,34 @@ public class FingerprintEnrollIntroductionTest { final ArrayList props = new ArrayList<>(); props.add(prop); when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); + + when(mUserManager.getCredentialOwnerProfile(anyInt())).thenAnswer( + (Answer) invocation -> (int) invocation.getArgument(0)); + + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), anyInt())) + .thenAnswer((Answer) invocation -> + newGoodCredential(invocation.getArgument(0), EXPECTED_TOKEN)); + doNothing().when(mLockPatternUtils).removeGatekeeperPasswordHandle(anyLong()); } - void setupFingerprintEnrollIntroWith(Intent intent) { - ActivityController controller = - Robolectric.buildActivity(FingerprintEnrollIntroduction.class, intent); - mFingerprintEnrollIntroduction = spy(controller.get()); - ReflectionHelpers.setField( - mFingerprintEnrollIntroduction, "mFingerprintManager", mFingerprintManager); + void setupFingerprintEnrollIntroWith(@NonNull Intent intent) { + + final ActivityController 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); + } + } } diff --git a/tests/unit/src/com/android/settings/biometrics/GatekeeperPasswordProviderTest.java b/tests/unit/src/com/android/settings/biometrics/GatekeeperPasswordProviderTest.java new file mode 100644 index 00000000000..ab7a5afed18 --- /dev/null +++ b/tests/unit/src/com/android/settings/biometrics/GatekeeperPasswordProviderTest.java @@ -0,0 +1,188 @@ +/* + * 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.biometrics; + +import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException; +import static com.android.settings.biometrics.GatekeeperPasswordProvider.containsGatekeeperPasswordHandle; +import static com.android.settings.biometrics.GatekeeperPasswordProvider.getGatekeeperPasswordHandle; +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.Mockito.doNothing; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Intent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.VerifyCredentialResponse; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class GatekeeperPasswordProviderTest { + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock private LockPatternUtils mLockPatternUtils; + private GatekeeperPasswordProvider mGatekeeperPasswordProvider; + + @Before + public void setUp() { + mGatekeeperPasswordProvider = new GatekeeperPasswordProvider(mLockPatternUtils); + } + + @Test + public void testRequestGatekeeperHatWithHandle_success() { + final long gkPwHandle = 1L; + final long challenge = 2L; + final int userId = 0; + final byte[] expectedToken = new byte[] { 3, 2, 1 }; + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)) + .thenReturn(newGoodCredential(gkPwHandle, expectedToken)); + final byte[] actualToken = mGatekeeperPasswordProvider.requestGatekeeperHat(gkPwHandle, + challenge, userId); + assertThat(actualToken).isNotNull(); + assertThat(actualToken.length).isEqualTo(expectedToken.length); + for (int i = 0; i < actualToken.length; ++i) { + assertWithMessage("actualToken[" + i + "] is " + actualToken[i] + " not " + + expectedToken[i]).that(actualToken[i]).isEqualTo(expectedToken[i]); + } + } + + @Test(expected = GatekeeperCredentialNotMatchException.class) + public void testRequestGatekeeperHatWithHandle_GatekeeperCredentialNotMatchException() { + final long gkPwHandle = 10L; + final long challenge = 20L; + final int userId = 300; + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)) + .thenReturn(newBadCredential(0)); + + mGatekeeperPasswordProvider.requestGatekeeperHat(gkPwHandle, challenge, userId); + } + + @Test + public void testRequestGatekeeperHatWithIntent_success() { + final long gkPwHandle = 11L; + final long challenge = 21L; + final int userId = 145; + final byte[] expectedToken = new byte[] { 4, 5, 6, 7 }; + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)) + .thenReturn(newGoodCredential(gkPwHandle, expectedToken)); + final byte[] actualToken = mGatekeeperPasswordProvider.requestGatekeeperHat( + new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle), challenge, userId); + assertThat(actualToken).isNotNull(); + assertThat(actualToken.length).isEqualTo(expectedToken.length); + for (int i = 0; i < actualToken.length; ++i) { + assertWithMessage("actualToken[" + i + "] is " + actualToken[i] + " not " + + expectedToken[i]).that(actualToken[i]).isEqualTo(expectedToken[i]); + } + } + + @Test(expected = GatekeeperCredentialNotMatchException.class) + public void testRequestGatekeeperHatWithIntent_GatekeeperCredentialNotMatchException() { + final long gkPwHandle = 12L; + final long challenge = 22L; + final int userId = 0; + when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)) + .thenReturn(newBadCredential(0)); + + mGatekeeperPasswordProvider.requestGatekeeperHat( + new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle), challenge, userId); + } + + @Test(expected = IllegalStateException.class) + public void testRequestGatekeeperHatWithIntent_IllegalStateException() { + mGatekeeperPasswordProvider.requestGatekeeperHat(new Intent(), 1L, 0); + } + + @Test + public void testContainsGatekeeperPasswordHandle() { + assertThat(containsGatekeeperPasswordHandle(null)).isEqualTo(false); + assertThat(containsGatekeeperPasswordHandle(new Intent())).isEqualTo(false); + assertThat(containsGatekeeperPasswordHandle( + new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, 2L))).isEqualTo(true); + } + + @Test + public void testGetGatekeeperPasswordHandle() { + assertThat(getGatekeeperPasswordHandle(new Intent())).isEqualTo(0L); + assertThat(getGatekeeperPasswordHandle( + new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, 3L))).isEqualTo(3L); + } + + @Test + public void testRemoveGatekeeperPasswordHandleAsHandle() { + final long gkPwHandle = 1L; + doNothing().when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle); + + mGatekeeperPasswordProvider.removeGatekeeperPasswordHandle(gkPwHandle); + + verify(mLockPatternUtils, only()).removeGatekeeperPasswordHandle(gkPwHandle); + } + + @Test + public void testRemoveGatekeeperPasswordHandleAsIntent() { + final long gkPwHandle = 1234L; + final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); + doNothing().when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle); + + mGatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false); + + verify(mLockPatternUtils, only()).removeGatekeeperPasswordHandle(gkPwHandle); + assertThat(intent.getLongExtra(EXTRA_KEY_GK_PW_HANDLE, 0L)).isEqualTo(gkPwHandle); + } + + @Test + public void testRemoveGatekeeperPasswordHandleAsIntent_removeKey() { + final long gkPwHandle = 1234L; + final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle); + doNothing().when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle); + + mGatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, true); + + verify(mLockPatternUtils, only()).removeGatekeeperPasswordHandle(gkPwHandle); + assertThat(intent.hasExtra(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(false); + } + + private VerifyCredentialResponse newGoodCredential(long gkPwHandle, @NonNull byte[] hat) { + return new VerifyCredentialResponse.Builder() + .setGatekeeperPasswordHandle(gkPwHandle) + .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/model/CredentialModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java index 1fac8b5b084..4dbc6232082 100644 --- a/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java +++ b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java @@ -19,6 +19,7 @@ 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.biometrics2.ui.model.CredentialModel.INVALID_GK_PW_HANDLE; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE; import static com.google.common.truth.Truth.assertThat; @@ -58,11 +59,13 @@ public class CredentialModelTest { } public static Bundle newValidTokenCredentialIntentExtras(int userId) { - return newCredentialModelIntentExtras(userId, 1L, 1, new byte[] { 0 }, 0L); + return newCredentialModelIntentExtras(userId, 1L, 1, new byte[] { 0, 1, 2 }, + INVALID_GK_PW_HANDLE); } - public static Bundle newInvalidChallengeCredentialIntentExtras(int userId) { - return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null, 0L); + public static Bundle newOnlySensorValidCredentialIntentExtras(int userId) { + return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null, + INVALID_GK_PW_HANDLE); } public static Bundle newGkPwHandleCredentialIntentExtras(int userId, long gkPwHandle) { 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 3abbca6cb68..4db005ec609 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 @@ -26,11 +26,9 @@ import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENS 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.CredentialModel.isValidGkPwHandle; -import static com.android.settings.biometrics2.ui.model.CredentialModel.isValidToken; 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.newOnlySensorValidCredentialIntentExtras; 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; @@ -40,10 +38,12 @@ import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewMo 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.biometrics2.ui.viewmodel.AutoCredentialViewModel.KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL; 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.Mockito.when; @@ -109,7 +109,7 @@ public class AutoCredentialViewModelTest { mViewModel.setCredentialModel(null, new Intent().putExtras(extras)); final Bundle savedInstance = new Bundle(); - savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras); + mViewModel.onSaveInstanceState(savedInstance); viewModel2.setCredentialModel(savedInstance, new Intent()); final Bundle bundle1 = mViewModel.createCredentialIntentExtra(); @@ -144,7 +144,7 @@ public class AutoCredentialViewModelTest { mViewModel.setCredentialModel(null, new Intent().putExtras(extras)); final Bundle savedInstance = new Bundle(); - savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras); + mViewModel.onSaveInstanceState(savedInstance); viewModel2.setCredentialModel(savedInstance, new Intent()); final Bundle bundle1 = mViewModel.createCredentialIntentExtra(); @@ -170,68 +170,118 @@ public class AutoCredentialViewModelTest { // Run credential check @CredentialAction final int action = mViewModel.checkCredential(); + // Check viewModel behavior assertThat(action).isEqualTo(CREDENTIAL_VALID); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + + // Check onSaveInstanceState() + final Bundle actualBundle = new Bundle(); + mViewModel.onSaveInstanceState(actualBundle); + assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL)) + .isFalse(); } @Test public void testCheckCredential_needToChooseLock() { final int userId = 100; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_UNSPECIFIED); // Run credential check @CredentialAction final int action = mViewModel.checkCredential(); + // Check viewModel behavior assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + + // Check onSaveInstanceState() + final Bundle actualBundle = new Bundle(); + mViewModel.onSaveInstanceState(actualBundle); + assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL)) + .isFalse(); } @Test - public void testCheckCredential_needToConfirmLockFoSomething() { + public void testCheckCredential_needToConfirmLockForSomething() { final int userId = 101; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); // Run credential check @CredentialAction final int action = mViewModel.checkCredential(); + // Check viewModel behavior assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + + // Check onSaveInstanceState() + final Bundle actualBundle = new Bundle(); + mViewModel.onSaveInstanceState(actualBundle); + assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL)) + .isFalse(); } @Test public void testCheckCredential_needToConfirmLockForNumeric() { final int userId = 102; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_NUMERIC); // Run credential check @CredentialAction final int action = mViewModel.checkCredential(); + // Check viewModel behavior assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + + // Check onSaveInstanceState() + final Bundle actualBundle = new Bundle(); + mViewModel.onSaveInstanceState(actualBundle); + assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL)) + .isFalse(); } @Test public void testCheckCredential_needToConfirmLockForAlphabetic() { final int userId = 103; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_ALPHABETIC); // Run credential check @CredentialAction final int action = mViewModel.checkCredential(); + // Check viewModel behavior assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + + // Check onSaveInstanceState() + final Bundle actualBundle = new Bundle(); + mViewModel.onSaveInstanceState(actualBundle); + assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL)) + .isFalse(); } @Test @@ -252,14 +302,33 @@ public class AutoCredentialViewModelTest { // Run credential check @CredentialAction final int action = mViewModel.checkCredential(); + // Check viewModel behavior assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); + assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); + + // Check data inside CredentialModel final Bundle extras = mViewModel.createCredentialIntentExtra(); assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); - assertThat(isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))).isTrue(); - assertThat(isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))).isFalse(); - assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); + assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull(); + assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE); + assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isNotEqualTo(INVALID_CHALLENGE); + + // Check createGeneratingChallengeExtras() + final Bundle generatingChallengeExtras = mViewModel.createGeneratingChallengeExtras(); + assertThat(generatingChallengeExtras).isNotNull(); + assertThat(generatingChallengeExtras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); + final byte[] tokens = generatingChallengeExtras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN); + assertThat(tokens).isNotNull(); + assertThat(tokens.length).isEqualTo(1); + assertThat(tokens[0]).isEqualTo(1); + + // Check onSaveInstanceState() + final Bundle actualBundle = new Bundle(); + mViewModel.onSaveInstanceState(actualBundle); + assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL)) + .isTrue(); } @Test @@ -283,13 +352,22 @@ public class AutoCredentialViewModelTest { assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isTrue(); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + + // Check onSaveInstanceState() + final Bundle actualBundle = new Bundle(); + mViewModel.onSaveInstanceState(actualBundle); + assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL)) + .isTrue(); } @Test public void testGetUserId_fromIntent() { final int userId = 106; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); // Get userId assertThat(mViewModel.getUserId()).isEqualTo(userId); @@ -300,13 +378,61 @@ public class AutoCredentialViewModelTest { final int userId = 106; final Bundle savedInstance = new Bundle(); savedInstance.putBundle(KEY_CREDENTIAL_MODEL, - newInvalidChallengeCredentialIntentExtras(userId)); + newOnlySensorValidCredentialIntentExtras(userId)); mViewModel.setCredentialModel(savedInstance, new Intent()); // Get userId assertThat(mViewModel.getUserId()).isEqualTo(userId); } + @Test + public void testCreateGeneratingChallengeExtras_generateChallenge() { + final Bundle credentialExtras = newValidTokenCredentialIntentExtras(200); + final Bundle savedInstance = new Bundle(); + savedInstance.putBundle(KEY_CREDENTIAL_MODEL, credentialExtras); + savedInstance.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL, true); + mViewModel.setCredentialModel(savedInstance, new Intent()); + + // Check createGeneratingChallengeExtras() + final Bundle actualExtras = mViewModel.createGeneratingChallengeExtras(); + assertThat(actualExtras).isNotNull(); + assertThat(actualExtras.getLong(EXTRA_KEY_CHALLENGE)) + .isEqualTo(credentialExtras.getLong(EXTRA_KEY_CHALLENGE)); + final byte[] actualToken = actualExtras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN); + final byte[] expectedToken = credentialExtras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN); + assertThat(actualToken).isNotNull(); + assertThat(expectedToken).isNotNull(); + assertThat(actualToken.length).isEqualTo(expectedToken.length); + for (int i = 0; i < actualToken.length; ++i) { + assertWithMessage("tokens[" + i + "] not match").that(actualToken[i]) + .isEqualTo(expectedToken[i]); + } + } + + @Test + public void testCreateGeneratingChallengeExtras_notGenerateChallenge() { + final Bundle credentialExtras = newValidTokenCredentialIntentExtras(201); + final Bundle savedInstance = new Bundle(); + savedInstance.putBundle(KEY_CREDENTIAL_MODEL, credentialExtras); + savedInstance.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL, false); + mViewModel.setCredentialModel(savedInstance, new Intent()); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + } + + @Test + public void testCreateGeneratingChallengeExtras_invalidToken() { + final Bundle credentialExtras = newOnlySensorValidCredentialIntentExtras(202); + final Bundle savedInstance = new Bundle(); + savedInstance.putBundle(KEY_CREDENTIAL_MODEL, credentialExtras); + savedInstance.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL, true); + mViewModel.setCredentialModel(savedInstance, new Intent()); + + // Check createGeneratingChallengeExtras() + assertThat(mViewModel.createGeneratingChallengeExtras()).isNull(); + } + @Test public void testCheckNewCredentialFromActivityResult_invalidChooseLock() { final int userId = 107; @@ -360,7 +486,7 @@ public class AutoCredentialViewModelTest { public void testCheckNewCredentialFromActivityResult_nullDataConfirmLock() { final int userId = 109; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); // run checkNewCredentialFromActivityResult() final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false, @@ -374,7 +500,7 @@ public class AutoCredentialViewModelTest { public void testCheckNewCredentialFromActivityResult_validChooseLock() { final int userId = 108; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); @@ -395,17 +521,16 @@ public class AutoCredentialViewModelTest { final Bundle extras = mViewModel.createCredentialIntentExtra(); assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); - assertThat(isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))).isTrue(); - assertThat(isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))).isFalse(); + assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull(); + assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); } - @Test public void testCheckNewCredentialFromActivityResult_validConfirmLock() { final int userId = 109; mViewModel.setCredentialModel(null, - new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); + new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId))); when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( PASSWORD_QUALITY_SOMETHING); @@ -426,8 +551,8 @@ public class AutoCredentialViewModelTest { final Bundle extras = mViewModel.createCredentialIntentExtra(); assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); - assertThat(isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))).isTrue(); - assertThat(isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))).isFalse(); + assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull(); + assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); }