summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java7
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl5
-rw-r--r--core/java/android/app/admin/PasswordMetrics.aidl20
-rw-r--r--core/java/android/app/admin/PasswordMetrics.java238
4 files changed, 264 insertions, 6 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4ddcfe546f95..9ce9decaab35 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -26,6 +26,7 @@ import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.Activity;
+import android.app.admin.PasswordMetrics;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -3523,12 +3524,10 @@ public class DevicePolicyManager {
/**
* @hide
*/
- public void setActivePasswordState(int quality, int length, int letters, int uppercase,
- int lowercase, int numbers, int symbols, int nonletter, int userHandle) {
+ public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
if (mService != null) {
try {
- mService.setActivePasswordState(quality, length, letters, uppercase, lowercase,
- numbers, symbols, nonletter, userHandle);
+ mService.setActivePasswordState(metrics, userHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8c376bbb670f..22219d789799 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -18,6 +18,7 @@
package android.app.admin;
import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.PasswordMetrics;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -29,6 +30,7 @@ import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.UserHandle;
+
import java.util.List;
/**
@@ -117,8 +119,7 @@ interface IDevicePolicyManager {
void forceRemoveActiveAdmin(in ComponentName policyReceiver, int userHandle);
boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
- void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase,
- int numbers, int symbols, int nonletter, int userHandle);
+ void setActivePasswordState(in PasswordMetrics metrics, int userHandle);
void reportFailedPasswordAttempt(int userHandle);
void reportSuccessfulPasswordAttempt(int userHandle);
void reportFailedFingerprintAttempt(int userHandle);
diff --git a/core/java/android/app/admin/PasswordMetrics.aidl b/core/java/android/app/admin/PasswordMetrics.aidl
new file mode 100644
index 000000000000..90d7c6974f1b
--- /dev/null
+++ b/core/java/android/app/admin/PasswordMetrics.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2016, 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 android.app.admin;
+
+parcelable PasswordMetrics;
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
new file mode 100644
index 000000000000..ea3f560d02db
--- /dev/null
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2016 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 android.app.admin;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.io.IOException;
+
+/**
+ * A class that represents the metrics of a password that are used to decide whether or not a
+ * password meets the requirements.
+ *
+ * {@hide}
+ */
+public class PasswordMetrics implements Parcelable {
+ // Maximum allowed number of repeated or ordered characters in a sequence before we'll
+ // consider it a complex PIN/password.
+ public static final int MAX_ALLOWED_SEQUENCE = 3;
+
+ public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ public int length = 0;
+ public int letters = 0;
+ public int upperCase = 0;
+ public int lowerCase = 0;
+ public int numeric = 0;
+ public int symbols = 0;
+ public int nonLetter = 0;
+
+ public PasswordMetrics() {}
+
+ public PasswordMetrics(int quality, int length) {
+ this.quality = quality;
+ this.length = length;
+ }
+
+ public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase,
+ int numeric, int symbols, int nonLetter) {
+ this(quality, length);
+ this.letters = letters;
+ this.upperCase = upperCase;
+ this.lowerCase = lowerCase;
+ this.numeric = numeric;
+ this.symbols = symbols;
+ this.nonLetter = nonLetter;
+ }
+
+ private PasswordMetrics(Parcel in) {
+ quality = in.readInt();
+ length = in.readInt();
+ letters = in.readInt();
+ upperCase = in.readInt();
+ lowerCase = in.readInt();
+ numeric = in.readInt();
+ symbols = in.readInt();
+ nonLetter = in.readInt();
+ }
+
+ public boolean isDefault() {
+ return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
+ && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
+ && numeric == 0 && symbols == 0 && nonLetter == 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(quality);
+ dest.writeInt(length);
+ dest.writeInt(letters);
+ dest.writeInt(upperCase);
+ dest.writeInt(lowerCase);
+ dest.writeInt(numeric);
+ dest.writeInt(symbols);
+ dest.writeInt(nonLetter);
+ }
+
+ public static final Parcelable.Creator<PasswordMetrics> CREATOR
+ = new Parcelable.Creator<PasswordMetrics>() {
+ public PasswordMetrics createFromParcel(Parcel in) {
+ return new PasswordMetrics(in);
+ }
+
+ public PasswordMetrics[] newArray(int size) {
+ return new PasswordMetrics[size];
+ }
+ };
+
+ public static PasswordMetrics computeForPassword(@NonNull String password) {
+ // Analyse the characters used
+ int letters = 0;
+ int upperCase = 0;
+ int lowerCase = 0;
+ int numeric = 0;
+ int symbols = 0;
+ int nonLetter = 0;
+ final int length = password.length();
+ for (int i = 0; i < length; i++) {
+ switch (categoryChar(password.charAt(i))) {
+ case CHAR_LOWER_CASE:
+ letters++;
+ lowerCase++;
+ break;
+ case CHAR_UPPER_CASE:
+ letters++;
+ upperCase++;
+ break;
+ case CHAR_DIGIT:
+ numeric++;
+ nonLetter++;
+ break;
+ case CHAR_SYMBOL:
+ symbols++;
+ nonLetter++;
+ break;
+ }
+ }
+
+ // Determine the quality of the password
+ final boolean hasNumeric = numeric > 0;
+ final boolean hasNonNumeric = (letters + symbols) > 0;
+ final int quality;
+ if (hasNonNumeric && hasNumeric) {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+ } else if (hasNonNumeric) {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+ } else if (hasNumeric) {
+ quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE
+ ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ } else {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ }
+
+ return new PasswordMetrics(
+ quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter);
+ }
+
+ /*
+ * Returns the maximum length of a sequential characters. A sequence is defined as
+ * monotonically increasing characters with a constant interval or the same character repeated.
+ *
+ * For example:
+ * maxLengthSequence("1234") == 4
+ * maxLengthSequence("13579") == 5
+ * maxLengthSequence("1234abc") == 4
+ * maxLengthSequence("aabc") == 3
+ * maxLengthSequence("qwertyuio") == 1
+ * maxLengthSequence("@ABC") == 3
+ * maxLengthSequence(";;;;") == 4 (anything that repeats)
+ * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits)
+ *
+ * @param string the pass
+ * @return the number of sequential letters or digits
+ */
+ public static int maxLengthSequence(@NonNull String string) {
+ if (string.length() == 0) return 0;
+ char previousChar = string.charAt(0);
+ @CharacterCatagory int category = categoryChar(previousChar); //current sequence category
+ int diff = 0; //difference between two consecutive characters
+ boolean hasDiff = false; //if we are currently targeting a sequence
+ int maxLength = 0; //maximum length of a sequence already found
+ int startSequence = 0; //where the current sequence started
+ for (int current = 1; current < string.length(); current++) {
+ char currentChar = string.charAt(current);
+ @CharacterCatagory int categoryCurrent = categoryChar(currentChar);
+ int currentDiff = (int) currentChar - (int) previousChar;
+ if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) {
+ maxLength = Math.max(maxLength, current - startSequence);
+ startSequence = current;
+ hasDiff = false;
+ category = categoryCurrent;
+ }
+ else {
+ if(hasDiff && currentDiff != diff) {
+ maxLength = Math.max(maxLength, current - startSequence);
+ startSequence = current - 1;
+ }
+ diff = currentDiff;
+ hasDiff = true;
+ }
+ previousChar = currentChar;
+ }
+ maxLength = Math.max(maxLength, string.length() - startSequence);
+ return maxLength;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({CHAR_UPPER_CASE, CHAR_LOWER_CASE, CHAR_DIGIT, CHAR_SYMBOL})
+ private @interface CharacterCatagory {}
+ private static final int CHAR_LOWER_CASE = 0;
+ private static final int CHAR_UPPER_CASE = 1;
+ private static final int CHAR_DIGIT = 2;
+ private static final int CHAR_SYMBOL = 3;
+
+ @CharacterCatagory
+ private static int categoryChar(char c) {
+ if ('a' <= c && c <= 'z') return CHAR_LOWER_CASE;
+ if ('A' <= c && c <= 'Z') return CHAR_UPPER_CASE;
+ if ('0' <= c && c <= '9') return CHAR_DIGIT;
+ return CHAR_SYMBOL;
+ }
+
+ private static int maxDiffCategory(@CharacterCatagory int category) {
+ switch (category) {
+ case CHAR_LOWER_CASE:
+ case CHAR_UPPER_CASE:
+ return 1;
+ case CHAR_DIGIT:
+ return 10;
+ default:
+ return 0;
+ }
+ }
+}