aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java361
1 files changed, 361 insertions, 0 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java
new file mode 100644
index 000000000..1487c3053
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java
@@ -0,0 +1,361 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 Matt Allen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.sufficientlysecure.keychain.ui.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import org.sufficientlysecure.keychain.R;
+
+/**
+ * Created by Matt Allen
+ * 01/07/14
+ * http://www.mattallensoftware.co.uk
+ * mattallen092@gmail.com
+ * <p/>
+ * https://github.com/matt-allen/android-password-strength-indicator
+ * <p/>
+ * <p>
+ * This View is designed to indicate how secure a user-entered password is in a visual way to
+ * relay to the user if they need to make it stronger. The strength of the password can be set
+ * at creation (or after) which will decide whether their password is strong enough.
+ * </p>
+ * <p/>
+ * <p>
+ * The password strength is decided by an index of 20. The minimum score needed to pass is 10
+ * which means the String has met the conditions imposed by the strength test, but can be improved.
+ * If the password scores 10-19 it is considered weak, and only if it scores 20 will it be
+ * considered strong.
+ * </p>
+ */
+public class PasswordStrengthView extends View {
+
+ protected int mMinWidth;
+ protected int mMinHeight;
+
+ protected Paint mIndicatorPaint;
+ protected Paint mGuidePaint;
+
+ protected int mIndicatorHeight;
+ protected int mIndicatorWidth;
+ protected int mCurrentScore;
+
+ protected int mColorFail;
+ protected int mColorWeak;
+ protected int mColorStrong;
+
+ protected boolean mShowGuides = true;
+
+ /**
+ * Used to define that the indicator should only be looking
+ * for a weak password. The bare minimum is used here to let
+ * the user continue.
+ */
+ public static final int STRENGTH_WEAK = 0;
+
+ /**
+ * A fairly strict rule for generating a password. It encourages a password that is
+ * less easy to crack.
+ */
+ public static final int STRENGTH_MEDIUM = 1;
+
+ /**
+ * A strong algorithm that encourages very strong passwords that should be fairly long, with
+ * non-alphanumeric, numbers, and upper case.
+ */
+ public static final int STRENGTH_STRONG = 2;
+
+ private int mStrengthRequirement = -1;
+ protected String mPassword;
+
+ public PasswordStrengthView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ int COLOR_FAIL = context.getResources().getColor(R.color.android_red_light);
+ int COLOR_WEAK = context.getResources().getColor(R.color.android_orange_light);
+ int COLOR_STRONG = context.getResources().getColor(R.color.android_green_light);
+
+ TypedArray style = context.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.PasswordStrengthView,
+ 0, 0);
+
+ try {
+ mStrengthRequirement = style.getInteger(R.styleable.PasswordStrengthView_strength,
+ STRENGTH_MEDIUM);
+ mShowGuides = style.getBoolean(R.styleable.PasswordStrengthView_showGuides, true);
+ mColorFail = style.getColor(R.styleable.PasswordStrengthView_color_fail, COLOR_FAIL);
+ mColorWeak = style.getColor(R.styleable.PasswordStrengthView_color_weak, COLOR_WEAK);
+ mColorStrong = style.getColor(R.styleable.PasswordStrengthView_color_strong,
+ COLOR_STRONG);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ // Create and style the paint used for drawing the guide on the indicator
+ mGuidePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mGuidePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mGuidePaint.setColor(Color.BLACK);
+ // Create and style paint for indicator
+ mIndicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mIndicatorPaint.setStyle(Paint.Style.FILL);
+ }
+
+ /**
+ * This view can determine if the password entered by the user is acceptable for
+ * use by your use case. This is based on the strength requirement you have set.
+ *
+ * @return True if requirement has been met
+ */
+ public boolean isStrengthRequirementMet() {
+ return (mCurrentScore >= 10);
+ }
+
+ /**
+ * Change the strength requirement of the password entered by the user. This will also
+ * re-check the password already entered against these new requirements.
+ *
+ * @param requiredStrength Use the public constants of this class to set
+ */
+ public void setStrengthRequirement(int requiredStrength) {
+ if (requiredStrength >= 0 && requiredStrength <= 2) {
+ mStrengthRequirement = requiredStrength;
+ if (mPassword != null && mPassword.length() > 0) {
+ generatePasswordScore();
+ // Update view with new score
+ invalidate();
+ requestLayout();
+ }
+ } else {
+ throw new IndexOutOfBoundsException("Input out of expected range");
+ }
+ }
+
+ /**
+ * Update the password string to check strength of
+ *
+ * @param passwordString String representation of user-input
+ */
+ public void setPassword(String passwordString) {
+ if (passwordString != null && passwordString.length() > 0) {
+ mPassword = passwordString;
+ generatePasswordScore();
+ } else {
+ mPassword = "";
+ mCurrentScore = 0;
+ }
+
+ // Update view with new score
+ invalidate();
+ requestLayout();
+ }
+
+ /**
+ * Private convenience method for adding to the password score
+ *
+ * @param score Amount to be added to current score
+ */
+ protected void addToPasswordScore(int score) {
+ int newScore = mCurrentScore + score;
+
+ // Limit max score
+ if (newScore > 20) {
+ mCurrentScore = 20;
+ } else {
+ mCurrentScore = newScore;
+ }
+ }
+
+ /**
+ * Call this to determine the current strength requirement set on the algorithm
+ *
+ * @return Int representation of the current strength set for the indicator
+ */
+ public int getStrengthRequirement() {
+ return mStrengthRequirement;
+ }
+
+ /**
+ * Generate a score based on the password. The password will already need to be stored
+ * as a class member before running this.
+ */
+ protected void generatePasswordScore() {
+ mCurrentScore = 0;
+ int upperCase = getUppercaseCount(mPassword);
+ int nonAlpha = getNonAlphanumericCount(mPassword);
+ int numbers = getNumberCount(mPassword);
+ switch (mStrengthRequirement) {
+ case STRENGTH_WEAK:
+ addToPasswordScore(mPassword.length() * 2);
+ addToPasswordScore(upperCase * 2);
+ addToPasswordScore(nonAlpha * 2);
+ addToPasswordScore(numbers * 2);
+ break;
+
+ case STRENGTH_MEDIUM:
+ addToPasswordScore(mPassword.length());
+ addToPasswordScore(upperCase);
+ addToPasswordScore(nonAlpha * 2);
+ addToPasswordScore(numbers);
+ break;
+
+ case STRENGTH_STRONG:
+ addToPasswordScore(mPassword.length() / 2);
+ // Cut the score in half to make this a very high requirement
+ addToPasswordScore(upperCase);
+ addToPasswordScore(nonAlpha);
+ addToPasswordScore(numbers);
+ break;
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldW, int oldH) {
+ super.onSizeChanged(w, h, oldW, oldH);
+ int paddingX = getPaddingLeft();
+ int paddingY = getPaddingTop();
+ mIndicatorHeight = h - paddingY;
+ mIndicatorWidth = w - paddingX;
+ }
+
+ /**
+ * The standard parts of the onMeasure needed to create the password strength
+ * indicator. Subclasses should call super.onMeasure, but also need to set
+ * the minimum height and width in the constructor.
+ *
+ * @param widthMeasureSpec The measurement given by the system
+ * @param heightMeasureSpec The measurement given by the system
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Set minimum space for the view to do it's thing
+ int minW = getPaddingLeft() + getPaddingRight() + mMinWidth;
+ int w = resolveSizeAndState(minW, widthMeasureSpec, 1);
+ // And give it enough height so it's visible
+ int minH = mMinHeight + getPaddingBottom() + getPaddingTop();
+ int h = resolveSizeAndState(minH, heightMeasureSpec, 0);
+ // Feed these back into UIKit
+ setMeasuredDimension(w, h);
+ }
+
+ /**
+ * Set the colour of the indicator {@code Paint} to one that is appropriate
+ * for the strength of the password.
+ */
+ protected void generateIndicatorColor() {
+ int color = mColorFail;
+ if (mCurrentScore >= 18) {
+ color = mColorStrong;
+ } else if (mCurrentScore >= 10) {
+ color = mColorWeak;
+ }
+ mIndicatorPaint.setColor(color);
+ }
+
+ /**
+ * Quick method to determine how many of the characters in a given string are upper case
+ *
+ * @param stringToCheck The string to examine
+ * @return Number of upper case characters
+ */
+ protected int getUppercaseCount(String stringToCheck) {
+ int score = 0;
+ int loops = stringToCheck.length() - 1;
+ for (int i = 0; i <= loops; i++) {
+ if (Character.isUpperCase(stringToCheck.charAt(i))) {
+ score++;
+ }
+ }
+ return score;
+ }
+
+ /**
+ * A convenience method to determine how many characters in the given String aren't
+ * letters or numbers.
+ *
+ * @param stringToCheck
+ * @return Number of characters that aren't numbers or letters
+ */
+ protected int getNonAlphanumericCount(String stringToCheck) {
+ int score = 0;
+ int loops = stringToCheck.length() - 1;
+ for (int i = 0; i <= loops; i++) {
+ if (!Character.isLetter(stringToCheck.charAt(i)) &&
+ !Character.isDigit(stringToCheck.charAt(i))) {
+ score++;
+ }
+ }
+ return score;
+ }
+
+ /**
+ * A convenience method for returning the count of numbers in a given String.
+ *
+ * @param stringToCheck
+ * @return The numbers of digits in the String
+ */
+ protected int getNumberCount(String stringToCheck) {
+ int score = 0;
+ int loops = stringToCheck.length() - 1;
+ for (int i = 0; i <= loops; i++) {
+ if (Character.isDigit(stringToCheck.charAt(i))) {
+ score++;
+ }
+ }
+ return score;
+ }
+
+ /**
+ * Set the guides to show on the view.<br />
+ * On the line style, the guides will show underneath<br />
+ * On the rounded style, the guides will be shown on the outer edges.<br />
+ * The view will be redrawn after the method is called.
+ *
+ * @param showGuides True if you want the guides to be shown
+ */
+ public void setShowGuides(boolean showGuides) {
+ mShowGuides = showGuides;
+ if (mPassword != null && mPassword.length() > 0) {
+ generatePasswordScore();
+ } else {
+ mCurrentScore = 0;
+ }
+
+ invalidate();
+ requestLayout();
+ }
+
+ /**
+ * Determine whether the view is showing the guides for the password score
+ *
+ * @return True if the guides are being shown
+ */
+ public boolean isShowingGuides() {
+ return mShowGuides;
+ }
+}