summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java41
-rw-r--r--core/java/android/app/admin/PasswordMetrics.java148
2 files changed, 163 insertions, 26 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7cc953cc254f..b0a08edfcaaf 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1362,16 +1362,23 @@ public class DevicePolicyManager {
public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
/**
- * Activity action: have the user enter a new password. This activity should
- * be launched after using {@link #setPasswordQuality(ComponentName, int)},
- * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
- * enter a new password that meets the current requirements. You can use
- * {@link #isActivePasswordSufficient()} to determine whether you need to
- * have the user select a new password in order to meet the current
- * constraints. Upon being resumed from this activity, you can check the new
+ * Activity action: have the user enter a new password.
+ *
+ * <p>For admin apps, this activity should be launched after using {@link
+ * #setPasswordQuality(ComponentName, int)}, or {@link
+ * #setPasswordMinimumLength(ComponentName, int)} to have the user enter a new password that
+ * meets the current requirements. You can use {@link #isActivePasswordSufficient()} to
+ * determine whether you need to have the user select a new password in order to meet the
+ * current constraints. Upon being resumed from this activity, you can check the new
* password characteristics to see if they are sufficient.
*
- * If the intent is launched from within a managed profile with a profile
+ * <p>Non-admin apps can use {@link #getPasswordComplexity()} to check the current screen lock
+ * complexity, and use this activity with extra {@link #EXTRA_PASSWORD_COMPLEXITY} to suggest
+ * to users how complex the app wants the new screen lock to be. Note that both {@link
+ * #getPasswordComplexity()} and the extra {@link #EXTRA_PASSWORD_COMPLEXITY} require the
+ * calling app to have the permission {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}.
+ *
+ * <p>If the intent is launched from within a managed profile with a profile
* owner built against {@link android.os.Build.VERSION_CODES#M} or before,
* this will trigger entering a new password for the parent of the profile.
* For all other cases it will trigger entering a new password for the user
@@ -1384,6 +1391,24 @@ public class DevicePolicyManager {
= "android.app.action.SET_NEW_PASSWORD";
/**
+ * An integer indicating the complexity level of the new password an app would like the user to
+ * set when launching the action {@link #ACTION_SET_NEW_PASSWORD}.
+ *
+ * <p>Must be one of
+ * <ul>
+ * <li>{@link #PASSWORD_COMPLEXITY_HIGH}
+ * <li>{@link #PASSWORD_COMPLEXITY_MEDIUM}
+ * <li>{@link #PASSWORD_COMPLEXITY_LOW}
+ * <li>{@link #PASSWORD_COMPLEXITY_NONE}
+ * </ul>
+ *
+ * <p>If an invalid value is used, it will be treated as {@link #PASSWORD_COMPLEXITY_NONE}.
+ */
+ @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY)
+ public static final String EXTRA_PASSWORD_COMPLEXITY =
+ "android.app.extra.PASSWORD_COMPLEXITY";
+
+ /**
* Constant for {@link #getPasswordComplexity()}: no password.
*
* <p>Note that these complexity constants are ordered so that higher values are more complex.
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 8b41755f6dec..e5df2c70eeec 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -20,6 +20,11 @@ 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_UNSPECIFIED;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,6 +32,8 @@ import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -85,6 +92,101 @@ public class PasswordMetrics implements Parcelable {
nonLetter = in.readInt();
}
+ /** Returns the min quality allowed by {@code complexityLevel}. */
+ public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
+ // this would be the quality of the first metrics since mMetrics is sorted in ascending
+ // order of quality
+ return PasswordComplexityBucket
+ .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
+ }
+
+ /**
+ * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
+ * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
+ * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
+ * and {@code complexityLevel}.
+ *
+ * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
+ * more than one set of metrics to meet the minimum complexity requirement and inspecting what
+ * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
+ * should be used. For example, suppose minimum 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.
+ */
+ public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
+ int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
+ boolean requiresLettersOrSymbols) {
+ int targetQuality = Math.max(
+ userEnteredPasswordQuality,
+ getActualRequiredQuality(
+ requestedQuality, requiresNumeric, requiresLettersOrSymbols));
+ return getTargetQualityMetrics(complexityLevel, targetQuality);
+ }
+
+ /**
+ * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
+ * is the same as {@code targetQuality}.
+ *
+ * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
+ * with the min quality at {@code complexityLevel}.
+ */
+ // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+ @VisibleForTesting
+ public static PasswordMetrics getTargetQualityMetrics(
+ @PasswordComplexity int complexityLevel, int targetQuality) {
+ PasswordComplexityBucket targetBucket =
+ PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
+ for (PasswordMetrics metrics : targetBucket.mMetrics) {
+ if (targetQuality == metrics.quality) {
+ return metrics;
+ }
+ }
+ // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
+ // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
+ // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
+ // requested quality is NUMERIC
+ return targetBucket.mMetrics[0];
+ }
+
+ /**
+ * Finds out the actual quality requirement based on whether quality is {@link
+ * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
+ * required.
+ */
+ @VisibleForTesting
+ // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+ public static int getActualRequiredQuality(
+ int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
+ if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
+ return requestedQuality;
+ }
+
+ // find out actual password quality from complex requirements
+ if (requiresNumeric && requiresLettersOrSymbols) {
+ return PASSWORD_QUALITY_ALPHANUMERIC;
+ }
+ if (requiresLettersOrSymbols) {
+ return PASSWORD_QUALITY_ALPHABETIC;
+ }
+ if (requiresNumeric) {
+ // cannot specify numeric complex using complex quality so this must be numeric
+ return PASSWORD_QUALITY_NUMERIC;
+ }
+
+ // reaching here means dpm sets quality to complex without specifying any requirements
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ }
+
+ /**
+ * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
+ * if {@code complexityLevel} is not valid.
+ */
+ @PasswordComplexity
+ public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
+ return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+ }
+
public boolean isDefault() {
return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
&& length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
@@ -280,7 +382,7 @@ public class PasswordMetrics implements Parcelable {
@PasswordComplexity
public int determineComplexity() {
for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
- if (satisfiesBucket(bucket.getMetrics())) {
+ if (satisfiesBucket(bucket.mMetrics)) {
return bucket.mComplexityLevel;
}
}
@@ -290,7 +392,7 @@ public class PasswordMetrics implements Parcelable {
/**
* Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
*/
- public static class PasswordComplexityBucket {
+ private static class PasswordComplexityBucket {
/**
* Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
* {@link PasswordMetrics}.
@@ -299,12 +401,13 @@ public class PasswordMetrics implements Parcelable {
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_HIGH,
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6),
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 8),
new PasswordMetrics(
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 8));
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
+ 6));
/**
* Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
@@ -314,11 +417,12 @@ public class PasswordMetrics implements Parcelable {
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_MEDIUM,
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4),
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 4),
new PasswordMetrics(
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
4));
/**
@@ -328,11 +432,11 @@ public class PasswordMetrics implements Parcelable {
private static final PasswordComplexityBucket LOW =
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING));
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
/**
* A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
@@ -348,19 +452,27 @@ public class PasswordMetrics implements Parcelable {
private final int mComplexityLevel;
private final PasswordMetrics[] mMetrics;
+ /**
+ * @param metricsArray must be sorted in ascending order of {@link #quality}.
+ */
private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
- PasswordMetrics... metrics) {
+ PasswordMetrics... metricsArray) {
+ int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
+ for (PasswordMetrics metrics : metricsArray) {
+ if (metrics.quality < previousQuality) {
+ throw new IllegalArgumentException("metricsArray must be sorted in ascending"
+ + " order of quality");
+ }
+ previousQuality = metrics.quality;
+ }
+
+ this.mMetrics = metricsArray;
this.mComplexityLevel = complexityLevel;
- this.mMetrics = metrics;
- }
- /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */
- public PasswordMetrics[] getMetrics() {
- return mMetrics;
}
/** Returns the bucket that {@code complexityLevel} represents. */
- public static PasswordComplexityBucket complexityLevelToBucket(
+ private static PasswordComplexityBucket complexityLevelToBucket(
@PasswordComplexity int complexityLevel) {
for (PasswordComplexityBucket bucket : BUCKETS) {
if (bucket.mComplexityLevel == complexityLevel) {