Android PIN Complexity

HYPR SDK for Android: Android Authenticators

HYPR SDK for Android includes new Personal Identification Number (PIN) rules and a new User Interface (UI).

We use the same layout for enrolling, confirming, and authorizing. If you want just to change the UI and not the behavior, reimplement the .XML file.

🚧

Don't Forget

The app now verifies PIN complexity on the fly during input; if you override this class, you must verify PIN complexity in your own code.

If you change the layout, remember to retain the class types and ids.

User Interface (UI)

Enroll (Create PIN)ConfirmAuthorize
Enroll - Create PINConfirm PINAuthorize
A Rule Is BrokenIncorrect PIN on ConfirmationIncorrect PIN on Authorization
A Rule is BrokenIncorrect PIN on ConfirmationIncorrect PIN on Authorization

All three fragments are based on one .XML file. Fields are set in proper fragments to adjust or customize the view.

  • hypr_header_layout_button_cancel: ImageView in the hypr_pin_layout_header layout that calls the cancel action. Image source is @drawable/hypr_common_view_exit_selector drawables:
<item android:drawable="@drawable/hypr_common_view_exit_pressed" android:state_pressed="true" /><item android:drawable="@drawable/hypr_common_view_exit_default" />
  • hypr_title_logo: ImageView with a logo image (in the SDK this is a placeholder). The image source is @drawable/hypr_logo_small_blue

  • hypr_pin_layout_title: Title of the fragment:

    • Enroll: @string/hypr_enroll_pin_title
    • Confirm: @string/hypr_confirm_pin_title
    • Authorize: @string/hypr_authorize_pin_title
  • hypr_pin_layout_hint_second: Hint about the repeating rule @string/hypr_enroll_pin_repeating_error, which is in the Enroll fragment

  • hypr_pin_layout_hint_first: Hint about the sequential rule @string/hypr_enroll_pin_sequence_error, which is in the Enroll fragment

  • hypr_pin_layout_prompt: Prompt for PIN input, @string/hypr_authorize_pin_hint, which is in the Authorize fragment

Hints are instances of com.hypr.hyprandroidpinframework.ui.screens.pinentry.HintTextView and its action when the rule(s) are violated: change of font color and addition of the X icon; the image source is @drawable/hypr_common_cross_red_small

  • hypr_pin_layout_input: The input field should be an instance of com.hypr.hyprandroidpinframework.ui.screens.pinentry.PinTextEditor; hypr_pin_layout_input works with com.hypr.hyprandroidpinframework.ui.screens.pinentry.SecretWatcher and masks the input; hypr_pin_layout_input will auto-submit upon typing the sixth digit unless a rule is broken

  • hypr_pin_keyboard_separator: The line to which the top of the keyboard is anchored

