Skip to content

Create Passcode

The passcode creation screen consists of two parts: the set passcode and the confirm passcode screen.

The layouts of SetPasscodeActivity and ConfirmPasscodeActivity

Basically, these screens contain the following elements:

  • a headline label,
  • a passcode field,
  • and a floating button for "Done" (disabled by default and will enabled only if the passcode field is not empty).

The set passcode layout is extended with passcode policy check; a group of policy based labels indicating if the given policy rule is already satisfied by the current passcode. The policy based labels should be removed if the given policy rule is missing from the policy. The floating button is enabled when the given passcode satisfies the passcode policy.

The user is able to skip the passcode screen if it's configured via the Settings object. Then the set passcode layout extends with a "Not now" button (hidden by default).

All the mentioned labels can be customized via the Settings configuration, that is you can overwrite them via the corresponding set methods of the SetPasscodeSettings (included the passcode policies' labels) and the ConfirPasscodeSettings class. These class can be instantiated with default constructor. The set methods should be called before calling the saveToIntent method, which saves the configuration into an Intent. Finally, this intent should be used when starting the SetPasscodeActivity via the startActivityForResult method.

Passcode policy

Passcode policy determine passcode-related requirements, such as passcode strength. Passcode policy is configured via PasscodePolicy object setter methods.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
     * The minimum number of characters in passcode.
     * The `validate` function will return false if
     * the length of the passcode is less than this `minLength`.
     */
    final public int getMinLength() {
        return this.minLength;
    }

    final public void setMinLength(int minLength) {
        this.minLength = minLength;
    }

    /**
     * Should passcode need to have digit.
     * The `validate` function will return false if
     * `hasDigit` is true and there is no digit in the passcode.
     */
    final public boolean hasDigit() {
        return this.hasDigit;
    }

    final public void setHasDigit(boolean hasDigit) {
        this.hasDigit = hasDigit;
    }

    /**
     * Should passcode need to have uppercase characters.
     * The `validate` function will return false if
     * `hasUpper` is true and there is no upper-case characters in the passcode.
     */
    final public boolean hasUpper() {
        return this.hasUpper;
    }

    final public void setHasUpper(boolean hasUpper) {
        this.hasUpper = hasUpper;
    }

    /**
     * Should passcode need to have lowercase characters.
     * The `validate` function will return false if
     * `hasLower` is true and there are no lower-case characters in the passcode.
     */
    final public boolean hasLower() {
        return this.hasLower;
    }

    final public void setHasLower(boolean hasLower) {
        this.hasLower = hasLower;
    }

    /**
     * Should passcode need to have special characters.
     * The `validate` function will return false if
     * `hasSpecial` is true and there are no special characters in the passcode.
     */
    final public boolean hasSpecial() {
        return this.hasSpecial;
    }

    final public void setHasSpecial(boolean hasSpecial) {
        this.hasSpecial = hasSpecial;
    }

    /**
     * How many unique characters should there be in the passcode.
     * The `validate` function will return false if
     * the number of unique characters in the passcode is less than `minUniqueChars`.
     */
    final public int getMinUniqueChars() {
        return this.minUniqueChars;
    }

    final public void setMinUniqueChars(int minUniqueChars) {
        this.minUniqueChars = minUniqueChars;
    }

    /**
     * How many retries are available to the user, before they are permanently locked-out.
     */
    final public int getRetryLimit() {
        return this.retryLimit;
    }

    final public void setRetryLimit(int retryLimit) {
        this.retryLimit = retryLimit;
    }

    /**
     * Allows the passcode to be stored in fingerprint protected keystore.
     *
     * @return true if fingerprint is allowed
     */
    final public boolean allowsFingerprint() {
        return this.allowsFingerprint;
    }

    /**
     * Sets the passcode be stored in fingerprint.
     *
     * @param allowsFingerprint the fingerprint is allowed
     */
    final public void setAllowsFingerprint(boolean allowsFingerprint) {
        this.allowsFingerprint = allowsFingerprint;
    }

    /**
     * The passcode should contain digit only if this property is true.
     * All the hasDigit, hasUpper, hasLower, and hasSpecial will be ignored
     * if this property is true.
     */
    final public boolean isDigitsOnly() {
        return this.isDigitsOnly;
    }

    final public void setIsDigitsOnly(boolean isDigitsOnly) {
        this.isDigitsOnly = isDigitsOnly;
    }

Passcode validation

Over the basic passcode policy validation mechanism, user has an extra passcode validation handler for special cases. There may be cases when passcode policy is not enough. For example: check that the previous and the new passcode cannot match or choose "simple" passcodes. PasscodeValidationActionHandler can be implemented handling such rules.

1
2
3
4
5
6
@Override
    public void validate(Fragment fragment, char[] passcode) throws InterruptedException, PasscodeValidationException {
        if (String.valueOf(passcode).equals("12345678")) {
            throw new PasscodeValidationFailedToMeetPolicy("Passcode is not complex enough", null);
        }
    }

Customizing the Set passcode screen

You can use the SetPasscodeSettings object for customize the labels of the set passcode screen. The following options are available:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public boolean isSkipEnabled() {
    return skipEnabled;
}

public void setSkipEnabled(boolean skipEnabled) {
    this.skipEnabled = skipEnabled;
}

public String getLowercaseLabel() {
    return lowerCaseLabel;
}

public void setLowercaseLabel(String lowercaseLabel) {
    this.lowerCaseLabel = lowercaseLabel;
}

public String getUpperCaseLabel() {
    return upperCaseLabel;
}

public void setUpperCaseLabel(String upperCaseLabel) {
    this.upperCaseLabel = upperCaseLabel;
}

public String getSymbolCaseLabel() {
    return symbolCaseLabel;
}

public void setSymbolCaseLabel(String symbolCaseLabel) {
    this.symbolCaseLabel = symbolCaseLabel;
}

public String getDigitCaseLabel() {
    return digitCaseLabel;
}

public void setDigitCaseLabel(String digitCaseLabel) {
    this.digitCaseLabel = digitCaseLabel;
}

public String getGroupLabel() {
    return groupLabel;
}

public void setGroupLabel(String groupLabel) {
    this.groupLabel = groupLabel;
}

public String getSkipButtonText() {
    return skipButtonText;
}

public void setSkipButtonText(String skipButtonText) {
    this.skipButtonText = skipButtonText;
}

public String getPasscodeLabelChar() {
    return passcodeLabelChar;
}

public void setPasscodeLabelChar(String passcodeLabelChar) {
    this.passcodeLabelChar = passcodeLabelChar;
}

public String getPasscodeLabelDigit() {
    return passcodeLabelDigit;
}

public void setPasscodeLabelDigit(String passcodeLabelDigit) {
    this.passcodeLabelDigit = passcodeLabelDigit;
}

Customizing the Confirm passcode screen

You can use the ConfirmPasscodeSettings object for customize the labels of the confirm passcode screen. The following options are available:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public String getConfirmYourPasscode() {
    return confirmYourPasscode;
}

public void setConfirmYourPasscode(String confirmYourPasscode) {
    this.confirmYourPasscode = confirmYourPasscode;
}

public char[] getCurrentPasscode() {
    return currentPasscode;
}

public void setCurrentPasscode(char[] currentPasscode) {
    this.currentPasscode = currentPasscode;
}

Add Passcode screen activity and the Confirm Screen to your manifest file

The SetPasscodeActivity and the ConfirmPasscodeActivity should be added to the AndroidManifest.xml file in an activity xml tag. It is required to specify also the "action_handler" meta-data tag for both activities and a "validation_action_handler" for the SetPasscodeActivity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
        <activity
            android:name="com.sap.cloud.mobile.onboarding.passcode.SetPasscodeActivity"
            android:label="@string/app_name"
            android:parentActivityName=".ApiDemos"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <meta-data
                android:name="action_handler"
                android:value="<your_package>.PasscodeActionHandlerImpl" />
            <meta-data
                android:name="validation_action_handler"
                android:value="<your_package>.PasscodeValidationActionHandlerImpl" />
        </activity>
        <activity
            android:name="com.sap.cloud.mobile.onboarding.passcode.ConfirmPasscodeActivity"
            android:label="@string/app_name"
            android:parentActivityName=".ApiDemos"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <meta-data
                android:name="action_handler"
                android:value="<your_package>.PasscodeActionHandlerImpl" />
        </activity>

Start the SetPasscodeActivity

The SetPasscodeActivity can be started with standard startActivityResult method of the Activity and you can configure it via the SetPasscodeSettings class. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        Intent i = new Intent(getIntent());
        i.setComponent(new ComponentName(
                getPackageName(),
                "com.sap.cloud.mobile.onboarding.passcode.SetPasscodeActivity"));
        SetPasscodeSettings setSettings = new SetPasscodeSettings();
        ConfirmPasscodeSettings confirmSettings = new ConfirmPasscodeSettings();
        // config via set methods

        setSettings.saveToIntent(i);
        confirmSettings.saveToIntent(i);

        startActivityForResult(i, SET_PASSCODE);

You have to implement all the methods of the PasscodeActionHandler interface in your action handler implementation class. In addition, you have to implement the validate method of the PasscodeValidationActionHandler interface as well.

Implementation of the callback methods

The implementation of these methods will be executed on a dedicated background thread of the caller activity, more precisely the background thread of the headless fragment of the caller activity. This fragment is handed over to the method as parameter. You can access the caller activity via the getActivity() method of this fragment. Please note that the result of the getActivity call might be null value if there is no active activity in the background, for example, due to an ongoing change orientation process.

The callback method waits for response synchronously, so if you need asynchronous communication, then you have to wait on this thread until the async response arrives. You are allowed block the thread because you are on a dedicated background thread.

As a consequence, you have to be prepared to handle the interrupted flag of the thread. For example: if the user taps on the back button, or if the system destroys the caller fragment, then this thread will be interrupted. If the execution of your callback method was successfully interrupted, that is the normal processing was not completed, then you should throw InterruptedException from your implementation in order to indicate that the processing was cancelled.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
        public class PasscodeActionHandlerImpl implements PasscodeActionHandler {
        final private static String TAG = "PasscodeAHandlerImpl"; //abbreviated
        private static int attempts = 0;

        @Override
        public void shouldTryPasscode(char[] passcode, PasscodeInputMode mode, Fragment fragment) throws PasscodeValidationException,
        InterruptedException {
        Log.i(TAG, "shouldTryPasscode");
        switch (mode) {
            case CREATE:
            case CHANGE:
                ((DemoApplication) fragment.getActivity().getApplication()).setCurrentPasscode(passcode);
                break;
            case MATCH:
            case MATCHFORCHANGE:
                if (!((DemoApplication) fragment.getActivity().getApplication()).checkPasscode(passcode)) {
                    throw new PasscodeValidationFailedToMatchException("Passcode match",
                            ((DemoApplication) fragment.getActivity().getApplication()).getPolicy().getRetryLimit() - ++attempts,
                            null);
                }
                break;
            default:
                throw new Error("Unknown input mode");
        }
    }

        @Override
        public void shouldResetPasscode(Fragment fragment) throws InterruptedException {
        Log.i(TAG, "shouldResetPasscode");
        Intent intent = new Intent();
        // reset the registration
        fragment.getActivity().setResult(Activity.RESULT_OK, intent);
        fragment.getActivity().finish();
    }

        @Override
         public void didSkipPasscodeSetup(Fragment fragment) throws InterruptedException {
        Log.i(TAG, "didSkipPasscodeSetup");
        Intent intent = new Intent();
        fragment.getActivity().setResult(Activity.RESULT_OK, intent);
        fragment.getActivity().finish();
    }

        @Override
         public void didCancelPasscodeEntry(Fragment fragment) throws InterruptedException {
        Log.i(TAG, "didCancelPasscodeEntry");
        Intent intent = new Intent();
        intent.putExtra("CANCELLATION", true);
        if (((DemoApplication) fragment.getActivity().getApplication()).isPasscodeCancelEnabled()) {
            fragment.getActivity().setResult(Activity.RESULT_OK, intent);
            fragment.getActivity().finish();
        }
    }

    /**
     * Starts retrieving the passcode policy.
     *
     * @param fragment the enclosing fragment invoking this handler, must be non-null
     * @return the passcode policy
     */
    @Override
    public PasscodePolicy getPasscodePolicy(Fragment fragment) throws InterruptedException {
        Log.i(TAG, "getPasscodePolicy");
        return ((DemoApplication) fragment.getActivity().getApplication()).getPolicy();
    }