diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 35e369e9422..8b0c2c92ec5 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -221,9 +221,19 @@ public class ChooseLockGeneric extends SettingsActivity { mForBiometrics = intent.getBooleanExtra( ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false); - mRequestedMinComplexity = intent + final int complexityFromIntent = intent .getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE); - mCallerAppName = + final int complexityFromAdmin = mLockPatternUtils.getRequestedPasswordComplexity( + mUserId); + mRequestedMinComplexity = Math.max(complexityFromIntent, complexityFromAdmin); + final boolean isComplexityProvidedByAdmin = (complexityFromAdmin > complexityFromIntent) + && mRequestedMinComplexity > PASSWORD_COMPLEXITY_NONE; + + // 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 : intent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME); mIsCallingAppAdmin = intent .getBooleanExtra(EXTRA_KEY_IS_CALLING_APP_ADMIN, /* defValue= */ false); @@ -669,8 +679,9 @@ public class ChooseLockGeneric extends SettingsActivity { final PreferenceScreen entries = getPreferenceScreen(); int adminEnforcedQuality = mDpm.getPasswordQuality(null, mUserId); - EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet( - getActivity(), mUserId); + 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 diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index a73b73a8e3b..0c84ba97628 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -421,6 +421,8 @@ public class ChooseLockPassword extends SettingsActivity { if (mUnificationProfileId != UserHandle.USER_NULL) { mMinMetrics.maxWith( mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId)); + mMinComplexity = Math.max(mMinComplexity, + mLockPatternUtils.getRequestedPasswordComplexity(mUnificationProfileId)); } if (intent.getBooleanExtra( diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java index 036df2cea8b..60979805c25 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java @@ -21,6 +21,7 @@ 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_COMPLEX; import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.KEY_LOCK_SETTINGS_FOOTER; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME; @@ -90,6 +91,7 @@ public class ChooseLockGenericTest { Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1); ShadowStorageManager.reset(); ShadowPersistentDataBlockManager.reset(); + ShadowLockPatternUtils.reset(); } @Test @@ -377,6 +379,64 @@ public class ChooseLockGenericTest { ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)).isNotNull(); } + @Test + public void updatePreferencesOrFinish_ComplexityIsReadFromDPM() { + ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false); + ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH); + + initActivity(null); + mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */); + + FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER); + assertThat(footer.getTitle()).isEqualTo(null); + + Intent intent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX); + assertThat(intent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, + PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH); + } + + @Test + public void updatePreferencesOrFinish_ComplexityIsMergedWithDPM() { + ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false); + ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH); + Intent intent = new Intent() + .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name") + .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_LOW); + initActivity(intent); + + mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */); + + // Footer should be null because admin complexity wins. + FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER); + assertThat(footer.getTitle()).isEqualTo(null); + + Intent passwordIntent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX); + assertThat(passwordIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, + PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH); + } + + @Test + public void updatePreferencesOrFinish_ComplexityIsMergedWithDPM_AppIsHigher() { + ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false); + ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW); + Intent intent = new Intent() + .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name") + .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH); + initActivity(intent); + + mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */); + + // Footer should include app name because app requirement is higher. + CharSequence expectedTitle = + mActivity.getString(R.string.unlock_footer_high_complexity_requested, "app name"); + FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER); + assertThat(footer.getTitle()).isEqualTo(expectedTitle); + + Intent passwordIntent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX); + assertThat(passwordIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, + PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH); + } + private void initActivity(@Nullable Intent intent) { if (intent == null) { intent = new Intent(); diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java index 030bb80d5ba..0ca6a3ee114 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java @@ -31,6 +31,7 @@ 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_REQUESTED_MIN_COMPLEXITY; +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; @@ -67,6 +68,7 @@ import org.robolectric.shadows.ShadowDrawable; @RunWith(RobolectricTestRunner.class) @Config(shadows = { SettingsShadowResources.class, + ShadowLockPatternUtils.class, ShadowUtils.class, ShadowDevicePolicyManager.class, }) @@ -84,6 +86,7 @@ public class ChooseLockPasswordTest { @After public void tearDown() { SettingsShadowResources.reset(); + ShadowLockPatternUtils.reset(); } @Test @@ -378,6 +381,29 @@ public class ChooseLockPasswordTest { assertThat(drawable.getCreatedFromResId()).isNotEqualTo(R.drawable.ic_fingerprint_header); } + @Test + public void validateComplexityMergedFromDpmOnCreate() { + ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW); + + assertPasswordValidationResult( + /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH, + /* passwordType= */ PASSWORD_QUALITY_NUMERIC, + /* userEnteredPassword= */ LockscreenCredential.createNone(), + "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(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(); } @@ -400,14 +426,27 @@ public class ChooseLockPasswordTest { private void assertPasswordValidationResult(@PasswordComplexity int minComplexity, int passwordType, LockscreenCredential userEnteredPassword, String... expectedValidationResult) { - Intent intent = new Intent(); - intent.putExtra(CONFIRM_CREDENTIALS, false); - intent.putExtra(PASSWORD_TYPE_KEY, passwordType); - intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity); + Intent intent = createIntentForPasswordValidation(minComplexity, passwordType); + assertPasswordValidationResultForIntent(userEnteredPassword, intent, + expectedValidationResult); + } + + private void assertPasswordValidationResultForIntent(LockscreenCredential userEnteredPassword, + Intent intent, String... expectedValidationResult) { ChooseLockPassword activity = buildChooseLockPasswordActivity(intent); ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(activity); fragment.validatePassword(userEnteredPassword); String[] messages = fragment.convertErrorCodeToMessages(); - assertThat(messages).asList().containsExactly((Object[]) expectedValidationResult); + assertThat(messages).asList().containsExactly(expectedValidationResult); + } + + private Intent createIntentForPasswordValidation( + @PasswordComplexity int minComplexity, + int passwordType) { + Intent intent = new Intent(); + intent.putExtra(CONFIRM_CREDENTIALS, false); + intent.putExtra(PASSWORD_TYPE_KEY, passwordType); + intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity); + return intent; } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java index 38756ac7102..3a159b209ba 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java @@ -18,19 +18,31 @@ package com.android.settings.testutils.shadow; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; +import android.os.UserHandle; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Implements(LockPatternUtils.class) public class ShadowLockPatternUtils { private static boolean sDeviceEncryptionEnabled; + private static Map sUserToComplexityMap = new HashMap<>(); + + + @Resetter + public static void reset() { + sUserToComplexityMap.clear(); + sDeviceEncryptionEnabled = false; + } @Implementation protected boolean hasSecureLockScreen() { @@ -76,4 +88,18 @@ public class ShadowLockPatternUtils { protected boolean checkPasswordHistory(byte[] passwordToCheck, byte[] hashFactor, int userId) { return false; } + + @Implementation + public @DevicePolicyManager.PasswordComplexity int getRequestedPasswordComplexity(int userId) { + return sUserToComplexityMap.getOrDefault(userId, + DevicePolicyManager.PASSWORD_COMPLEXITY_NONE); + } + + public static void setRequiredPasswordComplexity(int userId, int complexity) { + sUserToComplexityMap.put(userId, complexity); + } + + public static void setRequiredPasswordComplexity(int complexity) { + setRequiredPasswordComplexity(UserHandle.myUserId(), complexity); + } }