org.benetech.secureapp.activities.FormGroupActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.benetech.secureapp.activities.FormGroupActivity.java

Source

/*
 * The Martus(tm) free, social justice documentation and
 * monitoring software. Copyright (C) 2016, Beneficent
 * Technology, Inc. (Benetech).
 *
 * Martus is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later
 * version with the additions and exceptions described in the
 * accompanying Martus license file entitled "license.txt".
 *
 * It is distributed WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, including warranties of fitness of purpose or
 * merchantability.  See the accompanying Martus License and
 * GPL license for more details on the required license terms
 * for this software.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */

package org.benetech.secureapp.activities;

import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import org.benetech.secureapp.R;
import org.benetech.secureapp.application.MainApplication;
import org.benetech.secureapp.collect.io.SecureFileStorageManager;
import org.benetech.secureapp.collect.tasks.SecureSaveToDiskTask;
import org.javarosa.core.model.FormDef;
import org.javarosa.core.model.FormIndex;
import org.javarosa.core.model.IFormElement;
import org.javarosa.form.api.FormEntryCaption;
import org.javarosa.form.api.FormEntryController;
import org.javarosa.form.api.FormEntryPrompt;
import org.martus.android.library.common.dialog.ProgressDialogHandler;
import org.odk.collect.android.activities.FormHierarchyActivity;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.exception.JavaRosaException;
import org.odk.collect.android.logic.FormController;
import org.odk.collect.android.logic.HierarchyElement;
import org.odk.collect.android.provider.FormsProviderAPI;
import org.odk.collect.android.provider.InstanceProviderAPI;

import java.util.ArrayList;
import java.util.List;

public class FormGroupActivity extends FormHierarchyActivity {
    private static final String TAG = "FormGroupActivity";

    protected static final int GROUP = 555;

    private EditText mFormTitle;
    private boolean mFormTitleNeedsSaving;
    private ProgressDialogHandler mProgressDialogHandler;

    /** Every time the Form Title EditText is changed mFormTitleNeedsSaving is set true.
     *
     * Both the IME_ACTION on mFormTitle (Keyboard "Done") and {@link #onPause()} will check
     * if mFormTitleNeedsSaving is true at their invocation, calling {@link #saveFormTitle()}
     * if appropriate.
     */
    private TextWatcher mFormTitleWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            mFormTitleNeedsSaving = true;
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Enable "Up" Navigation
        if (getActionBar() != null) {
            getActionBar().setDisplayShowHomeEnabled(true);
            getActionBar().setDisplayHomeAsUpEnabled(false);
        }
        // Note that calling setContentView after super.onCreate
        // invalidates any View references made there.
        setContentView(R.layout.form_group_layout);
        mFormTitle = (EditText) findViewById(R.id.formTitle);

        setFormTitle();
        addFormFieldEditorActionListener(mFormTitle);

        this.mFormTitle.addTextChangedListener(mFormTitleWatcher);

