From f0f927a82e8cfb148b752ff24069fd49391bfe32 Mon Sep 17 00:00:00 2001 From: Prabhav Chopra Date: Fri, 2 Jun 2023 13:01:33 +0530 Subject: [PATCH 1/5] Added network_security_config.xml, Made Changes to AndroidManifest.xml and Added an Initial-Setup Guide. --- INITIAL-SETUP.md | 252 ++++++++++++++++++ sample/src/main/AndroidManifest.xml | 1 + .../main/res/xml/network_security_config.xml | 9 + 3 files changed, 262 insertions(+) create mode 100644 INITIAL-SETUP.md create mode 100644 sample/src/main/res/xml/network_security_config.xml diff --git a/INITIAL-SETUP.md b/INITIAL-SETUP.md new file mode 100644 index 00000000000..93a2ae6cb67 --- /dev/null +++ b/INITIAL-SETUP.md @@ -0,0 +1,252 @@ +# Inital Setup + +Initial setup of the project will be divided into 2 main steps + +* Setting up the ODK Central Server on your local machine +* Setting up the Android app in Android Studio +--- +## Setting up the ODK Central Server on your local machine + +For installing the ODK Central server locally on your machine, Make sure you are running **Docker Engine v23.x** and **Docker Compose v2.16.x** or greater. + +``` +docker --version && docker compose version +``` +
+ +## Setting Up Central +
+ +* Download the software. + + ``` + git clone https://github.com/getodk/central + ``` +* Go into the new central folder: + ``` + cd central + ``` +* Get the latest client and server: + ``` + git submodule update -i + ``` +* Update settings. + ``` + cp .env.template .env + nano .env + ``` +* Make the following changes in `.env` file + * Change the ``Domain`` value to your machine's local IP address. For Eg. DOMAIN=192.168.29.147. + * Change the ``SSL_TYPE`` value to selfsign + * Hold Ctrl and press x to quit the text editor. Press y to indicate that you want to save the file, and then press Enter to confirm the file name. Do not change the file name. + +* Let the system know that you want the latest version of the database + + ``` + touch ./files/allow-postgres14-upgrade + ``` + +--- + +If you are on macOS you need to do the following additional changes: + +* Inside the central folder, open `docker-compose.yml` +* Inside the `service volumes` you need to edit the `/data/transfer` line
+ Your `service` should look like this: + + ```Dockerfile + service: + build: + context: . + dockerfile: service.dockerfile + depends_on: + - secrets + - postgres14 + - mail + - pyxform + - enketo + volumes: + - secrets:/etc/secrets + - $PWD/data/transfer:/data/transfer + environment: + - DOMAIN=${DOMAIN} + - SYSADMIN_EMAIL=${SYSADMIN_EMAIL} + - HTTPS_PORT=${HTTPS_PORT:-443} + - NODE_OPTIONS=${SERVICE_NODE_OPTIONS:-''} + - DB_HOST=${DB_HOST:-postgres14} + - DB_USER=${DB_USER:-odk} + - DB_PASSWORD=${DB_PASSWORD:-odk} + - DB_NAME=${DB_NAME:-odk} + - DB_SSL=${DB_SSL:-null} + - EMAIL_FROM=${EMAIL_FROM:-no-reply@${DOMAIN}} + - EMAIL_HOST=${EMAIL_HOST:-mail} + - EMAIL_PORT=${EMAIL_PORT:-25} + - EMAIL_SECURE=${EMAIL_SECURE:-false} + - EMAIL_IGNORE_TLS=${EMAIL_IGNORE_TLS:-true} + - EMAIL_USER=${EMAIL_USER:-''} + - EMAIL_PASSWORD=${EMAIL_PASSWORD:-''} + - SENTRY_ORG_SUBDOMAIN=${SENTRY_ORG_SUBDOMAIN:-o130137} + - SENTRY_KEY=${SENTRY_KEY:-3cf75f54983e473da6bd07daddf0d2ee} + - SENTRY_PROJECT=${SENTRY_PROJECT:-1298632} + command: [ "./wait-for-it.sh", "${DB_HOST:-postgres14}:5432", "--", "./start-odk.sh" ] + restart: always + logging: + driver: local + ``` + +--- +* Now you will need to bundle everyting together. This may take some time + + ``` + docker compose build + ``` + When it finishes, you should see some "Successfully built" type text + +* Start the server you just created. If its the first time its going to take a while to get ready + + ``` + docker compose up -d + ``` +* Check whether ODK has finished loading. + + ``` + docker compose ps + ``` + Under the Status column, for the ```central-nginx-1``` row, you will want to see text that reads Up or Up (healthy). If you see Up (health: starting), give it a few minutes. If you see some other text, something has gone wrong. + +Now You can check if everything is working by putting your local machine's IP in the browser. + +* Now you need to create an account. Make sure you are in the ```central``` directory before entering this command + + ``` + docker compose exec service odk-cmd --email YOUREMAIL@ADDRESSHERE.com user-create + ``` +* Make the account an administrator + ``` + docker compose exec service odk-cmd --email YOUREMAIL@ADDRESSHERE.com user-promote + ``` + Log into the Central website. Go to your domain name and enter in your new credentials. Once you have one administrator account, you do not have to go through this process again for future accounts: you can log into the website with your new account, and directly create new users that way. + +Congratulations now you have a working ODK Central server. + +--- + +## Securing the local endpoints +
+ +Now you need to create SSL certificates and add them to your endpoints. By doing this you are making your local https endpoints secure. We will use ``mkcert`` to create self-signed certificates. + +To do so follow these steps: + +* First make sure you have ``mkcert`` installed. If not follow these [instructions](https://github.com/FiloSottile/mkcert#installation) for installing mkcert on your operating system. + +* Add mkcert to your local root CAs. In your terminal, run the following command: + + ``` + mkcert -install + ``` + This generates a local certificate authority (CA). Your mkcert-generated local CA is only trusted locally, on your device. + +* Generate a certificate for your site, signed by mkcert. Create a new folder named `ssl-certs` in the central directory. In the terminal, navigate to this newly created folder. Then run: + + ``` + mkcert [Your Local Machine's IP] + ``` + For Eg. run the following command: `mkcert 192.168.29.147`. This should create the certificate and the key inside the folder. + +Now you will need to edit some files related to docker in order to use this newly created SSL certificate. + +* Inside the central folder, open `docker-compose.yml` +* Add the following volume to `nginx`. + Your nginx should look like this: + ```DockerFile + nginx: + build: + context: . + dockerfile: nginx.dockerfile + depends_on: + - service + - enketo + environment: + - DOMAIN=${DOMAIN} + - CERTBOT_EMAIL=${SYSADMIN_EMAIL} + - SSL_TYPE=${SSL_TYPE:-letsencrypt} + - SENTRY_ORG_SUBDOMAIN=${SENTRY_ORG_SUBDOMAIN:-o130137} + - SENTRY_KEY=${SENTRY_KEY:-3cf75f54983e473da6bd07daddf0d2ee} + - SENTRY_PROJECT=${SENTRY_PROJECT:-1298632} + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + volumes: + - $PWD/ssl-certs:/etc/sslcerts + healthcheck: + test: [ "CMD-SHELL", "nc -z localhost 80 || exit 1" ] + restart: always + logging: + driver: local + options: + max-file: "30" + ``` + +* Now you need to edit the `odk.conf.template` file. You should find this file in `central/files/nginx`.
+ Change the following code inside server to this: + ``` + ssl_certificate /etc/sslcerts/[Your Local Machine's IP].pem; + ssl_certificate_key /etc/sslcerts/[Your Local Machine's IP]-key.pem; + ssl_trusted_certificate /etc/sslcerts/[Your Local Machine's IP].pem; + ``` + Basically we are attatching the certificates we created earlier to the server endpoints. + +Now you can just delete the previous docker images and containers you created and create new images and containers using the updated code. Previous build was just to see that your server was successfully built or not. If everything goes as planned you should be able to connect to the server using your browser securely. + +--- + +## Setting up the Android app in Android Studio + +Congratulations!! Now your ODK Central server is up and ready. Now you need to connect your app to the server. + +* Clone the app repo and open the project in android studio. You should see a gradle error. To resolve this error add the following code to `local.properties` + + ``` + sonatypeStagingProfileId="" + ossrhUsername="" + ossrhPassword="" + signing.keyId="" + signing.key="" + signing.password="" + ``` +* Next you need to create the `settings.json`. create this file inside the sample/src/main/res/raw. Use [this](https://docs.getodk.org/collect-import-export/#list-of-keys-for-all-settings) to create this file. + +* For the `server-url`. + open your server -> create a project -> create a user +create a form inside the project and go to form access tab +check the checkbox on your created user +go to user access tab, you'll see that there is a generate QR code option +you need to scan this QR on odk collect app (available on playstore) +once you scan this QR you'll setup a project on odk collect app. Go to settings and then project management. You'll see a server url there +copy that url and paste it in your settings file. Url should look something like this https://192.169.29.147:443/v1/key/jP5eczJBZq08ECf9OKoNJGwjx55wBa3cpHkJ0r5dPQYEXcXM8uzBOTi38E9NPX$t/projects/2.
+Instead of 192.169.29.147 it should be your local machine's IP + +Now you need to add the CA you previously created to your android emulator so that the device knows its a trusted CA. + +* Find the location of the root CA you created earlier. Run the following command in the terminal. + + ``` + echo "$(mkcert -CAROOT)/rootCA.pem" + ``` +* Now put this file inside your emulator + +* Now install this CA certificate on your emulator. +To do this
+ * Open settings + * Go to 'Security' + * Go to 'Encryption & Credentials' + * Go to 'Install from storage' + * Select 'CA Certificate' from the list of types available + * Accept a large scary warning + * Browse to the certificate file on the device and open it + * Confirm the certificate install + + + +After All this you should be able to run the app successfully. \ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 34ebf01b08c..31571416084 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:networkSecurityConfig="@xml/network_security_config" android:theme="@style/Theme.ODKShellApp" tools:targetApi="31"> + + + + + + + + \ No newline at end of file From 984ce3f90b665c3e813da0211151b76fe9116d81 Mon Sep 17 00:00:00 2001 From: Prabhav Chopra Date: Fri, 2 Jun 2023 13:14:18 +0530 Subject: [PATCH 2/5] Incorporated Intial-Setup.md into Readme.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 197b366f32c..3c0d35d097c 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ ODK Collect is a standalone Android application which can be communicated via in ODK Collect Extension is a library which can be integrated into any existing Android application to embed ODK directly into them. +## [Installation Set-Up Guide](./INITIAL-SETUP.md) + ## Versions * Current version: 0.0.1 From 4f865e6cb5e5ad9bced81147176ee923d608d15e Mon Sep 17 00:00:00 2001 From: Prabhav Chopra Date: Sun, 4 Jun 2023 23:33:17 +0530 Subject: [PATCH 3/5] Made the review changes --- INITIAL-SETUP.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/INITIAL-SETUP.md b/INITIAL-SETUP.md index 93a2ae6cb67..b984439cc6b 100644 --- a/INITIAL-SETUP.md +++ b/INITIAL-SETUP.md @@ -37,6 +37,7 @@ docker --version && docker compose version ``` * Make the following changes in `.env` file * Change the ``Domain`` value to your machine's local IP address. For Eg. DOMAIN=192.168.29.147. + * You can check your ip by running the command `ifconfig`. If you're using ethernet, it should probably be named similar to `eth*`; if you're on WLAN network, it should be `wlo*`. * Change the ``SSL_TYPE`` value to selfsign * Hold Ctrl and press x to quit the text editor. Press y to indicate that you want to save the file, and then press Enter to confirm the file name. Do not change the file name. @@ -100,7 +101,7 @@ If you are on macOS you need to do the following additional changes: ``` docker compose build ``` - When it finishes, you should see some "Successfully built" type text + When it finishes, you should see some "Successfully built" type text. Make sure that you do not have any service running on your local port 80. * Start the server you just created. If its the first time its going to take a while to get ready @@ -217,14 +218,13 @@ Congratulations!! Now your ODK Central server is up and ready. Now you need to c ``` * Next you need to create the `settings.json`. create this file inside the sample/src/main/res/raw. Use [this](https://docs.getodk.org/collect-import-export/#list-of-keys-for-all-settings) to create this file. -* For the `server-url`. - open your server -> create a project -> create a user -create a form inside the project and go to form access tab -check the checkbox on your created user -go to user access tab, you'll see that there is a generate QR code option -you need to scan this QR on odk collect app (available on playstore) -once you scan this QR you'll setup a project on odk collect app. Go to settings and then project management. You'll see a server url there -copy that url and paste it in your settings file. Url should look something like this https://192.169.29.147:443/v1/key/jP5eczJBZq08ECf9OKoNJGwjx55wBa3cpHkJ0r5dPQYEXcXM8uzBOTi38E9NPX$t/projects/2.
+* For the `server-url`. Follow these instructions: + + * open your server -> create a project -> create a user create a form inside the project + * go to form access tab check the checkbox on your created user + * go to user access tab, you'll see that there is a generate QR code option you need to scan this QR on odk collect app (available on playstore) once you scan this QR you'll setup a project on odk collect app. + * Go to settings and then project management. You'll see a server url there + * copy that url and paste it in your settings file. Url should look something like this https://192.169.29.147:443/v1/key/jP5eczJBZq08ECf9OKoNJGwjx55wBa3cpHkJ0r5dPQYEXcXM8uzBOTi38E9NPX$t/projects/2.
Instead of 192.169.29.147 it should be your local machine's IP Now you need to add the CA you previously created to your android emulator so that the device knows its a trusted CA. From 9e20f12ca380959ba1d1caed743a4a27183fdd7c Mon Sep 17 00:00:00 2001 From: Prabhav Chopra <73053857+prabs3257@users.noreply.github.com> Date: Tue, 6 Jun 2023 13:24:07 +0530 Subject: [PATCH 4/5] implemented parallel download --- .../formmanagement/ServerFormDownloader.java | 7 +- .../android/tasks/DownloadFormsTask.java | 64 ++++++++++++------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java index eda34cf5aa9..0a06172fc38 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; import javax.annotation.Nullable; @@ -46,6 +48,8 @@ public ServerFormDownloader(FormSource formSource, FormsRepository formsReposito this.clock = clock; } + private static Lock lock = new ReentrantLock(); + @Override public void downloadForm(ServerFormDetails form, @Nullable ProgressReporter progressReporter, @Nullable Supplier isCancelled) throws FormDownloadException { Form formOnDevice; @@ -94,8 +98,8 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe // get the xml file // if we've downloaded a duplicate, this gives us the file fileResult = downloadXform(fd.getFormName(), fd.getDownloadUrl(), stateListener, tempDir, formsDirPath); - // download media files if there are any + lock.lock(); if (fd.getManifest() != null && !fd.getManifest().getMediaFiles().isEmpty()) { FormMediaDownloader mediaDownloader = new FormMediaDownloader(formsRepository, formSource); newAttachmentsDetected = mediaDownloader.download(fd, fd.getManifest().getMediaFiles(), tempMediaPath, tempDir, stateListener); @@ -138,6 +142,7 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe try { installEverything(tempMediaPath, fileResult, parsedFields, formsDirPath, newAttachmentsDetected); + lock.unlock(); } catch (FormDownloadException.DiskError e) { cleanUp(fileResult, tempMediaPath); throw e; diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java b/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java index f5c25ed43a9..e0bb6c78c35 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java @@ -18,6 +18,8 @@ import static java.util.Collections.emptyMap; import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; import org.odk.collect.android.R; import org.odk.collect.android.application.Collect; @@ -30,6 +32,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * Background task for downloading a given list of forms. We assume right now that the forms are @@ -53,34 +57,48 @@ public DownloadFormsTask(FormDownloader formDownloader) { protected Map doInBackground(ArrayList... values) { HashMap results = new HashMap<>(); + ExecutorService executorService = Executors.newCachedThreadPool(); int index = 1; for (ServerFormDetails serverFormDetails : values[0]) { - try { - String currentFormNumber = String.valueOf(index); - String totalForms = String.valueOf(values[0].size()); - publishProgress(serverFormDetails.getFormName(), currentFormNumber, totalForms); - - formDownloader.downloadForm(serverFormDetails, count -> { - String message = getLocalizedString(Collect.getInstance(), R.string.form_download_progress, - serverFormDetails.getFormName(), - String.valueOf(count), - String.valueOf(serverFormDetails.getManifest().getMediaFiles().size()) - ); - - publishProgress(message, currentFormNumber, totalForms); - }, this::isCancelled); - - results.put(serverFormDetails, null); - FormEventBus.INSTANCE.formDownloaded(serverFormDetails.getFormId()); - } catch (FormDownloadException.DownloadingInterrupted e) { - return emptyMap(); - } catch (FormDownloadException e) { - results.put(serverFormDetails, e); - FormEventBus.INSTANCE.formDownloadFailed(serverFormDetails.getFormId(), e.getMessage()); - } + String currentFormNumber = String.valueOf(index); + String totalForms = String.valueOf(values[0].size()); + publishProgress(serverFormDetails.getFormName(), currentFormNumber, totalForms); + + Thread thread = new Thread() { + public void run() { + + try { + formDownloader.downloadForm(serverFormDetails, count -> { + String message = getLocalizedString(Collect.getInstance(), R.string.form_download_progress, + serverFormDetails.getFormName(), + String.valueOf(count), + String.valueOf(serverFormDetails.getManifest().getMediaFiles().size()) + ); + + publishProgress(message, currentFormNumber, totalForms); + + }, DownloadFormsTask.this::isCancelled); + } catch (FormDownloadException e) { + results.put(serverFormDetails, e); + FormEventBus.INSTANCE.formDownloadFailed(serverFormDetails.getFormId(), e.getMessage()); + throw new RuntimeException(e); + } + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + results.put(serverFormDetails, null); + FormEventBus.INSTANCE.formDownloaded(serverFormDetails.getFormId()); + } + }); + } + }; + + executorService.submit(thread); index++; } + executorService.shutdown(); return results; } From 2e706dd6482b05e1171c7b3cdd5f928cde0ca454 Mon Sep 17 00:00:00 2001 From: Prabhav Chopra <73053857+prabs3257@users.noreply.github.com> Date: Wed, 7 Jun 2023 23:52:10 +0530 Subject: [PATCH 5/5] Fixed media download and shutdown problems --- .../android/formmanagement/FormMediaDownloader.kt | 11 ++++++----- .../android/formmanagement/ServerFormDownloader.java | 8 ++++++-- .../odk/collect/android/tasks/DownloadFormsTask.java | 7 +++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt index 65c443e196b..3b6b363e81e 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt @@ -3,20 +3,19 @@ package org.odk.collect.android.formmanagement import org.odk.collect.android.utilities.FileUtils.copyFile import org.odk.collect.android.utilities.FileUtils.interuptablyWriteFile import org.odk.collect.async.OngoingWorkListener -import org.odk.collect.forms.Form -import org.odk.collect.forms.FormSource -import org.odk.collect.forms.FormSourceException -import org.odk.collect.forms.FormsRepository -import org.odk.collect.forms.MediaFile +import org.odk.collect.forms.* import org.odk.collect.shared.strings.Md5.getMd5Hash import java.io.File import java.io.IOException +import java.util.concurrent.locks.Lock +import java.util.concurrent.locks.ReentrantLock class FormMediaDownloader( private val formsRepository: FormsRepository, private val formSource: FormSource ) { + private val lock: Lock = ReentrantLock() @Throws(IOException::class, FormSourceException::class, InterruptedException::class) fun download( formToDownload: ServerFormDetails, @@ -39,7 +38,9 @@ class FormMediaDownloader( } else { atLeastOneNewMediaFileDetected = true val file = formSource.fetchMediaFile(mediaFile.downloadUrl) + lock.lock() interuptablyWriteFile(file, tempMediaFile, tempDir, stateListener) + lock.unlock() } } } diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java index 0a06172fc38..4aec842f4c7 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java @@ -99,7 +99,7 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe // if we've downloaded a duplicate, this gives us the file fileResult = downloadXform(fd.getFormName(), fd.getDownloadUrl(), stateListener, tempDir, formsDirPath); // download media files if there are any - lock.lock(); + lock.unlock(); if (fd.getManifest() != null && !fd.getManifest().getMediaFiles().isEmpty()) { FormMediaDownloader mediaDownloader = new FormMediaDownloader(formsRepository, formSource); newAttachmentsDetected = mediaDownloader.download(fd, fd.getManifest().getMediaFiles(), tempMediaPath, tempDir, stateListener); @@ -107,8 +107,10 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe } catch (FormDownloadException.DownloadingInterrupted | InterruptedException e) { Timber.i(e); cleanUp(fileResult, tempMediaPath); + lock.unlock(); throw new FormDownloadException.DownloadingInterrupted(); } catch (IOException e) { + lock.unlock(); throw new FormDownloadException.DiskError(); } @@ -141,10 +143,12 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe } try { + lock.lock(); installEverything(tempMediaPath, fileResult, parsedFields, formsDirPath, newAttachmentsDetected); lock.unlock(); } catch (FormDownloadException.DiskError e) { cleanUp(fileResult, tempMediaPath); + lock.unlock(); throw e; } } @@ -249,7 +253,7 @@ private Form saveNewForm(Map formInfo, File formFile, String med */ private FileResult downloadXform(String formName, String url, OngoingWorkListener stateListener, File tempDir, String formsDirPath) throws FormSourceException, IOException, FormDownloadException.DownloadingInterrupted, InterruptedException { InputStream xform = formSource.fetchForm(url); - + lock.lock(); String fileName = getFormFileName(formName, formsDirPath); File tempFormFile = new File(tempDir + File.separator + fileName); interuptablyWriteFile(xform, tempFormFile, tempDir, stateListener); diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java b/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java index e0bb6c78c35..4bc8390e256 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java @@ -34,6 +34,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Background task for downloading a given list of forms. We assume right now that the forms are @@ -99,6 +100,12 @@ public void run() { index++; } executorService.shutdown(); + // Wait for all tasks to complete + try { + executorService.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + e.printStackTrace(); + } return results; }