public class StorageAccess {
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
    // Assert Constants
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

    private static final boolean CHECK_THREADS = false;

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
    // Static Fields
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

    private static StorageAccess instance = new StorageAccess();

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
    // Fields
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

    private FileAccess fileAccess;
    private PinCodeConfig pinCodeConfig;
    private AppDatabase appDatabase;
    private EncryptionProvider encryptionProvider;

    private Handler handler = new Handler(Looper.getMainLooper());

    private List<StorageAccessListener> listeners = Collections.synchronizedList(new ArrayList<>());

    private StorageAccess() {
    }

    /**
     * Returns the singleton instance of this class.
     *
     * @return the singleton instance of this class
     */
    public static StorageAccess getInstance() {
        return instance;
    }

    /**
     * Initializes the storage access singleton with the provided dependencies. It is best to call
     * this method inside your {@link Application#onCreate()} method.
     *
     * @param pinCodeConfig      an instance of the pin code configuration for your app
     * @param encryptionProvider an encryption provider
     * @param fileAccess         an implementation of FileAccess
     * @param appDatabase        an implementation of AppDatabase
     */
    public void init(PinCodeConfig pinCodeConfig, EncryptionProvider encryptionProvider, FileAccess fileAccess, AppDatabase appDatabase) {
        this.pinCodeConfig = pinCodeConfig;
        this.appDatabase = appDatabase;
        this.fileAccess = fileAccess;
        this.encryptionProvider = encryptionProvider;
    }

    /**
     * Returns the FileAccess singleton for this application. Should only be called after {@link
     * StorageAccessListener#onDataReady()}.
     *
     * @return the FileAccess singleton
     */
    public FileAccess getFileAccess() {
        return fileAccess;
    }

    /**
     * Returns the AppDatabase singleton for this application. Should only be called after {@link
     * StorageAccessListener#onDataReady()}.
     *
     * @return the AppDatabase singleton
     */
    public AppDatabase getAppDatabase() {
        return appDatabase;
    }

    /**
     * Returns the pin code configuration for the app
     *
     * @return the pin code configuration
     */
    public PinCodeConfig getPinCodeConfig() {
        return pinCodeConfig;
    }

    /**
     * Returns whether the user has created a pin code or not
     *
     * @param context android context
     * @return a boolean indicating if the user has created a pin code
     */
    public boolean hasPinCode(Context context) {
        return encryptionProvider.hasPinCode(context);
    }


    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
    // Storage Access request and notification
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

    /**
     * Checks and inits storage access. Once storage access is ready, registered listeners will be
     * notified via the {@link StorageAccessListener}. This is true whether there is an auth flow or
     * if the system is already prepared. The callback will happen on the main thread looper, after
     * all scheduled events, which will include (in the standard case) onCreate/onStart/onResume,
     * and the equivalent Fragment callbacks. This is to ensure one path, and reduce edge case race
     * conditions.
     *
     * @param context Calling context. If you are calling from an Activity, its best to send it and
     *                not the application context, because we may want to start new activities, and
     *                the application context can only do so with a new task, which will possibly
     *                complicate the back stack. We won't do anything weird that causes memory
     *                leaks. Promise ;)
     */
    @MainThread
    public void requestStorageAccess(Context context) {
        UiThreadContext.assertUiThread();

        if (encryptionProvider.needsAuth(context, pinCodeConfig)) {
            // just need to re-auth
            notifySoftFail();
        } else {
            notifyReady();
        }
    }

    /**
     * Registers a listener. If you want to read/write data, you'll need to implement this to know
     * when file access is ready.
     *
     * @param storageAccessListener
     */
    @MainThread
    public final void register(StorageAccessListener storageAccessListener) {
        if (CHECK_THREADS) {
            UiThreadContext.assertUiThread();
        }
        if (listeners.contains(storageAccessListener)) {
            throw new StorageAccessException("Listener already registered");
        }

        listeners.add(storageAccessListener);
    }

    /**
     * Guess what this does. Yes, you'll need to call it if you called register or you'll have
     * memory leaks and possibly crashes on callbacks to dead clients.
     *
     * @param storageAccessListener
     */
    @MainThread
    public final void unregister(StorageAccessListener storageAccessListener) {
        if (CHECK_THREADS) {
            UiThreadContext.assertUiThread();
        }
        listeners.remove(storageAccessListener);
    }

    private void notifyReady() {
        handler.post(this::notifyListenersReady);
    }

    @MainThread
    private void notifyListenersReady() {
        if (CHECK_THREADS) {
            UiThreadContext.assertUiThread();
        }

        for (StorageAccessListener listener : listeners) {
            listener.onDataReady();
        }
    }

    private void notifyHardFail() {
        handler.post(this::notifyListenersHardFail);
    }

    @MainThread
    private void notifyListenersHardFail() {
        if (CHECK_THREADS) {
            UiThreadContext.assertUiThread();
        }

        for (StorageAccessListener listener : listeners) {
            listener.onDataFailed();
        }
    }

    private void notifySoftFail() {
        handler.post(this::notifyListenersSoftFail);
    }

    @MainThread
    private void notifyListenersSoftFail() {
        if (CHECK_THREADS) {
            UiThreadContext.assertUiThread();
        }

        for (StorageAccessListener listener : listeners) {
            listener.onDataAuth();
        }
    }

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
    // Encryption-only methods
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

    /**
     * Logs the last access time in order to time out the pin code after the amount of time set in
     * the {@link PinCodeConfig}.
     * <p>
     * An example of how this should be used is in {@link PinCodeActivity#onPause()}. This will
     * ensure that it updates frequently while the user is still inside of the app so as not to
     * trigger a reset, but will reset if the user has been outside of the app for too long.
     */
    public void logAccessTime() {
        encryptionProvider.logAccessTime();
    }

    /**
     * Attempts authenticating with the provided pin. On an invalid pin, it will throw {@link
     * StorageAccessException}.
     *
     * @param context android context
     * @param pin     string of the pin to attempt authentication
     * @throws StorageAccessException
     */
    public void authenticate(Context context, String pin) {
        encryptionProvider.startWithPassphrase(context, pin);
        injectEncrypter();
    }

    /**
     * Creates a master key encrypted by the pin code provided. This can only happen once and will
     * throw a {@link StorageAccessException} on subsequent calls.
     *
     * @param context android context
     * @param pin     the user's pin, this should already be validated (enter + confirm)
     * @throws StorageAccessException if there is already a pin code
     */
    public void createPinCode(Context context, String pin) {
        if (hasPinCode(context)) {
            throw new StorageAccessException("Attempted to create a pin when one already exists");
        }

        encryptionProvider.createPinCode(context, pin);
        injectEncrypter();
    }