Fragment Layout

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/hypr_pin_background"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/hyprColorWhiteBackground"
    tools:context="com.hypr.hyprandroidpinframework.ui.activities.HyprPinActivity"
    tools:ignore="Overdraw">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/hypr_pin_header_separator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.12" />

    <include
        android:id="@+id/hypr_pin_layout_header"
        layout="@layout/hypr_common_screen_header"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/hypr_pin_header_separator"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@id/hypr_pin_layout_input"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/hypr_pin_header_separator">

        <TextView
            android:id="@+id/hypr_pin_layout_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:text="TITLE"
            android:textColor="@color/hyprColorHyprBlueDark"
            android:textSize="@dimen/hypr_complex_pin_hint_text_size"
            android:textStyle="bold"
            tools:ignore="HardcodedText" />

        <com.hypr.hyprandroidpinframework.ui.screens.pinentry.HintTextView
            android:id="@+id/hypr_pin_layout_hint_second"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:drawablePadding="@dimen/hypr_complex_pin_rule_violation_img_padding"
            android:text="@string/hypr_enroll_pin_repeating_error"
            android:textColor="@color/hyprColorGrey"
            android:textSize="@dimen/hypr_complex_pin_hint_text_size"
            app:defaultPromptColor="@color/hyprColorGrey"
            app:failPromptColor="@color/hyprColorRedFail"
            app:failPromptLeftIcon="@drawable/hypr_common_cross_red_small"
            tools:ignore="UseCompatTextViewDrawableXml" />

        <com.hypr.hyprandroidpinframework.ui.screens.pinentry.HintTextView
            android:id="@+id/hypr_pin_layout_hint_first"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="@string/hypr_enroll_pin_sequence_error"
            android:textColor="@color/hyprColorGrey"
            android:textSize="@dimen/hypr_complex_pin_hint_text_size"
            app:defaultPromptColor="@color/hyprColorGrey"
            app:failPromptColor="@color/hyprColorRedFail"
            app:failPromptLeftIcon="@drawable/hypr_common_cross_red_small" />

        <com.hypr.hyprandroidpinframework.ui.screens.pinentry.HintTextView
            android:id="@+id/hypr_pin_layout_prompt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:labelFor="@+id/hypr_pin_layout_input"
            android:text="@string/hypr_authorize_pin_hint"
            android:textColor="@color/hyprColorGrey"
            android:textSize="@dimen/hypr_complex_pin_hint_text_size"
            app:defaultPromptColor="@color/hyprColorGrey"
            app:failPromptColor="@color/hyprColorRedFail"
            app:failPromptLeftIcon="@drawable/hypr_common_cross_red_small" />

    </LinearLayout>

    <com.hypr.hyprandroidpinframework.ui.screens.pinentry.PinTextEditor
        android:id="@+id/hypr_pin_layout_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginStart="@dimen/hypr_complex_pin_input_center"
        android:layout_marginEnd="@dimen/hypr_complex_pin_input_center"
        android:backgroundTint="@color/hyprColorLogoBlue"
        android:inputType="number"
        android:letterSpacing="0.3"
        android:maxLength="6"
        android:text="******"
        android:textColor="@color/hyprColorLogoBlue"
        android:textSize="@dimen/hypr_complex_pin_input_text_size"
        app:layout_constraintBottom_toBottomOf="@+id/hypr_pin_keyboard_separator"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/hypr_pin_header_separator"
        android:textColorHint="@color/hyprColorTrans"
        android:importantForAccessibility="yes"
        android:accessibilityTraversalAfter="@+id/hypr_pin_layout_title"
        tools:ignore="HardcodedText" />


    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/hypr_pin_keyboard_separator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.6" />

    <View
        android:id="@+id/hypr_pin_view_bottom_anchor"
        android:layout_width="match_parent"
        android:layout_height="@dimen/hypr_common_keyboard_divider_bar_height"
        app:layout_constraintBottom_toBottomOf="parent" />

    <include layout="@layout/hypr_numeric_keyboard_view_layout" />

</androidx.constraintlayout.widget.ConstraintLayout>

Rules

  • Repeating rules: PINs may have no more than two consecutive numbers. Example: 11632 will pass (two repeating 1s), but 145558 will not (three repeating 5s).
  • Sequential rules: PINs must have no more than two sequential numbers, ascending or descending. Example: 124587 will work, but 123900 and 348765 will not.
  • Pattern rules: Repeating patterns in pairs and triplets will also be detected and rejected. Repeating a separate set of pairs will also fail. Example: 123123 or 121212 will both fail; and 112266 will fail.

A message will warn the user if one or more of the rules is broken as they type.

Keyboard

The Android keyboard is implemented in com.hypr.hyprandroidcommon.uiadapter.ui.views.keyboard.HyprNumericKeyboardView, and uses the hypr_numeric_keyboard_view_layout.xml layout with labels from hypr_numeric_keyboard_view.

Keyboard View

You can change separate elements as follows:

  • Background color: #E9EAEE
  • Default key background color: #@color/hyprColorWhite
  • Backspace key background color: #FFCBCED5
  • Ack key background: #FF1A73E9
  • Button shadow color: #09000000

Shadow is defined in the hypr_numeric_key_shadow.xml file.