        mProgressDialogHandler = new ProgressDialogHandler(this);
    }

    private void addFormFieldEditorActionListener(EditText formField) {
        formField.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (mFormTitleNeedsSaving)
                    saveFormTitle();

                return false;
            }
        });
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mFormTitleNeedsSaving)
            saveFormTitle();
    }

    /**
     * Updates the UI with the Form title
     */
    private void setFormTitle() {
        String instanceTitle = Util.getFormInstanceTitle(this);
        if (isValidValue(instanceTitle)) {
            mFormTitle.setText(instanceTitle);
            setTitle(instanceTitle);
        } else {
            // For ActionBar use Form title if no instance available
            setTitle(Collect.getInstance().getFormController().getFormTitle());
        }
    }

    private boolean isValidValue(String value) {
        return value != null && value.length() > 0;
    }

    private void saveFormTitle() {
        mFormTitleNeedsSaving = false;
        if (isDatabaseEmpty())
            insertNewRow();
        else
            updateExistingRow();
    }

    private boolean isDatabaseEmpty() {
        Cursor cursor = null;
        try {
            cursor = Collect.getInstance().getContentResolver().query(
                    InstanceProviderAPI.InstanceColumns.CONTENT_URI, null,
                    InstanceProviderAPI.InstanceColumns.INSTANCE_FILE_PATH + "=?",
                    new String[] { getInstancePath(), }, null);
            if (cursor == null)
                return true;

            cursor.moveToFirst();
            return cursor.getCount() == 0;
        } finally {
            cursor.close();
        }
    }

    private String getInstancePath() {
        return Collect.getInstance().getFormController().getInstancePath().getAbsolutePath();
    }

    private void updateExistingRow() {
        String path = getInstancePath();
        ContentValues updatedValues = new ContentValues();
        updatedValues.put(InstanceProviderAPI.InstanceColumns.DISPLAY_NAME, mFormTitle.getText().toString());

        getContentResolver().update(InstanceProviderAPI.InstanceColumns.CONTENT_URI, updatedValues,
                InstanceProviderAPI.InstanceColumns.INSTANCE_FILE_PATH + "=?", new String[] { path, });
    }

    private void insertNewRow() {
        final Uri contentUri = FormsProviderAPI.FormsColumns.CONTENT_URI;
        Cursor cursor = Collect.getInstance().getContentResolver().query(contentUri, null, null, null, null);
        cursor.moveToFirst();
        String jrformid = cursor.getString(cursor.getColumnIndex(FormsProviderAPI.FormsColumns.JR_FORM_ID));
        String jrversion = cursor.getString(cursor.getColumnIndex(FormsProviderAPI.FormsColumns.JR_VERSION));
        String submissionUri = null;
        if (!cursor.isNull(cursor.getColumnIndex(FormsProviderAPI.FormsColumns.SUBMISSION_URI))) {
            submissionUri = cursor.getString(cursor.getColumnIndex(FormsProviderAPI.FormsColumns.SUBMISSION_URI));
        }

        String path = getInstancePath();
        ContentValues values = new ContentValues();
        values.put(InstanceProviderAPI.InstanceColumns.DISPLAY_NAME, mFormTitle.getText().toString());

        values.put(InstanceProviderAPI.InstanceColumns.INSTANCE_FILE_PATH, path);
        values.put(InstanceProviderAPI.InstanceColumns.JR_FORM_ID, jrformid);
        values.put(InstanceProviderAPI.InstanceColumns.JR_VERSION, jrversion);
        values.put(InstanceProviderAPI.InstanceColumns.SUBMISSION_URI, submissionUri);
        getContentResolver().insert(InstanceProviderAPI.InstanceColumns.CONTENT_URI, values);
        cursor.close();

        saveNewForm(contentUri);
    }

    private void saveNewForm(Uri contentUri) {
        final SecureFileStorageManager mountedSecureStorage = ((MainApplication) getApplication())
                .getMountedSecureStorage();
        final String formTitle = mFormTitle.getText().toString();
        SecureSaveToDiskTask saveTask = new SecureSaveToDiskTask(contentUri, mountedSecureStorage, false, false,
                formTitle);
        saveTask.execute();
    }

    /**
     * Override refreshView to only iterate over groups, and to not step within groups
     */
    @Override
    public void refreshView() {
        try {
            FormController formController = Collect.getInstance().getFormController();
            // Record the current index so we can return to the same place if the user hits 'back'.
            currentIndex = formController.getFormIndex();

            // If we're not at the first level, we're inside a repeated group so we want to only display
            // everything enclosed within that group.
            String contextGroupRef = "";
            formList = new ArrayList();

            // We always want to go back to the beginning of group page.  Group page only displays group fields.
            formController.jumpToIndex(FormIndex.createBeginningOfFormIndex());

            int event = formController.getEvent();
            if (event == FormEntryController.EVENT_BEGINNING_OF_FORM) {
                // The beginning of form has no valid prompt to display.
                formController.stepToNextEvent(FormController.STEP_OVER_GROUP);
                contextGroupRef = formController.getFormIndex().getReference().getParentRef().toString(true);
                mPath.setVisibility(View.GONE);
                jumpPreviousButton.setEnabled(false);
            } else {
                mPath.setVisibility(View.VISIBLE);
                mPath.setText(getCurrentPath());
                jumpPreviousButton.setEnabled(true);
            }

            // Refresh the current event in case we did step forward.
            event = formController.getEvent();

            // Big change from prior implementation:
            //
            // The ref strings now include the instance number designations
            // i.e., [0], [1], etc. of the repeat groups (and also [1] for
            // non-repeat elements).
            //
            // The contextGroupRef is now also valid for the top-level form.
            //
            // The repeatGroupRef is null if we are not skipping a repeat
            // section.
            //
            String repeatGroupRef = null;
            FormDef formDef = formController.getFormDef();
            List<IFormElement> childrenOfRoot = formDef.getChildren();
            event_search: while (event != FormEntryController.EVENT_END_OF_FORM) {

                // get the ref to this element
                String currentRef = formController.getFormIndex().getReference().toString(true);

                // retrieve the current group
                String currentGroup = (repeatGroupRef == null) ? contextGroupRef : repeatGroupRef;

                if (!currentRef.startsWith(currentGroup)) {
                    // We have left the current group
                    if (repeatGroupRef == null) {
                        // We are done.
                        break event_search;
                    } else {
                        // exit the inner repeat group
                        repeatGroupRef = null;
                    }
                }

                if (repeatGroupRef != null) {
                    // We're in a repeat group within the one we want to list
                    // skip this question/group/repeat and move to the next index.
                    event = formController.stepToNextEvent(FormController.STEP_OVER_GROUP);
                    continue;
                }

                switch (event) {
                case FormEntryController.EVENT_QUESTION:
                    FormEntryPrompt formEntryPrompt = formController.getQuestionPrompt();
                    IFormElement formElement = formEntryPrompt.getFormElement();
                    boolean isOutsideOfAGroup = childrenOfRoot.contains(formElement);
                    if (isOutsideOfAGroup) {
                        String questionLabel = formEntryPrompt.getLongText();
                        if (!formEntryPrompt.isReadOnly()
                                || (questionLabel != null && questionLabel.length() > 0)) {
                            // show the question if it is an editable field.
                            // or if it is read-only and the label is not blank.
                            String orphanedQuestionGroupName = getString(
                                    R.string.label_auto_generated_orphan_group_name);
                            HierarchyElement hierarchyElement = new HierarchyElement(orphanedQuestionGroupName,
                                    formEntryPrompt.getAnswerText(), null, Color.WHITE, QUESTION,
                                    formEntryPrompt.getIndex());
                            formList.add(hierarchyElement);

                            //Loop past all questions
                            while (event == (FormEntryController.EVENT_QUESTION)) {
                                event = formController.stepToNextEvent(FormController.STEP_INTO_GROUP);
                            }
                            continue;
                        }
                    }

                    break;
                case FormEntryController.EVENT_GROUP:
                    // add group events
                    FormEntryCaption fec = formController.getCaptionPrompt();
                    formList.add(new HierarchyElement(fec.getLongText(), null, null, Color.WHITE, GROUP,
                            fec.getIndex()));

                    break;
                case FormEntryController.EVENT_PROMPT_NEW_REPEAT:
                    // this would display the 'add new repeat' dialog
                    // ignore it.
                    break;
                case FormEntryController.EVENT_REPEAT:
                    FormEntryCaption fc = formController.getCaptionPrompt();
                    // push this repeat onto the stack.
                    repeatGroupRef = currentRef;
                    // Because of the guard conditions above, we will skip
                    // everything until we exit this repeat.
                    //
                    // Note that currentRef includes the multiplicity of the
                    // repeat (e.g., [0], [1], ...), so every repeat will be
                    // detected as different and reach this case statement.
                    // Only the [0] emits the repeat header.
                    // Every one displays the descend-into action element.

                    if (fc.getMultiplicity() == 0) {
                        // Display the repeat header for the group.
                        HierarchyElement group = new HierarchyElement(fc.getLongText(), null,
                                getResources()
                                        .getDrawable(org.odk.collect.android.R.drawable.expander_ic_minimized),
                                Color.WHITE, COLLAPSED, fc.getIndex());
                        formList.add(group);
                    }
                    // Add this group name to the drop down list for this repeating group.
                    HierarchyElement h = formList.get(formList.size() - 1);
                    h.addChild(new HierarchyElement(mIndent + fc.getLongText() + " " + (fc.getMultiplicity() + 1),
                            null, null, Color.WHITE, CHILD, fc.getIndex()));
                    break;
                }
                event = formController.stepToNextEvent(FormController.STEP_INTO_GROUP);
            }

            setAdapterItems(formList);

            // set the controller back to the current index in case the user hits 'back'
            formController.jumpToIndex(currentIndex);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
            createErrorDialog(e.getMessage());
        }
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        HierarchyElement h = (HierarchyElement) l.getItemAtPosition(position);
        FormIndex index = h.getFormIndex();
        if (index == null) {
            goUpLevel();
            return;
        }

        switch (h.getType()) {
        case GROUP:
        case COLLAPSED:
            Collect.getInstance().getFormController().jumpToIndex(index);
            setResult(RESULT_OK);
            goBackToMainFormEntryActivity();
            break;
        case QUESTION:
            Collect.getInstance().getActivityLogger().logInstanceAction(this, "onListItemClick", "QUESTION-JUMP",
                    index);
            Collect.getInstance().getFormController().jumpToIndex(index);
            if (Collect.getInstance().getFormController().indexIsInFieldList()) {
                try {
                    Collect.getInstance().getFormController().stepToPreviousScreenEvent();
                } catch (JavaRosaException e) {
                    Log.e(TAG, e.getMessage(), e);
                    createErrorDialog(e.getCause().getMessage());
                    return;
                }
            }
            setResult(RESULT_OK);
            goBackToMainFormEntryActivity();
            return;
        default:
            throw new IllegalStateException(
                    getString(R.string.error_message_activity_should_only_should_groups, h.getType()));
        }
    }

    private void goBackToMainFormEntryActivity() {
        Intent entryIntent = new Intent(this, MainFormEntryActivity.class);
        entryIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivity(entryIntent);
    }

    @Override
    public void onBackPressed() {
        if (mFormTitleNeedsSaving) {
            saveFormTitle();
        }

        // Go back to MainActivity
        Intent mainIntent = new Intent(this, MainActivity.class);
        mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(mainIntent);
    }

    @Override
    protected void setAdapterItems(List<HierarchyElement> elements) {
        Log.i("list", "setting array adapter");
        ArrayAdapter<HierarchyElement> adapter = new ArrayAdapter(this, R.layout.group_list_item, elements);
        setListAdapter(adapter);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        // Respond to the action bar's Up/Home button
        case android.R.id.home:
            NavUtils.navigateUpFromSameTask(this);
            return true;

        case R.id.show_version_menu_item:
            MainActivity.showVersionNumberAsToast(this);
            return true;

        case R.id.show_receiving_contact_public_key_menu_item:
            MainActivity.showReceivingContactPublicKey(this);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        getMenuInflater().inflate(R.menu.main, menu);

        return true;
    }

    private ProgressDialogHandler getProgressDialogHandler() {
        return mProgressDialogHandler;
    }
}