    /**
     * Changes the pin code if the provided old pin matches, otherwise throws a {@link
     * StorageAccessException}.
     *
     * @param context android context
     * @param oldPin  the old pin
     * @param newPin  the new pin, which should already be validated (enter + confirm)
     * @throws StorageAccessException
     */
    public void changePinCode(Context context, String oldPin, String newPin) {
        encryptionProvider.changePinCode(context, oldPin, newPin);
        injectEncrypter();
    }

    /**
     * Removes the pin code if one exists.
     *
     * @param context android context
     */
    public void removePinCode(Context context) {
        encryptionProvider.removePinCode(context);
    }

    private void injectEncrypter() {
        fileAccess.setEncrypter(encryptionProvider.getEncrypter());
        appDatabase.setEncryptionKey(encryptionProvider.getEncrypter().getDbKey());
    }
} public class StepResult<T> extends Result {
    /**
     * When StepResult only has a single value, pair that value with the following key
     */
    public static final String DEFAULT_KEY = "answer";

    private Map<String, T> results;

    private AnswerFormat answerFormat;

    /**
     * Creates a StepResult from a {@link Step}.
     * <p>
     * Using this constructor ensures that the StepResult has the correct identifier and answer
     * format for the corresponding step.
     *
     * @param step the step from which to create the StepResult
     */
    public StepResult(Step step) {
        super(step.getIdentifier());
        this.results = new HashMap<>();

        if (step instanceof QuestionStep) {
            answerFormat = ((QuestionStep) step).getAnswerFormat();
        }
        setStartDate(new Date());
        // this will be updated when the result is set
        setEndDate(new Date());
    }

    public Map<String, T> getResults() {
        return results;
    }

    public void setResults(Map<String, T> results) {
        this.results = results;
    }

    /**
     * Returns the result stored using {@link #setResult}.
     *
     * @return the result with the default identifier
     */
    public T getResult() {
        return getResultForIdentifier(DEFAULT_KEY);
    }

    /**
     * Sets the result using the default key, useful when there is only a single result.
     *
     * @param result the result to save with the default key
     */
    public void setResult(T result) {
        setResultForIdentifier(DEFAULT_KEY, result);
        setEndDate(new Date());
    }

    /**
     * Returns the result for the given identifier, use this when there are multiple results for the
     * step.
     *
     * @param identifier the identifier used as the key for storing this result
     * @return the result for the given identifier
     */
    public T getResultForIdentifier(String identifier) {
        return results.get(identifier);
    }

    /**
     * Sets the result for the given identifier, use when there are multiple results for the step.
     * <p>
     * If there is only one result, use the {@link #setResult} convenience method instead.
     *
     * @param identifier the identifier for the result
     * @param result     the result to save
     */
    public void setResultForIdentifier(String identifier, T result) {
        results.put(identifier, result);
    }

    /**
     * Gets the {@link AnswerFormat} for this step result. May be useful when processing the
     * result.
     *
     * @return the answer format associated with the step
     */
    public AnswerFormat getAnswerFormat() {
        return answerFormat;
    }
} public class Step implements Serializable {
    private String identifier;

    private Class stepLayoutClass;

    private int stepTitle;

    private boolean optional = true;

    private String title;

    private String text;

    // The following fields are in RK but not implemented in ResearchStack
    // These options can be developed as needed or removed if we find they are not necessary
    private boolean restorable;
    private Task task;
    private boolean shouldTintImages;
    private boolean showsProgress;
    private boolean allowsBackNavigation;
    private boolean useSurveyMode;

    /**
     * Returns a new step initialized with the specified identifier.
     *
     * @param identifier The unique identifier of the step.
     */
    public Step(String identifier) {
        this.identifier = identifier;
    }

    /**
     * Returns a new step initialized with the specified identifier and title.
     *
     * @param identifier The unique identifier of the step.
     * @param title      The primary text to display for this step.
     */
    public Step(String identifier, String title) {
        this.identifier = identifier;
        this.title = title;
    }

    /**
     * A short string that uniquely identifies the step within the task.
     * <p>
     * The identifier is reproduced in the results of a step. In fact, the only way to link a result
     * (a {@link org.researchstack.backbone.result.StepResult} object) to the step that generated it
     * is to look at the value of <code>identifier</code>. To accurately identify step results, you
     * need to ensure that step identifiers are unique within each task.
     * <p>
     * In some cases, it can be useful to link the step identifier to a unique identifier in a
     * database; in other cases, it can make sense to make the identifier human readable.
     */
    public String getIdentifier() {
        return identifier;
    }

    /**
     * A boolean value indicating whether the user can skip the step without providing an answer.
     * <p>
     * The default value of this property is <code>true</code>. When the value is
     * <code>false</code>, the Skip button does not appear on this step.
     * <p>
     * This property may not be meaningful for all steps; for example, an active step might not
     * provide a way to skip, because it requires a timer to finish.
     *
     * @return a boolean indicating whether the step is skippable
     */
    public boolean isOptional() {
        return optional;
    }

    /**
     * Sets whether the step is skippable
     *
     * @param optional
     * @see #isOptional()
     */
    public void setOptional(boolean optional) {
        this.optional = optional;
    }

    /**
     * The primary text to display for the step in a localized string.
     * <p>
     * This text is also used as the label when a step is shown in the more compact version in a
     * {@link FormStep}.
     *
     * @return the primary text for the question
     */
    public String getTitle() {
        return title;
    }

    /**
     * Sets the primary text to display for the step in a localized string.
     *
     * @param title the primary text for the question.
     * @see #getTitle()
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * Additional text to display for the step in a localized string.
     * <p>
     * The additional text is displayed in a smaller font below <code>title</code>. If you need to
     * display a long question, it can work well to keep the title short and put the additional
     * content in the <code>text</code> property.
     *
     * @return
     */
    public String getText() {
        return text;
    }

    /**
     * Sets the additional text for the step.
     *
     * @param text the detail text for the step
     * @see #getText()
     */
    public void setText(String text) {
        this.text = text;
    }

    /**
     * Gets the int id for the title to display in the action bar (optional).
     *
     * @return the id for the title to display in the action bar
     */
    public int getStepTitle() {
        return stepTitle;
    }

    /**
     * Gets the int id for the title to display in the action bar (optional).
     *
     * @param stepTitle the Android resource id for the title
     */
    public void setStepTitle(int stepTitle) {
        this.stepTitle = stepTitle;
    }

    /**
     * Returns the class that the {@link org.researchstack.backbone.ui.ViewTaskActivity} should
     * instantiate to display this step.
     * <p>
     * This method is used within the framework so that steps can define their step view controller
     * pairing.
     * <p>
     * Outside the framework, developers should instantiate the required view controller in their
     * ViewTaskActivity delegate to override the ViewTaskActivity's default.
     *
     * @return the class of the {@link org.researchstack.backbone.ui.step.layout.StepLayout} for
     * this step
     */
    public Class getStepLayoutClass() {
        return stepLayoutClass;
    }

    /**
     * Sets the class that should be used to display this step
     *
     * @param stepLayoutClass the {@link org.researchstack.backbone.ui.step.layout.StepLayout} class
     *                        to be used to display this step
     */
    public void setStepLayoutClass(Class stepLayoutClass) {
        this.stepLayoutClass = stepLayoutClass;
    }
} public abstract class Task implements Serializable {
    private String identifier;

    /**
     * Class constructor specifying a unique identifier.
     *
     * @param identifier the task identifier, see {@link #getIdentifier()}
     */
    public Task(String identifier) {
        this.identifier = identifier;
    }

    /**
     * Gets the unique identifier for this task.
     * <p>
     * The identifier should be a short string that identifies the task. The identifier is copied
     * into the {@link TaskResult} objects generated  for this task. You can use a human-readable
     * string for the task identifier or a UUID; the exact string you use depends on your app.
     * <p>
     * In the case of apps whose tasks come from a server, the unique identifier for the task may be
     * in an external database.
     * <p>
     * The task identifier is used when constructing the task result. The identifier can also be
     * used during UI state restoration to identify the task that needs to be restored.
     *
     * @return the task identifier
     */
    public String getIdentifier() {
        return identifier;
    }

    /**
     * Gets the title to display in the toolbar for a given step.
     * <p>
     * Override this method to return a custom title, such a text representation of the current
     * progress instead of the step's title.
     * <p>
     * The default implementation gets the title from string resources if it exists, otherwise it
     * returns an empty string.
     *
     * @param context for fetching resources
     * @param step    the current step
     * @return the title to display
     */
    public String getTitleForStep(Context context, Step step) {
        return step.getStepTitle() != 0 ? context.getString(step.getStepTitle()) : "";
    }

    /**
     * Returns the step after the specified step, if there is one.
     * <p>
     * This method lets you use a result to determine the next step.
     * <p>
     * The {@link org.researchstack.backbone.ui.ViewTaskActivity}  calls this method to determine
     * the step to display after the specified step. The ViewTaskActivity can also call this
     * method every time the result updates, to determine if the new result changes which steps are
     * available.
     * <p>
     * If you need to implement this method, take care to avoid creating a confusing sequence of
     * steps. As much as possible, use {@link OrderedTask} instead.
     *
     * @param step   The reference step. Pass null to specify the first step.
     * @param result A snapshot of the current set of results.
     * @return The step that comes after the specified step, or null if there isn't one.
     */
    public abstract Step getStepAfterStep(Step step, TaskResult result);

    /**
     * Returns the step that precedes the specified step, if there is one.
     * <p>
     * The {@link org.researchstack.backbone.ui.ViewTaskActivity} calls this method to determine the
     * step to display before the specified step. The ViewTaskActivity
     * can also call this method every time the result changes, to determine if the new result
     * changes which steps are available.
     * <p>
     * If you need to implement this method, take care to avoid creating a confusing sequence of
     * steps. As much as possible, use {@link OrderedTask} instead. Returning null prevents the user
     * from navigating back to a previous step.
     *
     * @param step   The reference step. Pass null to specify the last step.
     * @param result A snapshot of the current set of results.
     * @return The step that precedes the reference step, or null if there isn't one.
     */
    public abstract Step getStepBeforeStep(Step step, TaskResult result);

    /**
     * Returns the step that matches the specified identifier, if there is one.
     *
     * @param identifier The identifier of the step to retrieve.
     * @return The step that matches the specified identifier.
     */
    public abstract Step getStepWithIdentifier(String identifier);

    /**
     * Returns the progress of the current step.
     * <p>
     * During a task, the {@link org.researchstack.backbone.ui.ViewTaskActivity}  can display the
     * progress (that is, the current step number out of the total number of steps) in the
     * navigation bar. Implement this method to control what is displayed; if you don't implement
     * this method, the progress label does not appear.
     * <p>
     * If the returned {@link TaskProgress} object has a count of 0, the progress is not displayed.
     *
     * @param step   The current step.
     * @param result A snapshot of the current set of results.
     * @return The current step's index and the total number of steps in the task, as an
     * TaskProgress object.
     */
    public abstract TaskProgress getProgressOfCurrentStep(Step step, TaskResult result);

    /**
     * Validates the task parameters.
     * <p>
     * The implementation of this method should check that all the task parameters are correct. An
     * invalid task is considered an unrecoverable error: the implementation should throw an
     * exception on parameter validation failure. For example, the {@link OrderedTask}
     * implementation makes sure that all its step identifiers are unique, throwing an exception
     * otherwise.
     * <p>
     * This method is usually called by {@link org.researchstack.backbone.ui.ViewTaskActivity} when
     * its task is set.
     *
     * @throws InvalidTaskException
     */
    public abstract void validateParameters();

    /**
     * Function that can be overridden in order to access the low level changes in the view.
     * The function is called at Activity lifecycle events (creation, pause, resume, stop and whenever
     * the content of the activity is changed, according to the step.
     *
     * @param type        lifecycle event
     * @param activity    current activity
     * @param currentStep the current step being shown
     */
    public void onViewChange(ViewChangeType type, ViewTaskActivity activity, Step currentStep) {

    }

    public static enum ViewChangeType {
        ActivityCreate,
        ActivityPause,
        ActivityResume,
        ActivityStop,
        StepChanged
    }

    /**
     * A structure that represents how far a task has progressed.
     * <p>
     * Objects that extend Task return the task progress structure to indicate to the {@link
     * org.researchstack.backbone.ui.ViewTaskActivity} how far the task has progressed.
     * <p>
     * Note that the values in an {@link TaskProgress} structure are used only for display; you
     * don't use the values to access the steps in a task.
     */
    public static class TaskProgress {

        private final int current;

        private final int total;

        /**
         * Constructor specifying current index and total number of steps in the task.
         *
         * @param current current step number, zero-based
         * @param total   number of steps in the task
         */
        public TaskProgress(int current, int total) {
            this.current = current;
            this.total = total;
        }

        /**
         * Gets the current step number in the task, zero-based.
         *
         * @return current step number
         */
        public int getCurrent() {
            return current;
        }

        /**
         * Gets the current total number of steps in the task.
         *
         * @return the total number of steps
         */
        public int getTotal() {
            return total;
        }
    }

    /**
     * Runtime exception that is thrown by {@link Task} subclasses in {@link #validateParameters()}.
     */
    public static class InvalidTaskException extends RuntimeException {
        public InvalidTaskException() {
            super();
        }

        public InvalidTaskException(String detailMessage) {
            super(detailMessage);
        }

        public InvalidTaskException(String detailMessage, Throwable throwable) {
            super(detailMessage, throwable);
        }

        public InvalidTaskException(Throwable throwable) {
            super(throwable);
        }
    }
} public class ViewTaskActivity extends PinCodeActivity implements StepCallbacks {
    public static final String EXTRA_TASK = "ViewTaskActivity.ExtraTask";
    public static final String EXTRA_TASK_RESULT = "ViewTaskActivity.ExtraTaskResult";
    public static final String EXTRA_STEP = "ViewTaskActivity.ExtraStep";

    private StepSwitcher root;

    private Step currentStep;
    private Task task;
    private TaskResult taskResult;

    public static Intent newIntent(Context context, Task task) {
        Intent intent = new Intent(context, ViewTaskActivity.class);
        intent.putExtra(EXTRA_TASK, task);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setResult(RESULT_CANCELED);
        super.setContentView(R.layout.rsb_activity_step_switcher);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        root = (StepSwitcher) findViewById(R.id.container);

        if (savedInstanceState == null) {
            task = (Task) getIntent().getSerializableExtra(EXTRA_TASK);
            taskResult = new TaskResult(task.getIdentifier());
            taskResult.setStartDate(new Date());
        } else {
            task = (Task) savedInstanceState.getSerializable(EXTRA_TASK);
            taskResult = (TaskResult) savedInstanceState.getSerializable(EXTRA_TASK_RESULT);
            currentStep = (Step) savedInstanceState.getSerializable(EXTRA_STEP);
        }

        task.validateParameters();

        task.onViewChange(Task.ViewChangeType.ActivityCreate, this, currentStep);
    }

    /**
     * Returns the actual current step being shown.
     *
     * @return an instance of @Step
     */
    public Step getCurrentStep() {
        return currentStep;
    }

    protected void showNextStep() {
        Step nextStep = task.getStepAfterStep(currentStep, taskResult);
        if (nextStep == null) {
            saveAndFinish();
        } else {
            showStep(nextStep);
        }
    }

    protected void showPreviousStep() {
        Step previousStep = task.getStepBeforeStep(currentStep, taskResult);
        if (previousStep == null) {
            finish();
        } else {
            showStep(previousStep);
        }
    }

    private void showStep(Step step) {
        int currentStepPosition = task.getProgressOfCurrentStep(currentStep, taskResult)
                .getCurrent();
        int newStepPosition = task.getProgressOfCurrentStep(step, taskResult).getCurrent();

        StepLayout stepLayout = getLayoutForStep(step);
        stepLayout.getLayout().setTag(R.id.rsb_step_layout_id, step.getIdentifier());
        root.show(stepLayout,
                newStepPosition >= currentStepPosition
                        ? StepSwitcher.SHIFT_LEFT
                        : StepSwitcher.SHIFT_RIGHT);
        currentStep = step;
    }

    protected StepLayout getLayoutForStep(Step step) {
        // Change the title on the activity
        String title = task.getTitleForStep(this, step);
        setActionBarTitle(title);

        // Get result from the TaskResult, can be null
        StepResult result = taskResult.getStepResult(step.getIdentifier());

        // Return the Class & constructor
        StepLayout stepLayout = createLayoutFromStep(step);
        stepLayout.initialize(step, result);
        stepLayout.setCallbacks(this);

        return stepLayout;
    }

    @NonNull
    private StepLayout createLayoutFromStep(Step step) {
        try {
            Class cls = step.getStepLayoutClass();
            Constructor constructor = cls.getConstructor(Context.class);
            return (StepLayout) constructor.newInstance(this);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void saveAndFinish() {
        taskResult.setEndDate(new Date());
        Intent resultIntent = new Intent();
        resultIntent.putExtra(EXTRA_TASK_RESULT, taskResult);
        setResult(RESULT_OK, resultIntent);
        finish();
    }

    @Override
    protected void onPause() {
        hideKeyboard();
        super.onPause();

        task.onViewChange(Task.ViewChangeType.ActivityPause, this, currentStep);
    }

    @Override
    protected void onResume() {
        super.onResume();
        task.onViewChange(Task.ViewChangeType.ActivityResume, this, currentStep);
    }

    @Override
    protected void onStop() {
        super.onStop();
        task.onViewChange(Task.ViewChangeType.ActivityStop, this, currentStep);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            notifyStepOfBackPress();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        notifyStepOfBackPress();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable(EXTRA_TASK, task);
        outState.putSerializable(EXTRA_TASK_RESULT, taskResult);
        outState.putSerializable(EXTRA_STEP, currentStep);
    }

    private void notifyStepOfBackPress() {
        StepLayout currentStepLayout = (StepLayout) findViewById(R.id.rsb_current_step);
        currentStepLayout.isBackEventConsumed();
    }

    @Override
    public void onDataReady() {
        super.onDataReady();

        if (currentStep == null) {
            currentStep = task.getStepAfterStep(null, taskResult);
        }

        showStep(currentStep);
    }

    @Override
    public void onDataFailed() {
        super.onDataFailed();
        Toast.makeText(this, R.string.rsb_error_data_failed, Toast.LENGTH_LONG).show();
        finish();
    }

    @Override
    public void onSaveStep(int action, Step step, StepResult result) {
        onSaveStepResult(step.getIdentifier(), result);

        onExecuteStepAction(action);
    }

    protected void onSaveStepResult(String id, StepResult result) {
        taskResult.setStepResultForStepIdentifier(id, result);
    }

    protected void onExecuteStepAction(int action) {
        if (action == StepCallbacks.ACTION_NEXT) {
            showNextStep();
        } else if (action == StepCallbacks.ACTION_PREV) {
            showPreviousStep();
        } else if (action == StepCallbacks.ACTION_END) {
            showConfirmExitDialog();
        } else if (action == StepCallbacks.ACTION_NONE) {
            // Used when onSaveInstanceState is called of a view. No action is taken.
        } else {
            throw new IllegalArgumentException("Action with value " + action + " is invalid. " +
                    "See StepCallbacks for allowable arguments");
        }
    }

    private void hideKeyboard() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
        if (imm.isActive() && imm.isAcceptingText()) {
            imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
        }
    }

    private void showConfirmExitDialog() {
        AlertDialog alertDialog = new AlertDialog.Builder(this).setTitle(
                "Are you sure you want to exit?")
                .setMessage(R.string.lorem_medium)
                .setPositiveButton("End Task", (dialog, which) -> finish())
                .setNegativeButton("Cancel", null)
                .create();
        alertDialog.show();
    }

    @Override
    public void onCancelStep() {
        setResult(Activity.RESULT_CANCELED);
        finish();
    }

    public void setActionBarTitle(String title) {
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setTitle(title);
        }
    }
} public interface ActivityCallback {
    public void onRequestPermission(String id);

    @Deprecated
    public void startConsentTask();
} public interface StepLayout {
    void initialize(Step step, StepResult result);

    View getLayout();

    /**
     * Method allowing a step layout to consume a back event.
     *
     * @return
     */
    boolean isBackEventConsumed();

    void setCallbacks(StepCallbacks callbacks);
} public class TextUtils {
    public static final Pattern EMAIL_ADDRESS = Pattern.compile(
            "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
                    "\\@" +
                    "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
                    "(" +
                    "\\." +
                    "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
                    ")+");

    private TextUtils() {
    }

    /**
     * Returns true if the string is null or 0-length.
     *
     * @param str the string to be examined
     * @return true if str is null or zero length
     */
    public static boolean isEmpty(CharSequence str) {
        if (str == null || str.length() == 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns true if the char sequence is a valid email address
     *
     * @param text the email address to be validated
     * @return a boolean indicating whether the email is valid
     */
    public static boolean isValidEmail(CharSequence text) {
        return !isEmpty(text) && EMAIL_ADDRESS.matcher(text).matches();
    }


    public static class AlphabeticFilter implements InputFilter {
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
            for (int i = start; i < end; i++) {
                if (!Character.isLetter(source.charAt(i))) {
                    return "";
                }
            }
            return null;
        }
    }

    public static class NumericFilter implements InputFilter {
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
            for (int i = start; i < end; i++) {
                if (!Character.isDigit(source.charAt(i))) {
                    return "";
                }
            }
            return null;
        }
    }

    public static class AlphanumericFilter implements InputFilter {
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
            for (int i = start; i < end; i++) {
                if (!Character.isLetterOrDigit(source.charAt(i))) {
                    return "";
                }
            }
            return null;
        }
    }
} public abstract class DataProvider {
    public final static String ERROR_NOT_AUTHENTICATED = "ERROR_NOT_AUTHENTICATED";
    public final static String ERROR_CONSENT_REQUIRED = "ERROR_CONSENT_REQUIRED";

    private static DataProvider instance;

    /**
     * Default Constructor
     */
    public DataProvider() {
    }

    /**
     * Returns the singleton instance of this class.
     *
     * @return the singleton instance of this class
     */
    public static DataProvider getInstance() {
        if (instance == null) {
            throw new RuntimeException(
                    "DataProvider instance is null. Make sure to init a concrete implementation of ResearchStack in Application.onCreate()");
        }

        return instance;
    }

    /**
     * Initializes the DataProvider singleton. It is best to call this method inside your {@link
     * Application#onCreate()} method.
     *
     * @param instance an implementation of DataProvider
     */
    public static void init(DataProvider instance) {
        DataProvider.instance = instance;
    }

    /**
     * Called in {@link SplashActivity} to initialize the state of the app. The state includes if
     * the user is not signed in/up, not consented, etc..
     *
     * @param context android context
     * @return Observable of the result of the method, with {@link DataResponse#isSuccess()}
     * returning true if signUp was successful
     */
    public abstract Observable<DataResponse> initialize(Context context);

    /**
     * Called in {@link SignUpStepLayout} to sign the user up to the backend service
     *
     * @param context android context
     * @return Observable of the result of the method, with {@link DataResponse#isSuccess()}
     * returning true if signUp was successful
     */
    public abstract Observable<DataResponse> signUp(Context context, String email, String username, String password);

    /**
     * Called in {@link SignUpStepLayout} to sign the user in to the backend service
     *
     * @param context android context
     * @return Observable of the result of the method, with {@link DataResponse#isSuccess()}
     * returning true if signIn was successful
     */
    public abstract Observable<DataResponse> signIn(Context context, String username, String password);

    /**
     * Currently not used within the framework
     *
     * @param context android context
     * @return Observable of the result of the method, with {@link DataResponse#isSuccess()}
     * returning true if signOut was successful
     */
    public abstract Observable<DataResponse> signOut(Context context);

    /**
     * Called in {@link EmailVerificationActivity} to alert the backend to resend a vertification
     * email
     *
     * @param context android context
     * @return Observable of the result of the method, with {@link DataResponse#isSuccess()}
     * returning true if signIn was successful
     */
    public abstract Observable<DataResponse> resendEmailVerification(Context context, String email);

    /**
     * Returns true if user is currently signed up
     *
     * @param context android context
     * @return true if user is signed up
     */
    public abstract boolean isSignedUp(Context context);

    /**
     * Returns true if user is currently signed in
     *
     * @param context android context
     * @return true if user is signed in
     */
    public abstract boolean isSignedIn(Context context);

    /**
     * Returns true if user is currently consented to the study
     *
     * @param context android context
     * @return true if user is currently consented
     */
    public abstract boolean isConsented(Context context);

    /**
     * Called in {@link SettingsFragment} to alert the backend that the user wants to withdraw from
     * the study
     *
     * @param context android context
     * @return Observable of the result of the method, with {@link DataResponse#isSuccess()}
     * returning true if withdrawl was successful
     */
    public abstract Observable<DataResponse> withdrawConsent(Context context, String reason);

    /**
     * This method is responsible in uploading the user consent information (e.g. Name, Birthdate,
     * Signature) to the backend
     *
     * @param context android context
     */
    public abstract void uploadConsent(Context context, TaskResult consentResult);

    /**
     * This method is responsible in saving user consent information (e.g. Name, Birthdate,
     * Signature) locally.
     * <p>
     * Please use {@link FileAccess} class to encrypt user information when saving.
     *
     * @param context android context
     */
    public abstract void saveConsent(Context context, TaskResult consentResult);

    /**
     * Returns the user object that contains any sort of information. This information can be
     * collected in the inital survey and sorted using this object
     *
     * @param context android context
     * @return User object
     */
    public abstract User getUser(Context context);

    /**
     * Gets the current sharing scope of the user.
     * <p>
     * This scope can be: <li>sponsors_and_partners</li> <li>all_quali5dfied_researchers</li>
     *
     * @param context android context
     * @return the sharing scope of the user
     */
    public abstract String getUserSharingScope(Context context);

    /**
     * Sets the current sharing scope of the user. This method should tell the backend that the
     * sharing scope has changed.
     *
     * @param context android context
     * @param scope   the new sharing scope of the user
     */
    public abstract void setUserSharingScope(Context context, String scope);

    /**
     * Returns the email that the user used to sign in / up
     *
     * @param context android context
     * @return return the email that the user used to sign in/up
     */
    public abstract String getUserEmail(Context context);

    /**
     * Method used to upload a TaskResult to the backend
     *
     * @param context    android context
     * @param taskResult TaskResult object to upload
     */
    public abstract void uploadTaskResult(Context context, TaskResult taskResult);

    /**
     * Loads the SchedulesAndTasksModel object, this should be used in conjunction with the {@link
     * ResourceManager} if inflating the TaskAndSchedules object from assets folder
     *
     * @param context android context
     * @return a SchedulesAndTasksModel object
     */
    public abstract SchedulesAndTasksModel loadTasksAndSchedules(Context context);

    /**
     * Loads a Task object, this should be used in conjunction with the {@link ResourceManager} if
     * inflating a Task from assets folder
     *
     * @param context android context
     * @param task    the TaskScheduleModel model
     * @return a Task object with defined sub-steps
     */
    public abstract Task loadTask(Context context, SchedulesAndTasksModel.TaskScheduleModel task);

    /**
     * This initial task may include profile items such as height and weight that may need to be
     * processed differently than a normal task result.
     * <p>
     * This task is presented to the user when initially entering the {@link MainActivity}
     *
     * @param context    android context
     * @param taskResult initial TaskResult object to process
     */
    public abstract void processInitialTaskResult(Context context, TaskResult taskResult);

    /**
     * Called during sign-in if the user has forgotten their password. This should make a network
     * call to notify the backend.
     *
     * @param context android context
     * @param email   email of the user
     * @return Observable of the result of the method, with {@link DataResponse#isSuccess()}
     * returning true if forgitpassword request was successful
     */
    public abstract Observable<DataResponse> forgotPassword(Context context, String email);
} public abstract class PermissionRequestManager {
    /**
     * Request code for handling a system permission on 6.0+
     */
    public static final int PERMISSION_REQUEST_CODE = 142;

    private static PermissionRequestManager instance;
    private Map<String, PermissionRequest> permissionRequests = new HashMap<>();

    /**
     * Initializes the UiManager singleton. It is best to call this method inside your {@link
     * Application#onCreate()} method.
     *
     * @param manager an implementation of ResourcePathManager
     */
    public static void init(PermissionRequestManager manager) {
        PermissionRequestManager.instance = manager;
    }

    /**
     * Returns a singleton static instance of the this class
     *
     * @return A singleton static instance of the this class
     */
    public static PermissionRequestManager getInstance() {
        if (instance == null) {
            throw new RuntimeException(
                    "PermissionRequestManager instance is null. Make sure to init a concrete implementation of ResearchStack in Application.onCreate()");
        }

        return instance;
    }

    /**
     * Returns a list of permission requests
     *
     * @return list of permission requests
     */
    public List<PermissionRequest> getPermissionRequests() {
        return new ArrayList<>(permissionRequests.values());
    }

    /**
     * Clears the internal map where PermissionRequests are stored and adds the requests that are
     * passed in
     *
     * @param permissionRequests list of permission requests
     */
    public void setPermissionRequests(List<PermissionRequest> permissionRequests) {
        this.permissionRequests.clear();
        addPermissions(permissionRequests);
    }

    /**
     * Bulk add of PermissionRequests
     *
     * @param permissionRequests collection of PermissionRequests
     */
    public void addPermissions(Collection<PermissionRequest> permissionRequests) {
        for (PermissionRequest permissionRequest : permissionRequests) {
            addPermission(permissionRequest);
        }
    }

    /**
     * Bulk add of PermissionRequests
     *
     * @param permissionRequests array of PermissionRequests
     */
    public void addPermissions(PermissionRequest... permissionRequests) {
        for (PermissionRequest permissionRequest : permissionRequests) {
            addPermission(permissionRequest);
        }
    }

    /**
     * Adds a PermissionRequest to the internal storage map. The id of the PermissionRequest is used
     * as the key and PermissionRequest object is the value
     *
     * @param permissionRequest the permission you seek to be granted
     */
    public void addPermission(PermissionRequest permissionRequest) {
        this.permissionRequests.put(permissionRequest.getId(), permissionRequest);
    }

    /**
     * Checks if the permission exists in the internal map and returns true if the permission is not
     * handled by the system. This property is during the creation of a PermissionRequest object
     *
     * @param permissionId the ID of the PermissionRequest
     * @return true if the permission is a non-system permission. This means that its a permission
     * that is not handled by the system grant flow.
     */
    public boolean isNonSystemPermission(String permissionId) {
        return !permissionRequests.isEmpty() &&
                permissionRequests.get(permissionId) != null &&
                !permissionRequests.get(permissionId).isSystemPermission();
    }

    /**
     * Method is used to check if a permission has been granted for a specific permission. This
     * method needs to be able to handle both system and non-system permissions. This can be done by
     * using {@link ContextCompat#checkSelfPermission} for system permissions.
     *
     * @param context      android context
     * @param permissionId the ID of the PermissionRequest
     * @return true if the permission is granted
     */
    public abstract boolean hasPermission(Context context, String permissionId);

    /**
     * This method is called when a non-system permission needs to go through a grant flow. You
     * should be starting an activity using the Activity param passed through and start it via
     * {@link Activity#startActivityForResult(Intent, int)}
     *
     * @param activity the calling activity for when you request a non-system permission
     * @param id       the id of the permission
     */
    public abstract void onRequestNonSystemPermission(Activity activity, String id);

    /**
     * This method is called for handling the result of a non-system permission.
     *
     * @param activity    the receiving activity of the permission result
     * @param requestCode the request code from {@link Activity#onActivityResult(int, int, Intent)}
     * @param resultCode  the result code from {@link Activity#onActivityResult(int, int, Intent)}
     * @param data        the intent from {@link Activity#onActivityResult(int, int, Intent)}
     * @return true if the result was from a permission permission flow was handled
     */
    public abstract boolean onNonSystemPermissionResult(Activity activity, int requestCode, int resultCode, Intent data);

    /**
     * Class represents a permission that the user needs to grant
     */
    public static class PermissionRequest {
        private String id;

        private int iconRes;

        private int titleRes;

        private int textRes;

        private boolean blocking;

        private boolean system;

        /**
         * Default constructor to initialize a PermissinoRequest
         *
         * @param id       the id of the request
         * @param iconRes  the icon used to display a UI in the {@link PermissionStepLayout}
         * @param titleRes the title used to display a UI in the {@link PermissionStepLayout}
         * @param textRes  the explanation used to display a UI in the {@link PermissionStepLayout}
         */
        public PermissionRequest(String id, @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int textRes) {
            this.id = id;
            this.iconRes = iconRes;
            this.titleRes = titleRes;
            this.textRes = textRes;
        }

        /**
         * Returns the id of the permission. If this is a system permission then it should originate
         * from {@link Manifest.permission}
         *
         * @return id of the permission
         */
        public String getId() {
            return id;
        }

        /**
         * Icon of the permission used when displaying UI in the {@link PermissionStepLayout}
         *
         * @return drawable resource id
         */
        @DrawableRes
        public int getIcon() {
            return iconRes;
        }

        /**
         * Title of the permission used when displaying UI in the {@link PermissionStepLayout}
         *
         * @return string resource id
         */
        @StringRes
        public int getTitle() {
            return titleRes;
        }

        /**
         * The explanation used when displaying UI in the {@link PermissionStepLayout}
         *
         * @return string resource id
         */
        @StringRes
        public int getText() {
            return textRes;
        }

        /**
         * This paramter is used in the {@link PermissionStepLayout} that makes a permission
         * optional or not. The user will not be allowed past the step if there are still some
         * permissions havent been granted which this property set to true
         *
         * @param blocking true if the user needs to take action on this permission
         */
        public void setIsBlockingPermission(boolean blocking) {
            this.blocking = blocking;
        }

        /**
         * Returns true if this permissino is blocking
         *
         * @return true if the permission needs to be granted in {@link PermissionStepLayout}
         */
        public boolean isBlockingPermission() {
            return blocking;
        }

        /**
         * Sets to true if the permission should be handled by the system grant flow
         *
         * @param system true if this is a system permission
         */
        public void setIsSystemPermission(boolean system) {
            this.system = system;
        }

        /**
         * Returns true if PermissionRequest should be handled by the system grant flow
         *
         * @return true if PermissionRequest should be handled by the system grant flow
         */
        public boolean isSystemPermission() {
            return system;
        }
    }

} public abstract class TaskProvider {
    /**
     * Task ID used by the framework for looking up the "initial" task
     */
    public static final String TASK_ID_INITIAL = "TaskProvider.TASK_ID_INITIAL";

    /**
     * Task ID used by the framework for looking up the consent task
     */
    public static final String TASK_ID_CONSENT = "TaskProvider.TASK_ID_CONSENT";


    /**
     * Task ID used by the framework for looking up the sign-in task
     */
    public static final String TASK_ID_SIGN_IN = "TaskProvider.TASK_ID_SIGN_IN";

    /**
     * Task ID used by the framework for looking up the sign-up task
     */
    public static final String TASK_ID_SIGN_UP = "TaskProvider.TASK_ID_SIGN_UP";

    private static TaskProvider instance;

    /**
     * Initializes the TaskProvider singleton. It is best to call this method inside your {@link
     * Application#onCreate()} method.
     *
     * @param manager an implementation of ResourcePathManager
     */
    public static void init(TaskProvider manager) {
        TaskProvider.instance = manager;
    }

    /**
     * Returns a singleton static instance of the this class
     *
     * @return A singleton static instance of the this class
     */
    public static TaskProvider getInstance() {
        if (instance == null) {
            throw new RuntimeException(
                    "TaskProvider instance is null. Make sure to init a concrete implementation of ResearchStack in Application.onCreate()");
        }

        return instance;
    }

    /**
     * Used, in combination of {@link #put(String, Task)}, for task lookup and resuse
     *
     * @param taskId the task id
     * @return a task object with an id of {@param taskId}
     */
    public abstract Task get(String taskId);

    /**
     * Used to store a task object for reuse
     *
     * @param id   the task id
     * @param task the task object
     */
    public abstract void put(String id, Task task);

} public abstract class OnboardingTask extends Task {
    public static final String SignUpInclusionCriteriaStepIdentifier = "InclusionCriteria";
    public static final String SignUpEligibleStepIdentifier = "Eligible";
    public static final String SignUpIneligibleStepIdentifier = "Ineligible";
    public static final String SignUpPassCodeCreationStepIdentifier = "PassCodeCreation";
    public static final String SignUpThankYouStepIdentifier = "ThankYou";
    public static final String SignInStepIdentifier = "SignIn";
    public static final String SignUpStepIdentifier = "SignUp";
    //    public static final String SignUpGeneralInfoStepIdentifier        = "GeneralInfo";
    //    public static final String SignUpMedicalInfoStepIdentifier        = "MedicalInfo";
    //    public static final String SignUpCustomInfoStepIdentifier         = "CustomInfo";
    public static final String SignUpPermissionsStepIdentifier = "Permissions";
    //    public static final String SignUpPermissionsPrimingStepIdentifier = "PermissionsPriming";

    @Deprecated
    protected int currentStepNumber;
    private Step eligibleStep;
    private Step ineligibleStep;
    private PassCodeCreationStep passcodeCreationStep;
    private Step signUpStep;
    private Step permissionsPrimingStep;
    private Step generalInfoStep;
    private Step medicalInfoStep;
    private Step customInfoStep;
    private Step permissionsStep;
    private Step thankyouStep;
    private Step signInStep;
    private boolean eligible = true;
    private boolean customStepIncluded = false;
    private boolean permissionScreenSkipped = false;


    public OnboardingTask(String identifier) {
        super(identifier);
    }

    @Override
    public Step getStepWithIdentifier(String identifier) {
        return null;
    }

    @Override
    public TaskProgress getProgressOfCurrentStep(Step step, TaskResult result) {
        return null;
    }

    @Override
    public void validateParameters() {

    }

    public boolean isEligible(TaskResult result) {
        StepResult<Boolean> stepResult = (StepResult<Boolean>) result.getStepResult(SignUpTask.SignUpInclusionCriteriaStepIdentifier);

        if (stepResult != null) {
            return stepResult.getResult();
        }

        return false;
    }

    public Step getSignInStep() {
        if (signInStep == null) {
            signInStep = new Step(SignInStepIdentifier);
            signInStep.setStepTitle(R.string.rss_sign_in);
            signInStep.setStepLayoutClass(SignInStepLayout.class);
        }
        return signInStep;
    }

    public Step getThankyouStep() {
        if (thankyouStep == null) {
            thankyouStep = new Step(SignUpThankYouStepIdentifier);
            thankyouStep.setStepTitle(R.string.rss_thank_you);
            thankyouStep.setStepLayoutClass(NotImplementedStepBody.class);
            //            thankyouStep.setStepLayoutClass(SignUpThankYouStepLayout.class);
        }
        return thankyouStep;
    }

    public Step getIneligibleStep() {
        if (ineligibleStep == null) {
            ineligibleStep = new Step(SignUpIneligibleStepIdentifier);
            ineligibleStep.setStepTitle(R.string.rss_eligibility);
            ineligibleStep.setStepLayoutClass(SignUpIneligibleStepLayout.class);
        }
        return ineligibleStep;
    }

    public Step getEligibleStep() {
        if (eligibleStep == null) {
            eligibleStep = new Step(SignUpEligibleStepIdentifier);
            eligibleStep.setStepTitle(R.string.rss_eligibility);
            eligibleStep.setStepLayoutClass(SignUpEligibleStepLayout.class);
        }
        return eligibleStep;
    }

    public Step getPassCodeCreationStep() {
        if (passcodeCreationStep == null) {
            passcodeCreationStep = new PassCodeCreationStep(SignUpPassCodeCreationStepIdentifier,
                    R.string.rss_passcode);
        }
        return passcodeCreationStep;
    }

    public Step getSignUpStep() {
        if (signUpStep == null) {
            signUpStep = new Step(SignUpStepIdentifier);
            signUpStep.setStepTitle(R.string.rss_sign_up);
            signUpStep.setStepLayoutClass(SignUpStepLayout.class);
        }
        return signUpStep;
    }

    public Step getPermissionStep() {
        if (permissionsStep == null) {
            permissionsStep = new Step(SignUpPermissionsStepIdentifier);
            permissionsStep.setStepTitle(R.string.rss_permissions);
            permissionsStep.setStepLayoutClass(PermissionStepLayout.class);
        }
        return permissionsStep;
    }
} public class SignUpEligibleStepLayout extends RelativeLayout implements StepLayout {

    public static final int CONSENT_REQUEST = 1001;

    private ActivityCallback permissionCallback;
    private Step step;
    private StepResult result;
    private StepCallbacks callbacks;

    public SignUpEligibleStepLayout(Context context) {
        super(context);
    }

    public SignUpEligibleStepLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SignUpEligibleStepLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void initialize(Step step, StepResult result) {
        this.step = step;
        this.result = result;

        if (getContext() instanceof ActivityCallback) {
            permissionCallback = (ActivityCallback) getContext();
        }

        initializeStep();
    }

    private void initializeStep() {
        LayoutInflater.from(getContext()).inflate(R.layout.rss_layout_eligible, this, true);

        SubmitBar submitBar = (SubmitBar) findViewById(R.id.submit_bar);
        submitBar.setPositiveAction((v) -> startConsentActivity());
        submitBar.getNegativeActionView().setVisibility(GONE);
    }

    private void startConsentActivity() {
        permissionCallback.startConsentTask();
    }

    private void exitSignUpActivity() {
        callbacks.onSaveStep(StepCallbacks.ACTION_END, step, null);
    }

    @Override
    public View getLayout() {
        return this;
    }

    @Override
    public boolean isBackEventConsumed() {
        callbacks.onSaveStep(StepCallbacks.ACTION_PREV, step, null);
        return false;
    }

    @Override
    public void setCallbacks(StepCallbacks callbacks) {
        this.callbacks = callbacks;
    }
}
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import org.researchstack.backbone.StorageAccess;
import org.researchstack.backbone.result.StepResult;
import org.researchstack.backbone.result.TaskResult;
import org.researchstack.backbone.step.Step;
import org.researchstack.backbone.task.Task;
import org.researchstack.backbone.ui.ViewTaskActivity;
import org.researchstack.backbone.ui.callbacks.ActivityCallback;
import org.researchstack.backbone.ui.step.layout.StepLayout;
import org.researchstack.backbone.ui.step.layout.StepPermissionRequest;
import org.researchstack.backbone.utils.TextUtils;
import org.researchstack.skin.DataProvider;
import org.researchstack.skin.PermissionRequestManager;
import org.researchstack.skin.R;
import org.researchstack.skin.TaskProvider;
import org.researchstack.skin.task.OnboardingTask;
import org.researchstack.skin.ui.layout.SignUpEligibleStepLayout;
package org.researchstack.skin.ui;



