public static Set<File> getFormSubmissionDirectories(File formDirectory) {
    Set<File> files = new TreeSet<File>();

    File formInstancesDir = null;
    try {
      formInstancesDir = getFormInstancesDirectory(formDirectory);
    } catch (FileSystemException e) {
      e.printStackTrace();
      return files;
    }

    File[] briefcaseInstances = formInstancesDir.listFiles();
    if (briefcaseInstances != null) {
      for (File briefcaseInstance : briefcaseInstances) {
        if (!briefcaseInstance.isDirectory() || briefcaseInstance.getName().startsWith(".")) {
          logger.warn("skipping non-directory or dot-file in form instances subdirectory");
          continue;
        }
        files.add(briefcaseInstance);
      }
    }

    return files;
  }
  private static boolean decryptSubmissionFiles(
      String base64EncryptedSymmetricKey,
      FormInstanceMetadata fim,
      List<String> mediaNames,
      String encryptedSubmissionFile,
      String base64EncryptedElementSignature,
      PrivateKey rsaPrivateKey,
      File instanceDir,
      File unencryptedDir)
      throws FileSystemException, CryptoException, ParsingException {

    EncryptionInformation ei =
        new EncryptionInformation(base64EncryptedSymmetricKey, fim.instanceId, rsaPrivateKey);

    byte[] elementDigest;
    try {
      // construct the base64-encoded RSA-encrypted symmetric key
      Cipher pkCipher;
      pkCipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM);
      // extract digest
      pkCipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
      byte[] encryptedElementSignature = Base64.decodeBase64(base64EncryptedElementSignature);
      elementDigest = pkCipher.doFinal(encryptedElementSignature);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
      throw new CryptoException(
          "Error decrypting base64EncryptedElementSignature Cause: " + e.toString());
    } catch (NoSuchPaddingException e) {
      e.printStackTrace();
      throw new CryptoException(
          "Error decrypting base64EncryptedElementSignature Cause: " + e.toString());
    } catch (InvalidKeyException e) {
      e.printStackTrace();
      throw new CryptoException(
          "Error decrypting base64EncryptedElementSignature Cause: " + e.toString());
    } catch (IllegalBlockSizeException e) {
      e.printStackTrace();
      throw new CryptoException(
          "Error decrypting base64EncryptedElementSignature Cause: " + e.toString());
    } catch (BadPaddingException e) {
      e.printStackTrace();
      throw new CryptoException(
          "Error decrypting base64EncryptedElementSignature Cause: " + e.toString());
    }

    // NOTE: will decrypt only the files in the media list, plus the encryptedSubmissionFile

    File[] allFiles = instanceDir.listFiles();
    List<File> filesToProcess = new ArrayList<File>();
    for (File f : allFiles) {
      if (mediaNames.contains(f.getName())) {
        filesToProcess.add(f);
      } else if (encryptedSubmissionFile.equals(f.getName())) {
        filesToProcess.add(f);
      }
    }

    // should have all media files plus one submission.xml.enc file
    if (filesToProcess.size() != mediaNames.size() + 1) {
      // figure out what we're missing...
      int lostFileCount = 0;
      List<String> missing = new ArrayList<String>();
      for (String name : mediaNames) {
        if (name == null) {
          // this was lost due to an pre-ODK Aggregate 1.4.5 mark-as-complete action
          ++lostFileCount;
          continue;
        }
        File f = new File(instanceDir, name);
        if (!filesToProcess.contains(f)) {
          missing.add(name);
        }
      }
      StringBuilder b = new StringBuilder();
      for (String name : missing) {
        b.append(" ").append(name);
      }
      if (!filesToProcess.contains(new File(instanceDir, encryptedSubmissionFile))) {
        b.append(" ").append(encryptedSubmissionFile);
        throw new FileSystemException(
            "Error decrypting: " + instanceDir.getName() + " Missing files:" + b.toString());
      } else {
        // ignore the fact that we don't have the lost files
        if (filesToProcess.size() + lostFileCount != mediaNames.size() + 1) {
          throw new FileSystemException(
              "Error decrypting: " + instanceDir.getName() + " Missing files:" + b.toString());
        }
      }
    }

    // decrypt the media files IN ORDER.
    for (String mediaName : mediaNames) {
      String displayedName = (mediaName == null) ? "<missing .enc file>" : mediaName;
      File f = (mediaName == null) ? null : new File(instanceDir, mediaName);
      try {
        decryptFile(ei, f, unencryptedDir);
      } catch (InvalidKeyException e) {
        e.printStackTrace();
        throw new CryptoException("Error decrypting:" + displayedName + " Cause: " + e.toString());
      } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        throw new CryptoException("Error decrypting:" + displayedName + " Cause: " + e.toString());
      } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
        throw new CryptoException("Error decrypting:" + displayedName + " Cause: " + e.toString());
      } catch (NoSuchPaddingException e) {
        e.printStackTrace();
        throw new CryptoException("Error decrypting:" + displayedName + " Cause: " + e.toString());
      } catch (IOException e) {
        e.printStackTrace();
        throw new FileSystemException(
            "Error decrypting:" + displayedName + " Cause: " + e.toString());
      }
    }

    // decrypt the submission file
    File f = new File(instanceDir, encryptedSubmissionFile);
    try {
      decryptFile(ei, f, unencryptedDir);
    } catch (InvalidKeyException e) {
      e.printStackTrace();
      throw new CryptoException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
      throw new CryptoException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
    } catch (InvalidAlgorithmParameterException e) {
      e.printStackTrace();
      throw new CryptoException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
    } catch (NoSuchPaddingException e) {
      e.printStackTrace();
      throw new CryptoException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
    } catch (IOException e) {
      e.printStackTrace();
      throw new FileSystemException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
    }

    // get the FIM for the decrypted submission file
    File submissionFile =
        new File(
            unencryptedDir,
            encryptedSubmissionFile.substring(0, encryptedSubmissionFile.lastIndexOf(".enc")));

    FormInstanceMetadata submissionFim;
    try {
      Document subDoc = XmlManipulationUtils.parseXml(submissionFile);
      submissionFim = XmlManipulationUtils.getFormInstanceMetadata(subDoc.getRootElement());
    } catch (ParsingException e) {
      e.printStackTrace();
      throw new FileSystemException(
          "Error decrypting: " + submissionFile.getName() + " Cause: " + e.toString());
    } catch (FileSystemException e) {
      e.printStackTrace();
      throw new FileSystemException(
          "Error decrypting: " + submissionFile.getName() + " Cause: " + e.getMessage());
    }

    boolean same = submissionFim.xparam.formId.equals(fim.xparam.formId);

    if (!same) {
      throw new FileSystemException(
          "Error decrypting:"
              + unencryptedDir.getName()
              + " Cause: form instance metadata differs from that in manifest");
    }

    // Construct the element signature string
    StringBuilder b = new StringBuilder();
    appendElementSignatureSource(b, fim.xparam.formId);
    if (fim.xparam.modelVersion != null) {
      appendElementSignatureSource(b, Long.toString(fim.xparam.modelVersion));
    }
    appendElementSignatureSource(b, base64EncryptedSymmetricKey);

    appendElementSignatureSource(b, fim.instanceId);

    boolean missingFile = false;
    for (String encFilename : mediaNames) {
      if (encFilename == null) {
        missingFile = true;
        continue;
      }
      File decryptedFile =
          new File(unencryptedDir, encFilename.substring(0, encFilename.lastIndexOf(".enc")));
      if (decryptedFile.getName().endsWith(".missing")) {
        // this is a missing file -- we will not be able to
        // confirm the signature of the submission.
        missingFile = true;
        continue;
      }
      String md5 = FileSystemUtils.getMd5Hash(decryptedFile);
      appendElementSignatureSource(b, decryptedFile.getName() + "::" + md5);
    }

    String md5 = FileSystemUtils.getMd5Hash(submissionFile);
    appendElementSignatureSource(b, submissionFile.getName() + "::" + md5);

    // compute the digest of the element signature string
    byte[] messageDigest;
    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      md.update(b.toString().getBytes("UTF-8"));
      messageDigest = md.digest();
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
      throw new CryptoException("Error computing xml signature Cause: " + e.toString());
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
      throw new CryptoException("Error computing xml signature Cause: " + e.toString());
    }

    same = true;
    for (int i = 0; i < messageDigest.length; ++i) {
      if (messageDigest[i] != elementDigest[i]) {
        same = false;
        break;
      }
    }

    return same;
  }
  public boolean downloadFormAndSubmissionFiles(List<FormStatus> formsToTransfer) {
    boolean allSuccessful = true;

    // boolean error = false;
    int total = formsToTransfer.size();

    for (int i = 0; i < total; i++) {
      FormStatus fs = formsToTransfer.get(i);

      if (isCancelled()) {
        fs.setStatusString("aborted. Skipping fetch of form and submissions...", true);
        EventBus.publish(new FormStatusEvent(fs));
        return false;
      }

      RemoteFormDefinition fd = (RemoteFormDefinition) fs.getFormDefinition();
      fs.setStatusString("Fetching form definition", true);
      EventBus.publish(new FormStatusEvent(fs));
      try {

        File tmpdl = FileSystemUtils.getTempFormDefinitionFile();
        AggregateUtils.commonDownloadFile(serverInfo, tmpdl, fd.getDownloadUrl());

        fs.setStatusString("resolving against briefcase form definitions", true);
        EventBus.publish(new FormStatusEvent(fs));

        boolean successful = false;
        BriefcaseFormDefinition briefcaseLfd;
        DatabaseUtils formDatabase = null;
        try {
          try {
            briefcaseLfd = BriefcaseFormDefinition.resolveAgainstBriefcaseDefn(tmpdl);
            if (briefcaseLfd.needsMediaUpdate()) {

              if (fd.getManifestUrl() != null) {
                File mediaDir = FileSystemUtils.getMediaDirectory(briefcaseLfd.getFormDirectory());
                String error = downloadManifestAndMediaFiles(mediaDir, fs);
                if (error != null) {
                  allSuccessful = false;
                  fs.setStatusString("Error fetching form definition: " + error, false);
                  EventBus.publish(new FormStatusEvent(fs));
                  continue;
                }
              }
            }
            formDatabase =
                new DatabaseUtils(FileSystemUtils.getFormDatabase(briefcaseLfd.getFormDirectory()));

          } catch (BadFormDefinition e) {
            e.printStackTrace();
            allSuccessful = false;
            fs.setStatusString("Error parsing form definition: " + e.getMessage(), false);
            EventBus.publish(new FormStatusEvent(fs));
            continue;
          }

          fs.setStatusString("preparing to retrieve instance data", true);
          EventBus.publish(new FormStatusEvent(fs));

          File formInstancesDir =
              FileSystemUtils.getFormInstancesDirectory(briefcaseLfd.getFormDirectory());

          // this will publish events
          successful =
              downloadAllSubmissionsForForm(formInstancesDir, formDatabase, briefcaseLfd, fs);
        } catch (FileSystemException e) {
          e.printStackTrace();
          allSuccessful = false;
          fs.setStatusString("unable to open form database: " + e.getMessage(), false);
          EventBus.publish(new FormStatusEvent(fs));
          continue;
        } finally {
          if (formDatabase != null) {
            try {
              formDatabase.close();
            } catch (SQLException e) {
              e.printStackTrace();
              allSuccessful = false;
              fs.setStatusString("unable to close form database: " + e.getMessage(), false);
              EventBus.publish(new FormStatusEvent(fs));
              continue;
            }
          }
        }

        allSuccessful = allSuccessful && successful;

        // on success, we haven't actually set a success event (because we don't know we're done)
        if (successful) {
          fs.setStatusString("SUCCESS!", true);
          EventBus.publish(new FormStatusEvent(fs));
        } else {
          fs.setStatusString("FAILED.", true);
          EventBus.publish(new FormStatusEvent(fs));
        }

      } catch (SocketTimeoutException se) {
        se.printStackTrace();
        allSuccessful = false;
        fs.setStatusString(
            "Communications to the server timed out. Detailed message: "
                + se.getLocalizedMessage()
                + " while accessing: "
                + fd.getDownloadUrl()
                + " A network login screen may be interfering with the transmission to the server.",
            false);
        EventBus.publish(new FormStatusEvent(fs));
        continue;
      } catch (IOException e) {
        e.printStackTrace();
        allSuccessful = false;
        fs.setStatusString(
            "Unexpected error: "
                + e.getLocalizedMessage()
                + " while accessing: "
                + fd.getDownloadUrl()
                + " A network login screen may be interfering with the transmission to the server.",
            false);
        EventBus.publish(new FormStatusEvent(fs));
        continue;
      } catch (FileSystemException e) {
        e.printStackTrace();
        allSuccessful = false;
        fs.setStatusString(
            "Unexpected error: "
                + e.getLocalizedMessage()
                + " while accessing: "
                + fd.getDownloadUrl(),
            false);
        EventBus.publish(new FormStatusEvent(fs));
        continue;
      } catch (URISyntaxException e) {
        e.printStackTrace();
        allSuccessful = false;
        fs.setStatusString(
            "Unexpected error: "
                + e.getLocalizedMessage()
                + " while accessing: "
                + fd.getDownloadUrl(),
            false);
        EventBus.publish(new FormStatusEvent(fs));
        continue;
      } catch (TransmissionException e) {
        e.printStackTrace();
        allSuccessful = false;
        fs.setStatusString(
            "Unexpected error: "
                + e.getLocalizedMessage()
                + " while accessing: "
                + fd.getDownloadUrl(),
            false);
        EventBus.publish(new FormStatusEvent(fs));
        continue;
      }
    }
    return allSuccessful;
  }