/** * Initialize {@link FormEntryController} with {@link FormDef} from binary or from XML. If given * an instance, it will be used to fill the {@link FormDef}. */ @Override protected SaveResult doInBackground(Void... nothing) { SaveResult saveResult = new SaveResult(); FormController formController = Collect.getInstance().getFormController(); publishProgress(Collect.getInstance().getString(R.string.survey_saving_validating_message)); try { int validateStatus = formController.validateAnswers(mMarkCompleted); if (validateStatus != FormEntryController.ANSWER_OK) { // validation failed, pass specific failure saveResult.setSaveResult(validateStatus); return saveResult; } } catch (Exception e) { Log.e(t, e.getMessage(), e); // SCTO-825 // that means that we have a bad design // save the exception to be used in the error dialog. saveResult.setSaveErrorMessage(e.getMessage()); saveResult.setSaveResult(SAVE_ERROR); return saveResult; } // check if the "Cancel" was hit and exit. if (isCancelled()) { return null; } if (mMarkCompleted) { formController.postProcessInstance(); } Collect.getInstance() .getActivityLogger() .logInstanceAction(this, "save", Boolean.toString(mMarkCompleted)); // close all open databases of external data. Collect.getInstance().getExternalDataManager().close(); // if there is a meta/instanceName field, be sure we are using the latest value // just in case the validate somehow triggered an update. String updatedSaveName = formController.getSubmissionMetadata().instanceName; if (updatedSaveName != null) { mInstanceName = updatedSaveName; } try { exportData(mMarkCompleted); // attempt to remove any scratch file File shadowInstance = savepointFile(formController.getInstancePath()); if (shadowInstance.exists()) { FileUtils.deleteAndReport(shadowInstance); } saveResult.setSaveResult(mSave ? SAVED_AND_EXIT : SAVED); } catch (Exception e) { Log.e(t, e.getMessage(), e); saveResult.setSaveErrorMessage(e.getMessage()); saveResult.setSaveResult(SAVE_ERROR); } return saveResult; }
/** * Write's the data to the sdcard, and updates the instances content provider. In theory we don't * have to write to disk, and this is where you'd add other methods. * * @param markCompleted * @return */ private void exportData(boolean markCompleted) throws IOException, EncryptionException { FormController formController = Collect.getInstance().getFormController(); publishProgress(Collect.getInstance().getString(R.string.survey_saving_collecting_message)); ByteArrayPayload payload = formController.getFilledInFormXml(); // write out xml String instancePath = formController.getInstancePath().getAbsolutePath(); publishProgress(Collect.getInstance().getString(R.string.survey_saving_saving_message)); exportXmlFile(payload, instancePath); // update the mUri. We have exported the reloadable instance, so update status... // Since we saved a reloadable instance, it is flagged as re-openable so that if any error // occurs during the packaging of the data for the server fails (e.g., encryption), // we can still reopen the filled-out form and re-save it at a later time. updateInstanceDatabase(true, true); if (markCompleted) { // now see if the packaging of the data for the server would make it // non-reopenable (e.g., encryption or send an SMS or other fraction of the form). boolean canEditAfterCompleted = formController.isSubmissionEntireForm(); boolean isEncrypted = false; // build a submission.xml to hold the data being submitted // and (if appropriate) encrypt the files on the side // pay attention to the ref attribute of the submission profile... File instanceXml = formController.getInstancePath(); File submissionXml = new File(instanceXml.getParentFile(), "submission.xml"); payload = formController.getSubmissionXml(); // write out submission.xml -- the data to actually submit to aggregate publishProgress(Collect.getInstance().getString(R.string.survey_saving_finalizing_message)); exportXmlFile(payload, submissionXml.getAbsolutePath()); // see if the form is encrypted and we can encrypt it... EncryptedFormInformation formInfo = EncryptionUtils.getEncryptedFormInformation(mUri, formController.getSubmissionMetadata()); if (formInfo != null) { // if we are encrypting, the form cannot be reopened afterward canEditAfterCompleted = false; // and encrypt the submission (this is a one-way operation)... publishProgress(Collect.getInstance().getString(R.string.survey_saving_encrypting_message)); EncryptionUtils.generateEncryptedSubmission(instanceXml, submissionXml, formInfo); isEncrypted = true; } // At this point, we have: // 1. the saved original instanceXml, // 2. all the plaintext attachments // 2. the submission.xml that is the completed xml (whether encrypting or not) // 3. all the encrypted attachments if encrypting (isEncrypted = true). // // NEXT: // 1. Update the instance database (with status complete). // 2. Overwrite the instanceXml with the submission.xml // and remove the plaintext attachments if encrypting updateInstanceDatabase(false, canEditAfterCompleted); if (!canEditAfterCompleted) { // AT THIS POINT, there is no going back. We are committed // to returning "success" (true) whether or not we can // rename "submission.xml" to instanceXml and whether or // not we can delete the plaintext media files. // // Handle the fall-out for a failed "submission.xml" rename // in the InstanceUploader task. Leftover plaintext media // files are handled during form deletion. // delete the restore Xml file. if (!instanceXml.delete()) { String msg = "Error deleting " + instanceXml.getAbsolutePath() + " prior to renaming submission.xml"; Log.e(t, msg); throw new IOException(msg); } // rename the submission.xml to be the instanceXml if (!submissionXml.renameTo(instanceXml)) { String msg = "Error renaming submission.xml to " + instanceXml.getAbsolutePath(); Log.e(t, msg); throw new IOException(msg); } } else { // try to delete the submissionXml file, since it is // identical to the existing instanceXml file // (we don't need to delete and rename anything). if (!submissionXml.delete()) { String msg = "Error deleting " + submissionXml.getAbsolutePath() + " (instance is re-openable)"; Log.w(t, msg); } } // if encrypted, delete all plaintext files // (anything not named instanceXml or anything not ending in .enc) if (isEncrypted) { if (!EncryptionUtils.deletePlaintextFiles(instanceXml)) { Log.e(t, "Error deleting plaintext files for " + instanceXml.getAbsolutePath()); } } } }