diff options
Diffstat (limited to 'core/java/android')
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; + } + } +} |
