/**
   * 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());
        }
      }
    }
  }