Merge "Launch settings for clicking fingerprint unlock"

This commit is contained in:
TreeHugger Robot
2023-01-04 05:55:54 +00:00
committed by Android (Google) Code Review
14 changed files with 899 additions and 131 deletions

View File

@@ -31,6 +31,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R; 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_CONFIRMING_CREDENTIALS = "confirming_credentials";
private static final String KEY_SCROLLED_TO_BOTTOM = "scrolled"; private static final String KEY_SCROLLED_TO_BOTTOM = "scrolled";
private GatekeeperPasswordProvider mGatekeeperPasswordProvider;
private UserManager mUserManager; private UserManager mUserManager;
private boolean mHasPassword; private boolean mHasPassword;
private boolean mBiometricUnlockDisabledByAdmin; private boolean mBiometricUnlockDisabledByAdmin;
@@ -178,7 +180,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
mErrorText = getErrorTextView(); mErrorText = getErrorTextView();
mUserManager = UserManager.get(this); mUserManager = getUserManager();
updatePasswordQuality(); updatePasswordQuality();
if (!mConfirmingCredentials) { if (!mConfirmingCredentials) {
@@ -253,8 +255,28 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
return super.shouldFinishWhenBackgrounded() && !mConfirmingCredentials && !mNextClicked; 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() { private void updatePasswordQuality() {
final int passwordQuality = new LockPatternUtils(this) final int passwordQuality = getLockPatternUtils()
.getActivePasswordQuality(mUserManager.getCredentialOwnerProfile(mUserId)); .getActivePasswordQuality(mUserManager.getCredentialOwnerProfile(mUserId));
mHasPassword = passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; mHasPassword = passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
} }
@@ -301,6 +323,14 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST); 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 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, Log.d(TAG,
@@ -310,7 +340,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
&& BiometricUtils.isMultiBiometricFingerprintEnrollmentFlow(this); && BiometricUtils.isMultiBiometricFingerprintEnrollmentFlow(this);
if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) { if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) {
if (isResultFinished(resultCode)) { if (isResultFinished(resultCode)) {
handleBiometricResultSkipOrFinished(resultCode, data); handleBiometricResultSkipOrFinished(resultCode, getSetResultIntentExtra(data));
} else if (isResultSkipped(resultCode)) { } else if (isResultSkipped(resultCode)) {
if (!BiometricUtils.tryStartingNextBiometricEnroll(this, if (!BiometricUtils.tryStartingNextBiometricEnroll(this,
ENROLL_NEXT_BIOMETRIC_REQUEST, "BIOMETRIC_FIND_SENSOR_SKIPPED")) { ENROLL_NEXT_BIOMETRIC_REQUEST, "BIOMETRIC_FIND_SENSOR_SKIPPED")) {

View File

@@ -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 * Given the result from confirming or choosing a credential, request Gatekeeper to generate
* a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * 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 GatekeeperCredentialNotMatchException if Gatekeeper response is not match
* @throws IllegalStateException if Gatekeeper Password is missing * @throws IllegalStateException if Gatekeeper Password is missing
*/ */
@Deprecated
public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result, public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result,
int userId, long challenge) { int userId, long challenge) {
if (!containsGatekeeperPasswordHandle(result)) { if (!containsGatekeeperPasswordHandle(result)) {
@@ -93,6 +96,10 @@ public class BiometricUtils {
return requestGatekeeperHat(context, gatekeeperPasswordHandle, userId, challenge); 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, public static byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId,
long challenge) { long challenge) {
final LockPatternUtils utils = new LockPatternUtils(context); final LockPatternUtils utils = new LockPatternUtils(context);
@@ -104,15 +111,25 @@ public class BiometricUtils {
return response.getGatekeeperHAT(); return response.getGatekeeperHAT();
} }
/**
* @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead.
*/
@Deprecated
public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) {
return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); 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) { public static long getGatekeeperPasswordHandle(@NonNull Intent data) {
return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); 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 * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the
* gatekeeper password associated with a previous * gatekeeper password associated with a previous
* {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)}
@@ -120,6 +137,7 @@ public class BiometricUtils {
* @param context Caller's context * @param context Caller's context
* @param data The onActivityResult intent from ChooseLock* or ConfirmLock* * @param data The onActivityResult intent from ChooseLock* or ConfirmLock*
*/ */
@Deprecated
public static void removeGatekeeperPasswordHandle(@NonNull Context context, public static void removeGatekeeperPasswordHandle(@NonNull Context context,
@Nullable Intent data) { @Nullable Intent data) {
if (data == null) { if (data == null) {
@@ -131,6 +149,10 @@ public class BiometricUtils {
removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data)); removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data));
} }
/**
* @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead.
*/
@Deprecated
public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) { public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) {
final LockPatternUtils utils = new LockPatternUtils(context); final LockPatternUtils utils = new LockPatternUtils(context);
utils.removeGatekeeperPasswordHandle(handle); utils.removeGatekeeperPasswordHandle(handle);

View File

@@ -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");
}
}

View File

@@ -43,6 +43,7 @@ import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricEnrollIntroduction;
import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.android.settings.biometrics.MultiBiometricEnrollHelper;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils; import com.android.settingslib.HelpUtils;
@@ -68,7 +69,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
mFingerprintManager = Utils.getFingerprintManagerOrNull(this); mFingerprintManager = getFingerprintManager();
if (mFingerprintManager == null) { if (mFingerprintManager == null) {
Log.e(TAG, "Null FingerprintManager"); Log.e(TAG, "Null FingerprintManager");
finish(); finish();
@@ -127,11 +128,50 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
final ScrollView scrollView = findViewById(R.id.sud_scroll_view); final ScrollView scrollView = findViewById(R.id.sud_scroll_view);
scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 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 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) {
super.onActivityResult(requestCode, resultCode, data); 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 @Override
@@ -295,11 +335,6 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override @Override
protected void getChallenge(GenerateChallengeCallback callback) { protected void getChallenge(GenerateChallengeCallback callback) {
mFingerprintManager = Utils.getFingerprintManagerOrNull(this);
if (mFingerprintManager == null) {
callback.onChallengeGenerated(0, 0, 0L);
return;
}
mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated); mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated);
} }

View File

@@ -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.Utils.SETTINGS_PACKAGE_NAME;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY; 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.Activity;
import android.app.Dialog; import android.app.Dialog;
@@ -60,11 +61,13 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreference;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SubSettings; import com.android.settings.SubSettings;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity; import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
@@ -410,7 +413,7 @@ public class FingerprintSettings extends SubSettings {
launchChooseOrConfirmLock(); launchChooseOrConfirmLock();
} else if (!mHasFirstEnrolled) { } else if (!mHasFirstEnrolled) {
mIsEnrolling = true; mIsEnrolling = true;
addFirstFingerprint(); addFirstFingerprint(null);
} }
} }
updateFooterColumns(activity); updateFooterColumns(activity);
@@ -776,7 +779,7 @@ public class FingerprintSettings extends SubSettings {
if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) { if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) {
mLaunchedConfirm = false; mLaunchedConfirm = false;
if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
if (data != null && BiometricUtils.containsGatekeeperPasswordHandle(data)) { if (BiometricUtils.containsGatekeeperPasswordHandle(data)) {
if (!mHasFirstEnrolled && !mIsEnrolling) { if (!mHasFirstEnrolled && !mIsEnrolling) {
final Activity activity = getActivity(); final Activity activity = getActivity();
if (activity != null) { if (activity != null) {
@@ -784,21 +787,34 @@ public class FingerprintSettings extends SubSettings {
activity.overridePendingTransition(R.anim.sud_slide_next_in, activity.overridePendingTransition(R.anim.sud_slide_next_in,
R.anim.sud_slide_next_out); 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, mFingerprintManager.generateChallenge(mUserId,
(sensorId, userId, challenge) -> { (sensorId, userId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(getActivity(), final Activity activity = getActivity();
data, if (activity == null || activity.isFinishing()) {
mUserId, challenge); // Stop everything
mChallenge = challenge; Log.w(TAG, "activity detach or finishing");
BiometricUtils.removeGatekeeperPasswordHandle(getActivity(), return;
data);
updateAddPreference();
if (!mHasFirstEnrolled && !mIsEnrolling) {
mIsEnrolling = true;
addFirstFingerprint();
} }
final GatekeeperPasswordProvider provider =
new GatekeeperPasswordProvider(
new LockPatternUtils(activity));
mToken = provider.requestGatekeeperHat(data, challenge,
mUserId);
mChallenge = challenge;
provider.removeGatekeeperPasswordHandle(data, false);
updateAddPreference();
}); });
}
} else { } else {
Log.d(TAG, "Data null or GK PW missing"); Log.d(TAG, "Data null or GK PW missing");
finish(); finish();
@@ -815,12 +831,29 @@ public class FingerprintSettings extends SubSettings {
activity.finish(); activity.finish();
} }
} else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) { } 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; mIsEnrolling = false;
mHasFirstEnrolled = true; mHasFirstEnrolled = true;
if (resultCode != RESULT_FINISHED) { updateAddPreference();
Log.d(TAG, "Add first fingerprint fail, result:" + resultCode);
finish();
}
} }
} }
@@ -892,7 +925,7 @@ public class FingerprintSettings extends SubSettings {
} }
} }
private void addFirstFingerprint() { private void addFirstFingerprint(@Nullable Long gkPwHandle) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.setClassName(SETTINGS_PACKAGE_NAME, intent.setClassName(SETTINGS_PACKAGE_NAME,
FeatureFlagUtils.isEnabled(getActivity(), FeatureFlagUtils.isEnabled(getActivity(),
@@ -906,7 +939,13 @@ public class FingerprintSettings extends SubSettings {
SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
if (gkPwHandle != null) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
gkPwHandle.longValue());
} else {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, mChallenge);
}
startActivityForResult(intent, AUTO_ADD_FIRST_FINGERPRINT_REQUEST); startActivityForResult(intent, AUTO_ADD_FIRST_FINGERPRINT_REQUEST);
} }

View File

@@ -16,17 +16,13 @@
package com.android.settings.biometrics.fingerprint; package com.android.settings.biometrics.fingerprint;
import static android.util.FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.util.FeatureFlagUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.ParentalControlsUtils; import com.android.settings.biometrics.ParentalControlsUtils;
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.utils.StringUtil; 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. * Returns the class name of the Settings page corresponding to fingerprint settings.
*/ */
public String getSettingsClassName() { public String getSettingsClassName() {
return !hasEnrolled() && isAvailable() return FingerprintSettings.class.getName();
? (FeatureFlagUtils.isEnabled(mContext, SETTINGS_BIOMETRICS2_ENROLLMENT)
? FingerprintEnrollmentActivity.class.getName()
: FingerprintEnrollIntroductionInternal.class.getName())
: FingerprintSettings.class.getName();
} }
/** /**

View File

@@ -50,6 +50,7 @@ public final class CredentialModel {
/** /**
* Default value if GkPwHandle is invalid. * Default value if GkPwHandle is invalid.
*/ */
@VisibleForTesting
public static final long INVALID_GK_PW_HANDLE = 0L; public static final long INVALID_GK_PW_HANDLE = 0L;
/** /**
@@ -115,8 +116,8 @@ public final class CredentialModel {
/** /**
* Check user id is valid or not * Check user id is valid or not
*/ */
public static boolean isValidUserId(int userId) { public boolean isValidUserId() {
return userId != UserHandle.USER_NULL; return mUserId != UserHandle.USER_NULL;
} }
/** /**
@@ -134,6 +135,13 @@ public final class CredentialModel {
mChallenge = value; mChallenge = value;
} }
/**
* Check challenge is valid or not
*/
public boolean isValidChallenge() {
return mChallenge != INVALID_CHALLENGE;
}
/** /**
* Get challenge token * Get challenge token
*/ */
@@ -153,8 +161,8 @@ public final class CredentialModel {
/** /**
* Check challengeToken is valid or not * Check challengeToken is valid or not
*/ */
public static boolean isValidToken(@Nullable byte[] token) { public boolean isValidToken() {
return token != null; return mToken != null;
} }
/** /**
@@ -175,8 +183,8 @@ public final class CredentialModel {
/** /**
* Check gkPwHandle is valid or not * Check gkPwHandle is valid or not
*/ */
public static boolean isValidGkPwHandle(long gkPwHandle) { public boolean isValidGkPwHandle() {
return gkPwHandle != INVALID_GK_PW_HANDLE; return mGkPwHandle != INVALID_GK_PW_HANDLE;
} }
/** /**
@@ -206,10 +214,10 @@ public final class CredentialModel {
+ ", userId:" + mUserId + ", userId:" + mUserId
+ ", challenge:{len:" + challengeLen + ", challenge:{len:" + challengeLen
+ ", updateMillis:" + mUpdateChallengeMillis + "}" + ", updateMillis:" + mUpdateChallengeMillis + "}"
+ ", token:{len:" + tokenLen + ", isValid:" + isValidToken(mToken) + ", token:{len:" + tokenLen + ", isValid:" + isValidToken()
+ ", updateMillis:" + mUpdateTokenMillis + "}" + ", updateMillis:" + mUpdateTokenMillis + "}"
+ ", gkPwHandle:{len:" + gkPwHandleLen + ", isValid:" + ", gkPwHandle:{len:" + gkPwHandleLen + ", isValid:" + isValidGkPwHandle()
+ isValidGkPwHandle(mGkPwHandle) + ", clearMillis:" + mClearGkPwHandleMillis + "}" + ", clearMillis:" + mClearGkPwHandleMillis + "}"
+ ", mSensorId:{id:" + mSensorId + ", updateMillis:" + mUpdateSensorIdMillis + "}" + ", mSensorId:{id:" + mSensorId + ", updateMillis:" + mUpdateSensorIdMillis + "}"
+ " }"; + " }";
} }

View File

@@ -98,7 +98,6 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class); mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class);
mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent()); mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent());
checkCredential();
// Theme // Theme
setTheme(mViewModel.getRequest().getTheme()); setTheme(mViewModel.getRequest().getTheme());
@@ -107,12 +106,14 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
// fragment // fragment
setContentView(R.layout.biometric_enrollment_container); setContentView(R.layout.biometric_enrollment_container);
final FingerprintEnrollIntroViewModel introViewModel = final FingerprintEnrollIntroViewModel introViewModel =
viewModelProvider.get(FingerprintEnrollIntroViewModel.class); viewModelProvider.get(FingerprintEnrollIntroViewModel.class);
introViewModel.setEnrollmentRequest(mViewModel.getRequest()); introViewModel.setEnrollmentRequest(mViewModel.getRequest());
introViewModel.setUserId(mAutoCredentialViewModel.getUserId()); introViewModel.setUserId(mAutoCredentialViewModel.getUserId());
if (savedInstanceState == null) { if (savedInstanceState == null) {
checkCredential();
final String tag = "FingerprintEnrollIntroFragment"; final String tag = "FingerprintEnrollIntroFragment";
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true) .setReorderingAllowed(true)
@@ -138,11 +139,34 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
onSetActivityResult(new ActivityResult(RESULT_CANCELED, null)); 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) { private void onSetActivityResult(@NonNull ActivityResult result) {
setResult(mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction() final int resultCode = mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction()
? RESULT_CANCELED ? RESULT_CANCELED
: result.getResultCode(), : result.getResultCode();
result.getData()); final Intent intent = resultCode == BiometricEnrollBase.RESULT_FINISHED
? createSetResultIntentWithGeneratingChallengeExtra(result.getData())
: result.getData();
setResult(resultCode, intent);
finish(); finish();
} }

View File

@@ -65,6 +65,10 @@ public class AutoCredentialViewModel extends AndroidViewModel {
@VisibleForTesting @VisibleForTesting
static final String KEY_CREDENTIAL_MODEL = "credential_model"; 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; private static final boolean DEBUG = false;
/** /**
@@ -173,6 +177,9 @@ public class AutoCredentialViewModel extends AndroidViewModel {
@NonNull private final MutableLiveData<Boolean> mGenerateChallengeFailedLiveData = @NonNull private final MutableLiveData<Boolean> mGenerateChallengeFailedLiveData =
new MutableLiveData<>(); new MutableLiveData<>();
// flag if token is generating through checkCredential()'s generateChallenge()
private boolean mIsGeneratingChallengeDuringCheckingCredential;
public AutoCredentialViewModel( public AutoCredentialViewModel(
@NonNull Application application, @NonNull Application application,
@NonNull LockPatternUtils lockPatternUtils, @NonNull LockPatternUtils lockPatternUtils,
@@ -189,10 +196,13 @@ public class AutoCredentialViewModel extends AndroidViewModel {
final Bundle bundle; final Bundle bundle;
if (savedInstanceState != null) { if (savedInstanceState != null) {
bundle = savedInstanceState.getBundle(KEY_CREDENTIAL_MODEL); bundle = savedInstanceState.getBundle(KEY_CREDENTIAL_MODEL);
mIsGeneratingChallengeDuringCheckingCredential = savedInstanceState.getBoolean(
KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL);
} else { } else {
bundle = intent.getExtras(); bundle = intent.getExtras();
} }
mCredentialModel = new CredentialModel(bundle, SystemClock.elapsedRealtimeClock()); mCredentialModel = new CredentialModel(bundle != null ? bundle : new Bundle(),
SystemClock.elapsedRealtimeClock());
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "setCredentialModel " + mCredentialModel + ", savedInstanceState exist:" Log.d(TAG, "setCredentialModel " + mCredentialModel + ", savedInstanceState exist:"
@@ -204,6 +214,8 @@ public class AutoCredentialViewModel extends AndroidViewModel {
* Handle onSaveInstanceState from activity * Handle onSaveInstanceState from activity
*/ */
public void onSaveInstanceState(@NonNull Bundle outState) { public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL,
mIsGeneratingChallengeDuringCheckingCredential);
outState.putBundle(KEY_CREDENTIAL_MODEL, mCredentialModel.getBundle()); outState.putBundle(KEY_CREDENTIAL_MODEL, mCredentialModel.getBundle());
} }
@@ -212,6 +224,24 @@ public class AutoCredentialViewModel extends AndroidViewModel {
return mGenerateChallengeFailedLiveData; 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. * Check credential status for biometric enrollment.
*/ */
@@ -220,11 +250,11 @@ public class AutoCredentialViewModel extends AndroidViewModel {
if (isValidCredential()) { if (isValidCredential()) {
return CREDENTIAL_VALID; return CREDENTIAL_VALID;
} }
final long gkPwHandle = mCredentialModel.getGkPwHandle();
if (isUnspecifiedPassword()) { if (isUnspecifiedPassword()) {
return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK; return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
} else if (CredentialModel.isValidGkPwHandle(gkPwHandle)) { } else if (mCredentialModel.isValidGkPwHandle()) {
generateChallenge(gkPwHandle); generateChallenge(mCredentialModel.getGkPwHandle());
mIsGeneratingChallengeDuringCheckingCredential = true;
return CREDENTIAL_IS_GENERATING_CHALLENGE; return CREDENTIAL_IS_GENERATING_CHALLENGE;
} else { } else {
return CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK; return CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
@@ -261,8 +291,7 @@ public class AutoCredentialViewModel extends AndroidViewModel {
} }
private boolean isValidCredential() { private boolean isValidCredential() {
return !isUnspecifiedPassword() return !isUnspecifiedPassword() && mCredentialModel.isValidToken();
&& CredentialModel.isValidToken(mCredentialModel.getToken());
} }
private boolean isUnspecifiedPassword() { private boolean isUnspecifiedPassword() {
@@ -316,17 +345,14 @@ public class AutoCredentialViewModel extends AndroidViewModel {
@NonNull @NonNull
public Bundle createCredentialIntentExtra() { public Bundle createCredentialIntentExtra() {
final Bundle retBundle = new Bundle(); final Bundle retBundle = new Bundle();
final long gkPwHandle = mCredentialModel.getGkPwHandle(); if (mCredentialModel.isValidGkPwHandle()) {
if (CredentialModel.isValidGkPwHandle(gkPwHandle)) { retBundle.putLong(EXTRA_KEY_GK_PW_HANDLE, mCredentialModel.getGkPwHandle());
retBundle.putLong(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
} }
final byte[] token = mCredentialModel.getToken(); if (mCredentialModel.isValidToken()) {
if (CredentialModel.isValidToken(token)) { retBundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, mCredentialModel.getToken());
retBundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, token);
} }
final int userId = getUserId(); if (mCredentialModel.isValidUserId()) {
if (CredentialModel.isValidUserId(userId)) { retBundle.putInt(Intent.EXTRA_USER_ID, mCredentialModel.getUserId());
retBundle.putInt(Intent.EXTRA_USER_ID, userId);
} }
retBundle.putLong(EXTRA_KEY_CHALLENGE, mCredentialModel.getChallenge()); retBundle.putLong(EXTRA_KEY_CHALLENGE, mCredentialModel.getChallenge());
retBundle.putInt(EXTRA_KEY_SENSOR_ID, mCredentialModel.getSensorId()); 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_REQUEST_GK_PW_HANDLE, true);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true);
final int userId = getUserId(); if (mCredentialModel.isValidUserId()) {
if (CredentialModel.isValidUserId(userId)) { intent.putExtra(Intent.EXTRA_USER_ID, mCredentialModel.getUserId());
intent.putExtra(Intent.EXTRA_USER_ID, userId);
} }
return intent; return intent;
} }
@@ -367,9 +392,8 @@ public class AutoCredentialViewModel extends AndroidViewModel {
.setForegroundOnly(true) .setForegroundOnly(true)
.setReturnCredentials(true); .setReturnCredentials(true);
final int userId = mCredentialModel.getUserId(); if (mCredentialModel.isValidUserId()) {
if (CredentialModel.isValidUserId(userId)) { builder.setUserId(mCredentialModel.getUserId());
builder.setUserId(userId);
} }
return builder.build(); return builder.build();
} }

View File

@@ -104,7 +104,7 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
* Handle activity result from FingerprintFindSensor * Handle activity result from FingerprintFindSensor
*/ */
public void onContinueEnrollActivityResult(@NonNull ActivityResult result, int userId) { 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"); Log.w(TAG, "fail to reset isWaiting flag for enrollment");
} }
if (result.getResultCode() == RESULT_FINISHED if (result.getResultCode() == RESULT_FINISHED

View File

@@ -16,9 +16,20 @@
package com.android.settings.biometrics.fingerprint; 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.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt; 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.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -32,8 +43,15 @@ import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 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.R;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -42,11 +60,11 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController; import org.robolectric.android.controller.ActivityController;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -54,17 +72,25 @@ import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class FingerprintEnrollIntroductionTest { public class FingerprintEnrollIntroductionTest {
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private FingerprintManager mFingerprintManager; @Mock private FingerprintManager mFingerprintManager;
@Mock private UserManager mUserManager;
private GatekeeperPasswordProvider mGatekeeperPasswordProvider;
private Context mContext; private Context mContext;
private FingerprintEnrollIntroduction mFingerprintEnrollIntroduction; private TestFingerprintEnrollIntroduction mFingerprintEnrollIntroduction;
private static final int MAX_ENROLLMENTS = 5; 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 @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mGatekeeperPasswordProvider = new GatekeeperPasswordProvider(mLockPatternUtils);
mContext = spy(RuntimeEnvironment.application.getApplicationContext()); mContext = spy(RuntimeEnvironment.application.getApplicationContext());
final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -79,14 +105,34 @@ public class FingerprintEnrollIntroductionTest {
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
props.add(prop); props.add(prop);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
when(mUserManager.getCredentialOwnerProfile(anyInt())).thenAnswer(
(Answer<Integer>) invocation -> (int) invocation.getArgument(0));
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), anyInt()))
.thenAnswer((Answer<VerifyCredentialResponse>) invocation ->
newGoodCredential(invocation.getArgument(0), EXPECTED_TOKEN));
doNothing().when(mLockPatternUtils).removeGatekeeperPasswordHandle(anyLong());
} }
void setupFingerprintEnrollIntroWith(Intent intent) { void setupFingerprintEnrollIntroWith(@NonNull Intent intent) {
ActivityController<FingerprintEnrollIntroduction> controller =
Robolectric.buildActivity(FingerprintEnrollIntroduction.class, intent); final ActivityController<TestFingerprintEnrollIntroduction> controller =
mFingerprintEnrollIntroduction = spy(controller.get()); Robolectric.buildActivity(TestFingerprintEnrollIntroduction.class, intent);
ReflectionHelpers.setField( mFingerprintEnrollIntroduction = controller.get();
mFingerprintEnrollIntroduction, "mFingerprintManager", mFingerprintManager); 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(); controller.create();
} }
@@ -102,7 +148,7 @@ public class FingerprintEnrollIntroductionTest {
@Test @Test
public void intro_CheckCanEnrollNormal() { public void intro_CheckCanEnrollNormal() {
setupFingerprintEnrollIntroWith(new Intent()); setupFingerprintEnrollIntroWith(newTokenOnlyIntent());
setFingerprintManagerToHave(3 /* numEnrollments */); setFingerprintManagerToHave(3 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -111,7 +157,7 @@ public class FingerprintEnrollIntroductionTest {
@Test @Test
public void intro_CheckMaxEnrolledNormal() { public void intro_CheckMaxEnrolledNormal() {
setupFingerprintEnrollIntroWith(new Intent()); setupFingerprintEnrollIntroWith(newTokenOnlyIntent());
setFingerprintManagerToHave(7 /* numEnrollments */); setFingerprintManagerToHave(7 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -126,10 +172,7 @@ public class FingerprintEnrollIntroductionTest {
when(resources.getInteger(anyInt())).thenReturn(5); when(resources.getInteger(anyInt())).thenReturn(5);
when(mContext.getResources()).thenReturn(resources); when(mContext.getResources()).thenReturn(resources);
setupFingerprintEnrollIntroWith( setupFingerprintEnrollIntroWith(newFirstSuwIntent());
new Intent()
.putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true)
.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true));
setFingerprintManagerToHave(0 /* numEnrollments */); setFingerprintManagerToHave(0 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -144,10 +187,7 @@ public class FingerprintEnrollIntroductionTest {
when(mContext.getResources()).thenReturn(resources); when(mContext.getResources()).thenReturn(resources);
when(resources.getInteger(anyInt())).thenReturn(1); when(resources.getInteger(anyInt())).thenReturn(1);
setupFingerprintEnrollIntroWith( setupFingerprintEnrollIntroWith(newFirstSuwIntent());
new Intent()
.putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true)
.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true));
setFingerprintManagerToHave(1 /* numEnrollments */); setFingerprintManagerToHave(1 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -156,8 +196,7 @@ public class FingerprintEnrollIntroductionTest {
@Test @Test
public void intro_CheckCanEnrollDuringDeferred() { public void intro_CheckCanEnrollDuringDeferred() {
setupFingerprintEnrollIntroWith( setupFingerprintEnrollIntroWith(newDeferredSuwIntent());
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true));
setFingerprintManagerToHave(2 /* numEnrollments */); setFingerprintManagerToHave(2 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -166,8 +205,7 @@ public class FingerprintEnrollIntroductionTest {
@Test @Test
public void intro_CheckMaxEnrolledDuringDeferred() { public void intro_CheckMaxEnrolledDuringDeferred() {
setupFingerprintEnrollIntroWith( setupFingerprintEnrollIntroWith(newDeferredSuwIntent());
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true));
setFingerprintManagerToHave(6 /* numEnrollments */); setFingerprintManagerToHave(6 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -176,8 +214,7 @@ public class FingerprintEnrollIntroductionTest {
@Test @Test
public void intro_CheckCanEnrollDuringPortal() { public void intro_CheckCanEnrollDuringPortal() {
setupFingerprintEnrollIntroWith( setupFingerprintEnrollIntroWith(newPortalSuwIntent());
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, true));
setFingerprintManagerToHave(2 /* numEnrollments */); setFingerprintManagerToHave(2 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
@@ -186,11 +223,124 @@ public class FingerprintEnrollIntroductionTest {
@Test @Test
public void intro_CheckMaxEnrolledDuringPortal() { public void intro_CheckMaxEnrolledDuringPortal() {
setupFingerprintEnrollIntroWith( setupFingerprintEnrollIntroWith(newPortalSuwIntent());
new Intent().putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, true));
setFingerprintManagerToHave(6 /* numEnrollments */); setFingerprintManagerToHave(6 /* numEnrollments */);
int result = mFingerprintEnrollIntroduction.checkMaxEnrolled(); int result = mFingerprintEnrollIntroduction.checkMaxEnrolled();
assertThat(result).isEqualTo(R.string.fingerprint_intro_error_max); 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);
}
}
} }

View File

@@ -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();
}
}
}

View File

@@ -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_CHALLENGE;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID; 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_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.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -58,11 +59,13 @@ public class CredentialModelTest {
} }
public static Bundle newValidTokenCredentialIntentExtras(int userId) { 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) { public static Bundle newOnlySensorValidCredentialIntentExtras(int userId) {
return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null, 0L); return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null,
INVALID_GK_PW_HANDLE);
} }
public static Bundle newGkPwHandleCredentialIntentExtras(int userId, long gkPwHandle) { public static Bundle newGkPwHandleCredentialIntentExtras(int userId, long gkPwHandle) {

View File

@@ -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_CHALLENGE;
import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_GK_PW_HANDLE; 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.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.newCredentialModelIntentExtras;
import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newGkPwHandleCredentialIntentExtras; 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.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_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_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.CredentialAction;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.GenerateChallengeCallback; 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_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_CHALLENGE_TOKEN;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE; 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.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -109,7 +109,7 @@ public class AutoCredentialViewModelTest {
mViewModel.setCredentialModel(null, new Intent().putExtras(extras)); mViewModel.setCredentialModel(null, new Intent().putExtras(extras));
final Bundle savedInstance = new Bundle(); final Bundle savedInstance = new Bundle();
savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras); mViewModel.onSaveInstanceState(savedInstance);
viewModel2.setCredentialModel(savedInstance, new Intent()); viewModel2.setCredentialModel(savedInstance, new Intent());
final Bundle bundle1 = mViewModel.createCredentialIntentExtra(); final Bundle bundle1 = mViewModel.createCredentialIntentExtra();
@@ -144,7 +144,7 @@ public class AutoCredentialViewModelTest {
mViewModel.setCredentialModel(null, new Intent().putExtras(extras)); mViewModel.setCredentialModel(null, new Intent().putExtras(extras));
final Bundle savedInstance = new Bundle(); final Bundle savedInstance = new Bundle();
savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras); mViewModel.onSaveInstanceState(savedInstance);
viewModel2.setCredentialModel(savedInstance, new Intent()); viewModel2.setCredentialModel(savedInstance, new Intent());
final Bundle bundle1 = mViewModel.createCredentialIntentExtra(); final Bundle bundle1 = mViewModel.createCredentialIntentExtra();
@@ -170,68 +170,118 @@ public class AutoCredentialViewModelTest {
// Run credential check // Run credential check
@CredentialAction final int action = mViewModel.checkCredential(); @CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_VALID); assertThat(action).isEqualTo(CREDENTIAL_VALID);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); 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 @Test
public void testCheckCredential_needToChooseLock() { public void testCheckCredential_needToChooseLock() {
final int userId = 100; final int userId = 100;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
PASSWORD_QUALITY_UNSPECIFIED); PASSWORD_QUALITY_UNSPECIFIED);
// Run credential check // Run credential check
@CredentialAction final int action = mViewModel.checkCredential(); @CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK); assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); 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 @Test
public void testCheckCredential_needToConfirmLockFoSomething() { public void testCheckCredential_needToConfirmLockForSomething() {
final int userId = 101; final int userId = 101;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
PASSWORD_QUALITY_SOMETHING); PASSWORD_QUALITY_SOMETHING);
// Run credential check // Run credential check
@CredentialAction final int action = mViewModel.checkCredential(); @CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); 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 @Test
public void testCheckCredential_needToConfirmLockForNumeric() { public void testCheckCredential_needToConfirmLockForNumeric() {
final int userId = 102; final int userId = 102;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
PASSWORD_QUALITY_NUMERIC); PASSWORD_QUALITY_NUMERIC);
// Run credential check // Run credential check
@CredentialAction final int action = mViewModel.checkCredential(); @CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); 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 @Test
public void testCheckCredential_needToConfirmLockForAlphabetic() { public void testCheckCredential_needToConfirmLockForAlphabetic() {
final int userId = 103; final int userId = 103;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
PASSWORD_QUALITY_ALPHABETIC); PASSWORD_QUALITY_ALPHABETIC);
// Run credential check // Run credential check
@CredentialAction final int action = mViewModel.checkCredential(); @CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK); assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); 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 @Test
@@ -252,14 +302,33 @@ public class AutoCredentialViewModelTest {
// Run credential check // Run credential check
@CredentialAction final int action = mViewModel.checkCredential(); @CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE); assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull(); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
// Check data inside CredentialModel
final Bundle extras = mViewModel.createCredentialIntentExtra(); final Bundle extras = mViewModel.createCredentialIntentExtra();
assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId);
assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge);
assertThat(isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))).isTrue(); assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))).isFalse(); assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); 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 @Test
@@ -283,13 +352,22 @@ public class AutoCredentialViewModelTest {
assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE); assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isTrue(); assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isTrue();
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); 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 @Test
public void testGetUserId_fromIntent() { public void testGetUserId_fromIntent() {
final int userId = 106; final int userId = 106;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
// Get userId // Get userId
assertThat(mViewModel.getUserId()).isEqualTo(userId); assertThat(mViewModel.getUserId()).isEqualTo(userId);
@@ -300,13 +378,61 @@ public class AutoCredentialViewModelTest {
final int userId = 106; final int userId = 106;
final Bundle savedInstance = new Bundle(); final Bundle savedInstance = new Bundle();
savedInstance.putBundle(KEY_CREDENTIAL_MODEL, savedInstance.putBundle(KEY_CREDENTIAL_MODEL,
newInvalidChallengeCredentialIntentExtras(userId)); newOnlySensorValidCredentialIntentExtras(userId));
mViewModel.setCredentialModel(savedInstance, new Intent()); mViewModel.setCredentialModel(savedInstance, new Intent());
// Get userId // Get userId
assertThat(mViewModel.getUserId()).isEqualTo(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 @Test
public void testCheckNewCredentialFromActivityResult_invalidChooseLock() { public void testCheckNewCredentialFromActivityResult_invalidChooseLock() {
final int userId = 107; final int userId = 107;
@@ -360,7 +486,7 @@ public class AutoCredentialViewModelTest {
public void testCheckNewCredentialFromActivityResult_nullDataConfirmLock() { public void testCheckNewCredentialFromActivityResult_nullDataConfirmLock() {
final int userId = 109; final int userId = 109;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
// run checkNewCredentialFromActivityResult() // run checkNewCredentialFromActivityResult()
final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false, final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false,
@@ -374,7 +500,7 @@ public class AutoCredentialViewModelTest {
public void testCheckNewCredentialFromActivityResult_validChooseLock() { public void testCheckNewCredentialFromActivityResult_validChooseLock() {
final int userId = 108; final int userId = 108;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
PASSWORD_QUALITY_SOMETHING); PASSWORD_QUALITY_SOMETHING);
@@ -395,17 +521,16 @@ public class AutoCredentialViewModelTest {
final Bundle extras = mViewModel.createCredentialIntentExtra(); final Bundle extras = mViewModel.createCredentialIntentExtra();
assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId);
assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge);
assertThat(isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))).isTrue(); assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))).isFalse(); assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
} }
@Test @Test
public void testCheckNewCredentialFromActivityResult_validConfirmLock() { public void testCheckNewCredentialFromActivityResult_validConfirmLock() {
final int userId = 109; final int userId = 109;
mViewModel.setCredentialModel(null, mViewModel.setCredentialModel(null,
new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId))); new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn( when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
PASSWORD_QUALITY_SOMETHING); PASSWORD_QUALITY_SOMETHING);
@@ -426,8 +551,8 @@ public class AutoCredentialViewModelTest {
final Bundle extras = mViewModel.createCredentialIntentExtra(); final Bundle extras = mViewModel.createCredentialIntentExtra();
assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId); assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId);
assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge); assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge);
assertThat(isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN))).isTrue(); assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE))).isFalse(); assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1); assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
} }