@Override
  protected HashMap<String, FormDetails> doInBackground(Void... values) {
    SharedPreferences settings =
        PreferenceManager.getDefaultSharedPreferences(Collect.getInstance().getBaseContext());
    String downloadListUrl =
        settings.getString(
            PreferencesActivity.KEY_SERVER_URL,
            Collect.getInstance().getString(R.string.default_server_url));
    String downloadPath = settings.getString(PreferencesActivity.KEY_FORMLIST_URL, "/formlist");
    downloadListUrl += downloadPath;
    String auth = settings.getString(PreferencesActivity.KEY_AUTH, "");

    // We populate this with available forms from the specified server.
    // <formname, details>
    HashMap<String, FormDetails> formList = new HashMap<String, FormDetails>();

    // get shared HttpContext so that authentication and cookies are retained.
    HttpContext localContext = Collect.getInstance().getHttpContext();
    HttpClient httpclient = WebUtils.createHttpClient(WebUtils.CONNECTION_TIMEOUT);

    DocumentFetchResult result =
        WebUtils.getXmlDocument(downloadListUrl, localContext, httpclient, auth);

    // If we can't get the document, return the error, cancel the task
    if (result.errorMessage != null) {
      if (result.responseCode == 401) {
        formList.put(DL_AUTH_REQUIRED, new FormDetails(result.errorMessage));
      } else {
        formList.put(DL_ERROR_MSG, new FormDetails(result.errorMessage));
      }
      return formList;
    }

    if (result.isOpenRosaResponse) {
      // Attempt OpenRosa 1.0 parsing
      Element xformsElement = result.doc.getRootElement();
      if (!xformsElement.getName().equals("xforms")) {
        String error = "root element is not <xforms> : " + xformsElement.getName();
        Log.e(t, "Parsing OpenRosa reply -- " + error);
        formList.put(
            DL_ERROR_MSG,
            new FormDetails(
                Collect.getInstance().getString(R.string.parse_openrosa_formlist_failed, error)));
        return formList;
      }
      String namespace = xformsElement.getNamespace();
      if (!isXformsListNamespacedElement(xformsElement)) {
        String error = "root element namespace is incorrect:" + namespace;
        Log.e(t, "Parsing OpenRosa reply -- " + error);
        formList.put(
            DL_ERROR_MSG,
            new FormDetails(
                Collect.getInstance().getString(R.string.parse_openrosa_formlist_failed, error)));
        return formList;
      }
      int nElements = xformsElement.getChildCount();
      for (int i = 0; i < nElements; ++i) {
        if (xformsElement.getType(i) != Element.ELEMENT) {
          // e.g., whitespace (text)
          continue;
        }
        Element xformElement = (Element) xformsElement.getElement(i);
        if (!isXformsListNamespacedElement(xformElement)) {
          // someone else's extension?
          continue;
        }
        String name = xformElement.getName();
        if (!name.equalsIgnoreCase("xform")) {
          // someone else's extension?
          continue;
        }

        // this is something we know how to interpret
        String formId = null;
        String formName = null;
        String majorMinorVersion = null;
        String description = null;
        String downloadUrl = null;
        String manifestUrl = null;
        // don't process descriptionUrl
        int fieldCount = xformElement.getChildCount();
        for (int j = 0; j < fieldCount; ++j) {
          if (xformElement.getType(j) != Element.ELEMENT) {
            // whitespace
            continue;
          }
          Element child = xformElement.getElement(j);
          if (!isXformsListNamespacedElement(child)) {
            // someone else's extension?
            continue;
          }
          String tag = child.getName();
          if (tag.equals("formID")) {
            formId = XFormParser.getXMLText(child, true);
            if (formId != null && formId.length() == 0) {
              formId = null;
            }
          } else if (tag.equals("name")) {
            formName = XFormParser.getXMLText(child, true);
            if (formName != null && formName.length() == 0) {
              formName = null;
            }
          } else if (tag.equals("majorMinorVersion")) {
            majorMinorVersion = XFormParser.getXMLText(child, true);
            if (majorMinorVersion != null && majorMinorVersion.length() == 0) {
              majorMinorVersion = null;
            }
          } else if (tag.equals("descriptionText")) {
            description = XFormParser.getXMLText(child, true);
            if (description != null && description.length() == 0) {
              description = null;
            }
          } else if (tag.equals("downloadUrl")) {
            downloadUrl = XFormParser.getXMLText(child, true);
            if (downloadUrl != null && downloadUrl.length() == 0) {
              downloadUrl = null;
            }
          } else if (tag.equals("manifestUrl")) {
            manifestUrl = XFormParser.getXMLText(child, true);
            if (manifestUrl != null && manifestUrl.length() == 0) {
              manifestUrl = null;
            }
          }
        }
        if (formId == null || downloadUrl == null || formName == null) {
          String error =
              "Forms list entry "
                  + Integer.toString(i)
                  + " is missing one or more tags: formId, name, or downloadUrl";
          Log.e(t, "Parsing OpenRosa reply -- " + error);
          formList.clear();
          formList.put(
              DL_ERROR_MSG,
              new FormDetails(
                  Collect.getInstance().getString(R.string.parse_openrosa_formlist_failed, error)));
          return formList;
        }
        /*
         * TODO: We currently don't care about major/minor version. maybe someday we will.
         */
        // Integer modelVersion = null;
        // Integer uiVersion = null;
        // try {
        // if (majorMinorVersion == null || majorMinorVersion.length() == 0) {
        // modelVersion = null;
        // uiVersion = null;
        // } else {
        // int idx = majorMinorVersion.indexOf(".");
        // if (idx == -1) {
        // modelVersion = Integer.parseInt(majorMinorVersion);
        // uiVersion = null;
        // } else {
        // modelVersion = Integer.parseInt(majorMinorVersion.substring(0, idx));
        // uiVersion =
        // (idx == majorMinorVersion.length() - 1) ? null : Integer
        // .parseInt(majorMinorVersion.substring(idx + 1));
        // }
        // }
        // } catch (Exception e) {
        // e.printStackTrace();
        // String error = "Forms list entry " + Integer.toString(i) +
        // " has an invalid majorMinorVersion: " + majorMinorVersion;
        // Log.e(t, "Parsing OpenRosa reply -- " + error);
        // formList.clear();
        // formList.put(DL_ERROR_MSG, new FormDetails(
        // Collect.getInstance().getString(R.string.parse_openrosa_formlist_failed,
        // error)));
        // return formList;
        // }
        formList.put(formId, new FormDetails(formName, downloadUrl, manifestUrl, formId));
      }
    } else {
      // Aggregate 0.9.x mode...
      // populate HashMap with form names and urls
      Element formsElement = result.doc.getRootElement();
      int formsCount = formsElement.getChildCount();
      for (int i = 0; i < formsCount; ++i) {
        if (formsElement.getType(i) != Element.ELEMENT) {
          // whitespace
          continue;
        }
        Element child = formsElement.getElement(i);
        String tag = child.getName();
        String formId = null;
        if (tag.equals("formID")) {
          formId = XFormParser.getXMLText(child, true);
          if (formId != null && formId.length() == 0) {
            formId = null;
          }
        }
        if (tag.equalsIgnoreCase("form")) {
          String formName = XFormParser.getXMLText(child, true);
          if (formName != null && formName.length() == 0) {
            formName = null;
          }
          String downloadUrl = child.getAttributeValue(null, "url");
          downloadUrl = downloadUrl.trim();
          if (downloadUrl != null && downloadUrl.length() == 0) {
            downloadUrl = null;
          }
          if (downloadUrl == null || formName == null) {
            String error =
                "Forms list entry "
                    + Integer.toString(i)
                    + " is missing form name or url attribute";
            Log.e(t, "Parsing OpenRosa reply -- " + error);
            formList.clear();
            formList.put(
                DL_ERROR_MSG,
                new FormDetails(
                    Collect.getInstance().getString(R.string.parse_legacy_formlist_failed, error)));
            return formList;
          }
          formList.put(formName, new FormDetails(formName, downloadUrl, null, formName));
        }
      }
    }
    return formList;
  }
  public static DecryptOutcome decryptAndValidateSubmission(
      Document doc, PrivateKey rsaPrivateKey, File instanceDir, File unEncryptedDir)
      throws ParsingException, FileSystemException, CryptoException {

    Element rootElement = doc.getRootElement();

    String base64EncryptedSymmetricKey;
    String instanceIdMetadata = null;
    List<String> mediaNames = new ArrayList<String>();
    String encryptedSubmissionFile;
    String base64EncryptedElementSignature;

    {
      Element base64Key = null;
      Element base64Signature = null;
      Element encryptedXml = null;
      for (int i = 0; i < rootElement.getChildCount(); ++i) {
        if (rootElement.getType(i) == Node.ELEMENT) {
          Element child = rootElement.getElement(i);
          String name = child.getName();
          if (name.equals("base64EncryptedKey")) {
            base64Key = child;
          } else if (name.equals("base64EncryptedElementSignature")) {
            base64Signature = child;
          } else if (name.equals("encryptedXmlFile")) {
            encryptedXml = child;
          } else if (name.equals("media")) {
            Element media = child;
            for (int j = 0; j < media.getChildCount(); ++j) {
              if (media.getType(j) == Node.ELEMENT) {
                Element mediaChild = media.getElement(j);
                String mediaFileElementName = mediaChild.getName();
                if (mediaFileElementName.equals("file")) {
                  String mediaName = XFormParser.getXMLText(mediaChild, true);
                  if (mediaName == null || mediaName.length() == 0) {
                    mediaNames.add(null);
                  } else {
                    mediaNames.add(mediaName);
                  }
                }
              }
            }
          }
        }
      }

      // verify base64Key
      if (base64Key == null) {
        throw new ParsingException("Missing base64EncryptedKey element in encrypted form.");
      }
      base64EncryptedSymmetricKey = XFormParser.getXMLText(base64Key, true);

      // get instanceID out of OpenRosa meta block
      instanceIdMetadata = XmlManipulationUtils.getOpenRosaInstanceId(rootElement);
      if (instanceIdMetadata == null) {
        throw new ParsingException("Missing instanceID within meta block of encrypted form.");
      }

      // get submission filename
      if (encryptedXml == null) {
        throw new ParsingException("Missing encryptedXmlFile element in encrypted form.");
      }
      encryptedSubmissionFile = XFormParser.getXMLText(encryptedXml, true);
      if (base64Signature == null) {
        throw new ParsingException(
            "Missing base64EncryptedElementSignature element in encrypted form.");
      }
      base64EncryptedElementSignature = XFormParser.getXMLText(base64Signature, true);
    }

    if (instanceIdMetadata == null
        || base64EncryptedSymmetricKey == null
        || base64EncryptedElementSignature == null
        || encryptedSubmissionFile == null) {
      throw new ParsingException("Missing one or more required elements of encrypted form.");
    }

    FormInstanceMetadata fim;
    try {
      fim = XmlManipulationUtils.getFormInstanceMetadata(rootElement);
    } catch (ParsingException e) {
      e.printStackTrace();
      throw new ParsingException(
          "Unable to extract form instance medatadata from submission manifest. Cause: "
              + e.toString());
    }

    if (!instanceIdMetadata.equals(fim.instanceId)) {
      throw new ParsingException(
          "InstanceID within metadata does not match that on top level element.");
    }

    boolean isValidated =
        FileSystemUtils.decryptSubmissionFiles(
            base64EncryptedSymmetricKey,
            fim,
            mediaNames,
            encryptedSubmissionFile,
            base64EncryptedElementSignature,
            rsaPrivateKey,
            instanceDir,
            unEncryptedDir);

    // and change doc to be the decrypted submission document
    File decryptedSubmission = new File(unEncryptedDir, "submission.xml");
    doc = XmlManipulationUtils.parseXml(decryptedSubmission);
    if (doc == null) {
      return null;
    }

    // verify that the metadata matches between the manifest and the submission
    rootElement = doc.getRootElement();
    FormInstanceMetadata sim = XmlManipulationUtils.getFormInstanceMetadata(rootElement);
    if (!fim.xparam.equals(sim.xparam)) {
      throw new ParsingException(
          "FormId or version in decrypted submission does not match that in manifest!");
    }
    if (!fim.instanceId.equals(sim.instanceId)) {
      throw new ParsingException(
          "InstanceId in decrypted submission does not match that in manifest!");
    }

    return new DecryptOutcome(doc, isValidated);
  }