public class SignUpTaskActivity extends ViewTaskActivity implements ActivityCallback {

    TaskResult consentResult;

    public static Intent newIntent(Context context, Task task) {
        Intent intent = new Intent(context, SignUpTaskActivity.class);
        intent.putExtra(EXTRA_TASK, task);
        return intent;
    }

    @Override
    public void onDataAuth() {
        if (StorageAccess.getInstance().hasPinCode(this)) {
            super.onDataAuth();
        } else // allow signup/in if no pincode
        {
            onDataReady();
        }
    }

    @Override
    public void onSaveStep(int action, Step step, StepResult result) {
        // Save result to task
        onSaveStepResult(step.getIdentifier(), result);

        // Save Pin to disk, then save our consent info
        if (action == ACTION_NEXT &&
                step.getIdentifier().equals(OnboardingTask.SignUpPassCodeCreationStepIdentifier)) {
            String pin = (String) result.getResult();
            if (!TextUtils.isEmpty(pin)) {
                StorageAccess.getInstance().createPinCode(this, pin);
            }

            if (consentResult != null) {
                saveConsentResultInfo();
            }
        }

        // Show next step
        onExecuteStepAction(action);
    }

    @Override
    public void startConsentTask() {
        Intent intent = ConsentTaskActivity.newIntent(this,
                TaskProvider.getInstance().get(TaskProvider.TASK_ID_CONSENT));
        startActivityForResult(intent, SignUpEligibleStepLayout.CONSENT_REQUEST);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == SignUpEligibleStepLayout.CONSENT_REQUEST) {
            // User has passed through the entire consent flow
            if (resultCode == Activity.RESULT_OK) {
                consentResult = (TaskResult) data.getSerializableExtra(ViewTaskActivity.EXTRA_TASK_RESULT);

                // If they've already created a pincode, save consent, otherwise go to pin creation

                if (StorageAccess.getInstance().hasPinCode(this)) {
                    saveConsentResultInfo();
                }

                if (getCurrentStep().getIdentifier()
                        .equals(OnboardingTask.SignUpEligibleStepIdentifier)) {
                    showNextStep();
                }
            }

            // User has exited
            else {
                finish();
            }
        } else if (PermissionRequestManager.getInstance()
                .onNonSystemPermissionResult(this, requestCode, resultCode, data)) {
            updateStepLayoutForPermission();
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }

    }

    private void saveConsentResultInfo() {
        DataProvider.getInstance().saveConsent(this, consentResult);
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public void onRequestPermission(String id) {
        if (PermissionRequestManager.getInstance().isNonSystemPermission(id)) {
            PermissionRequestManager.getInstance().onRequestNonSystemPermission(this, id);
        } else {
            requestPermissions(new String[]{id}, PermissionRequestManager.PERMISSION_REQUEST_CODE);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == PermissionRequestManager.PERMISSION_REQUEST_CODE) {
            updateStepLayoutForPermission();
        }
    }

    private void updateStepLayoutForPermission() {
        StepLayout stepLayout = (StepLayout) findViewById(R.id.rsb_current_step);