Mapping for keyLabels, which extends labels, is hardcoded:

private val labels = mapOf(
    "2" to "ABC",
    "3" to "DEF",
    "4" to "GHI",
    "5" to "JKL",
    "6" to "MNO",
    "7" to "PQRS",
    "8" to "TUV",
    "9" to "WXYZ",
    "0" to " + "
)

For other buttons we use images:

private val icons = mapOf(
    -5 to R.drawable.hypr_common_backspace,
    -6 to R.drawable.hypr_common_success_vector
)

Colors

  • @color/hyprPinInput: The input field text color (hypr_pin_layout_input)
  • @color/hyprPinTitle: The title color (hypr_pin_layout_title)
  • @color/hyprPinHint: The default hint color
  • @color/hyprPinHintViolation: The color of a hint when it is related to the violated rule. The X icon displays in this color
  • @color/hyprHeaderExitDefault: The default color of the exit X (hypr_header_layout_button_cancel)
  • @color/hyprHeaderExitPressed: The pressed color of exit X (hypr_header_layout_button_cancel)
  • @color/hyprComplexPinDialogLabel: Error dialog label color

Drawables

  • @drawable/hypr_logo_header_placeholder: A placeholder for the logo on the screen header, currently with transparent paths
  • @drawable/hypr_common_view_exit_selector: A source set in the screen header. It includes the following:
    -- @drawable/hypr_common_view_exit_default: The default image selector
    -- @drawable/hypr_common_view_exit_pressed: The image for the pressed state
  • @drawable/hypr_cross_red_small: A small red X shown when the rule is violated
  • @drawable/hypr_numeric_key_ack: The keyboard success button background (without shadow)
  • @drawable/hypr_numeric_key_backspace
  • @drawable/hypr_numeric_key_background

Strings

Error Dialog Strings

<string name="hypr_complex_pin_not_confirmed">PINs did not match. Try creating a PIN again.</string>
<string name="hypr_complex_pin_not_match_title">Incorrect PIN</string>
<string name="hypr_complex_pin_not_match">Please check your PIN and try again!</string>
  • @string/hypr_complex_pin_not_confirmed: A message shown in the Confirm fragment when the PIN does not match what was previously set.
  • @string/hypr_complex_pin_not_match_title: The title on a dialog shown when the authorization PIN does not pass.
  • @string/hypr_complex_pin_not_match: A message shown when the authorization PIN does not pass.

Other Strings

<string name="hypr_enroll_pin_title">Create a 6 digit PIN</string>
<string name="hypr_confirm_pin_title">Confirm PIN</string>
<string name="hypr_authorize_pin_title">Pin Authentication</string>
<string name="hypr_authorize_pin_hint">Enter your 6 digit PIN</string>
<string name="hypr_enroll_pin_repeating_error">No more than 2 repeating numbers</string>
<string name="hypr_enroll_pin_sequence_error">No more than 2 consecutive numbers</string>

<string name="hypr_content_description_enroll_pin_input">Input for create your 6 digit PIN.</string>
<string name="hypr_content_description_confirm_pin_input">Input for confirm 6 digit PIN.</string>
<string name="hypr_content_description_auth_pin_input">Input for enter 6 digit authorization PIN.</string>
  • @string/hypr_enroll_pin_title
  • @string/hypr_confirm_pin_title: The fragment title
  • @string/hypr_authorize_pin_title
  • @string/hypr_authorize_pin_hint: The Enroll prompt
  • @string/hypr_enroll_pin_repeating_error: The Enroll hint and error message when the repeating rule is violated (color changed to @color/hyprPinHintViolation)
  • @string/hypr_enroll_pin_sequence_error: An Enroll hint and error message when the sequential rule is violated (color changed to @color/hyprPinHintViolation)

🚧

Reminder

If you want to override the rules, you must override the entire fragment.

  • @string/hypr_content_description_enroll_pin_input
  • @string/hypr_content_description_confirm_pin_input: A content description set into the proper fragment for the accessibility feature
  • @string/hypr_content_description_auth_pin_input