Since there are more than one set of metrics to meet the min complexity requirement, + * and we are not hard-coding any one of them to be the requirements the user must fulfil, + * we are taking what the user has already entered into account when compiling the list of + * requirements from min complexity. Then we merge this list with the DPM requirements, and + * present the merged set as validation results to the user on the UI. + * + *
For example, suppose min complexity requires either ALPHABETIC(8+), or + * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI + * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering + * an alphanumeric password so we would update the min complexity required min length to 6. + * This might result in a little confusion for the user but the UI does not support showing + * multiple sets of requirements / validation results as options to users, this is the best + * we can do now. + */ + private void mergeMinComplexityAndDpmRequirements(int userEnteredPasswordQuality) { + if (mRequestedMinComplexity == PASSWORD_COMPLEXITY_NONE) { + // dpm requirements are dominant if min complexity is none + return; + } + + // reset dpm requirements + loadDpmPasswordRequirements(); + + PasswordMetrics minMetrics = PasswordMetrics.getMinimumMetrics( + mRequestedMinComplexity, userEnteredPasswordQuality, mRequestedQuality, + requiresNumeric(), requiresLettersOrSymbols()); + mPasswordNumSequenceAllowed = mPasswordNumSequenceAllowed + && minMetrics.quality != PASSWORD_QUALITY_NUMERIC_COMPLEX; + mPasswordMinLength = Math.max(mPasswordMinLength, minMetrics.length); + mPasswordMinLetters = Math.max(mPasswordMinLetters, minMetrics.letters); + mPasswordMinUpperCase = Math.max(mPasswordMinUpperCase, minMetrics.upperCase); + mPasswordMinLowerCase = Math.max(mPasswordMinLowerCase, minMetrics.lowerCase); + mPasswordMinNumeric = Math.max(mPasswordMinNumeric, minMetrics.numeric); + mPasswordMinSymbols = Math.max(mPasswordMinSymbols, minMetrics.symbols); + mPasswordMinNonLetter = Math.max(mPasswordMinNonLetter, minMetrics.nonLetter); + + if (minMetrics.quality == PASSWORD_QUALITY_ALPHABETIC) { + if (!requiresLettersOrSymbols()) { + mPasswordMinLetters = 1; + } + } + if (minMetrics.quality == PASSWORD_QUALITY_ALPHANUMERIC) { + if (!requiresLettersOrSymbols()) { + mPasswordMinLetters = 1; + } + if (!requiresNumeric()) { + mPasswordMinNumeric = 1; + } + } + + mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies(); + } + + private boolean requiresLettersOrSymbols() { + // This is the condition for the password to be considered ALPHABETIC according to + // PasswordMetrics.computeForPassword() + return mPasswordMinLetters + mPasswordMinUpperCase + + mPasswordMinLowerCase + mPasswordMinSymbols + mPasswordMinNonLetter > 0; + } + + private boolean requiresNumeric() { + return mPasswordMinNumeric > 0; + } + /** * Validates PIN/Password and returns the validation result. * * @param password the raw password the user typed in * @return the validation result. */ - private int validatePassword(String password) { + @VisibleForTesting + int validatePassword(String password) { int errorCode = NO_ERROR; final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); - + mergeMinComplexityAndDpmRequirements(metrics.quality); if (password.length() < mPasswordMinLength) { if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) { @@ -668,14 +725,25 @@ public class ChooseLockPassword extends SettingsActivity { errorCode |= TOO_LONG; } else { // The length requirements are fulfilled. - final int dpmQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId); - if (dpmQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX && - metrics.numeric == password.length()) { + if (!mPasswordNumSequenceAllowed + && !requiresLettersOrSymbols() + && metrics.numeric == password.length()) { // Check for repeated characters or sequences (e.g. '1234', '0000', '2468') - // if DevicePolicyManager requires a complex numeric password. There can be - // two cases in the UI: 1. User chooses to enroll a PIN, 2. User chooses to - // enroll a password but enters a numeric-only pin. We should carry out the - // sequence check in both cases. + // if DevicePolicyManager or min password complexity requires a complex numeric + // password. There can be two cases in the UI: 1. User chooses to enroll a + // PIN, 2. User chooses to enroll a password but enters a numeric-only pin. We + // should carry out the sequence check in both cases. + // + // Conditions for the !requiresLettersOrSymbols() to be necessary: + // - DPM requires NUMERIC_COMPLEX + // - min complexity not NONE, user picks PASSWORD type so ALPHABETIC or + // ALPHANUMERIC is required + // Imagine user has entered "12345678", if we don't skip the sequence check, the + // validation result would show both "requires a letter" and "sequence not + // allowed", while the only requirement the user needs to know is "requires a + // letter" because once the user has fulfilled the alphabetic requirement, the + // password would not be containing only digits so this check would not be + // performed anyway. final int sequence = PasswordMetrics.maxLengthSequence(password); if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) { errorCode |= CONTAIN_SEQUENTIAL_DIGITS; @@ -706,43 +774,24 @@ public class ChooseLockPassword extends SettingsActivity { } } - // Check the requirements one by one. - for (int i = 0; i < mPasswordRequirements.length; i++) { - int passwordRestriction = mPasswordRequirements[i]; - switch (passwordRestriction) { - case MIN_LETTER_IN_PASSWORD: - if (metrics.letters < mPasswordMinLetters) { - errorCode |= NOT_ENOUGH_LETTER; - } - break; - case MIN_UPPER_LETTERS_IN_PASSWORD: - if (metrics.upperCase < mPasswordMinUpperCase) { - errorCode |= NOT_ENOUGH_UPPER_CASE; - } - break; - case MIN_LOWER_LETTERS_IN_PASSWORD: - if (metrics.lowerCase < mPasswordMinLowerCase) { - errorCode |= NOT_ENOUGH_LOWER_CASE; - } - break; - case MIN_SYMBOLS_IN_PASSWORD: - if (metrics.symbols < mPasswordMinSymbols) { - errorCode |= NOT_ENOUGH_SYMBOLS; - } - break; - case MIN_NUMBER_IN_PASSWORD: - if (metrics.numeric < mPasswordMinNumeric) { - errorCode |= NOT_ENOUGH_DIGITS; - } - break; - case MIN_NON_LETTER_IN_PASSWORD: - if (metrics.nonLetter < mPasswordMinNonLetter) { - errorCode |= NOT_ENOUGH_NON_LETTER; - } - break; - } + if (metrics.letters < mPasswordMinLetters) { + errorCode |= NOT_ENOUGH_LETTER; + } + if (metrics.upperCase < mPasswordMinUpperCase) { + errorCode |= NOT_ENOUGH_UPPER_CASE; + } + if (metrics.lowerCase < mPasswordMinLowerCase) { + errorCode |= NOT_ENOUGH_LOWER_CASE; + } + if (metrics.symbols < mPasswordMinSymbols) { + errorCode |= NOT_ENOUGH_SYMBOLS; + } + if (metrics.numeric < mPasswordMinNumeric) { + errorCode |= NOT_ENOUGH_DIGITS; + } + if (metrics.nonLetter < mPasswordMinNonLetter) { + errorCode |= NOT_ENOUGH_NON_LETTER; } - return errorCode; } diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java index 8d0fa602d66..32e8eafa2f6 100644 --- a/src/com/android/settings/password/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java @@ -45,6 +45,18 @@ public final class ChooseLockSettingsHelper { public static final String EXTRA_KEY_FOR_FACE = "for_face"; public static final String EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT = "for_cred_req_boot"; + /** + * Intent extra for passing the requested min password complexity to later steps in the set new + * screen lock flow. + */ + public static final String EXTRA_KEY_REQUESTED_MIN_COMPLEXITY = "requested_min_complexity"; + + /** + * Intent extra for passing the label of the calling app to later steps in the set new screen + * lock flow. + */ + public static final String EXTRA_KEY_CALLER_APP_NAME = "caller_app_name"; + /** * When invoked via {@link ConfirmLockPassword.InternalActivity}, this flag * controls if we relax the enforcement of diff --git a/src/com/android/settings/password/PasswordUtils.java b/src/com/android/settings/password/PasswordUtils.java new file mode 100644 index 00000000000..5f118cf0e6c --- /dev/null +++ b/src/com/android/settings/password/PasswordUtils.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019 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.password; + +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.settings.Utils; + +public final class PasswordUtils extends com.android.settingslib.Utils { + + private static final String TAG = "Settings"; + + private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; + + /** + * Returns whether the uid which the activity with {@code activityToken} is launched from has + * been granted the {@code permission}. + */ + public static boolean isCallingAppPermitted(Context context, IBinder activityToken, + String permission) { + try { + return context.checkPermission(permission, /* pid= */ -1, + ActivityManager.getService().getLaunchedFromUid(activityToken)) + == PackageManager.PERMISSION_GRANTED; + } catch (RemoteException e) { + Log.v(TAG, "Could not talk to activity manager.", e); + return false; + } + } + + /** + * Returns the label of the package which the activity with {@code activityToken} is launched + * from or {@code null} if it is launched from the settings app itself. + */ + @Nullable + public static CharSequence getCallingAppLabel(Context context, IBinder activityToken) { + String pkg = getCallingAppPackageName(activityToken); + if (pkg == null || pkg.equals(SETTINGS_PACKAGE_NAME)) { + return null; + } + + return Utils.getApplicationLabel(context, pkg); + } + + /** + * Returns the package name which the activity with {@code activityToken} is launched from. + */ + @Nullable + private static String getCallingAppPackageName(IBinder activityToken) { + String pkg = null; + try { + pkg = ActivityManager.getService().getLaunchedFromPackage(activityToken); + } catch (RemoteException e) { + Log.v(TAG, "Could not talk to activity manager.", e); + } + return pkg; + } + + /** Crashes the calling application and provides it with {@code message}. */ + public static void crashCallingApplication(IBinder activityToken, String message) { + IActivityManager am = ActivityManager.getService(); + try { + int uid = am.getLaunchedFromUid(activityToken); + int userId = UserHandle.getUserId(uid); + am.crashApplication( + uid, + /* initialPid= */ -1, + getCallingAppPackageName(activityToken), + userId, + message); + } catch (RemoteException e) { + Log.v(TAG, "Could not talk to activity manager.", e); + } + } +} diff --git a/src/com/android/settings/password/SetNewPasswordActivity.java b/src/com/android/settings/password/SetNewPasswordActivity.java index 99f67cb13d5..8ea85144ea7 100644 --- a/src/com/android/settings/password/SetNewPasswordActivity.java +++ b/src/com/android/settings/password/SetNewPasswordActivity.java @@ -16,13 +16,22 @@ package com.android.settings.password; +import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY; import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD; import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD; +import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; import android.app.Activity; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.PasswordComplexity; +import android.app.admin.PasswordMetrics; import android.content.Intent; import android.os.Bundle; +import android.os.IBinder; import android.util.Log; import com.android.settings.Utils; @@ -37,6 +46,21 @@ public class SetNewPasswordActivity extends Activity implements SetNewPasswordCo private String mNewPasswordAction; private SetNewPasswordController mSetNewPasswordController; + /** + * From intent extra {@link DevicePolicyManager#EXTRA_PASSWORD_COMPLEXITY}. + * + *
This is used only if caller has the required permission and activity is launched by + * {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD}. + */ + private @PasswordComplexity int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE; + + /** + * Label of the app which launches this activity. + * + *
Value would be {@code null} if launched from settings app.
+ */
+ private String mCallerAppName = null;
+
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
@@ -48,6 +72,25 @@ public class SetNewPasswordActivity extends Activity implements SetNewPasswordCo
finish();
return;
}
+
+ IBinder activityToken = getActivityToken();
+ mCallerAppName = (String) PasswordUtils.getCallingAppLabel(this, activityToken);
+ if (ACTION_SET_NEW_PASSWORD.equals(mNewPasswordAction)
+ && getIntent().hasExtra(EXTRA_PASSWORD_COMPLEXITY)) {
+ boolean hasPermission = PasswordUtils.isCallingAppPermitted(
+ this, activityToken, GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ if (hasPermission) {
+ mRequestedMinComplexity = PasswordMetrics.sanitizeComplexityLevel(getIntent()
+ .getIntExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_NONE));
+ } else {
+ PasswordUtils.crashCallingApplication(activityToken,
+ "Must have permission " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY
+ + " to use extra " + EXTRA_PASSWORD_COMPLEXITY);
+ finish();
+ return;
+ }
+ }
+
mSetNewPasswordController = SetNewPasswordController.create(
this, this, getIntent(), getActivityToken());
mSetNewPasswordController.dispatchSetNewPasswordIntent();
@@ -60,6 +103,12 @@ public class SetNewPasswordActivity extends Activity implements SetNewPasswordCo
: new Intent(this, ChooseLockGeneric.class);
intent.setAction(mNewPasswordAction);
intent.putExtras(chooseLockFingerprintExtras);
+ if (mCallerAppName != null) {
+ intent.putExtra(EXTRA_KEY_CALLER_APP_NAME, mCallerAppName);
+ }
+ if (mRequestedMinComplexity != PASSWORD_COMPLEXITY_NONE) {
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, mRequestedMinComplexity);
+ }
startActivity(intent);
finish();
}
diff --git a/src/com/android/settings/password/SetupChooseLockGeneric.java b/src/com/android/settings/password/SetupChooseLockGeneric.java
index a0f8bae0502..33c3edb9d38 100644
--- a/src/com/android/settings/password/SetupChooseLockGeneric.java
+++ b/src/com/android/settings/password/SetupChooseLockGeneric.java
@@ -16,11 +16,17 @@
package com.android.settings.password;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
+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;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
@@ -48,6 +54,7 @@ import com.google.android.setupdesign.GlifPreferenceLayout;
* Other changes should be done to ChooseLockGeneric class instead and let this class inherit
* those changes.
*/
+// TODO(b/123225425): Restrict SetupChooseLockGeneric to be accessible by SUW only
public class SetupChooseLockGeneric extends ChooseLockGeneric {
private static final String KEY_UNLOCK_SET_DO_LATER = "unlock_set_do_later";
@@ -71,6 +78,20 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric {
@Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
+
+ if(getIntent().hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)) {
+ IBinder activityToken = getActivityToken();
+ boolean hasPermission = PasswordUtils.isCallingAppPermitted(
+ this, activityToken, GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ if (!hasPermission) {
+ PasswordUtils.crashCallingApplication(activityToken,
+ "Must have permission " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY
+ + " to use extra " + EXTRA_PASSWORD_COMPLEXITY);
+ finish();
+ return;
+ }
+ }
+
LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent);
layout.setFitsSystemWindows(false);
}
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java
index cbc5765ccc5..2b7bdeb6948 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericControllerTest.java
@@ -16,15 +16,22 @@
package com.android.settings.password;
+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 com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
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.content.ComponentName;
import com.android.settings.R;
@@ -58,11 +65,7 @@ public class ChooseLockGenericControllerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new ChooseLockGenericController(
- application,
- 0 /* userId */,
- mDevicePolicyManager,
- mManagedLockPasswordProvider);
+ mController = createController(PASSWORD_COMPLEXITY_NONE);
SettingsShadowResources.overrideResource(R.bool.config_hide_none_security_option, false);
SettingsShadowResources.overrideResource(R.bool.config_hide_swipe_security_option, false);
}
@@ -225,4 +228,44 @@ public class ChooseLockGenericControllerTest {
assertThat(upgradedQuality).named("upgradedQuality")
.isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
}
+
+ @Test
+ public void upgradeQuality_complexityHigh_minQualityNumericComplex() {
+ when(mDevicePolicyManager.getPasswordQuality(nullable(ComponentName.class), anyInt()))
+ .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_HIGH);
+
+ assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED))
+ .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ }
+
+ @Test
+ public void upgradeQuality_complexityMedium_minQualityNumericComplex() {
+ when(mDevicePolicyManager.getPasswordQuality(nullable(ComponentName.class), anyInt()))
+ .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_MEDIUM);
+
+ assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED))
+ .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ }
+
+ @Test
+ public void upgradeQuality_complexityLow_minQualitySomething() {
+ when(mDevicePolicyManager.getPasswordQuality(nullable(ComponentName.class), anyInt()))
+ .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ ChooseLockGenericController controller = createController(PASSWORD_COMPLEXITY_LOW);
+
+ assertThat(controller.upgradeQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED))
+ .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ }
+
+ private ChooseLockGenericController createController(
+ @PasswordComplexity int minPasswordComplexity) {
+ return new ChooseLockGenericController(
+ application,
+ 0 /* userId */,
+ minPasswordComplexity,
+ mDevicePolicyManager,
+ mManagedLockPasswordProvider);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
index e3242148ee4..a1db12ccf24 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
@@ -16,6 +16,15 @@
package com.android.settings.password;
+import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
+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 com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.RuntimeEnvironment.application;
@@ -27,8 +36,10 @@ import android.content.Intent;
import android.provider.Settings.Global;
import androidx.annotation.Nullable;
+import androidx.preference.Preference;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
import com.android.settings.search.SearchFeatureProvider;
@@ -36,6 +47,7 @@ import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowStorageManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
+import com.android.settingslib.widget.FooterPreference;
import org.junit.After;
import org.junit.Before;
@@ -112,6 +124,70 @@ public class ChooseLockGenericTest {
assertThat(shadowOf(mActivity).getNextStartedActivity()).isNull();
}
+ @Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedHighComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_high_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedMediumComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_MEDIUM);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_medium_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedLowComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_LOW);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_low_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_footerPreferenceAddedNoneComplexityText() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
+ initActivity(intent);
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_none_complexity_requested, "app name");
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ FooterPreference footer = mFragment.findPreference(FooterPreference.KEY_FOOTER);
+
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+ }
+
@Test
public void onActivityResult_requestcode0_shouldNotFinish() {
initActivity(null);
@@ -165,6 +241,48 @@ public class ChooseLockGenericTest {
assertThat(mActivity.isFinishing()).isTrue();
}
+ @Test
+ public void onPreferenceTreeClick_fingerprintPassesMinComplexityInfoOntoNextActivity() {
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD)
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH)
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name");
+ initActivity(intent);
+
+ Preference fingerprintPref = new Preference(application);
+ fingerprintPref.setKey("unlock_skip_fingerprint");
+ boolean result = mFragment.onPreferenceTreeClick(fingerprintPref);
+
+ assertThat(result).isTrue();
+ Intent actualIntent = shadowOf(mActivity).getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(actualIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME))
+ .isEqualTo("app name");
+ }
+
+ @Test
+ public void onPreferenceTreeClick_facePassesMinComplexityInfoOntoNextActivity() {
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD)
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH)
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name");
+ initActivity(intent);
+
+ Preference facePref = new Preference(application);
+ facePref.setKey("unlock_skip_face");
+ boolean result = mFragment.onPreferenceTreeClick(facePref);
+
+ assertThat(result).isTrue();
+ Intent actualIntent = shadowOf(mActivity).getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(actualIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME))
+ .isEqualTo("app name");
+ }
+
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 367cb4c36b2..404d2057aa4 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
@@ -16,19 +16,37 @@
package com.android.settings.password;
+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_ALPHANUMERIC;
+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.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.google.common.truth.Truth.assertThat;
import static org.robolectric.RuntimeEnvironment.application;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.content.Intent;
import android.os.UserHandle;
-import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment;
import com.android.settings.password.ChooseLockPassword.IntentBuilder;
import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
+import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.google.android.setupdesign.GlifLayout;
@@ -44,13 +62,21 @@ import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowDrawable;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {SettingsShadowResources.class, ShadowUtils.class})
+@Config(shadows = {
+ SettingsShadowResources.class,
+ ShadowUtils.class,
+ ShadowDevicePolicyManager.class,
+})
public class ChooseLockPasswordTest {
+ private ShadowDevicePolicyManager mShadowDpm;
+
@Before
public void setUp() {
SettingsShadowResources.overrideResource(
com.android.internal.R.string.config_headlineFontFamily, "");
+ mShadowDpm = ShadowDevicePolicyManager.getShadow();
+ mShadowDpm.setPasswordMaximumLength(16);
}
@After
@@ -72,7 +98,7 @@ public class ChooseLockPasswordTest {
assertThat(intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD))
.named("EXTRA_KEY_PASSWORD")
.isEqualTo("password");
- assertThat(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 0))
+ assertThat(intent.getIntExtra(PASSWORD_TYPE_KEY, 0))
.named("PASSWORD_TYPE_KEY")
.isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, 0))
@@ -84,7 +110,7 @@ public class ChooseLockPasswordTest {
public void intentBuilder_setChallenge_shouldAddExtras() {
Intent intent = new IntentBuilder(application)
.setChallenge(12345L)
- .setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC)
+ .setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC)
.setUserId(123)
.build();
@@ -94,14 +120,213 @@ public class ChooseLockPasswordTest {
assertThat(intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0L))
.named("EXTRA_KEY_CHALLENGE")
.isEqualTo(12345L);
- assertThat(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 0))
+ assertThat(intent.getIntExtra(PASSWORD_TYPE_KEY, 0))
.named("PASSWORD_TYPE_KEY")
- .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
+ .isEqualTo(PASSWORD_QUALITY_ALPHANUMERIC);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, 0))
.named("EXTRA_USER_ID")
.isEqualTo(123);
}
+ @Test
+ public void intentBuilder_setMinComplexityMedium_hasMinComplexityExtraMedium() {
+ Intent intent = new IntentBuilder(application)
+ .setRequestedMinComplexity(PASSWORD_COMPLEXITY_MEDIUM)
+ .build();
+
+ assertThat(intent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(intent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_MEDIUM);
+ }
+
+ @Test
+ public void intentBuilder_setMinComplexityNotCalled() {
+ Intent intent = new IntentBuilder(application).build();
+
+ assertThat(intent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_noMinPasswordComplexity() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_ALPHABETIC);
+ mShadowDpm.setPasswordMinimumLength(10);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must be at least 10 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_minPasswordComplexityStricter_pin() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_SOMETHING);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "",
+ "PIN must be at least 8 digits");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_minPasswordComplexityStricter_password() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_SOMETHING);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_MEDIUM,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must be at least 4 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_dpmRestrictionsStricter_password() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
+ mShadowDpm.setPasswordMinimumLength(9);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_LOW,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must contain at least 1 numerical digit",
+ "Must be at least 9 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_dpmLengthLonger_pin() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC);
+ mShadowDpm.setPasswordMinimumLength(11);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_MEDIUM,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "",
+ "PIN must be at least 11 digits");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_dpmQualityComplex() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_COMPLEX);
+ mShadowDpm.setPasswordMinimumSymbols(2);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 2 special symbols",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexNoMinComplexity_pinRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexNoMinComplexity_passwordRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexHighComplexity_pinRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericHighComplexity_pinRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ "12345678",
+ "Ascending, descending, or repeated sequence of digits isn't allowed");
+ }
+
+ @Test
+ @Config(shadows = ShadowLockPatternUtils.class)
+ public void processAndValidatePasswordRequirements_numericComplexLowComplexity_passwordRequested() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_LOW,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "12345678",
+ "Must contain at least 1 letter");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_empty() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "",
+ "Must contain at least 1 letter",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_numeric() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "1",
+ "Must contain at least 1 letter",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_alphabetic() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "b",
+ "Must be at least 6 characters");
+ }
+
+ @Test
+ public void processAndValidatePasswordRequirements_requirementsUpdateAccordingToMinComplexityAndUserInput_alphanumeric() {
+ mShadowDpm.setPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_ALPHABETIC,
+ /* userEnteredPassword= */ "b1",
+ "Must be at least 6 characters");
+ }
+
@Test
public void assertThat_chooseLockIconChanged_WhenFingerprintExtraSet() {
ShadowDrawable drawable = setActivityAndGetIconDrawable(true);
@@ -132,4 +357,18 @@ public class ChooseLockPasswordTest {
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
return Shadows.shadowOf(((GlifLayout) fragment.getView()).getIcon());
}
+
+ private void assertPasswordValidationResult(@PasswordComplexity int minComplexity,
+ int passwordType, String 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);
+ ChooseLockPassword activity = buildChooseLockPasswordActivity(intent);
+ ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(activity);
+ int validateResult = fragment.validatePassword(userEnteredPassword);
+ String[] messages = fragment.convertErrorCodeToMessages(validateResult);
+
+ assertThat(messages).asList().containsExactly((Object[]) expectedValidationResult);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/PasswordUtilsTest.java b/tests/robotests/src/com/android/settings/password/PasswordUtilsTest.java
new file mode 100644
index 00000000000..845d346da8f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/password/PasswordUtilsTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 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.password;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.settings.password.PasswordUtils.getCallingAppLabel;
+import static com.android.settings.password.PasswordUtils.isCallingAppPermitted;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.settings.testutils.shadow.ShadowActivityManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowActivityManager.class})
+public class PasswordUtilsTest {
+
+ private static final String PACKAGE_NAME = "com.android.app";
+ private static final String PERMISSION = "com.testing.permission";
+ private static final int UID = 1234;
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+ @Mock
+ private IActivityManager mActivityService;
+ @Mock
+ private IBinder mActivityToken;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ ShadowActivityManager.setService(mActivityService);
+ }
+
+ @Test
+ public void getCallingAppLabel_activityServiceThrowsRemoteException_returnsNull()
+ throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenThrow(new RemoteException());
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isNull();
+ }
+
+ @Test
+ public void getCallingAppLabel_activityServiceReturnsSettingsApp_returnsNull()
+ throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenReturn("com.android.settings");
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isNull();
+ }
+
+ @Test
+ public void getCallingAppLabel_packageManagerThrowsNameNotFound_returnsNull() throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenReturn(PACKAGE_NAME);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenThrow(new NameNotFoundException());
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isNull();
+ }
+
+ @Test
+ public void getCallingAppLabel_returnsLabel() throws Exception {
+ when(mActivityService.getLaunchedFromPackage(mActivityToken))
+ .thenReturn(PACKAGE_NAME);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(mApplicationInfo);
+ when(mApplicationInfo.loadLabel(mPackageManager)).thenReturn("label");
+
+ assertThat(getCallingAppLabel(mContext, mActivityToken)).isEqualTo("label");
+ }
+
+ @Test
+ public void isCallingAppPermitted_permissionGranted_returnsTrue() throws Exception {
+ when(mActivityService.getLaunchedFromUid(mActivityToken)).thenReturn(UID);
+ when(mContext.checkPermission(PERMISSION, -1, UID)).thenReturn(PERMISSION_GRANTED);
+
+ assertThat(isCallingAppPermitted(mContext, mActivityToken, PERMISSION)).isTrue();
+ }
+
+ @Test
+ public void isCallingAppPermitted_permissionDenied_returnsFalse() throws Exception {
+ when(mActivityService.getLaunchedFromUid(mActivityToken)).thenReturn(UID);
+ when(mContext.checkPermission(PERMISSION, -1, UID)).thenReturn(PERMISSION_DENIED);
+
+ assertThat(isCallingAppPermitted(mContext, mActivityToken, PERMISSION)).isFalse();
+ }
+
+ @Test
+ public void isCallingAppPermitted_throwsRemoteException_returnsFalse() throws Exception {
+ when(mActivityService.getLaunchedFromUid(mActivityToken)).thenThrow(new RemoteException());
+
+ assertThat(isCallingAppPermitted(mContext, mActivityToken, PERMISSION)).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java b/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java
index 99738e75c33..d1b2b74b3c3 100644
--- a/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java
+++ b/tests/robotests/src/com/android/settings/password/SetNewPasswordActivityTest.java
@@ -16,6 +16,16 @@
package com.android.settings.password;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
+import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
+import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.ComponentName;
@@ -23,6 +33,8 @@ import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
+import com.android.settings.testutils.shadow.ShadowPasswordUtils;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -31,11 +43,14 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowActivity;
@RunWith(RobolectricTestRunner.class)
public class SetNewPasswordActivityTest {
+ private static final String APP_LABEL = "label";
+
private int mProvisioned;
@Before
@@ -48,6 +63,7 @@ public class SetNewPasswordActivityTest {
public void tearDown() {
Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, mProvisioned);
+ ShadowPasswordUtils.reset();
}
@Test
@@ -77,4 +93,106 @@ public class SetNewPasswordActivityTest {
assertThat(intent.getComponent())
.isEqualTo(new ComponentName(activity, SetupChooseLockGeneric.class));
}
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraWithoutPermission() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ assertThat(shadowActivity.getNextStartedActivityForResult()).isNull();
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraWithPermission() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isTrue();
+ assertThat(actualIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE))
+ .isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraInvalidValue() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, -1);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewPasswordExtraNoneComplexity() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void testLaunchChooseLock_setNewParentProfilePasswordExtraWithPermission() {
+ ShadowPasswordUtils.setCallingAppLabel(APP_LABEL);
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+
+ Intent intent = new Intent(ACTION_SET_NEW_PARENT_PROFILE_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetNewPasswordActivity activity =
+ Robolectric.buildActivity(SetNewPasswordActivity.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ Intent actualIntent = shadowActivity.getNextStartedActivityForResult().intent;
+ assertThat(actualIntent.getAction()).isEqualTo(ACTION_SET_NEW_PARENT_PROFILE_PASSWORD);
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)).isFalse();
+ assertThat(actualIntent.hasExtra(EXTRA_KEY_CALLER_APP_NAME)).isTrue();
+ assertThat(actualIntent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME)).isEqualTo(APP_LABEL);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/SetupChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/SetupChooseLockGenericTest.java
new file mode 100644
index 00000000000..63bdc381926
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/password/SetupChooseLockGenericTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 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.password;
+
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+
+import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
+import com.android.settings.testutils.shadow.ShadowPasswordUtils;
+import com.android.settings.testutils.shadow.ShadowUserManager;
+import com.android.settings.testutils.shadow.ShadowUtils;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowActivity;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+ ShadowUserManager.class,
+ ShadowUtils.class,
+ ShadowLockPatternUtils.class,
+})
+public class SetupChooseLockGenericTest {
+
+ @After
+ public void tearDown() {
+ ShadowPasswordUtils.reset();
+ }
+
+ @Test
+ public void setupChooseLockGenericPasswordComplexityExtraWithoutPermission() {
+ Intent intent = new Intent("com.android.settings.SETUP_LOCK_SCREEN");
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetupChooseLockGeneric activity =
+ Robolectric.buildActivity(SetupChooseLockGeneric.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ assertThat(shadowActivity.isFinishing()).isTrue();
+ }
+
+ @Test
+ @Config(shadows = {ShadowPasswordUtils.class})
+ public void setupChooseLockGenericPasswordComplexityExtraWithPermission() {
+ ShadowPasswordUtils.addGrantedPermission(GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+
+ Intent intent = new Intent("com.android.settings.SETUP_LOCK_SCREEN");
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ SetupChooseLockGeneric activity =
+ Robolectric.buildActivity(SetupChooseLockGeneric.class, intent).create().get();
+
+ ShadowActivity shadowActivity = Shadows.shadowOf(activity);
+ assertThat(shadowActivity.isFinishing()).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java
index 38d658c3544..76bdaefa18e 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivityManager.java
@@ -17,6 +17,7 @@
package com.android.settings.testutils.shadow;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -24,6 +25,7 @@ import org.robolectric.annotation.Implements;
@Implements(ActivityManager.class)
public class ShadowActivityManager {
private static int sCurrentUserId = 0;
+ private static IActivityManager sService = null;
@Implementation
protected static int getCurrentUser() {
@@ -33,4 +35,13 @@ public class ShadowActivityManager {
public static void setCurrentUser(int userId) {
sCurrentUserId = userId;
}
+
+ @Implementation
+ public static IActivityManager getService() {
+ return sService;
+ }
+
+ public static void setService(IActivityManager service) {
+ sService = service;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java
index 6d2dbef467c..ca759164b99 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java
@@ -1,5 +1,7 @@
package com.android.settings.testutils.shadow;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -23,6 +25,10 @@ public class ShadowDevicePolicyManager extends org.robolectric.shadows.ShadowDev
private boolean mIsAdminActiveAsUser = false;
private ComponentName mDeviceOwnerComponentName;
private int mDeviceOwnerUserId = -1;
+ private int mPasswordMinQuality = PASSWORD_QUALITY_UNSPECIFIED;
+ private int mPasswordMaxLength = 16;
+ private int mPasswordMinLength = 0;
+ private int mPasswordMinSymbols = 0;
public void setShortSupportMessageForUser(ComponentName admin, int userHandle, String message) {
mSupportMessagesMap.put(Objects.hash(admin, userHandle), message);
@@ -70,6 +76,42 @@ public class ShadowDevicePolicyManager extends org.robolectric.shadows.ShadowDev
mDeviceOwnerComponentName = admin;
}
+ @Implementation
+ public int getPasswordQuality(ComponentName admin, int userHandle) {
+ return mPasswordMinQuality;
+ }
+
+ public void setPasswordQuality(int quality) {
+ mPasswordMinQuality = quality;
+ }
+
+ @Implementation
+ public int getPasswordMinimumLength(ComponentName admin, int userHandle) {
+ return mPasswordMinLength;
+ }
+
+ public void setPasswordMinimumLength(int length) {
+ mPasswordMinLength = length;
+ }
+
+ @Implementation
+ public int getPasswordMinimumSymbols(ComponentName admin, int userHandle) {
+ return mPasswordMinSymbols;
+ }
+
+ public void setPasswordMinimumSymbols(int numOfSymbols) {
+ mPasswordMinSymbols = numOfSymbols;
+ }
+
+ @Implementation
+ public int getPasswordMaximumLength(int quality) {
+ return mPasswordMaxLength;
+ }
+
+ public void setPasswordMaximumLength(int length) {
+ mPasswordMaxLength = length;
+ }
+
public static ShadowDevicePolicyManager getShadow() {
return (ShadowDevicePolicyManager) Shadow.extract(
RuntimeEnvironment.application.getSystemService(DevicePolicyManager.class));
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 663ab91e580..7ce098d9ea8 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
@@ -59,4 +59,14 @@ public class ShadowLockPatternUtils {
public static void setDeviceEncryptionEnabled(boolean deviceEncryptionEnabled) {
sDeviceEncryptionEnabled = deviceEncryptionEnabled;
}
+
+ @Implementation
+ protected byte[] getPasswordHistoryHashFactor(String currentPassword, int userId) {
+ return null;
+ }
+
+ @Implementation
+ protected boolean checkPasswordHistory(String passwordToCheck, byte[] hashFactor, int userId) {
+ return false;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPasswordUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPasswordUtils.java
new file mode 100644
index 00000000000..6a5c4ae7c57
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPasswordUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.testutils.shadow;
+
+import android.content.Context;
+import android.os.IBinder;
+
+import com.android.settings.password.PasswordUtils;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@Implements(PasswordUtils.class)
+public class ShadowPasswordUtils {
+
+ private static String sCallingAppLabel;
+ private static Set