From 8e4acdbf510428c2ab82ca1a32e701783a9f6017 Mon Sep 17 00:00:00 2001 From: Rubin Xu Date: Wed, 14 Apr 2021 14:32:26 +0100 Subject: [PATCH] Refactor ChooseLockGenericController * Move all logics around aggregating password policies to the controller * Replace HIDE_DISABLED_PREFS and MINIMUM_QUALITY_KEY with HIDE_INSECURE_OPTIONS as all call sites are just using them to hide insecure screenlock options. * Remove password policy aggregation logic from ChooseLockPassword and make it use policies passed in. * Remove padlock from disabled screen lock options, per UX mock. * Increase char limit for some strings Bug: 177638284 Bug: 177641868 Bug: 182561862 Test: m RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.password Test: 1. set profile password quality/complexity and enroll device lock 2. set profile password quality/complexity and enroll work challenge 3. set parent password quality/complexity and enroll device lock 4. set parent password quality/complexity and enroll work challenge 5. set profile and parent password complexity, then enroll work challenge 6. set profile and parent password complexity, then unify work challenge 7. Enroll device lock during SUW Change-Id: Iba1d37e6f33eba7b7e8e1f805f8e37aaec108404 --- res/values/strings.xml | 8 +- .../biometrics/BiometricEnrollActivity.java | 7 +- .../BiometricEnrollIntroduction.java | 4 +- .../combination/BiometricsSettingsBase.java | 6 +- .../fingerprint/FingerprintSettings.java | 4 +- .../settings/password/ChooseLockGeneric.java | 86 ++---- .../password/ChooseLockGenericController.java | 269 ++++++++++++------ .../settings/password/ChooseLockPassword.java | 13 - .../ChooseLockTypeDialogFragment.java | 10 +- .../password/SetNewPasswordController.java | 13 +- .../password/SetupChooseLockGeneric.java | 19 +- .../password/SetupChooseLockPassword.java | 9 +- .../security/ScreenPinningSettings.java | 4 +- .../android/settings/users/UserSettings.java | 4 +- .../ChooseLockGenericControllerTest.java | 212 ++++++++++---- .../password/ChooseLockPasswordTest.java | 13 - .../SetNewPasswordControllerTest.java | 21 +- .../password/SetupChooseLockPasswordTest.java | 3 +- 18 files changed, 386 insertions(+), 319 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 742ed97ae2e..ee3ecb48948 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1311,16 +1311,16 @@ Choose screen lock - + Choose a screen lock - + Choose a new screen lock - + Choose a lock for work apps - + Choose a new work lock diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index eede22ac868..97cda4b5118 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -298,9 +298,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private void launchChooseLock() { Log.d(TAG, "launchChooseLock"); Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent()); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true); + intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true); @@ -350,8 +348,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { final Intent intent; // If only device credential was specified, ask the user to only set that up. intent = new Intent(this, ChooseLockGeneric.class); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); launchEnrollActivity(intent); } diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index b44c2c9da1a..c34b2843706 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -222,9 +222,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase private void launchChooseLock() { Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent()); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true); + intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); intent.putExtra(getExtraKeyForBiometric(), true); if (mUserId != UserHandle.USER_NULL) { diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java index c449ff4bfce..b5f6ef328a6 100644 --- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java +++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java @@ -19,7 +19,6 @@ import static android.app.Activity.RESULT_OK; import static com.android.settings.password.ChooseLockPattern.RESULT_FINISHED; -import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.hardware.face.FaceManager; @@ -193,9 +192,8 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { if (!launched) { Intent intent = BiometricUtils.getChooseLockIntent(getActivity(), getIntent()); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true); + intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, + true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index a328f21e578..dbac4150380 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -685,9 +685,7 @@ public class FingerprintSettings extends SubSettings { // TODO: This should be cleaned up. ChooseLockGeneric should provide a way of // specifying arguments/requests, instead of relying on callers setting extras. intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric.class.getName()); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, + intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 67fc7efc67c..a1826ba0cbc 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -75,8 +75,6 @@ import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.search.SearchFeatureProvider; -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -112,8 +110,7 @@ public class ChooseLockGeneric extends SettingsActivity { private static final String KEY_SKIP_BIOMETRICS = "unlock_skip_biometrics"; private static final String PASSWORD_CONFIRMED = "password_confirmed"; private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation"; - public static final String MINIMUM_QUALITY_KEY = "minimum_quality"; - public static final String HIDE_DISABLED_PREFS = "hide_disabled_prefs"; + public static final String HIDE_INSECURE_OPTIONS = "hide_insecure_options"; public static final String TAG_FRP_WARNING_DIALOG = "frp_warning_dialog"; public static final String KEY_LOCK_SETTINGS_FOOTER ="lock_settings_footer"; @@ -277,21 +274,20 @@ public class ChooseLockGeneric extends SettingsActivity { arguments, intent.getExtras()).getIdentifier(); mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mUserId); - mController = new ChooseLockGenericController( - getContext(), mUserId, mRequestedMinComplexity, - mOnlyEnforceDevicePasswordRequirement, - mLockPatternUtils); - - final int aggregatedComplexity = mController.getAggregatedPasswordComplexity(); - final boolean isComplexityProvidedByAdmin = - aggregatedComplexity > mRequestedMinComplexity - && aggregatedComplexity > PASSWORD_COMPLEXITY_NONE; + mController = new ChooseLockGenericController.Builder( + getContext(), mUserId, mLockPatternUtils) + .setAppRequestedMinComplexity(mRequestedMinComplexity) + .setEnforceDevicePasswordRequirementOnly(mOnlyEnforceDevicePasswordRequirement) + .setProfileToUnify(mUnificationProfileId) + .setHideInsecureScreenLockTypes(alwaysHideInsecureScreenLockTypes() + || intent.getBooleanExtra(HIDE_INSECURE_OPTIONS, false)) + .build(); // If the complexity is provided by the admin, do not get the caller app's name. // If the app requires, for example, low complexity, and the admin requires high // complexity, it does not make sense to show a footer telling the user it's the app // requesting a particular complexity because the admin-set complexity will override it. - mCallerAppName = isComplexityProvidedByAdmin ? null : + mCallerAppName = mController.isComplexityProvidedByAdmin() ? null : intent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME); mManagedPasswordProvider = ManagedLockPasswordProvider.get(activity, mUserId); @@ -330,6 +326,10 @@ public class ChooseLockGeneric extends SettingsActivity { return super.onCreateView(inflater, container, savedInstanceState); } + protected boolean alwaysHideInsecureScreenLockTypes() { + return false; + } + private void updateActivityTitle() { if (mLockPatternUtils == null) { // mLockPatternUtils will be uninitialized if ChooseLockGenericFragment.onCreate() @@ -606,16 +606,12 @@ public class ChooseLockGeneric extends SettingsActivity { } if (quality == -1) { // If caller didn't specify password quality, show UI and allow the user to choose. - quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1); - quality = mController.upgradeQuality(quality); - final boolean hideDisabledPrefs = intent.getBooleanExtra( - HIDE_DISABLED_PREFS, false); final PreferenceScreen prefScreen = getPreferenceScreen(); if (prefScreen != null) { prefScreen.removeAll(); } addPreferences(); - disableUnusablePreferences(quality, hideDisabledPrefs); + disableUnusablePreferences(); updatePreferenceText(); updateCurrentPreference(); updatePreferenceSummaryIfNeeded(); @@ -746,71 +742,23 @@ public class ChooseLockGeneric extends SettingsActivity { return lock != null ? lock.preferenceKey : null; } - /*** - * Disables preferences that are less secure than required quality. The actual - * implementation is in disableUnusablePreferenceImpl. - * - * @param quality the requested quality. - * @param hideDisabledPrefs if false preferences show why they were disabled; otherwise - * they're not shown at all. - */ - protected void disableUnusablePreferences(final int quality, boolean hideDisabledPrefs) { - disableUnusablePreferencesImpl(quality, hideDisabledPrefs); - } - /*** * Disables preferences that are less secure than required quality. * - * @param quality the requested quality. - * @param hideDisabled whether to hide disable screen lock options. */ - protected void disableUnusablePreferencesImpl(final int quality, - boolean hideDisabled) { + private void disableUnusablePreferences() { final PreferenceScreen entries = getPreferenceScreen(); - int adminEnforcedQuality = LockPatternUtils.credentialTypeToPasswordQuality( - mLockPatternUtils.getRequestedPasswordMetrics( - mUserId, mOnlyEnforceDevicePasswordRequirement).credType); - EnforcedAdmin enforcedAdmin = - RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet(getActivity(), - mUserId); - // If we are to unify a work challenge at the end of the credential enrollment, manually - // merge any password policy from that profile here, so we are enrolling a compliant - // password. This is because once unified, the profile's password policy will - // be enforced on the new credential. - if (mUnificationProfileId != UserHandle.USER_NULL) { - int profileEnforceQuality = mDpm.getPasswordQuality(null, mUnificationProfileId); - if (profileEnforceQuality > adminEnforcedQuality) { - adminEnforcedQuality = profileEnforceQuality; - enforcedAdmin = EnforcedAdmin.combine(enforcedAdmin, - RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet( - getActivity(), mUnificationProfileId)); - } - } - for (ScreenLockType lock : ScreenLockType.values()) { String key = lock.preferenceKey; Preference pref = findPreference(key); if (pref instanceof RestrictedPreference) { boolean visible = mController.isScreenLockVisible(lock); - boolean enabled = mController.isScreenLockEnabled(lock, quality); - boolean disabledByAdmin = - mController.isScreenLockDisabledByAdmin(lock, adminEnforcedQuality); - if (hideDisabled) { - visible = visible && enabled; - } + boolean enabled = mController.isScreenLockEnabled(lock); if (!visible) { entries.removePreference(pref); - } else if (disabledByAdmin && enforcedAdmin != null) { - ((RestrictedPreference) pref).setDisabledByAdmin(enforcedAdmin); } else if (!enabled) { - // we need to setDisabledByAdmin to null first to disable the padlock - // in case it was set earlier. - ((RestrictedPreference) pref).setDisabledByAdmin(null); - pref.setSummary(R.string.unlock_set_unlock_disabled_summary); pref.setEnabled(false); - } else { - ((RestrictedPreference) pref).setDisabledByAdmin(null); } } } diff --git a/src/com/android/settings/password/ChooseLockGenericController.java b/src/com/android/settings/password/ChooseLockGenericController.java index 6900e3d0f60..1b951d4f968 100644 --- a/src/com/android/settings/password/ChooseLockGenericController.java +++ b/src/com/android/settings/password/ChooseLockGenericController.java @@ -17,6 +17,7 @@ package com.android.settings.password; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; @@ -24,6 +25,7 @@ import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.PasswordMetrics; import android.content.Context; import android.os.UserHandle; +import android.os.UserManager; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -36,84 +38,140 @@ import java.util.List; /** * A controller for ChooseLockGeneric, and other similar classes which shows a list of possible - * screen locks for the user to choose from. + * screen lock types for the user to choose from. This is the main place where different + * restrictions on allowed screen lock types are aggregated in Settings. + * + * Each screen lock type has two states: whether it is visible and whether it is enabled. + * Visibility is affected by things like resource configs, whether it's for a managed profile, + * or whether the caller allows it or not. This is determined by + * {@link #isScreenLockVisible(ScreenLockType)}. For visible screen lock types, they can be disabled + * by a combination of admin policies and request from the calling app, which is determined by + * {@link #isScreenLockEnabled(ScreenLockType}. */ + public class ChooseLockGenericController { private final Context mContext; private final int mUserId; - @PasswordComplexity private final int mRequestedMinComplexity; + private final boolean mHideInsecureScreenLockTypes; + @PasswordComplexity private final int mAppRequestedMinComplexity; private final boolean mDevicePasswordRequirementOnly; - private ManagedLockPasswordProvider mManagedPasswordProvider; + private final int mUnificationProfileId; + private final ManagedLockPasswordProvider mManagedPasswordProvider; private final LockPatternUtils mLockPatternUtils; - public ChooseLockGenericController(Context context, int userId) { - this( - context, - userId, - PASSWORD_COMPLEXITY_NONE, - /* mOnlyEnforceDevicePasswordRequirement */ false, - new LockPatternUtils(context)); - } - - /** - * @param requestedMinComplexity specifies the min password complexity to be taken into account - * when determining the available screen lock types - */ public ChooseLockGenericController(Context context, int userId, - @PasswordComplexity int requestedMinComplexity, - boolean devicePasswordRequirementOnly, - LockPatternUtils lockPatternUtils) { - this( - context, - userId, - requestedMinComplexity, - devicePasswordRequirementOnly, - ManagedLockPasswordProvider.get(context, userId), - lockPatternUtils); - } - - @VisibleForTesting - ChooseLockGenericController( - Context context, - int userId, - @PasswordComplexity int requestedMinComplexity, - boolean devicePasswordRequirementOnly, - ManagedLockPasswordProvider managedLockPasswordProvider, - LockPatternUtils lockPatternUtils) { + ManagedLockPasswordProvider managedPasswordProvider, LockPatternUtils lockPatternUtils, + boolean hideInsecureScreenLockTypes, int appRequestedMinComplexity, + boolean devicePasswordRequirementOnly, int unificationProfileId) { mContext = context; mUserId = userId; - mRequestedMinComplexity = requestedMinComplexity; - mDevicePasswordRequirementOnly = devicePasswordRequirementOnly; - mManagedPasswordProvider = managedLockPasswordProvider; + mManagedPasswordProvider = managedPasswordProvider; mLockPatternUtils = lockPatternUtils; + mHideInsecureScreenLockTypes = hideInsecureScreenLockTypes; + mAppRequestedMinComplexity = appRequestedMinComplexity; + mDevicePasswordRequirementOnly = devicePasswordRequirementOnly; + mUnificationProfileId = unificationProfileId; + } + + /** Builder class for {@link ChooseLockGenericController} */ + public static class Builder { + private final Context mContext; + private final int mUserId; + private final ManagedLockPasswordProvider mManagedPasswordProvider; + private final LockPatternUtils mLockPatternUtils; + + private boolean mHideInsecureScreenLockTypes = false; + @PasswordComplexity private int mAppRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE; + private boolean mDevicePasswordRequirementOnly = false; + private int mUnificationProfileId = UserHandle.USER_NULL; + + public Builder(Context context, int userId) { + this(context, userId, new LockPatternUtils(context)); + } + + public Builder(Context context, int userId, + LockPatternUtils lockPatternUtils) { + this( + context, + userId, + ManagedLockPasswordProvider.get(context, userId), + lockPatternUtils); + } + + @VisibleForTesting + Builder( + Context context, + int userId, + ManagedLockPasswordProvider managedLockPasswordProvider, + LockPatternUtils lockPatternUtils) { + mContext = context; + mUserId = userId; + mManagedPasswordProvider = managedLockPasswordProvider; + mLockPatternUtils = lockPatternUtils; + } + /** + * Sets the password complexity requested by the calling app via + * {@link android.app.admin.DevicePolicyManager#EXTRA_PASSWORD_COMPLEXITY}. + */ + public Builder setAppRequestedMinComplexity(int complexity) { + mAppRequestedMinComplexity = complexity; + return this; + } + + /** + * Sets whether the enrolment flow should discard any password policies originating from the + * work profile, even if the work profile currently has unified challenge. This can be + * requested by the calling app via + * {@link android.app.admin.DevicePolicyManager#EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY}. + */ + public Builder setEnforceDevicePasswordRequirementOnly(boolean deviceOnly) { + mDevicePasswordRequirementOnly = deviceOnly; + return this; + } + + /** + * Sets the user ID of any profile whose work challenge should be unified at the end of this + * enrolment flow. This will lead to all password policies from that profile to be taken + * into consideration by this class, so that we are enrolling a compliant password. This is + * because once unified, the profile's password policy will be enforced on the new + * credential. + */ + public Builder setProfileToUnify(int profileId) { + mUnificationProfileId = profileId; + return this; + } + + /** + * Sets whether insecure screen lock types (NONE and SWIPE) should be hidden in the UI. + */ + public Builder setHideInsecureScreenLockTypes(boolean hide) { + mHideInsecureScreenLockTypes = hide; + return this; + } + + /** Creates {@link ChooseLockGenericController} instance. */ + public ChooseLockGenericController build() { + return new ChooseLockGenericController(mContext, mUserId, mManagedPasswordProvider, + mLockPatternUtils, mHideInsecureScreenLockTypes, mAppRequestedMinComplexity, + mDevicePasswordRequirementOnly, mUnificationProfileId); + } } /** - * Returns the highest quality among the specified {@code quality}, the password requirement - * set by device admins (legacy password quality metrics and password complexity), and the - * min password complexity requested by the calling app. - */ - public int upgradeQuality(int quality) { - // Compare specified quality and dpm quality - // TODO(b/142781408): convert from quality to credential type once PIN is supported. - int dpmUpgradedQuality = Math.max(quality, LockPatternUtils.credentialTypeToPasswordQuality( - getAggregatedPasswordMetrics().credType)); - return Math.max(dpmUpgradedQuality, - PasswordMetrics.complexityLevelToMinQuality(getAggregatedPasswordComplexity())); - } - - /** - * Whether the given screen lock type should be visible in the given context. + * Returns whether the given screen lock type should be visible in the given context. */ public boolean isScreenLockVisible(ScreenLockType type) { - final boolean managedProfile = mUserId != UserHandle.myUserId(); + final boolean managedProfile = mContext.getSystemService(UserManager.class) + .isManagedProfile(mUserId); switch (type) { case NONE: - return !mContext.getResources().getBoolean(R.bool.config_hide_none_security_option) + return !mHideInsecureScreenLockTypes + && !mContext.getResources().getBoolean(R.bool.config_hide_none_security_option) && !managedProfile; // Profiles should use unified challenge instead. case SWIPE: - return !mContext.getResources().getBoolean(R.bool.config_hide_swipe_security_option) + return !mHideInsecureScreenLockTypes + && !mContext.getResources().getBoolean(R.bool.config_hide_swipe_security_option) && !managedProfile; // Swipe doesn't make sense for profiles. case MANAGED: return mManagedPasswordProvider.isManagedPasswordChoosable(); @@ -128,29 +186,27 @@ public class ChooseLockGenericController { } /** - * Whether screen lock with {@code type} should be enabled. - * - * @param type The screen lock type. - * @param quality The minimum required quality. This can either be requirement by device policy - * manager or because some flow only makes sense with secure lock screens. + * Whether screen lock with {@code type} should be enabled assuming all relevant password + * requirements. The lock's visibility ({@link #isScreenLockVisible}) is not considered here. */ - public boolean isScreenLockEnabled(ScreenLockType type, int quality) { - return type.maxQuality >= quality; + public boolean isScreenLockEnabled(ScreenLockType type) { + return type.maxQuality >= upgradeQuality(PASSWORD_QUALITY_UNSPECIFIED); } /** - * Whether screen lock with {@code type} is disabled by device policy admin. - * - * @param type The screen lock type. - * @param adminEnforcedQuality The minimum quality that the admin enforces. + * Increases the given quality to be as high as the combined quality from all relevant + * password requirements. */ - public boolean isScreenLockDisabledByAdmin(ScreenLockType type, int adminEnforcedQuality) { - boolean disabledByAdmin = type.maxQuality < adminEnforcedQuality; - if (type == ScreenLockType.MANAGED) { - disabledByAdmin = disabledByAdmin - || !mManagedPasswordProvider.isManagedPasswordChoosable(); - } - return disabledByAdmin; + // TODO(b/142781408): convert from quality to credential type once PIN is supported. + public int upgradeQuality(int quality) { + return Math.max(quality, + Math.max( + LockPatternUtils.credentialTypeToPasswordQuality( + getAggregatedPasswordMetrics().credType), + PasswordMetrics.complexityLevelToMinQuality( + getAggregatedPasswordComplexity()) + ) + ); } /** @@ -175,43 +231,72 @@ public class ChooseLockGenericController { } /** - * Gets a list of screen locks that should be visible for the given quality. The returned list - * is ordered in the natural order of the enum (the order those enums were defined). - * - * @param quality The minimum quality required in the context of the current flow. This should - * be one of the constants defined in - * {@code DevicePolicyManager#PASSWORD_QUALITY_*}. - * @param includeDisabled Whether to include screen locks disabled by {@code quality} - * requirements in the returned list. + * Gets a list of screen lock types that should be visible for the given quality. The returned + * list is ordered in the natural order of the enum (the order those enums were defined). Screen + * locks disabled by password policy will not be returned. */ @NonNull - public List getVisibleScreenLockTypes(int quality, boolean includeDisabled) { - int upgradedQuality = upgradeQuality(quality); + public List getVisibleAndEnabledScreenLockTypes() { List locks = new ArrayList<>(); // EnumSet's iterator guarantees the natural order of the enums for (ScreenLockType lock : ScreenLockType.values()) { - if (isScreenLockVisible(lock)) { - if (includeDisabled || isScreenLockEnabled(lock, upgradedQuality)) { - locks.add(lock); - } + if (isScreenLockVisible(lock) && isScreenLockEnabled(lock)) { + locks.add(lock); } } return locks; } + /** + * Returns the combined password metrics from all relevant policies which affects the current + * user. Normally password policies set on the current user's work profile instance will be + * taken into consideration here iff the work profile doesn't have its own work challenge. + * By setting {@link #mUnificationProfileId}, the work profile's password policy will always + * be combined here. Alternatively, by setting {@link #mDevicePasswordRequirementOnly}, its + * password policy will always be disregarded here. + */ public PasswordMetrics getAggregatedPasswordMetrics() { - return mLockPatternUtils.getRequestedPasswordMetrics(mUserId, + PasswordMetrics metrics = mLockPatternUtils.getRequestedPasswordMetrics(mUserId, mDevicePasswordRequirementOnly); + if (mUnificationProfileId != UserHandle.USER_NULL) { + metrics.maxWith(mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId)); + } + return metrics; } + /** + * Returns the combined password complexity from all relevant policies which affects the current + * user. The same logic of handling work profile password policies as + * {@link #getAggregatedPasswordMetrics} applies here. + */ public int getAggregatedPasswordComplexity() { - return Math.max(mRequestedMinComplexity, + int complexity = Math.max(mAppRequestedMinComplexity, mLockPatternUtils.getRequestedPasswordComplexity( mUserId, mDevicePasswordRequirementOnly)); + if (mUnificationProfileId != UserHandle.USER_NULL) { + complexity = Math.max(complexity, + mLockPatternUtils.getRequestedPasswordComplexity(mUnificationProfileId)); + } + return complexity; } + /** + * Returns whether any screen lock type has been disabled only due to password policy + * from the admin. Will return {@code false} if the restriction is purely due to calling + * app's request. + */ public boolean isScreenLockRestrictedByAdmin() { return getAggregatedPasswordMetrics().credType != CREDENTIAL_TYPE_NONE - || getAggregatedPasswordComplexity() != PASSWORD_COMPLEXITY_NONE; + || isComplexityProvidedByAdmin(); + } + + /** + * Returns whether the aggregated password complexity is non-zero and comes from + * admin policy. + */ + public boolean isComplexityProvidedByAdmin() { + final int aggregatedComplexity = getAggregatedPasswordComplexity(); + return aggregatedComplexity > mAppRequestedMinComplexity + && aggregatedComplexity > PASSWORD_COMPLEXITY_NONE; } } diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index de377a73d67..6382abf9dbf 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -416,19 +416,6 @@ public class ChooseLockPassword extends SettingsActivity { mMinComplexity = intent.getIntExtra(EXTRA_KEY_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE); mMinMetrics = intent.getParcelableExtra(EXTRA_KEY_MIN_METRICS); if (mMinMetrics == null) mMinMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE); - // If we are to unify a work challenge at the end of the credential enrollment, manually - // merge any password policy from that profile here, so we are enrolling a compliant - // password. This is because once unified, the profile's password policy will - // be enforced on the new credential. - //TODO: Move this logic to ChooseLockGeneric; let ChooseLockGeneric be the only place - //where password requirement mixing happens. ChooseLockPassword simply enforces what's - //set via IntentBuilder.setPasswordRequirement() - if (mUnificationProfileId != UserHandle.USER_NULL) { - mMinMetrics.maxWith( - mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId)); - mMinComplexity = Math.max(mMinComplexity, - mLockPatternUtils.getRequestedPasswordComplexity(mUnificationProfileId)); - } if (intent.getBooleanExtra( ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) { diff --git a/src/com/android/settings/password/ChooseLockTypeDialogFragment.java b/src/com/android/settings/password/ChooseLockTypeDialogFragment.java index 8bc29763e2c..6ec70574b11 100644 --- a/src/com/android/settings/password/ChooseLockTypeDialogFragment.java +++ b/src/com/android/settings/password/ChooseLockTypeDialogFragment.java @@ -18,7 +18,6 @@ package com.android.settings.password; import android.app.Activity; import android.app.Dialog; -import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.DialogInterface; @@ -100,7 +99,9 @@ public class ChooseLockTypeDialogFragment extends InstrumentedDialogFragment public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final int userId = getArguments().getInt(ARG_USER_ID); - mController = new ChooseLockGenericController(getContext(), userId); + mController = new ChooseLockGenericController.Builder(getContext(), userId) + .setHideInsecureScreenLockTypes(true) + .build(); } @Override @@ -124,10 +125,7 @@ public class ChooseLockTypeDialogFragment extends InstrumentedDialogFragment public Dialog onCreateDialog(Bundle savedInstanceState) { Context context = getContext(); Builder builder = new Builder(context); - List locks = - mController.getVisibleScreenLockTypes( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, - false /* includeDisabled */); + List locks = mController.getVisibleAndEnabledScreenLockTypes(); mAdapter = new ScreenLockAdapter(context, locks, mController); builder.setAdapter(mAdapter, this); builder.setTitle(R.string.setup_lock_settings_options_dialog_title); diff --git a/src/com/android/settings/password/SetNewPasswordController.java b/src/com/android/settings/password/SetNewPasswordController.java index 03efa605d53..ef4ff75497c 100644 --- a/src/com/android/settings/password/SetNewPasswordController.java +++ b/src/com/android/settings/password/SetNewPasswordController.java @@ -19,7 +19,6 @@ package com.android.settings.password; import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT; -import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; import static com.android.internal.util.Preconditions.checkNotNull; @@ -145,10 +144,8 @@ final class SetNewPasswordController { private Bundle getBiometricChooseLockExtras() { Bundle chooseLockExtras = new Bundle(); - chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - PASSWORD_QUALITY_SOMETHING); chooseLockExtras.putBoolean( - ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true); + ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true); return chooseLockExtras; @@ -156,10 +153,8 @@ final class SetNewPasswordController { private Bundle getFingerprintChooseLockExtras() { Bundle chooseLockExtras = new Bundle(); - chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - PASSWORD_QUALITY_SOMETHING); chooseLockExtras.putBoolean( - ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true); + ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true); return chooseLockExtras; @@ -167,10 +162,8 @@ final class SetNewPasswordController { private Bundle getFaceChooseLockExtras() { Bundle chooseLockExtras = new Bundle(); - chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - PASSWORD_QUALITY_SOMETHING); chooseLockExtras.putBoolean( - ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true); + ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, true); return chooseLockExtras; diff --git a/src/com/android/settings/password/SetupChooseLockGeneric.java b/src/com/android/settings/password/SetupChooseLockGeneric.java index 7818f0d2fc9..15a90e3fdbf 100644 --- a/src/com/android/settings/password/SetupChooseLockGeneric.java +++ b/src/com/android/settings/password/SetupChooseLockGeneric.java @@ -21,7 +21,6 @@ import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; -import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -160,22 +159,12 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { return SetupChooseLockGeneric.InternalActivity.class; } - /*** - * Disables preferences that are less secure than required quality and shows only secure - * screen lock options here. - * - * @param quality the requested quality. - */ @Override - protected void disableUnusablePreferences(final int quality, boolean hideDisabled) { + protected boolean alwaysHideInsecureScreenLockTypes() { // At this part of the flow, the user has already indicated they want to add a pin, - // pattern or password, so don't show "None" or "Slide". We disable them here and set - // the HIDE_DISABLED flag to true to hide them. This only happens for setup wizard. - // We do the following max check here since the device may already have a Device Admin - // installed with a policy we need to honor. - final int newQuality = Math.max(quality, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - super.disableUnusablePreferencesImpl(newQuality, true /* hideDisabled */); + // pattern or password, so don't show "None" or "Slide". We disable them here. + // This only happens for setup wizard. + return true; } @Override diff --git a/src/com/android/settings/password/SetupChooseLockPassword.java b/src/com/android/settings/password/SetupChooseLockPassword.java index 25f5a348904..7cf90c09929 100644 --- a/src/com/android/settings/password/SetupChooseLockPassword.java +++ b/src/com/android/settings/password/SetupChooseLockPassword.java @@ -17,7 +17,6 @@ package com.android.settings.password; import android.app.Activity; -import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -81,9 +80,11 @@ public class SetupChooseLockPassword extends ChooseLockPassword { super.onViewCreated(view, savedInstanceState); final Activity activity = getActivity(); ChooseLockGenericController chooseLockGenericController = - new ChooseLockGenericController(activity, mUserId); - boolean anyOptionsShown = chooseLockGenericController.getVisibleScreenLockTypes( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false).size() > 0; + new ChooseLockGenericController.Builder(activity, mUserId) + .setHideInsecureScreenLockTypes(true) + .build(); + boolean anyOptionsShown = chooseLockGenericController + .getVisibleAndEnabledScreenLockTypes().size() > 0; boolean showOptionsButton = activity.getIntent().getBooleanExtra( ChooseLockGeneric.ChooseLockGenericFragment.EXTRA_SHOW_OPTIONS_BUTTON, false); if (!anyOptionsShown) { diff --git a/src/com/android/settings/security/ScreenPinningSettings.java b/src/com/android/settings/security/ScreenPinningSettings.java index 3fa098b826a..e219b44084d 100644 --- a/src/com/android/settings/security/ScreenPinningSettings.java +++ b/src/com/android/settings/security/ScreenPinningSettings.java @@ -136,8 +136,8 @@ public class ScreenPinningSettings extends SettingsPreferenceFragment if (passwordQuality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { Intent chooseLockIntent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); chooseLockIntent.putExtra( - ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, + true); startActivityForResult(chooseLockIntent, CHANGE_LOCK_METHOD_REQUEST); return false; } diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index d88d8b5c316..5f14399a26b 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -432,8 +432,8 @@ public class UserSettings extends SettingsPreferenceFragment private void launchChooseLockscreen() { Intent chooseLockIntent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); - chooseLockIntent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + chooseLockIntent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, + true); startActivityForResult(chooseLockIntent, REQUEST_CHOOSE_LOCK); } diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java index 02b89cba845..049a34969c1 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java @@ -20,23 +20,31 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; import static org.robolectric.RuntimeEnvironment.application; import android.app.admin.DevicePolicyManager; -import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.PasswordPolicy; +import android.os.UserHandle; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.testutils.shadow.ShadowUserManager; import org.junit.After; import org.junit.Before; @@ -48,10 +56,11 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.util.Arrays; +import java.util.Set; import java.util.regex.Pattern; @RunWith(RobolectricTestRunner.class) -@Config(shadows = SettingsShadowResources.class) +@Config(shadows = {ShadowUserManager.class, SettingsShadowResources.class}) public class ChooseLockGenericControllerTest { private ChooseLockGenericController mController; @@ -68,7 +77,7 @@ public class ChooseLockGenericControllerTest { when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true); setDevicePolicyPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); - mController = createController(PASSWORD_COMPLEXITY_NONE); + mController = createBuilder().build(); SettingsShadowResources.overrideResource(R.bool.config_hide_none_security_option, false); SettingsShadowResources.overrideResource(R.bool.config_hide_swipe_security_option, false); } @@ -95,8 +104,8 @@ public class ChooseLockGenericControllerTest { } @Test - public void isScreenLockVisible_notCurrentUser_shouldHideInsecure() { - mController = new ChooseLockGenericController(application, 1 /* userId */); + public void isScreenLockVisible_ManagedProfile_shouldHideInsecure() { + ShadowUserManager.getShadow().setManagedProfiles(Set.of(0)); assertWithMessage("SWIPE visible").that( mController.isScreenLockVisible(ScreenLockType.SWIPE)).isFalse(); assertWithMessage("NONE visible").that(mController.isScreenLockVisible(ScreenLockType.NONE)) @@ -112,62 +121,116 @@ public class ChooseLockGenericControllerTest { } @Test - public void isScreenLockEnabled_lowerQuality_shouldReturnFalse() { - for (ScreenLockType lock : ScreenLockType.values()) { - assertWithMessage(lock + " enabled").that( - mController.isScreenLockEnabled(lock, lock.maxQuality + 1)).isFalse(); - } + public void isScreenLockEnabled_Default() { + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); } @Test - public void isScreenLockEnabled_equalQuality_shouldReturnTrue() { - for (ScreenLockType lock : ScreenLockType.values()) { - assertWithMessage(lock + " enabled").that( - mController.isScreenLockEnabled(lock, lock.defaultQuality)).isTrue(); - } + public void isScreenLockEnabled_QualityUnspecified() { + setDevicePolicyPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); } @Test - public void isScreenLockEnabled_higherQuality_shouldReturnTrue() { - for (ScreenLockType lock : ScreenLockType.values()) { - assertWithMessage(lock + " enabled").that( - mController.isScreenLockEnabled(lock, lock.maxQuality - 1)).isTrue(); - } + public void isScreenLockEnabled_QualitySomething() { + setDevicePolicyPasswordQuality(PASSWORD_QUALITY_SOMETHING); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); } @Test - public void isScreenLockDisabledByAdmin_lowerQuality_shouldReturnTrue() { - doReturn(true).when(mManagedLockPasswordProvider).isManagedPasswordChoosable(); - for (ScreenLockType lock : ScreenLockType.values()) { - assertWithMessage(lock + " disabledByAdmin").that( - mController.isScreenLockDisabledByAdmin(lock, lock.maxQuality + 1)).isTrue(); - } + public void isScreenLockEnabled_QualityNumeric() { + setDevicePolicyPasswordQuality(PASSWORD_QUALITY_NUMERIC); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); } @Test - public void isScreenLockDisabledByAdmin_equalQuality_shouldReturnFalse() { - doReturn(true).when(mManagedLockPasswordProvider).isManagedPasswordChoosable(); - for (ScreenLockType lock : ScreenLockType.values()) { - assertWithMessage(lock + " disabledByAdmin").that( - mController.isScreenLockDisabledByAdmin(lock, lock.maxQuality)).isFalse(); - } + public void isScreenLockEnabled_QualityNumericComplex() { + setDevicePolicyPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); } @Test - public void isScreenLockDisabledByAdmin_higherQuality_shouldReturnFalse() { - doReturn(true).when(mManagedLockPasswordProvider).isManagedPasswordChoosable(); - for (ScreenLockType lock : ScreenLockType.values()) { - assertWithMessage(lock + " disabledByAdmin").that( - mController.isScreenLockDisabledByAdmin(lock, lock.maxQuality - 1)).isFalse(); - } + public void isScreenLockEnabled_QualityAlphabetic() { + setDevicePolicyPasswordQuality(PASSWORD_QUALITY_ALPHABETIC); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); } @Test - public void isScreenLockDisabledByAdmin_managedNotChoosable_shouldReturnTrue() { - doReturn(false).when(mManagedLockPasswordProvider).isManagedPasswordChoosable(); - assertWithMessage("MANANGED disabledByAdmin").that(mController.isScreenLockDisabledByAdmin( - ScreenLockType.MANAGED, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)) - .isTrue(); + public void isScreenLockEnabled_QualityComplex() { + setDevicePolicyPasswordQuality(PASSWORD_QUALITY_COMPLEX); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); + } + + @Test + public void isScreenLockEnabled_NoneComplexity() { + when(mLockPatternUtils.getRequestedPasswordComplexity(anyInt(), anyBoolean())) + .thenReturn(PASSWORD_COMPLEXITY_NONE); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); + } + + @Test + public void isScreenLockEnabled_lowComplexity() { + when(mLockPatternUtils.getRequestedPasswordComplexity(anyInt(), anyBoolean())) + .thenReturn(PASSWORD_COMPLEXITY_LOW); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); + } + + @Test + public void isScreenLockEnabled_mediumComplexity() { + when(mLockPatternUtils.getRequestedPasswordComplexity(anyInt(), anyBoolean())) + .thenReturn(PASSWORD_COMPLEXITY_MEDIUM); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); + } + + @Test + public void isScreenLockEnabled_highComplexity() { + when(mLockPatternUtils.getRequestedPasswordComplexity(anyInt(), anyBoolean())) + .thenReturn(PASSWORD_COMPLEXITY_HIGH); + assertThat(mController.isScreenLockEnabled(ScreenLockType.NONE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.SWIPE)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PATTERN)).isFalse(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PIN)).isTrue(); + assertThat(mController.isScreenLockEnabled(ScreenLockType.PASSWORD)).isTrue(); } @Test @@ -181,8 +244,8 @@ public class ChooseLockGenericControllerTest { @Test public void getVisibleScreenLockTypes_qualitySomething_shouldReturnPatterPinPassword() { - assertThat(mController.getVisibleScreenLockTypes( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false)) + mController = createBuilder().setHideInsecureScreenLockTypes(true).build(); + assertThat(mController.getVisibleAndEnabledScreenLockTypes()) .isEqualTo(Arrays.asList( ScreenLockType.PATTERN, ScreenLockType.PIN, @@ -191,8 +254,7 @@ public class ChooseLockGenericControllerTest { @Test public void getVisibleScreenLockTypes_showDisabled_shouldReturnAllButManaged() { - assertThat(mController.getVisibleScreenLockTypes( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, true)) + assertThat(mController.getVisibleAndEnabledScreenLockTypes()) .isEqualTo(Arrays.asList( ScreenLockType.NONE, ScreenLockType.SWIPE, @@ -223,31 +285,68 @@ public class ChooseLockGenericControllerTest { @Test public void upgradeQuality_complexityHigh_minQualityNumericComplex() { + mController = createBuilder().setAppRequestedMinComplexity(PASSWORD_COMPLEXITY_HIGH) + .build(); setDevicePolicyPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); - ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_HIGH); - assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)) + assertThat(mController.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)) .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX); } @Test public void upgradeQuality_complexityMedium_minQualityNumericComplex() { + mController = createBuilder().setAppRequestedMinComplexity(PASSWORD_COMPLEXITY_MEDIUM) + .build(); setDevicePolicyPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); - ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_MEDIUM); - assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)) + assertThat(mController.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)) .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX); } @Test public void upgradeQuality_complexityLow_minQualitySomething() { + mController = createBuilder().setAppRequestedMinComplexity(PASSWORD_COMPLEXITY_LOW) + .build(); setDevicePolicyPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); - ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_LOW); - assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)) + assertThat(mController.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)) .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); } + @Test + public void getAggregatedPasswordComplexity_AppRequest() { + mController = createBuilder().setAppRequestedMinComplexity(PASSWORD_COMPLEXITY_HIGH) + .build(); + assertThat(mController.getAggregatedPasswordComplexity()) + .isEqualTo(PASSWORD_COMPLEXITY_HIGH); + } + + @Test + public void getAggregatedPasswordComplexity_DevicePolicy() { + mController = createBuilder().setAppRequestedMinComplexity(PASSWORD_COMPLEXITY_LOW) + .build(); + when(mLockPatternUtils.getRequestedPasswordComplexity(eq(UserHandle.myUserId()), eq(false))) + .thenReturn(PASSWORD_COMPLEXITY_MEDIUM); + + assertThat(mController.getAggregatedPasswordComplexity()) + .isEqualTo(PASSWORD_COMPLEXITY_MEDIUM); + } + + @Test + public void getAggregatedPasswordComplexity_ProfileUnification() { + mController = createBuilder() + .setProfileToUnify(123) + .setAppRequestedMinComplexity(PASSWORD_COMPLEXITY_LOW) + .build(); + when(mLockPatternUtils.getRequestedPasswordComplexity(eq(UserHandle.myUserId()), eq(false))) + .thenReturn(PASSWORD_COMPLEXITY_MEDIUM); + when(mLockPatternUtils.getRequestedPasswordComplexity(eq(123))) + .thenReturn(PASSWORD_COMPLEXITY_HIGH); + + assertThat(mController.getAggregatedPasswordComplexity()) + .isEqualTo(PASSWORD_COMPLEXITY_HIGH); + } + private void setDevicePolicyPasswordQuality(int quality) { PasswordPolicy policy = new PasswordPolicy(); policy.quality = quality; @@ -256,13 +355,10 @@ public class ChooseLockGenericControllerTest { .thenReturn(policy.getMinMetrics()); } - private ChooseLockGenericController createController( - @PasswordComplexity int minPasswordComplexity) { - return new ChooseLockGenericController( + private ChooseLockGenericController.Builder createBuilder() { + return new ChooseLockGenericController.Builder( application, 0 /* userId */, - minPasswordComplexity, - false, mManagedLockPasswordProvider, mLockPatternUtils); } diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java index 2233f1d1d00..7da9c505b8e 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java @@ -30,7 +30,6 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY; import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS; -import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -429,18 +428,6 @@ public class ChooseLockPasswordTest { "PIN must be at least 8 digits"); } - @Test - public void validateComplexityMergedFromUnificationUserOnCreate() { - ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW); - ShadowLockPatternUtils.setRequiredPasswordComplexity(123, PASSWORD_COMPLEXITY_HIGH); - - Intent intent = createIntentForPasswordValidation(null, PASSWORD_COMPLEXITY_NONE, - PASSWORD_QUALITY_NUMERIC); - intent.putExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID, 123); - assertPasswordValidationResultForIntent(LockscreenCredential.createNone(), intent, - "PIN must be at least 8 digits"); - } - private ChooseLockPassword buildChooseLockPasswordActivity(Intent intent) { return Robolectric.buildActivity(ChooseLockPassword.class, intent).setup().get(); } diff --git a/tests/robotests/src/com/android/settings/password/SetNewPasswordControllerTest.java b/tests/robotests/src/com/android/settings/password/SetNewPasswordControllerTest.java index 3034807baa6..f7b8d2d3f1b 100644 --- a/tests/robotests/src/com/android/settings/password/SetNewPasswordControllerTest.java +++ b/tests/robotests/src/com/android/settings/password/SetNewPasswordControllerTest.java @@ -19,8 +19,7 @@ package com.android.settings.password; import static android.content.pm.PackageManager.FEATURE_FACE; import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; -import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS; -import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY; +import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE; @@ -262,12 +261,9 @@ public final class SetNewPasswordControllerTest { private void compareFingerprintExtras(Bundle actualBundle) { assertEquals( - "Password quality must be something in order to config fingerprint.", - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, - actualBundle.getInt(MINIMUM_QUALITY_KEY)); - assertTrue( - "All disabled preference should be removed.", - actualBundle.getBoolean(HIDE_DISABLED_PREFS)); + "Insecure options must be disabled in order to config fingerprint.", + true, + actualBundle.getBoolean(HIDE_INSECURE_OPTIONS)); assertTrue( "Fingerprint enroll must request Gatekeeper Password.", actualBundle.getBoolean(EXTRA_KEY_REQUEST_GK_PW_HANDLE)); @@ -282,12 +278,9 @@ public final class SetNewPasswordControllerTest { private void compareFaceExtras(Bundle actualBundle) { assertEquals( - "Password quality must be something in order to config face.", - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, - actualBundle.getInt(MINIMUM_QUALITY_KEY)); - assertTrue( - "All disabled preference should be removed.", - actualBundle.getBoolean(HIDE_DISABLED_PREFS)); + "Insecure options must be disabled in order to config face.", + true, + actualBundle.getBoolean(HIDE_INSECURE_OPTIONS)); assertTrue( "Face enroll must request Gatekeeper Password", actualBundle.getBoolean(EXTRA_KEY_REQUEST_GK_PW_HANDLE)); diff --git a/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java index 5242e11a6e7..af4ae0b8a30 100644 --- a/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java +++ b/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java @@ -198,8 +198,7 @@ public class SetupChooseLockPasswordTest { @Implements(ChooseLockGenericController.class) public static class ShadowChooseLockGenericController { @Implementation - protected List getVisibleScreenLockTypes(int quality, - boolean includeDisabled) { + protected List getVisibleScreenLockTypes() { return Collections.emptyList(); } }