diff --git a/analytics/src/main/java/org/odk/collect/analytics/Analytics.kt b/analytics/src/main/java/org/odk/collect/analytics/Analytics.kt
index 6bfefeb6080..033392ea33d 100644
--- a/analytics/src/main/java/org/odk/collect/analytics/Analytics.kt
+++ b/analytics/src/main/java/org/odk/collect/analytics/Analytics.kt
@@ -41,5 +41,9 @@ interface Analytics {
fun setParam(key: String, value: String) {
params[key] = value
}
+
+ fun setUserProperty(name: String, value: String) {
+ instance.setUserProperty(name, value)
+ }
}
}
diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt b/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt
deleted file mode 100644
index 39faf56766e..00000000000
--- a/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.odk.collect.androidshared.network
-
-import android.content.Context
-import android.net.ConnectivityManager
-import android.net.NetworkInfo
-
-class ConnectivityProvider(private val context: Context) : NetworkStateProvider {
- override val isDeviceOnline: Boolean
- get() {
- val networkInfo = networkInfo
- return networkInfo != null && networkInfo.isConnected
- }
-
- override val networkInfo: NetworkInfo?
- get() = connectivityManager.activeNetworkInfo
-
- private val connectivityManager: ConnectivityManager
- get() = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
-}
diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt b/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt
deleted file mode 100644
index d3b9e983c58..00000000000
--- a/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.odk.collect.androidshared.network
-
-import android.net.NetworkInfo
-
-interface NetworkStateProvider {
- val isDeviceOnline: Boolean
- val networkInfo: NetworkInfo?
-}
diff --git a/async/build.gradle.kts b/async/build.gradle.kts
index 9c958ba7e33..d6eae2c48cb 100644
--- a/async/build.gradle.kts
+++ b/async/build.gradle.kts
@@ -42,6 +42,9 @@ dependencies {
implementation(Dependencies.androidx_core_ktx)
implementation(Dependencies.kotlinx_coroutines_android)
implementation(Dependencies.androidx_work_runtime)
+ implementation(project(":analytics")) {
+ exclude("com.google.firebase")
+ }
testImplementation(Dependencies.hamcrest)
testImplementation(Dependencies.robolectric)
diff --git a/async/src/main/AndroidManifest.xml b/async/src/main/AndroidManifest.xml
index c7e7078dcda..d7546021b58 100644
--- a/async/src/main/AndroidManifest.xml
+++ b/async/src/main/AndroidManifest.xml
@@ -1,2 +1,3 @@
-
\ No newline at end of file
+
+
diff --git a/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt b/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt
index d07aa1aad6c..d07a2448abd 100644
--- a/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt
+++ b/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt
@@ -13,25 +13,44 @@ import kotlinx.coroutines.Dispatchers
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
-class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, backgroundContext: CoroutineContext, private val workManager: WorkManager) : CoroutineScheduler(foregroundContext, backgroundContext) {
-
- constructor(workManager: WorkManager) : this(Dispatchers.Main, Dispatchers.IO, workManager) // Needed for Java construction
-
- override fun networkDeferred(tag: String, spec: TaskSpec, inputData: Map, networkConstraint: Scheduler.NetworkType?) {
+class CoroutineAndWorkManagerScheduler(
+ foregroundContext: CoroutineContext,
+ backgroundContext: CoroutineContext,
+ private val workManager: WorkManager
+) : CoroutineScheduler(foregroundContext, backgroundContext) {
+
+ constructor(workManager: WorkManager) : this(
+ Dispatchers.Main,
+ Dispatchers.IO,
+ workManager
+ ) // Needed for Java construction
+
+ override fun networkDeferred(
+ tag: String,
+ spec: TaskSpec,
+ inputData: Map,
+ networkConstraint: Scheduler.NetworkType?
+ ) {
val constraintNetworkType = when (networkConstraint) {
Scheduler.NetworkType.WIFI -> NetworkType.UNMETERED
Scheduler.NetworkType.CELLULAR -> NetworkType.METERED
- null -> NetworkType.CONNECTED
+ else -> NetworkType.CONNECTED
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(constraintNetworkType)
.build()
- val workManagerInputData = Data.Builder().putAll(inputData).build()
+ val workManagerInputData = Data.Builder()
+ .putString(TaskSpecWorker.DATA_TASK_SPEC_CLASS, spec.javaClass.name)
+ .putBoolean(
+ TaskSpecWorker.DATA_CELLULAR_ONLY,
+ networkConstraint == Scheduler.NetworkType.CELLULAR
+ )
+ .putAll(inputData)
+ .build()
- val worker = spec.getWorkManagerAdapter()
- val workRequest = OneTimeWorkRequest.Builder(worker)
+ val workRequest = OneTimeWorkRequest.Builder(TaskSpecWorker::class.java)
.addTag(tag)
.setConstraints(constraints)
.setInputData(workManagerInputData)
@@ -40,15 +59,26 @@ class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, back
workManager.beginUniqueWork(tag, ExistingWorkPolicy.REPLACE, workRequest).enqueue()
}
- override fun networkDeferredRepeat(tag: String, spec: TaskSpec, repeatPeriod: Long, inputData: Map) {
+ override fun networkDeferredRepeat(
+ tag: String,
+ spec: TaskSpec,
+ repeatPeriod: Long,
+ inputData: Map
+ ) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
- val workManagerInputData = Data.Builder().putAll(inputData).build()
+ val workManagerInputData = Data.Builder()
+ .putString(TaskSpecWorker.DATA_TASK_SPEC_CLASS, spec.javaClass.name)
+ .putAll(inputData)
+ .build()
- val worker = spec.getWorkManagerAdapter()
- val builder = PeriodicWorkRequest.Builder(worker, repeatPeriod, TimeUnit.MILLISECONDS)
+ val builder = PeriodicWorkRequest.Builder(
+ TaskSpecWorker::class.java,
+ repeatPeriod,
+ TimeUnit.MILLISECONDS
+ )
.addTag(tag)
.setInputData(workManagerInputData)
.setConstraints(constraints)
@@ -59,7 +89,11 @@ class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, back
}
}
- workManager.enqueueUniquePeriodicWork(tag, ExistingPeriodicWorkPolicy.REPLACE, builder.build())
+ workManager.enqueueUniquePeriodicWork(
+ tag,
+ ExistingPeriodicWorkPolicy.REPLACE,
+ builder.build()
+ )
}
override fun cancelDeferred(tag: String) {
diff --git a/async/src/main/java/org/odk/collect/async/Scheduler.kt b/async/src/main/java/org/odk/collect/async/Scheduler.kt
index 763e26da5c2..2d80d54bb26 100644
--- a/async/src/main/java/org/odk/collect/async/Scheduler.kt
+++ b/async/src/main/java/org/odk/collect/async/Scheduler.kt
@@ -81,7 +81,8 @@ interface Scheduler {
enum class NetworkType {
WIFI,
- CELLULAR
+ CELLULAR,
+ OTHER
}
}
diff --git a/async/src/main/java/org/odk/collect/async/TaskSpec.kt b/async/src/main/java/org/odk/collect/async/TaskSpec.kt
index 9a17d788a54..d6643e0842c 100644
--- a/async/src/main/java/org/odk/collect/async/TaskSpec.kt
+++ b/async/src/main/java/org/odk/collect/async/TaskSpec.kt
@@ -18,10 +18,4 @@ interface TaskSpec {
* once instead of doing that after every single execution.
*/
fun getTask(context: Context, inputData: Map, isLastUniqueExecution: Boolean): Supplier
-
- /**
- * Returns class that can be used to schedule this task using Android's
- * WorkManager framework
- */
- fun getWorkManagerAdapter(): Class
}
diff --git a/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt
new file mode 100644
index 00000000000..c3e41880436
--- /dev/null
+++ b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt
@@ -0,0 +1,47 @@
+package org.odk.collect.async
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import org.odk.collect.analytics.Analytics
+import org.odk.collect.async.network.ConnectivityProvider
+
+class TaskSpecWorker(
+ context: Context,
+ workerParams: WorkerParameters
+) : Worker(context, workerParams) {
+
+ private val connectivityProvider: ConnectivityProvider = ConnectivityProvider(context)
+
+ override fun doWork(): Result {
+ val cellularOnly = inputData.getBoolean(DATA_CELLULAR_ONLY, false)
+ if (cellularOnly && connectivityProvider.currentNetwork != Scheduler.NetworkType.CELLULAR) {
+ Analytics.setUserProperty("EncounteredMeteredNonCellularInTasks", "true")
+ return Result.retry()
+ }
+
+ val specClass = inputData.getString(DATA_TASK_SPEC_CLASS)!!
+ val spec = Class.forName(specClass).getConstructor().newInstance() as TaskSpec
+
+ val stringInputData = inputData.keyValueMap.mapValues { it.value.toString() }
+ val completed =
+ spec.getTask(applicationContext, stringInputData, isLastUniqueExecution(spec)).get()
+ val maxRetries = spec.maxRetries
+
+ return if (completed) {
+ Result.success()
+ } else if (maxRetries == null || runAttemptCount < maxRetries) {
+ Result.retry()
+ } else {
+ Result.failure()
+ }
+ }
+
+ private fun isLastUniqueExecution(spec: TaskSpec) =
+ spec.maxRetries?.let { runAttemptCount >= it } ?: true
+
+ companion object {
+ const val DATA_TASK_SPEC_CLASS = "taskSpecClass"
+ const val DATA_CELLULAR_ONLY = "cellularOnly"
+ }
+}
diff --git a/async/src/main/java/org/odk/collect/async/WorkerAdapter.kt b/async/src/main/java/org/odk/collect/async/WorkerAdapter.kt
deleted file mode 100644
index fc65d1575e0..00000000000
--- a/async/src/main/java/org/odk/collect/async/WorkerAdapter.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.odk.collect.async
-
-import android.content.Context
-import androidx.work.Worker
-import androidx.work.WorkerParameters
-
-abstract class WorkerAdapter(
- private val spec: TaskSpec,
- context: Context,
- workerParams: WorkerParameters
-) : Worker(context, workerParams) {
-
- override fun doWork(): Result {
- val stringInputData = inputData.keyValueMap.mapValues { it.value.toString() }
- val completed = spec.getTask(applicationContext, stringInputData, isLastUniqueExecution()).get()
- val maxRetries = spec.maxRetries
-
- return if (completed) {
- Result.success()
- } else if (maxRetries == null || runAttemptCount < maxRetries) {
- Result.retry()
- } else {
- Result.failure()
- }
- }
-
- private fun isLastUniqueExecution() = spec.maxRetries?.let { runAttemptCount >= it } ?: true
-}
diff --git a/async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt b/async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt
new file mode 100644
index 00000000000..526ad50c363
--- /dev/null
+++ b/async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt
@@ -0,0 +1,23 @@
+package org.odk.collect.async.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import org.odk.collect.async.Scheduler
+
+class ConnectivityProvider(private val context: Context) : NetworkStateProvider {
+ override val currentNetwork: Scheduler.NetworkType?
+ get() {
+ return if (connectivityManager.activeNetworkInfo?.isConnected == true) {
+ when (connectivityManager.activeNetworkInfo?.type) {
+ ConnectivityManager.TYPE_WIFI -> Scheduler.NetworkType.WIFI
+ ConnectivityManager.TYPE_MOBILE -> Scheduler.NetworkType.CELLULAR
+ else -> Scheduler.NetworkType.OTHER
+ }
+ } else {
+ null
+ }
+ }
+
+ private val connectivityManager: ConnectivityManager
+ get() = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+}
diff --git a/async/src/main/java/org/odk/collect/async/network/NetworkStateProvider.kt b/async/src/main/java/org/odk/collect/async/network/NetworkStateProvider.kt
new file mode 100644
index 00000000000..410454a5106
--- /dev/null
+++ b/async/src/main/java/org/odk/collect/async/network/NetworkStateProvider.kt
@@ -0,0 +1,12 @@
+package org.odk.collect.async.network
+
+import org.odk.collect.async.Scheduler
+
+interface NetworkStateProvider {
+ val currentNetwork: Scheduler.NetworkType?
+
+ val isDeviceOnline: Boolean
+ get() {
+ return currentNetwork != null
+ }
+}
diff --git a/async/src/test/java/org/odk/collect/async/WorkerAdapterTest.kt b/async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt
similarity index 51%
rename from async/src/test/java/org/odk/collect/async/WorkerAdapterTest.kt
rename to async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt
index b5ad9d83c19..df69f12ee0f 100644
--- a/async/src/test/java/org/odk/collect/async/WorkerAdapterTest.kt
+++ b/async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt
@@ -3,118 +3,157 @@ package org.odk.collect.async
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.work.BackoffPolicy
+import androidx.work.Data
import androidx.work.ListenableWorker
import androidx.work.Worker
-import androidx.work.WorkerParameters
import androidx.work.testing.TestWorkerBuilder
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
import java.util.concurrent.Executors
import java.util.function.Supplier
@RunWith(AndroidJUnit4::class)
-class WorkerAdapterTest {
+class TaskSpecWorkerTest {
private lateinit var worker: Worker
- companion object {
- private lateinit var spec: TaskSpec
- }
@Before
fun setup() {
- spec = mock()
- worker = TestWorkerBuilder(
+ worker = TestWorkerBuilder(
context = ApplicationProvider.getApplicationContext(),
executor = Executors.newSingleThreadExecutor(),
+ inputData = Data.Builder()
+ .putString(TaskSpecWorker.DATA_TASK_SPEC_CLASS, TestTaskSpec::class.java.name)
+ .build(),
runAttemptCount = 0 // without setting this explicitly attempts in tests are counted starting from 1 instead of 0 like in production code
).build()
+
+ TestTaskSpec.reset()
}
@Test
- fun `when task returns true should work succeed`() {
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { true })
-
+ fun `when task returns true work should succeed`() {
+ TestTaskSpec.doReturn(true)
assertThat(worker.doWork(), `is`(ListenableWorker.Result.success()))
}
@Test
fun `when task returns false, retries if maxRetries not specified`() {
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { false })
- whenever(spec.maxRetries).thenReturn(null)
-
+ TestTaskSpec.doReturn(false)
assertThat(worker.doWork(), `is`(ListenableWorker.Result.retry()))
}
@Test
fun `when task returns false, retries if maxRetries is specified and is higher than runAttemptCount`() {
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { false })
- whenever(spec.maxRetries).thenReturn(3)
+ TestTaskSpec
+ .withMaxRetries(1)
+ .doReturn(false)
assertThat(worker.doWork(), `is`(ListenableWorker.Result.retry()))
}
@Test
fun `when task returns false, fails if maxRetries is specified and is equal to runAttemptCount`() {
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { false })
- whenever(spec.maxRetries).thenReturn(0)
+ TestTaskSpec
+ .withMaxRetries(0)
+ .doReturn(false)
assertThat(worker.doWork(), `is`(ListenableWorker.Result.failure()))
}
@Test
fun `when task returns false, fails if maxRetries is specified and is lower than runAttemptCount`() {
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { false })
- whenever(spec.maxRetries).thenReturn(0)
+ TestTaskSpec
+ .withMaxRetries(-1)
+ .doReturn(false)
assertThat(worker.doWork(), `is`(ListenableWorker.Result.failure()))
}
@Test
fun `when maxRetries is not specified, task called with isLastUniqueExecution true`() {
- whenever(spec.maxRetries).thenReturn(null)
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { true })
+ TestTaskSpec
+ .doReturn(false)
worker.doWork()
-
- verify(spec).getTask(any(), any(), eq(true))
+ assertThat(TestTaskSpec.wasLastUniqueExecution, equalTo(true))
}
@Test
fun `when maxRetries is specified and it is higher than runAttemptCount, task called with isLastUniqueExecution false`() {
- whenever(spec.maxRetries).thenReturn(3)
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { true })
+ TestTaskSpec
+ .withMaxRetries(1)
+ .doReturn(false)
worker.doWork()
-
- verify(spec).getTask(any(), any(), eq(false))
+ assertThat(TestTaskSpec.wasLastUniqueExecution, equalTo(false))
}
@Test
fun `when maxRetries is specified and it is equal to runAttemptCount, task called with isLastUniqueExecution true`() {
- whenever(spec.maxRetries).thenReturn(0)
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { true })
+ TestTaskSpec
+ .withMaxRetries(0)
+ .doReturn(false)
worker.doWork()
-
- verify(spec).getTask(any(), any(), eq(true))
+ assertThat(TestTaskSpec.wasLastUniqueExecution, equalTo(true))
}
@Test
fun `when maxRetries is specified and it is lower than runAttemptCount, task called with isLastUniqueExecution true`() {
- whenever(spec.maxRetries).thenReturn(0)
- whenever(spec.getTask(any(), any(), any())).thenReturn(Supplier { true })
+ TestTaskSpec
+ .withMaxRetries(-1)
+ .doReturn(false)
worker.doWork()
+ assertThat(TestTaskSpec.wasLastUniqueExecution, equalTo(true))
+ }
+}
- verify(spec).getTask(any(), any(), eq(true))
+private class TestTaskSpec : TaskSpec {
+
+ companion object {
+
+ private var maxRetries: Int? = null
+ private var returnValue = true
+
+ var wasLastUniqueExecution = false
+ private set
+
+ fun reset() {
+ returnValue = true
+ maxRetries = null
+ wasLastUniqueExecution = false
+ }
+
+ fun doReturn(value: Boolean): Companion {
+ returnValue = value
+ return this
+ }
+
+ fun withMaxRetries(maxRetries: Int): Companion {
+ this.maxRetries = maxRetries
+ return this
+ }
}
- class TestWorker(context: Context, parameters: WorkerParameters) : WorkerAdapter(spec, context, parameters)
+ override val maxRetries: Int? = Companion.maxRetries
+ override val backoffPolicy: BackoffPolicy? = null
+ override val backoffDelay: Long? = null
+
+ override fun getTask(
+ context: Context,
+ inputData: Map,
+ isLastUniqueExecution: Boolean
+ ): Supplier {
+ wasLastUniqueExecution = isLastUniqueExecution
+
+ return Supplier {
+ returnValue
+ }
+ }
}
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formmanagement/BulkFinalizationTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formmanagement/BulkFinalizationTest.kt
index f0bc52421f5..b50d46a28d5 100644
--- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formmanagement/BulkFinalizationTest.kt
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formmanagement/BulkFinalizationTest.kt
@@ -171,7 +171,10 @@ class BulkFinalizationTest {
@Test
fun whenAutoSendIsEnabled_draftsAreSentAfterFinalizing() {
val mainMenuPage = rule.withProject(testDependencies.server.url)
- .enableAutoSend(testDependencies.scheduler)
+ .enableAutoSend(
+ testDependencies.scheduler,
+ string.wifi_cellular_autosend
+ )
.copyForm("one-question.xml", testDependencies.server.hostName)
.startBlankForm("One Question")
@@ -190,6 +193,26 @@ class BulkFinalizationTest {
assertThat(testDependencies.server.submissions.size, equalTo(1))
}
+ @Test
+ fun whenDraftFormHasAutoSendEnabled_draftsAreSentAfterFinalizing() {
+ val mainMenuPage = rule.withProject(testDependencies.server.url)
+ .copyForm("one-question-autosend.xml", testDependencies.server.hostName)
+ .startBlankForm("One Question Autosend")
+ .fillOutAndSave(QuestionAndAnswer("what is your age", "97"))
+
+ .clickDrafts(1)
+ .clickFinalizeAll(1)
+ .clickFinalize()
+ .pressBack(MainMenuPage())
+
+ testDependencies.scheduler.runDeferredTasks()
+
+ mainMenuPage.clickViewSentForm(1)
+ .assertText("One Question Autosend")
+
+ assertThat(testDependencies.server.submissions.size, equalTo(1))
+ }
+
@Test
fun canCancel() {
rule.withProject("http://example.com")
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/AutoSendTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/AutoSendTest.kt
index 7180ed94b92..bc4e5c1e37d 100644
--- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/AutoSendTest.kt
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/AutoSendTest.kt
@@ -13,6 +13,8 @@ import org.odk.collect.android.support.pages.ViewSentFormPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.NotificationDrawerRule
import org.odk.collect.android.support.rules.TestRuleChain
+import org.odk.collect.async.Scheduler
+import org.odk.collect.strings.R
@RunWith(AndroidJUnit4::class)
class AutoSendTest {
@@ -29,7 +31,10 @@ class AutoSendTest {
fun whenAutoSendEnabled_fillingAndFinalizingForm_sendsFormAndNotifiesUser() {
val mainMenuPage = rule.startAtMainMenu()
.setServer(testDependencies.server.url)
- .enableAutoSend(testDependencies.scheduler)
+ .enableAutoSend(
+ testDependencies.scheduler,
+ R.string.wifi_cellular_autosend
+ )
.copyForm("one-question.xml")
.startBlankForm("One Question")
.inputText("31")
@@ -58,7 +63,10 @@ class AutoSendTest {
val mainMenuPage = rule.startAtMainMenu()
.setServer(testDependencies.server.url)
- .enableAutoSend(testDependencies.scheduler)
+ .enableAutoSend(
+ testDependencies.scheduler,
+ R.string.wifi_cellular_autosend
+ )
.copyForm("one-question.xml")
.startBlankForm("One Question")
.inputText("31")
@@ -82,15 +90,20 @@ class AutoSendTest {
}
@Test
- fun whenFormHasAutoSend_fillingAndFinalizingForm_sendsFormAndNotifiesUser() {
+ fun whenFormHasAutoSend_fillingAndFinalizingForm_sendsFormAndNotifiesUser_regardlessOfSetting() {
val mainMenuPage = rule.startAtMainMenu()
.setServer(testDependencies.server.url)
+ .enableAutoSend(
+ testDependencies.scheduler,
+ R.string.wifi_autosend
+ )
.copyForm("one-question-autosend.xml")
.startBlankForm("One Question Autosend")
.inputText("31")
.swipeToEndScreen()
.clickSend()
+ testDependencies.networkStateProvider.goOnline(Scheduler.NetworkType.CELLULAR)
testDependencies.scheduler.runDeferredTasks()
mainMenuPage
@@ -108,17 +121,44 @@ class AutoSendTest {
}
@Test
- fun whenFormHasAutoSend_fillingAndFinalizingForm_notifiesUserWhenSendingFails() {
+ fun whenFormHasAutoSend_canAutoSendMultipleForms() {
+ val mainMenuPage = rule.startAtMainMenu()
+ .setServer(testDependencies.server.url)
+ .copyForm("one-question-autosend.xml")
+
+ .startBlankForm("One Question Autosend")
+ .inputText("31")
+ .swipeToEndScreen()
+ .clickSend()
+
+ .startBlankForm("One Question Autosend")
+ .inputText("32")
+ .swipeToEndScreen()
+ .clickSend()
+
+ testDependencies.scheduler.runDeferredTasks()
+
+ mainMenuPage
+ .clickViewSentForm(2)
+ }
+
+ @Test
+ fun whenFormHasAutoSend_fillingAndFinalizingForm_notifiesUserWhenSendingFails_regardlessOfSetting() {
testDependencies.server.alwaysReturnError()
val mainMenuPage = rule.startAtMainMenu()
.setServer(testDependencies.server.url)
+ .enableAutoSend(
+ testDependencies.scheduler,
+ R.string.wifi_autosend
+ )
.copyForm("one-question-autosend.xml")
.startBlankForm("One Question Autosend")
.inputText("31")
.swipeToEndScreen()
.clickSend()
+ testDependencies.networkStateProvider.goOnline(Scheduler.NetworkType.CELLULAR)
testDependencies.scheduler.runDeferredTasks()
mainMenuPage.clickViewSentForm(1)
@@ -135,6 +175,24 @@ class AutoSendTest {
)
}
+ @Test
+ fun whenFormHasAutoSendDisabled_fillingAndFinalizingForm_doesNotSendForm_regardlessOfSetting() {
+ val mainMenuPage = rule.startAtMainMenu()
+ .setServer(testDependencies.server.url)
+ .enableAutoSend(
+ testDependencies.scheduler,
+ R.string.wifi_cellular_autosend
+ )
+ .copyForm("one-question-autosend-disabled.xml")
+ .startBlankForm("One Question Autosend Disabled")
+ .inputText("31")
+ .swipeToEndScreen()
+ .clickFinalize()
+
+ testDependencies.scheduler.runDeferredTasks()
+ mainMenuPage.assertNumberOfFinalizedForms(1)
+ }
+
@Test
fun whenAutoSendDisabled_fillingAndFinalizingForm_doesNotSendFormAutomatically() {
val mainMenuPage = rule.startAtMainMenu()
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ConfigureWithQRCodeTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ConfigureWithQRCodeTest.java
index 0aa8b6231d5..7c52401dc15 100644
--- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ConfigureWithQRCodeTest.java
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ConfigureWithQRCodeTest.java
@@ -1,10 +1,11 @@
package org.odk.collect.android.feature.settings;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.work.WorkManager;
import org.junit.After;
import org.junit.Rule;
@@ -15,34 +16,31 @@
import org.odk.collect.android.configure.qr.AppConfigurationGenerator;
import org.odk.collect.android.configure.qr.QRCodeGenerator;
import org.odk.collect.android.injection.config.AppDependencyModule;
-import org.odk.collect.android.support.rules.CollectTestRule;
-import org.odk.collect.android.support.rules.ResetStateRule;
-import org.odk.collect.android.support.rules.RunnableRule;
import org.odk.collect.android.support.StubBarcodeViewDecoder;
-import org.odk.collect.android.support.TestScheduler;
-import org.odk.collect.android.support.pages.ProjectSettingsPage;
+import org.odk.collect.android.support.TestDependencies;
import org.odk.collect.android.support.pages.MainMenuPage;
+import org.odk.collect.android.support.pages.ProjectSettingsPage;
import org.odk.collect.android.support.pages.QRCodePage;
+import org.odk.collect.android.support.rules.CollectTestRule;
+import org.odk.collect.android.support.rules.ResetStateRule;
+import org.odk.collect.android.support.rules.RunnableRule;
import org.odk.collect.android.support.rules.TestRuleChain;
import org.odk.collect.android.views.BarcodeViewDecoder;
-import org.odk.collect.async.Scheduler;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Collection;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
@RunWith(AndroidJUnit4.class)
public class ConfigureWithQRCodeTest {
+ private final TestDependencies testDependencies = new TestDependencies();
private final CollectTestRule rule = new CollectTestRule();
private final StubQRCodeGenerator stubQRCodeGenerator = new StubQRCodeGenerator();
private final StubBarcodeViewDecoder stubBarcodeViewDecoder = new StubBarcodeViewDecoder();
- private final TestScheduler testScheduler = new TestScheduler();
@Rule
- public RuleChain copyFormChain = TestRuleChain.chain()
+ public RuleChain copyFormChain = TestRuleChain.chain(testDependencies)
.around(new ResetStateRule(new AppDependencyModule() {
@Override
@@ -54,11 +52,6 @@ public BarcodeViewDecoder providesBarcodeViewDecoder() {
public QRCodeGenerator providesQRCodeGenerator() {
return stubQRCodeGenerator;
}
-
- @Override
- public Scheduler providesScheduler(WorkManager workManager) {
- return testScheduler;
- }
}))
.around(new RunnableRule(stubQRCodeGenerator::setup))
.around(rule);
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeNetworkStateProvider.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeNetworkStateProvider.kt
new file mode 100644
index 00000000000..395e59f4db8
--- /dev/null
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeNetworkStateProvider.kt
@@ -0,0 +1,20 @@
+package org.odk.collect.android.support
+
+import org.odk.collect.async.Scheduler
+import org.odk.collect.async.network.NetworkStateProvider
+
+class FakeNetworkStateProvider : NetworkStateProvider {
+
+ private var type: Scheduler.NetworkType? = Scheduler.NetworkType.WIFI
+
+ fun goOnline(networkType: Scheduler.NetworkType) {
+ type = networkType
+ }
+
+ fun goOffline() {
+ type = null
+ }
+
+ override val currentNetwork: Scheduler.NetworkType?
+ get() = type
+}
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/TestDependencies.java b/collect_app/src/androidTest/java/org/odk/collect/android/support/TestDependencies.java
index 0b3b8b3427f..440b5144c74 100644
--- a/collect_app/src/androidTest/java/org/odk/collect/android/support/TestDependencies.java
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/TestDependencies.java
@@ -1,6 +1,7 @@
package org.odk.collect.android.support;
import android.app.Application;
+import android.content.Context;
import android.webkit.MimeTypeMap;
import androidx.work.WorkManager;
@@ -11,12 +12,14 @@
import org.odk.collect.android.version.VersionInformation;
import org.odk.collect.android.views.BarcodeViewDecoder;
import org.odk.collect.async.Scheduler;
+import org.odk.collect.async.network.NetworkStateProvider;
import org.odk.collect.utilities.UserAgentProvider;
public class TestDependencies extends AppDependencyModule {
public final StubOpenRosaServer server = new StubOpenRosaServer();
- public final TestScheduler scheduler = new TestScheduler();
+ public final FakeNetworkStateProvider networkStateProvider = new FakeNetworkStateProvider();
+ public final TestScheduler scheduler = new TestScheduler(networkStateProvider);
public final StoragePathProvider storagePathProvider = new StoragePathProvider();
public final StubBarcodeViewDecoder stubBarcodeViewDecoder = new StubBarcodeViewDecoder();
@@ -34,4 +37,9 @@ public Scheduler providesScheduler(WorkManager workManager) {
public BarcodeViewDecoder providesBarcodeViewDecoder() {
return stubBarcodeViewDecoder;
}
+
+ @Override
+ public NetworkStateProvider providesNetworkStateProvider(Context context) {
+ return networkStateProvider;
+ }
}
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/TestScheduler.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/TestScheduler.kt
index 6222acad5b7..6a3c2092c72 100644
--- a/collect_app/src/androidTest/java/org/odk/collect/android/support/TestScheduler.kt
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/TestScheduler.kt
@@ -11,11 +11,12 @@ import org.odk.collect.async.Cancellable
import org.odk.collect.async.CoroutineAndWorkManagerScheduler
import org.odk.collect.async.Scheduler
import org.odk.collect.async.TaskSpec
+import org.odk.collect.async.network.NetworkStateProvider
import java.util.function.Consumer
import java.util.function.Supplier
import kotlin.coroutines.CoroutineContext
-class TestScheduler : Scheduler, CoroutineDispatcher() {
+class TestScheduler(private val networkStateProvider: NetworkStateProvider) : Scheduler, CoroutineDispatcher() {
private val wrappedScheduler: Scheduler
private val lock = Any()
@@ -55,7 +56,8 @@ class TestScheduler : Scheduler, CoroutineDispatcher() {
inputData: Map,
networkConstraint: Scheduler.NetworkType?
) {
- deferredTasks.add(DeferredTask(tag, spec, null, inputData))
+ cancelDeferred(tag)
+ deferredTasks.add(DeferredTask(tag, spec, null, inputData, networkConstraint))
}
override fun networkDeferredRepeat(
@@ -65,7 +67,7 @@ class TestScheduler : Scheduler, CoroutineDispatcher() {
inputData: Map
) {
cancelDeferred(tag)
- deferredTasks.add(DeferredTask(tag, spec, repeatPeriod, inputData))
+ deferredTasks.add(DeferredTask(tag, spec, repeatPeriod, inputData, null))
}
override fun cancelDeferred(tag: String) {
@@ -77,13 +79,18 @@ class TestScheduler : Scheduler, CoroutineDispatcher() {
}
fun runDeferredTasks() {
- val applicationContext = ApplicationProvider.getApplicationContext()
- for (deferredTask in deferredTasks) {
- deferredTask.spec.getTask(applicationContext, deferredTask.inputData, true).get()
+ if (networkStateProvider.isDeviceOnline) {
+ val applicationContext = ApplicationProvider.getApplicationContext()
+ deferredTasks.removeIf { deferredTask ->
+ if (deferredTask.networkConstraint == null || deferredTask.networkConstraint == networkStateProvider.currentNetwork) {
+ deferredTask.spec.getTask(applicationContext, deferredTask.inputData, true)
+ .get()
+ deferredTask.repeatPeriod == null
+ } else {
+ false
+ }
+ }
}
-
- // Remove non repeating tasks
- deferredTasks.removeIf { deferredTask: DeferredTask -> deferredTask.repeatPeriod == null }
}
fun setFinishedCallback(callback: Runnable?) {
@@ -130,6 +137,7 @@ class TestScheduler : Scheduler, CoroutineDispatcher() {
val tag: String,
val spec: TaskSpec,
val repeatPeriod: Long?,
- val inputData: Map
+ val inputData: Map,
+ val networkConstraint: Scheduler.NetworkType?
)
}
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java
index 5741b6b8d62..aa5c7b9b893 100644
--- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java
@@ -191,12 +191,12 @@ public MainMenuPage enableMatchExactly() {
.pressBack(new MainMenuPage());
}
- public MainMenuPage enableAutoSend(TestScheduler scheduler) {
+ public MainMenuPage enableAutoSend(TestScheduler scheduler, int setting) {
MainMenuPage mainMenuPage = openProjectSettingsDialog()
.clickSettings()
.clickFormManagement()
.clickOnString(org.odk.collect.strings.R.string.autosend)
- .clickOnString(org.odk.collect.strings.R.string.wifi_cellular_autosend)
+ .clickOnString(setting)
.pressBack(new ProjectSettingsPage())
.pressBack(new MainMenuPage());
diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/FormDownloadListActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/FormDownloadListActivity.java
index 93d26061120..1693ff3a603 100644
--- a/collect_app/src/main/java/org/odk/collect/android/activities/FormDownloadListActivity.java
+++ b/collect_app/src/main/java/org/odk/collect/android/activities/FormDownloadListActivity.java
@@ -57,7 +57,7 @@
import org.odk.collect.android.utilities.DialogUtils;
import org.odk.collect.android.utilities.WebCredentialsUtils;
import org.odk.collect.android.views.DayNightProgressDialog;
-import org.odk.collect.androidshared.network.NetworkStateProvider;
+import org.odk.collect.async.network.NetworkStateProvider;
import org.odk.collect.androidshared.ui.DialogFragmentUtils;
import org.odk.collect.androidshared.ui.ToastUtils;
import org.odk.collect.forms.FormSourceException;
diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt
index 679f8a51723..fd9c6007091 100644
--- a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt
@@ -19,6 +19,7 @@ import org.odk.collect.android.formentry.backgroundlocation.BackgroundLocationMa
import org.odk.collect.android.formentry.backgroundlocation.BackgroundLocationViewModel
import org.odk.collect.android.formentry.saving.DiskFormSaver
import org.odk.collect.android.formentry.saving.FormSaveViewModel
+import org.odk.collect.android.instancemanagement.InstancesDataService
import org.odk.collect.android.instancemanagement.autosend.AutoSendSettingsProvider
import org.odk.collect.android.projects.ProjectsDataService
import org.odk.collect.android.utilities.ApplicationConstants
@@ -55,7 +56,8 @@ class FormEntryViewModelFactory(
private val instancesRepositoryProvider: InstancesRepositoryProvider,
private val savepointsRepositoryProvider: SavepointsRepositoryProvider,
private val qrCodeCreator: QRCodeCreator,
- private val htmlPrinter: HtmlPrinter
+ private val htmlPrinter: HtmlPrinter,
+ private val instancesDataService: InstancesDataService
) : AbstractSavedStateViewModelFactory(owner, null) {
override fun create(
@@ -86,7 +88,8 @@ class FormEntryViewModelFactory(
formSessionRepository.get(sessionId),
entitiesRepositoryProvider.get(projectId),
instancesRepositoryProvider.get(projectId),
- savepointsRepositoryProvider.get(projectId)
+ savepointsRepositoryProvider.get(projectId),
+ instancesDataService
)
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java
index 93f0c63cc96..b06279e6fbc 100644
--- a/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java
+++ b/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java
@@ -88,7 +88,6 @@
import org.odk.collect.android.audio.AudioControllerView;
import org.odk.collect.android.audio.AudioRecordingControllerFragment;
import org.odk.collect.android.audio.M4AAppender;
-import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler;
import org.odk.collect.android.dao.helpers.InstancesDaoHelper;
import org.odk.collect.android.entities.EntitiesRepositoryProvider;
import org.odk.collect.android.exception.JavaRosaException;
@@ -135,6 +134,7 @@
import org.odk.collect.android.fragments.dialogs.NumberPickerDialog;
import org.odk.collect.android.fragments.dialogs.RankingWidgetDialog;
import org.odk.collect.android.fragments.dialogs.SelectMinimalDialog;
+import org.odk.collect.android.instancemanagement.InstancesDataService;
import org.odk.collect.android.instancemanagement.autosend.AutoSendSettingsProvider;
import org.odk.collect.android.javarosawrapper.FailedValidationResult;
import org.odk.collect.android.javarosawrapper.FormController;
@@ -309,7 +309,7 @@ public void allowSwiping(boolean doSwipe) {
PropertyManager propertyManager;
@Inject
- InstanceSubmitScheduler instanceSubmitScheduler;
+ InstancesDataService instancesDataService;
@Inject
Scheduler scheduler;
@@ -433,7 +433,8 @@ public void onCreate(Bundle savedInstanceState) {
instancesRepositoryProvider,
new SavepointsRepositoryProvider(this, storagePathProvider),
new QRCodeCreatorImpl(),
- new HtmlPrinter()
+ new HtmlPrinter(),
+ instancesDataService
);
this.getSupportFragmentManager().setFragmentFactory(new FragmentFactoryBuilder()
@@ -1561,10 +1562,6 @@ private void handleSaveResult(FormSaveViewModel.SaveResult result) {
DialogFragmentUtils.dismissDialog(ChangesReasonPromptDialogFragment.class, getSupportFragmentManager());
if (result.getRequest().viewExiting()) {
- if (result.getRequest().shouldFinalize()) {
- instanceSubmitScheduler.scheduleSubmit(projectsDataService.getCurrentProject().getUuid());
- }
-
finishAndReturnInstance();
} else {
showShortToast(this, org.odk.collect.strings.R.string.data_saved_ok);
diff --git a/collect_app/src/main/java/org/odk/collect/android/application/Collect.java b/collect_app/src/main/java/org/odk/collect/android/application/Collect.java
index 602451ffeb0..b4ca27264d7 100644
--- a/collect_app/src/main/java/org/odk/collect/android/application/Collect.java
+++ b/collect_app/src/main/java/org/odk/collect/android/application/Collect.java
@@ -41,7 +41,7 @@
import org.odk.collect.android.utilities.LocaleHelper;
import org.odk.collect.androidshared.data.AppState;
import org.odk.collect.androidshared.data.StateStore;
-import org.odk.collect.androidshared.network.NetworkStateProvider;
+import org.odk.collect.async.network.NetworkStateProvider;
import org.odk.collect.androidshared.system.ExternalFilesUtils;
import org.odk.collect.async.Scheduler;
import org.odk.collect.audiorecorder.AudioRecorderDependencyComponent;
diff --git a/collect_app/src/main/java/org/odk/collect/android/application/initialization/FormUpdatesUpgrade.kt b/collect_app/src/main/java/org/odk/collect/android/application/initialization/ScheduledWorkUpgrade.kt
similarity index 57%
rename from collect_app/src/main/java/org/odk/collect/android/application/initialization/FormUpdatesUpgrade.kt
rename to collect_app/src/main/java/org/odk/collect/android/application/initialization/ScheduledWorkUpgrade.kt
index f3067e6e869..bb2b89d513d 100644
--- a/collect_app/src/main/java/org/odk/collect/android/application/initialization/FormUpdatesUpgrade.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/application/initialization/ScheduledWorkUpgrade.kt
@@ -1,14 +1,19 @@
package org.odk.collect.android.application.initialization
import org.odk.collect.android.backgroundwork.FormUpdateScheduler
+import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler
import org.odk.collect.async.Scheduler
import org.odk.collect.projects.ProjectsRepository
import org.odk.collect.upgrade.Upgrade
-class FormUpdatesUpgrade(
+/**
+ * Reschedule all background work to prevent problems with tag or class name changes etc
+ */
+class ScheduledWorkUpgrade(
private val scheduler: Scheduler,
private val projectsRepository: ProjectsRepository,
- private val formUpdateScheduler: FormUpdateScheduler
+ private val formUpdateScheduler: FormUpdateScheduler,
+ private val instanceSubmitScheduler: InstanceSubmitScheduler
) : Upgrade {
override fun key(): String? {
@@ -20,6 +25,8 @@ class FormUpdatesUpgrade(
projectsRepository.getAll().forEach {
formUpdateScheduler.scheduleUpdates(it.uuid)
+ instanceSubmitScheduler.scheduleAutoSend(it.uuid)
+ instanceSubmitScheduler.scheduleFormAutoSend(it.uuid)
}
}
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/application/initialization/upgrade/UpgradeInitializer.kt b/collect_app/src/main/java/org/odk/collect/android/application/initialization/upgrade/UpgradeInitializer.kt
index c5769313eb4..d7e24270755 100644
--- a/collect_app/src/main/java/org/odk/collect/android/application/initialization/upgrade/UpgradeInitializer.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/application/initialization/upgrade/UpgradeInitializer.kt
@@ -4,9 +4,9 @@ import android.content.Context
import org.odk.collect.android.BuildConfig
import org.odk.collect.android.application.initialization.ExistingProjectMigrator
import org.odk.collect.android.application.initialization.ExistingSettingsMigrator
-import org.odk.collect.android.application.initialization.FormUpdatesUpgrade
import org.odk.collect.android.application.initialization.GoogleDriveProjectsDeleter
import org.odk.collect.android.application.initialization.SavepointsImporter
+import org.odk.collect.android.application.initialization.ScheduledWorkUpgrade
import org.odk.collect.settings.SettingsProvider
import org.odk.collect.settings.keys.MetaKeys
import org.odk.collect.upgrade.AppUpgrader
@@ -16,7 +16,7 @@ class UpgradeInitializer(
private val settingsProvider: SettingsProvider,
private val existingProjectMigrator: ExistingProjectMigrator,
private val existingSettingsMigrator: ExistingSettingsMigrator,
- private val formUpdatesUpgrade: FormUpdatesUpgrade,
+ private val scheduledWorkUpgrade: ScheduledWorkUpgrade,
private val googleDriveProjectsDeleter: GoogleDriveProjectsDeleter,
private val savepointsImporter: SavepointsImporter
) {
@@ -30,7 +30,7 @@ class UpgradeInitializer(
listOf(
existingProjectMigrator,
existingSettingsMigrator,
- formUpdatesUpgrade,
+ scheduledWorkUpgrade,
googleDriveProjectsDeleter,
savepointsImporter
)
diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoUpdateTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoUpdateTaskSpec.kt
index 4588d142eb7..067baeedc4c 100644
--- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoUpdateTaskSpec.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoUpdateTaskSpec.kt
@@ -17,11 +17,9 @@ package org.odk.collect.android.backgroundwork
import android.content.Context
import androidx.work.BackoffPolicy
-import androidx.work.WorkerParameters
import org.odk.collect.android.formmanagement.FormsDataService
import org.odk.collect.android.injection.DaggerUtils
import org.odk.collect.async.TaskSpec
-import org.odk.collect.async.WorkerAdapter
import java.util.function.Supplier
import javax.inject.Inject
@@ -45,11 +43,4 @@ class AutoUpdateTaskSpec : TaskSpec {
}
}
}
-
- override fun getWorkManagerAdapter(): Class {
- return Adapter::class.java
- }
-
- class Adapter(context: Context, workerParams: WorkerParameters) :
- WorkerAdapter(AutoUpdateTaskSpec(), context, workerParams)
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitScheduler.java b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitScheduler.java
index 4b7229b095d..1ae95cf086c 100644
--- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitScheduler.java
+++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitScheduler.java
@@ -68,19 +68,31 @@ public void cancelUpdates(String projectId) {
}
@Override
- public void scheduleSubmit(String projectId) {
- Scheduler.NetworkType networkType = null;
+ public void scheduleAutoSend(String projectId) {
+ Scheduler.NetworkType networkConstraint;
Settings settings = settingsProvider.getUnprotectedSettings(projectId);
AutoSend autoSendSetting = StringIdEnumUtils.getAutoSend(settings, application);
if (autoSendSetting == AutoSend.WIFI_ONLY) {
- networkType = Scheduler.NetworkType.WIFI;
+ networkConstraint = Scheduler.NetworkType.WIFI;
} else if (autoSendSetting == AutoSend.CELLULAR_ONLY) {
- networkType = Scheduler.NetworkType.CELLULAR;
+ networkConstraint = Scheduler.NetworkType.CELLULAR;
+ } else if (autoSendSetting == AutoSend.WIFI_AND_CELLULAR) {
+ networkConstraint = null;
+ } else {
+ return;
}
HashMap inputData = new HashMap<>();
inputData.put(TaskData.DATA_PROJECT_ID, projectId);
- scheduler.networkDeferred(getAutoSendTag(projectId), new AutoSendTaskSpec(), inputData, networkType);
+ scheduler.networkDeferred(getAutoSendTag(projectId), new SendFormsTaskSpec(), inputData, networkConstraint);
+ }
+
+ @Override
+ public void scheduleFormAutoSend(String projectId) {
+ HashMap inputData = new HashMap<>();
+ inputData.put(TaskData.DATA_PROJECT_ID, projectId);
+ inputData.put(TaskData.DATA_FORM_AUTO_SEND, "");
+ scheduler.networkDeferred(getAutoSendFormTag(projectId), new SendFormsTaskSpec(), inputData, null);
}
@Override
@@ -93,6 +105,10 @@ public String getAutoSendTag(String projectId) {
return "AutoSendWorker:" + projectId;
}
+ public String getAutoSendFormTag(String projectId) {
+ return "auto_send_form:" + projectId;
+ }
+
@NotNull
private String getMatchExactlyTag(String projectId) {
return "match_exactly:" + projectId;
diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/InstanceSubmitScheduler.java b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/InstanceSubmitScheduler.java
index a2a16a6dc12..651750bbe99 100644
--- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/InstanceSubmitScheduler.java
+++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/InstanceSubmitScheduler.java
@@ -2,7 +2,9 @@
public interface InstanceSubmitScheduler {
- void scheduleSubmit(String projectId);
+ void scheduleAutoSend(String projectId);
+
+ void scheduleFormAutoSend(String projectId);
void cancelSubmit(String projectId);
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt
similarity index 76%
rename from collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendTaskSpec.kt
rename to collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt
index 6fbe7700c52..6cedcb5aeb9 100644
--- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendTaskSpec.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt
@@ -15,19 +15,17 @@ package org.odk.collect.android.backgroundwork
import android.content.Context
import androidx.work.BackoffPolicy
-import androidx.work.WorkerParameters
import org.odk.collect.android.injection.DaggerUtils
import org.odk.collect.android.instancemanagement.InstancesDataService
import org.odk.collect.async.TaskSpec
-import org.odk.collect.async.WorkerAdapter
import java.util.function.Supplier
import javax.inject.Inject
-class AutoSendTaskSpec : TaskSpec {
+class SendFormsTaskSpec : TaskSpec {
@Inject
lateinit var instancesDataService: InstancesDataService
- override val maxRetries: Int? = null
+ override val maxRetries: Int = 13 // Stop trying when backoff is > 5 days
override val backoffPolicy = BackoffPolicy.EXPONENTIAL
override val backoffDelay: Long = 60_000
@@ -35,18 +33,16 @@ class AutoSendTaskSpec : TaskSpec {
DaggerUtils.getComponent(context).inject(this)
return Supplier {
val projectId = inputData[TaskData.DATA_PROJECT_ID]
+ val formAutoSend = inputData[TaskData.DATA_FORM_AUTO_SEND] != null
if (projectId != null) {
- instancesDataService.autoSendInstances(projectId)
+ if (formAutoSend) {
+ instancesDataService.sendInstances(projectId, formAutoSend = true)
+ } else {
+ instancesDataService.sendInstances(projectId)
+ }
} else {
throw IllegalArgumentException("No project ID provided!")
}
}
}
-
- override fun getWorkManagerAdapter(): Class {
- return Adapter::class.java
- }
-
- class Adapter(context: Context, workerParams: WorkerParameters) :
- WorkerAdapter(AutoSendTaskSpec(), context, workerParams)
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SyncFormsTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SyncFormsTaskSpec.kt
index 8e39bb62d0b..36d286ac0eb 100644
--- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SyncFormsTaskSpec.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SyncFormsTaskSpec.kt
@@ -2,11 +2,9 @@ package org.odk.collect.android.backgroundwork
import android.content.Context
import androidx.work.BackoffPolicy
-import androidx.work.WorkerParameters
import org.odk.collect.android.formmanagement.FormsDataService
import org.odk.collect.android.injection.DaggerUtils
import org.odk.collect.async.TaskSpec
-import org.odk.collect.async.WorkerAdapter
import java.util.function.Supplier
import javax.inject.Inject
@@ -29,11 +27,4 @@ class SyncFormsTaskSpec : TaskSpec {
}
}
}
-
- override fun getWorkManagerAdapter(): Class {
- return Adapter::class.java
- }
-
- class Adapter(context: Context, workerParams: WorkerParameters) :
- WorkerAdapter(SyncFormsTaskSpec(), context, workerParams)
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/TaskData.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/TaskData.kt
index cc95ff0a663..3d71dac0d42 100644
--- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/TaskData.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/TaskData.kt
@@ -2,4 +2,5 @@ package org.odk.collect.android.backgroundwork
object TaskData {
const val DATA_PROJECT_ID = "projectId"
+ const val DATA_FORM_AUTO_SEND = "formAutoSend"
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/formentry/saving/FormSaveViewModel.java b/collect_app/src/main/java/org/odk/collect/android/formentry/saving/FormSaveViewModel.java
index 4b531f21aee..f31e6be7281 100644
--- a/collect_app/src/main/java/org/odk/collect/android/formentry/saving/FormSaveViewModel.java
+++ b/collect_app/src/main/java/org/odk/collect/android/formentry/saving/FormSaveViewModel.java
@@ -22,6 +22,7 @@
import org.odk.collect.android.formentry.FormSession;
import org.odk.collect.android.formentry.audit.AuditEvent;
import org.odk.collect.android.formentry.audit.AuditUtils;
+import org.odk.collect.android.instancemanagement.InstancesDataService;
import org.odk.collect.android.javarosawrapper.FormController;
import org.odk.collect.android.projects.ProjectsDataService;
import org.odk.collect.android.tasks.SaveFormToDisk;
@@ -90,12 +91,13 @@ public class FormSaveViewModel extends ViewModel implements MaterialProgressDial
private Form form;
private Instance instance;
private final Cancellable formSessionObserver;
+ private InstancesDataService instancesDataService;
public FormSaveViewModel(SavedStateHandle stateHandle, Supplier clock, FormSaver formSaver,
MediaUtils mediaUtils, Scheduler scheduler, AudioRecorder audioRecorder,
ProjectsDataService projectsDataService, LiveData formSession,
EntitiesRepository entitiesRepository, InstancesRepository instancesRepository,
- SavepointsRepository savepointsRepository
+ SavepointsRepository savepointsRepository, InstancesDataService instancesDataService
) {
this.stateHandle = stateHandle;
this.clock = clock;
@@ -107,6 +109,7 @@ public FormSaveViewModel(SavedStateHandle stateHandle, Supplier clock, For
this.entitiesRepository = entitiesRepository;
this.instancesRepository = instancesRepository;
this.savepointsRepository = savepointsRepository;
+ this.instancesDataService = instancesDataService;
if (stateHandle.get(ORIGINAL_FILES) != null) {
originalFiles = stateHandle.get(ORIGINAL_FILES);
@@ -268,6 +271,8 @@ private void handleTaskResult(SaveToDiskResult taskResult, SaveRequest saveReque
if (saveRequest.shouldFinalize) {
formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_EXIT, false, clock.get());
formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_FINALIZE, true, clock.get());
+
+ instancesDataService.instanceFinalized(projectsDataService.getCurrentProject().getUuid(), form);
} else {
formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_EXIT, true, clock.get());
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/formhierarchy/FormHierarchyActivity.java b/collect_app/src/main/java/org/odk/collect/android/formhierarchy/FormHierarchyActivity.java
index 9805ccaf458..77d4f5ff444 100644
--- a/collect_app/src/main/java/org/odk/collect/android/formhierarchy/FormHierarchyActivity.java
+++ b/collect_app/src/main/java/org/odk/collect/android/formhierarchy/FormHierarchyActivity.java
@@ -54,6 +54,7 @@
import org.odk.collect.android.formentry.ODKView;
import org.odk.collect.android.formentry.repeats.DeleteRepeatDialogFragment;
import org.odk.collect.android.injection.DaggerUtils;
+import org.odk.collect.android.instancemanagement.InstancesDataService;
import org.odk.collect.android.instancemanagement.autosend.AutoSendSettingsProvider;
import org.odk.collect.android.javarosawrapper.FormController;
import org.odk.collect.android.javarosawrapper.JavaRosaFormController;
@@ -195,6 +196,9 @@ public class FormHierarchyActivity extends LocalizedActivity implements DeleteRe
@Inject
public StoragePathProvider storagePathProvider;
+ @Inject
+ public InstancesDataService instancesDataService;
+
protected final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
@@ -230,7 +234,8 @@ public void onCreate(Bundle savedInstanceState) {
instancesRepositoryProvider,
new SavepointsRepositoryProvider(this, storagePathProvider),
new QRCodeCreatorImpl(),
- new HtmlPrinter()
+ new HtmlPrinter(),
+ instancesDataService
);
this.getSupportFragmentManager().setFragmentFactory(new FragmentFactoryBuilder()
diff --git a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt
index bbd4b92f2f9..2b17b3226a8 100644
--- a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt
@@ -14,9 +14,9 @@ import org.odk.collect.android.activities.FormMapActivity
import org.odk.collect.android.formmanagement.FormFillingIntentFactory
import org.odk.collect.android.injection.DaggerUtils
import org.odk.collect.android.preferences.dialogs.ServerAuthDialogFragment
-import org.odk.collect.androidshared.network.NetworkStateProvider
import org.odk.collect.androidshared.ui.DialogFragmentUtils
import org.odk.collect.androidshared.ui.SnackbarUtils
+import org.odk.collect.async.network.NetworkStateProvider
import org.odk.collect.lists.EmptyListView
import org.odk.collect.lists.RecyclerViewUtils
import org.odk.collect.permissions.PermissionListener
diff --git a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProvider.kt b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProvider.kt
index 5fb459ed6be..502ff038e32 100644
--- a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProvider.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProvider.kt
@@ -9,9 +9,9 @@ import androidx.core.view.MenuProvider
import org.odk.collect.android.R
import org.odk.collect.android.formlists.sorting.FormListSortingBottomSheetDialog
import org.odk.collect.android.formlists.sorting.FormListSortingOption
-import org.odk.collect.androidshared.network.NetworkStateProvider
import org.odk.collect.androidshared.ui.ToastUtils
import org.odk.collect.androidshared.ui.multiclicksafe.MultiClickGuard
+import org.odk.collect.async.network.NetworkStateProvider
class BlankFormListMenuProvider(
private val activity: ComponentActivity,
diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyComponent.java b/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyComponent.java
index 33299d6cf6b..245cf06bada 100644
--- a/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyComponent.java
+++ b/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyComponent.java
@@ -16,7 +16,7 @@
import org.odk.collect.android.application.initialization.ExistingProjectMigrator;
import org.odk.collect.android.audio.AudioRecordingControllerFragment;
import org.odk.collect.android.audio.AudioRecordingErrorDialogFragment;
-import org.odk.collect.android.backgroundwork.AutoSendTaskSpec;
+import org.odk.collect.android.backgroundwork.SendFormsTaskSpec;
import org.odk.collect.android.backgroundwork.AutoUpdateTaskSpec;
import org.odk.collect.android.backgroundwork.SyncFormsTaskSpec;
import org.odk.collect.android.configure.qr.QRCodeScannerFragment;
@@ -78,7 +78,7 @@
import org.odk.collect.android.utilities.ThemeUtils;
import org.odk.collect.android.widgets.QuestionWidget;
import org.odk.collect.android.widgets.items.SelectOneFromMapDialogFragment;
-import org.odk.collect.androidshared.network.NetworkStateProvider;
+import org.odk.collect.async.network.NetworkStateProvider;
import org.odk.collect.async.Scheduler;
import org.odk.collect.draw.DrawActivity;
import org.odk.collect.googlemaps.GoogleMapFragment;
@@ -169,7 +169,7 @@ interface Builder {
void inject(ShowQRCodeFragment showQRCodeFragment);
- void inject(AutoSendTaskSpec autoSendTaskSpec);
+ void inject(SendFormsTaskSpec sendFormsTaskSpec);
void inject(AdminPasswordDialogFragment adminPasswordDialogFragment);
diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java b/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java
index c1acd235054..77da92bd5d7 100644
--- a/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java
+++ b/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java
@@ -29,10 +29,10 @@
import org.odk.collect.android.application.initialization.ApplicationInitializer;
import org.odk.collect.android.application.initialization.ExistingProjectMigrator;
import org.odk.collect.android.application.initialization.ExistingSettingsMigrator;
-import org.odk.collect.android.application.initialization.FormUpdatesUpgrade;
import org.odk.collect.android.application.initialization.GoogleDriveProjectsDeleter;
import org.odk.collect.android.application.initialization.MapsInitializer;
import org.odk.collect.android.application.initialization.SavepointsImporter;
+import org.odk.collect.android.application.initialization.ScheduledWorkUpgrade;
import org.odk.collect.android.application.initialization.upgrade.UpgradeInitializer;
import org.odk.collect.android.backgroundwork.FormUpdateAndInstanceSubmitScheduler;
import org.odk.collect.android.backgroundwork.FormUpdateScheduler;
@@ -95,8 +95,8 @@
import org.odk.collect.android.version.VersionInformation;
import org.odk.collect.android.views.BarcodeViewDecoder;
import org.odk.collect.androidshared.bitmap.ImageCompressor;
-import org.odk.collect.androidshared.network.ConnectivityProvider;
-import org.odk.collect.androidshared.network.NetworkStateProvider;
+import org.odk.collect.async.network.ConnectivityProvider;
+import org.odk.collect.async.network.NetworkStateProvider;
import org.odk.collect.androidshared.system.IntentLauncher;
import org.odk.collect.androidshared.system.IntentLauncherImpl;
import org.odk.collect.androidshared.utils.ScreenUtils;
@@ -443,7 +443,7 @@ public InstancesDataService providesInstancesDataService(Application application
return null;
};
- return new InstancesDataService(application, instanceSubmitScheduler, projectsDependencyProviderFactory, notifier, propertyManager, httpInterface, onUpdate);
+ return new InstancesDataService(getState(application), instanceSubmitScheduler, projectsDependencyProviderFactory, notifier, propertyManager, httpInterface, onUpdate);
}
@Provides
@@ -522,8 +522,8 @@ public ExistingProjectMigrator providesExistingProjectMigrator(Context context,
}
@Provides
- public FormUpdatesUpgrade providesFormUpdatesUpgrader(Scheduler scheduler, ProjectsRepository projectsRepository, FormUpdateScheduler formUpdateScheduler) {
- return new FormUpdatesUpgrade(scheduler, projectsRepository, formUpdateScheduler);
+ public ScheduledWorkUpgrade providesFormUpdatesUpgrader(Scheduler scheduler, ProjectsRepository projectsRepository, FormUpdateScheduler formUpdateScheduler, InstanceSubmitScheduler instanceSubmitScheduler) {
+ return new ScheduledWorkUpgrade(scheduler, projectsRepository, formUpdateScheduler, instanceSubmitScheduler);
}
@Provides
@@ -537,13 +537,13 @@ public GoogleDriveProjectsDeleter providesGoogleDriveProjectsDeleter(ProjectsRep
}
@Provides
- public UpgradeInitializer providesUpgradeInitializer(Context context, SettingsProvider settingsProvider, ExistingProjectMigrator existingProjectMigrator, ExistingSettingsMigrator existingSettingsMigrator, FormUpdatesUpgrade formUpdatesUpgrade, GoogleDriveProjectsDeleter googleDriveProjectsDeleter, ProjectsRepository projectsRepository, ProjectDependencyProviderFactory projectDependencyProviderFactory) {
+ public UpgradeInitializer providesUpgradeInitializer(Context context, SettingsProvider settingsProvider, ExistingProjectMigrator existingProjectMigrator, ExistingSettingsMigrator existingSettingsMigrator, ScheduledWorkUpgrade scheduledWorkUpgrade, GoogleDriveProjectsDeleter googleDriveProjectsDeleter, ProjectsRepository projectsRepository, ProjectDependencyProviderFactory projectDependencyProviderFactory) {
return new UpgradeInitializer(
context,
settingsProvider,
existingProjectMigrator,
existingSettingsMigrator,
- formUpdatesUpgrade,
+ scheduledWorkUpgrade,
googleDriveProjectsDeleter,
new SavepointsImporter(projectsRepository, projectDependencyProviderFactory)
);
diff --git a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/InstancesDataService.kt b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/InstancesDataService.kt
index 1ad666cd226..310bda069c2 100644
--- a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/InstancesDataService.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/InstancesDataService.kt
@@ -1,6 +1,5 @@
package org.odk.collect.android.instancemanagement
-import android.app.Application
import androidx.lifecycle.LiveData
import kotlinx.coroutines.flow.Flow
import org.odk.collect.analytics.Analytics
@@ -15,13 +14,14 @@ import org.odk.collect.android.openrosa.OpenRosaHttpInterface
import org.odk.collect.android.projects.ProjectDependencyProviderFactory
import org.odk.collect.android.utilities.ExternalizableFormDefCache
import org.odk.collect.android.utilities.FormsUploadResultInterpreter
-import org.odk.collect.androidshared.data.getState
+import org.odk.collect.androidshared.data.AppState
+import org.odk.collect.forms.Form
import org.odk.collect.forms.instances.Instance
import org.odk.collect.metadata.PropertyManager
import java.io.File
class InstancesDataService(
- private val application: Application,
+ private val appState: AppState,
private val instanceSubmitScheduler: InstanceSubmitScheduler,
private val projectDependencyProviderFactory: ProjectDependencyProviderFactory,
private val notifier: Notifier,
@@ -29,7 +29,6 @@ class InstancesDataService(
private val httpInterface: OpenRosaHttpInterface,
private val onUpdate: () -> Unit
) {
- private val appState = application.getState()
val editableCount: LiveData = appState.getLive(EDITABLE_COUNT_KEY, 0)
val sendableCount: LiveData = appState.getLive(SENDABLE_COUNT_KEY, 0)
val sentCount: LiveData = appState.getLive(SENT_COUNT_KEY, 0)
@@ -122,6 +121,7 @@ class InstancesDataService(
if (finalizedInstance == null) {
result.copy(failureCount = result.failureCount + 1)
} else {
+ instanceFinalized(projectId, form)
result
}
}
@@ -133,7 +133,6 @@ class InstancesDataService(
}
update(projectId)
- instanceSubmitScheduler.scheduleSubmit(projectId)
return result.copy(successCount = instances.size - result.failureCount)
}
@@ -178,7 +177,7 @@ class InstancesDataService(
}
}
- fun autoSendInstances(projectId: String): Boolean {
+ fun sendInstances(projectId: String, formAutoSend: Boolean = false): Boolean {
val projectDependencyProvider =
projectDependencyProviderFactory.create(projectId)
@@ -195,10 +194,9 @@ class InstancesDataService(
).withLock { acquiredLock: Boolean ->
if (acquiredLock) {
val toUpload = InstanceAutoSendFetcher.getInstancesToAutoSend(
- application,
projectDependencyProvider.instancesRepository,
projectDependencyProvider.formsRepository,
- projectDependencyProvider.settingsProvider
+ formAutoSend
)
if (toUpload.isNotEmpty()) {
@@ -216,6 +214,14 @@ class InstancesDataService(
}
}
+ fun instanceFinalized(projectId: String, form: Form) {
+ if (form.autoSend != null && form.autoSend == "true") {
+ instanceSubmitScheduler.scheduleFormAutoSend(projectId)
+ } else {
+ instanceSubmitScheduler.scheduleAutoSend(projectId)
+ }
+ }
+
companion object {
private const val EDITABLE_COUNT_KEY = "instancesEditableCount"
private const val SENDABLE_COUNT_KEY = "instancesSendableCount"
diff --git a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProvider.kt b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProvider.kt
index 814c60c4da9..46c4eadb201 100644
--- a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProvider.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProvider.kt
@@ -1,8 +1,8 @@
package org.odk.collect.android.instancemanagement.autosend
import android.app.Application
-import android.net.ConnectivityManager
-import org.odk.collect.androidshared.network.NetworkStateProvider
+import org.odk.collect.async.Scheduler
+import org.odk.collect.async.network.NetworkStateProvider
import org.odk.collect.settings.SettingsProvider
import org.odk.collect.settings.enums.AutoSend
import org.odk.collect.settings.enums.StringIdEnumUtils.getAutoSend
@@ -14,7 +14,7 @@ class AutoSendSettingsProvider(
) {
fun isAutoSendEnabledInSettings(projectId: String? = null): Boolean {
- val currentNetworkInfo = networkStateProvider.networkInfo ?: return false
+ val currentNetworkType = networkStateProvider.currentNetwork ?: return false
val autosend = settingsProvider.getUnprotectedSettings(projectId).getAutoSend(application)
var sendwifi = autosend == AutoSend.WIFI_ONLY
@@ -25,7 +25,7 @@ class AutoSendSettingsProvider(
sendnetwork = true
}
- return currentNetworkInfo.type == ConnectivityManager.TYPE_WIFI &&
- sendwifi || currentNetworkInfo.type == ConnectivityManager.TYPE_MOBILE && sendnetwork
+ return currentNetworkType == Scheduler.NetworkType.WIFI &&
+ sendwifi || currentNetworkType == Scheduler.NetworkType.CELLULAR && sendnetwork
}
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcher.kt b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcher.kt
index eaed1ca48ff..25ff079ade1 100644
--- a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcher.kt
+++ b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcher.kt
@@ -1,33 +1,31 @@
package org.odk.collect.android.instancemanagement.autosend
-import android.app.Application
+import org.odk.collect.forms.Form
import org.odk.collect.forms.FormsRepository
import org.odk.collect.forms.instances.Instance
import org.odk.collect.forms.instances.InstancesRepository
-import org.odk.collect.settings.SettingsProvider
-import org.odk.collect.settings.enums.AutoSend
-import org.odk.collect.settings.enums.StringIdEnumUtils.getAutoSend
object InstanceAutoSendFetcher {
fun getInstancesToAutoSend(
- application: Application,
instancesRepository: InstancesRepository,
formsRepository: FormsRepository,
- settingsProvider: SettingsProvider
+ formAutoSend: Boolean = false
): List {
val allFinalizedForms = instancesRepository.getAllByStatus(
Instance.STATUS_COMPLETE,
Instance.STATUS_SUBMISSION_FAILED
)
- val autoSendSetting =
- settingsProvider.getUnprotectedSettings().getAutoSend(application)
+ val filter: (Form) -> Boolean = if (formAutoSend) {
+ { form -> form.autoSend != null && form.autoSend == "true" }
+ } else {
+ { form -> form.autoSend == null }
+ }
return allFinalizedForms.filter {
- formsRepository.getLatestByFormIdAndVersion(it.formId, it.formVersion)?.let { form ->
- form.shouldFormBeSentAutomatically(autoSendSetting != AutoSend.OFF)
- } ?: false
+ formsRepository.getLatestByFormIdAndVersion(it.formId, it.formVersion)
+ ?.let { form -> filter(form) } ?: false
}
}
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/send/InstanceUploaderListActivity.java b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/send/InstanceUploaderListActivity.java
index c499dfdf88d..2850da8e6f2 100644
--- a/collect_app/src/main/java/org/odk/collect/android/instancemanagement/send/InstanceUploaderListActivity.java
+++ b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/send/InstanceUploaderListActivity.java
@@ -63,7 +63,7 @@
import org.odk.collect.android.mainmenu.MainMenuActivity;
import org.odk.collect.android.preferences.screens.ProjectPreferencesActivity;
import org.odk.collect.android.projects.ProjectsDataService;
-import org.odk.collect.androidshared.network.NetworkStateProvider;
+import org.odk.collect.async.network.NetworkStateProvider;
import org.odk.collect.androidshared.ui.MenuExtKt;
import org.odk.collect.androidshared.ui.ToastUtils;
import org.odk.collect.androidshared.ui.multiclicksafe.MultiClickGuard;
diff --git a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragment.java b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragment.java
index 867b695984d..7828144fba0 100644
--- a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragment.java
+++ b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragment.java
@@ -83,7 +83,7 @@ public void onSettingChanged(@NotNull String key) {
}
if (key.equals(KEY_AUTOSEND) && !StringIdEnumUtils.getAutoSend(settingsProvider.getUnprotectedSettings(), requireContext()).equals(AutoSend.OFF)) {
- instanceSubmitScheduler.scheduleSubmit(projectsDataService.getCurrentProject().getUuid());
+ instanceSubmitScheduler.scheduleAutoSend(projectsDataService.getCurrentProject().getUuid());
}
}
diff --git a/collect_app/src/test/java/org/odk/collect/android/application/initialization/FormUpdatesUpgradeTest.kt b/collect_app/src/test/java/org/odk/collect/android/application/initialization/FormUpdatesUpgradeTest.kt
deleted file mode 100644
index 35c3de430f8..00000000000
--- a/collect_app/src/test/java/org/odk/collect/android/application/initialization/FormUpdatesUpgradeTest.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.odk.collect.android.application.initialization
-
-import org.junit.Test
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
-import org.odk.collect.android.backgroundwork.FormUpdateScheduler
-import org.odk.collect.async.Scheduler
-import org.odk.collect.projects.InMemProjectsRepository
-import org.odk.collect.projects.Project
-
-class FormUpdatesUpgradeTest {
-
- @Test
- fun `cancels all existing background jobs`() {
- val scheduler = mock()
- val formUpdatesUpgrade = FormUpdatesUpgrade(scheduler, InMemProjectsRepository(), mock())
-
- formUpdatesUpgrade.run()
- verify(scheduler).cancelAllDeferred()
- }
-
- @Test
- fun `schedules updates for all projects`() {
- val projectsRepository = InMemProjectsRepository()
- val project1 = projectsRepository.save(Project.New("1", "1", "#ffffff"))
- val project2 = projectsRepository.save(Project.New("2", "2", "#ffffff"))
-
- val formUpdateScheduler = mock()
- val formUpdatesUpgrade = FormUpdatesUpgrade(mock(), projectsRepository, formUpdateScheduler)
-
- formUpdatesUpgrade.run()
- verify(formUpdateScheduler).scheduleUpdates(project1.uuid)
- verify(formUpdateScheduler).scheduleUpdates(project2.uuid)
- }
-}
diff --git a/collect_app/src/test/java/org/odk/collect/android/application/initialization/ScheduledWorkUpgradeTest.kt b/collect_app/src/test/java/org/odk/collect/android/application/initialization/ScheduledWorkUpgradeTest.kt
new file mode 100644
index 00000000000..984d2164725
--- /dev/null
+++ b/collect_app/src/test/java/org/odk/collect/android/application/initialization/ScheduledWorkUpgradeTest.kt
@@ -0,0 +1,67 @@
+package org.odk.collect.android.application.initialization
+
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.odk.collect.android.backgroundwork.FormUpdateScheduler
+import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler
+import org.odk.collect.async.Scheduler
+import org.odk.collect.projects.InMemProjectsRepository
+import org.odk.collect.projects.Project
+
+class ScheduledWorkUpgradeTest {
+
+ @Test
+ fun `cancels all existing background jobs`() {
+ val scheduler = mock()
+ val scheduledWorkUpgrade = ScheduledWorkUpgrade(
+ scheduler,
+ InMemProjectsRepository(),
+ mock(),
+ mock()
+ )
+
+ scheduledWorkUpgrade.run()
+ verify(scheduler).cancelAllDeferred()
+ }
+
+ @Test
+ fun `schedules updates for all projects`() {
+ val projectsRepository = InMemProjectsRepository()
+ val project1 = projectsRepository.save(Project.New("1", "1", "#ffffff"))
+ val project2 = projectsRepository.save(Project.New("2", "2", "#ffffff"))
+
+ val formUpdateScheduler = mock()
+ val scheduledWorkUpgrade = ScheduledWorkUpgrade(
+ mock(),
+ projectsRepository,
+ formUpdateScheduler,
+ mock()
+ )
+
+ scheduledWorkUpgrade.run()
+ verify(formUpdateScheduler).scheduleUpdates(project1.uuid)
+ verify(formUpdateScheduler).scheduleUpdates(project2.uuid)
+ }
+
+ @Test
+ fun `schedules submits for all projects`() {
+ val projectsRepository = InMemProjectsRepository()
+ val project1 = projectsRepository.save(Project.New("1", "1", "#ffffff"))
+ val project2 = projectsRepository.save(Project.New("2", "2", "#ffffff"))
+
+ val instanceSubmitScheduler = mock()
+ val scheduledWorkUpgrade = ScheduledWorkUpgrade(
+ mock(),
+ projectsRepository,
+ mock(),
+ instanceSubmitScheduler
+ )
+
+ scheduledWorkUpgrade.run()
+ verify(instanceSubmitScheduler).scheduleAutoSend(project1.uuid)
+ verify(instanceSubmitScheduler).scheduleFormAutoSend(project1.uuid)
+ verify(instanceSubmitScheduler).scheduleAutoSend(project2.uuid)
+ verify(instanceSubmitScheduler).scheduleFormAutoSend(project2.uuid)
+ }
+}
diff --git a/collect_app/src/test/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitSchedulerTest.kt b/collect_app/src/test/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitSchedulerTest.kt
index b27fe87dc0b..545e5a622c4 100644
--- a/collect_app/src/test/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitSchedulerTest.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/backgroundwork/FormUpdateAndInstanceSubmitSchedulerTest.kt
@@ -9,6 +9,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoInteractions
import org.odk.collect.android.TestSettingsProvider
import org.odk.collect.async.Scheduler
import org.odk.collect.settings.enums.FormUpdateMode.MATCH_EXACTLY
@@ -83,50 +84,60 @@ class FormUpdateAndInstanceSubmitSchedulerTest {
}
@Test
- fun `scheduleSubmit passes current project ID`() {
+ fun `scheduleAutoSend passes current project ID`() {
settingsProvider.getUnprotectedSettings("myProject")
.save(ProjectKeys.KEY_AUTOSEND, "wifi_and_cellular")
val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application)
- manager.scheduleSubmit("myProject")
+ manager.scheduleAutoSend("myProject")
verify(scheduler).networkDeferred(
eq("AutoSendWorker:myProject"),
- any(),
+ any(),
eq(mapOf(TaskData.DATA_PROJECT_ID to "myProject")),
eq(null)
)
}
@Test
- fun `scheduleSubmit uses wifi network type when set in settings`() {
+ fun `scheduleAutoSend uses wifi network type when set in settings`() {
settingsProvider.getUnprotectedSettings("myProject")
.save(ProjectKeys.KEY_AUTOSEND, "wifi_only")
val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application)
- manager.scheduleSubmit("myProject")
+ manager.scheduleAutoSend("myProject")
verify(scheduler).networkDeferred(
eq("AutoSendWorker:myProject"),
- any(),
+ any(),
eq(mapOf(TaskData.DATA_PROJECT_ID to "myProject")),
eq(Scheduler.NetworkType.WIFI)
)
}
@Test
- fun `scheduleSubmit uses cellular network type when set in settings`() {
+ fun `scheduleAutoSend uses cellular network type when set in settings`() {
settingsProvider.getUnprotectedSettings("myProject")
.save(ProjectKeys.KEY_AUTOSEND, "cellular_only")
val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application)
- manager.scheduleSubmit("myProject")
+ manager.scheduleAutoSend("myProject")
verify(scheduler).networkDeferred(
eq("AutoSendWorker:myProject"),
- any(),
+ any(),
eq(mapOf(TaskData.DATA_PROJECT_ID to "myProject")),
eq(Scheduler.NetworkType.CELLULAR)
)
}
+ @Test
+ fun `scheduleAutoSend does nothing if auto send is disabled`() {
+ settingsProvider.getUnprotectedSettings("myProject")
+ .save(ProjectKeys.KEY_AUTOSEND, "off")
+ val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application)
+
+ manager.scheduleAutoSend("myProject")
+ verifyNoInteractions(scheduler)
+ }
+
@Test
fun `cancelSubmit cancels auto send for current project`() {
val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application)
diff --git a/collect_app/src/test/java/org/odk/collect/android/backgroundwork/AutoSendTaskSpecTest.kt b/collect_app/src/test/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpecTest.kt
similarity index 86%
rename from collect_app/src/test/java/org/odk/collect/android/backgroundwork/AutoSendTaskSpecTest.kt
rename to collect_app/src/test/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpecTest.kt
index 6cf2c3091bb..915ef9b1192 100644
--- a/collect_app/src/test/java/org/odk/collect/android/backgroundwork/AutoSendTaskSpecTest.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpecTest.kt
@@ -22,7 +22,7 @@ import org.odk.collect.metadata.PropertyManager
import org.odk.collect.testshared.RobolectricHelpers
@RunWith(AndroidJUnit4::class)
-class AutoSendTaskSpecTest {
+class SendFormsTaskSpecTest {
private val instancesDataService = mock()
private lateinit var projectId: String
@@ -47,27 +47,22 @@ class AutoSendTaskSpecTest {
projectId = CollectHelpers.setupDemoProject()
}
- @Test
- fun `maxRetries should not be limited`() {
- assertThat(AutoSendTaskSpec().maxRetries, equalTo(null))
- }
-
@Test
fun `returns false if sending instances fails`() {
- whenever(instancesDataService.autoSendInstances(projectId)).doReturn(false)
+ whenever(instancesDataService.sendInstances(projectId)).doReturn(false)
val inputData = mapOf(TaskData.DATA_PROJECT_ID to projectId)
- val spec = AutoSendTaskSpec()
+ val spec = SendFormsTaskSpec()
val task = spec.getTask(ApplicationProvider.getApplicationContext(), inputData, true)
assertThat(task.get(), equalTo(false))
}
@Test
fun `returns true if sending instances succeeds`() {
- whenever(instancesDataService.autoSendInstances(projectId)).doReturn(true)
+ whenever(instancesDataService.sendInstances(projectId)).doReturn(true)
val inputData = mapOf(TaskData.DATA_PROJECT_ID to projectId)
- val spec = AutoSendTaskSpec()
+ val spec = SendFormsTaskSpec()
val task = spec.getTask(ApplicationProvider.getApplicationContext(), inputData, true)
assertThat(task.get(), equalTo(true))
}
diff --git a/collect_app/src/test/java/org/odk/collect/android/formentry/audit/FormSaveViewModelTest.java b/collect_app/src/test/java/org/odk/collect/android/formentry/audit/FormSaveViewModelTest.java
index 585a3ca481f..a6d19e7d108 100644
--- a/collect_app/src/test/java/org/odk/collect/android/formentry/audit/FormSaveViewModelTest.java
+++ b/collect_app/src/test/java/org/odk/collect/android/formentry/audit/FormSaveViewModelTest.java
@@ -101,7 +101,7 @@ public void setup() {
when(projectsDataService.getCurrentProject()).thenReturn(Project.Companion.getDEMO_PROJECT());
formSession = new MutableLiveData<>(new FormSession(formController, form));
- viewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, audioRecorder, projectsDataService, formSession, entitiesRepository, instancesRepository, savepointsRepository);
+ viewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, audioRecorder, projectsDataService, formSession, entitiesRepository, instancesRepository, savepointsRepository, mock());
CollectHelpers.createDemoProject(); // Needed to deal with `new StoragePathProvider()` calls in `FormSaveViewModel`
}
@@ -386,7 +386,7 @@ public void deleteAnswerFile_whenAnswerFileHasAlreadyBeenDeleted_actuallyDeletes
public void deleteAnswerFile_whenAnswerFileHasAlreadyBeenDeleted_onRecreatingViewModel_actuallyDeletesNewFile() {
viewModel.deleteAnswerFile("index", "blah1");
- FormSaveViewModel restoredViewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, mock(AudioRecorder.class), projectsDataService, liveDataOf(new FormSession(formController, form)), entitiesRepository, instancesRepository, savepointsRepository);
+ FormSaveViewModel restoredViewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, mock(AudioRecorder.class), projectsDataService, liveDataOf(new FormSession(formController, form)), entitiesRepository, instancesRepository, savepointsRepository, mock());
restoredViewModel.deleteAnswerFile("index", "blah2");
verify(mediaUtils).deleteMediaFile("blah2");
@@ -408,7 +408,7 @@ public void replaceAnswerFile_whenAnswerFileHasAlreadyBeenReplaced_deletesPrevio
public void replaceAnswerFile_whenAnswerFileHasAlreadyBeenReplaced_afterRecreatingViewModel_deletesPreviousReplacement() {
viewModel.replaceAnswerFile("index", "blah1");
- FormSaveViewModel restoredViewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, mock(AudioRecorder.class), projectsDataService, liveDataOf(new FormSession(formController, form)), entitiesRepository, instancesRepository, savepointsRepository);
+ FormSaveViewModel restoredViewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, mock(AudioRecorder.class), projectsDataService, liveDataOf(new FormSession(formController, form)), entitiesRepository, instancesRepository, savepointsRepository, mock());
restoredViewModel.replaceAnswerFile("index", "blah2");
verify(mediaUtils).deleteMediaFile("blah1");
@@ -482,7 +482,7 @@ public void isSavingFileAnswerFile_isTrueWhenWhileIsSaving() throws Exception {
@Test
public void ignoreChanges_whenFormControllerNotSet_doesNothing() {
- FormSaveViewModel viewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, mock(AudioRecorder.class), projectsDataService, liveDataOf(new FormSession(formController, form)), entitiesRepository, instancesRepository, savepointsRepository);
+ FormSaveViewModel viewModel = new FormSaveViewModel(savedStateHandle, () -> CURRENT_TIME, formSaver, mediaUtils, scheduler, mock(AudioRecorder.class), projectsDataService, liveDataOf(new FormSession(formController, form)), entitiesRepository, instancesRepository, savepointsRepository, mock());
viewModel.ignoreChanges(); // Checks nothing explodes
}
diff --git a/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProviderTest.kt b/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProviderTest.kt
index 0aabbb025e5..b324cf20346 100644
--- a/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProviderTest.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuProviderTest.kt
@@ -23,7 +23,7 @@ import org.mockito.kotlin.whenever
import org.odk.collect.android.R
import org.odk.collect.android.formlists.sorting.FormListSortingBottomSheetDialog
import org.odk.collect.android.support.CollectHelpers
-import org.odk.collect.androidshared.network.NetworkStateProvider
+import org.odk.collect.async.network.NetworkStateProvider
import org.robolectric.Shadows
import org.robolectric.fakes.RoboMenuItem
import org.robolectric.shadows.ShadowDialog
diff --git a/collect_app/src/test/java/org/odk/collect/android/instancemanagement/InstancesDataServiceTest.kt b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/InstancesDataServiceTest.kt
index 13358a88773..b88b3eed413 100644
--- a/collect_app/src/test/java/org/odk/collect/android/instancemanagement/InstancesDataServiceTest.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/InstancesDataServiceTest.kt
@@ -19,6 +19,7 @@ import org.odk.collect.android.projects.ProjectDependencyProviderFactory
import org.odk.collect.android.utilities.ChangeLockProvider
import org.odk.collect.android.utilities.FormsRepositoryProvider
import org.odk.collect.android.utilities.InstancesRepositoryProvider
+import org.odk.collect.androidshared.data.AppState
import org.odk.collect.forms.instances.Instance.STATUS_COMPLETE
import org.odk.collect.formstest.FormFixtures
import org.odk.collect.formstest.InMemFormsRepository
@@ -26,7 +27,6 @@ import org.odk.collect.formstest.InMemInstancesRepository
import org.odk.collect.formstest.InstanceFixtures
import org.odk.collect.projects.Project
import org.odk.collect.settings.InMemSettingsProvider
-import org.odk.collect.settings.enums.AutoSend
import org.odk.collect.settings.keys.ProjectKeys
import org.odk.collect.testshared.BooleanChangeLock
@@ -50,8 +50,6 @@ class InstancesDataServiceTest {
val settingsProvider = InMemSettingsProvider().also {
it.getUnprotectedSettings(project.uuid)
.save(ProjectKeys.KEY_SERVER_URL, "http://example.com")
- it.getUnprotectedSettings()
- .save(ProjectKeys.KEY_AUTOSEND, AutoSend.WIFI_ONLY.getValue(application))
}
private val projectsDependencyProviderFactory = ProjectDependencyProviderFactory(
@@ -71,7 +69,7 @@ class InstancesDataServiceTest {
private val instancesDataService =
InstancesDataService(
- application,
+ AppState(),
mock(),
projectsDependencyProviderFactory,
notifier,
@@ -94,19 +92,19 @@ class InstancesDataServiceTest {
}
@Test
- fun `autoSendInstances() returns true when there are no instances to send`() {
- val result = instancesDataService.autoSendInstances(project.uuid)
+ fun `sendInstances() returns true when there are no instances to send`() {
+ val result = instancesDataService.sendInstances(project.uuid)
assertThat(result, equalTo(true))
}
@Test
- fun `autoSendInstances() does not notify when there are no instances to send`() {
- instancesDataService.autoSendInstances(project.uuid)
+ fun `sendInstances() does not notify when there are no instances to send`() {
+ instancesDataService.sendInstances(project.uuid)
verifyNoInteractions(notifier)
}
@Test
- fun `autoSendInstances() returns false when an instance fails to send`() {
+ fun `sendInstances() returns false when an instance fails to send`() {
val formsRepository = projectDependencyProvider.formsRepository
val form = formsRepository.save(FormFixtures.form())
@@ -116,7 +114,7 @@ class InstancesDataServiceTest {
whenever(httpInterface.executeGetRequest(any(), any(), any()))
.doReturn(HttpGetResult(null, emptyMap(), "", 500))
- val result = instancesDataService.autoSendInstances(project.uuid)
+ val result = instancesDataService.sendInstances(project.uuid)
assertThat(result, equalTo(false))
}
}
diff --git a/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProviderTest.kt b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProviderTest.kt
index 2f231fcdfbc..b41b652a45e 100644
--- a/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProviderTest.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProviderTest.kt
@@ -1,8 +1,6 @@
package org.odk.collect.android.instancemanagement.autosend
import android.app.Application
-import android.net.ConnectivityManager
-import android.net.NetworkInfo
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertFalse
@@ -11,7 +9,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-import org.odk.collect.androidshared.network.NetworkStateProvider
+import org.odk.collect.async.Scheduler
+import org.odk.collect.async.network.NetworkStateProvider
import org.odk.collect.projects.Project
import org.odk.collect.settings.InMemSettingsProvider
import org.odk.collect.settings.enums.AutoSend
@@ -39,7 +38,7 @@ class AutoSendSettingsProviderTest {
fun `return false when autosend is disabled in settings and network type is wifi`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.OFF.getValue(application),
- networkType = ConnectivityManager.TYPE_WIFI
+ networkType = Scheduler.NetworkType.WIFI
)
assertFalse(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -49,7 +48,7 @@ class AutoSendSettingsProviderTest {
fun `return false when autosend is disabled in settings and network type is cellular`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.OFF.getValue(application),
- networkType = ConnectivityManager.TYPE_MOBILE
+ networkType = Scheduler.NetworkType.CELLULAR
)
assertFalse(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -69,7 +68,7 @@ class AutoSendSettingsProviderTest {
fun `return false when autosend is enabled for 'wifi_only' and network type is cellular`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.WIFI_ONLY.getValue(application),
- networkType = ConnectivityManager.TYPE_MOBILE
+ networkType = Scheduler.NetworkType.CELLULAR
)
assertFalse(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -79,7 +78,7 @@ class AutoSendSettingsProviderTest {
fun `return true when autosend is enabled for 'wifi_only' and network type is wifi`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.WIFI_ONLY.getValue(application),
- networkType = ConnectivityManager.TYPE_WIFI
+ networkType = Scheduler.NetworkType.WIFI
)
assertTrue(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -99,7 +98,7 @@ class AutoSendSettingsProviderTest {
fun `return false when autosend is enabled for 'cellular_only' and network type is wifi`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.CELLULAR_ONLY.getValue(application),
- networkType = ConnectivityManager.TYPE_WIFI
+ networkType = Scheduler.NetworkType.WIFI
)
assertFalse(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -109,7 +108,7 @@ class AutoSendSettingsProviderTest {
fun `return true when autosend is enabled for 'cellular_only' and network type is cellular`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.CELLULAR_ONLY.getValue(application),
- networkType = ConnectivityManager.TYPE_MOBILE
+ networkType = Scheduler.NetworkType.CELLULAR
)
assertTrue(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -129,7 +128,7 @@ class AutoSendSettingsProviderTest {
fun `return true when autosend is enabled for 'wifi_and_cellular' and network type is wifi`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.WIFI_AND_CELLULAR.getValue(application),
- networkType = ConnectivityManager.TYPE_WIFI
+ networkType = Scheduler.NetworkType.WIFI
)
assertTrue(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -139,7 +138,7 @@ class AutoSendSettingsProviderTest {
fun `return true when autosend is enabled for 'wifi_and_cellular' and network type is cellular`() {
val autoSendSettingsProvider = setupAutoSendSettingProvider(
autoSendOption = AutoSend.WIFI_AND_CELLULAR.getValue(application),
- networkType = ConnectivityManager.TYPE_MOBILE
+ networkType = Scheduler.NetworkType.CELLULAR
)
assertTrue(autoSendSettingsProvider.isAutoSendEnabledInSettings(projectId))
@@ -147,15 +146,9 @@ class AutoSendSettingsProviderTest {
private fun setupAutoSendSettingProvider(
autoSendOption: String? = null,
- networkType: Int? = null
+ networkType: Scheduler.NetworkType? = null
): AutoSendSettingsProvider {
- var networkInfo: NetworkInfo? = null
- networkType?.let {
- networkInfo = mock().also {
- whenever(it.type).thenReturn(networkType)
- }
- }
- whenever(networkStateProvider.networkInfo).thenReturn(networkInfo)
+ whenever(networkStateProvider.currentNetwork).thenReturn(networkType)
settingsProvider.getUnprotectedSettings(projectId).save(ProjectKeys.KEY_AUTOSEND, autoSendOption)
return AutoSendSettingsProvider(application, networkStateProvider, settingsProvider)
diff --git a/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcherTest.kt b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcherTest.kt
index 16da2257080..aa0028a1f00 100644
--- a/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcherTest.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/InstanceAutoSendFetcherTest.kt
@@ -1,7 +1,5 @@
package org.odk.collect.android.instancemanagement.autosend
-import android.app.Application
-import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.contains
@@ -12,9 +10,6 @@ import org.odk.collect.formstest.FormUtils.buildForm
import org.odk.collect.formstest.InMemFormsRepository
import org.odk.collect.formstest.InMemInstancesRepository
import org.odk.collect.formstest.InstanceUtils.buildInstance
-import org.odk.collect.settings.InMemSettingsProvider
-import org.odk.collect.settings.enums.AutoSend
-import org.odk.collect.settings.keys.ProjectKeys
import org.odk.collect.shared.TempFiles.createTempDir
@RunWith(AndroidJUnit4::class)
@@ -51,10 +46,8 @@ class InstanceAutoSendFetcherTest {
private val instanceOfFormWithCustomAutoSendSubmissionFailed = buildInstance("4", "1", "instance 3", Instance.STATUS_SUBMISSION_FAILED, null, createTempDir().absolutePath).build()
private val instanceOfFormWithCustomAutoSendSubmitted = buildInstance("4", "1", "instance 4", Instance.STATUS_SUBMITTED, null, createTempDir().absolutePath).build()
- private val application = ApplicationProvider.getApplicationContext()
-
@Test
- fun `return all finalized instances of forms that do not have auto send disabled on a form level`() {
+ fun `return all finalized instances of forms that do not have auto send on a form level`() {
formsRepository.save(formWithEnabledAutoSend)
formsRepository.save(formWithoutSpecifiedAutoSend)
formsRepository.save(formWithDisabledAutoSend)
@@ -82,62 +75,60 @@ class InstanceAutoSendFetcherTest {
save(instanceOfFormWithCustomAutoSendSubmitted)
}
- val settingsProvider = InMemSettingsProvider().also {
- it.getUnprotectedSettings()
- .save(ProjectKeys.KEY_AUTOSEND, AutoSend.WIFI_ONLY.getValue(application))
- }
-
val instancesToSend = InstanceAutoSendFetcher.getInstancesToAutoSend(
- application,
instancesRepository,
- formsRepository,
- settingsProvider
+ formsRepository
)
assertThat(
instancesToSend.map { it.instanceFilePath },
contains(
- instanceOfFormWithEnabledAutoSendComplete.instanceFilePath,
- instanceOfFormWithEnabledAutoSendSubmissionFailed.instanceFilePath,
instanceOfFormWithoutSpecifiedAutoSendComplete.instanceFilePath,
instanceOfFormWithoutSpecifiedAutoSendSubmissionFailed.instanceFilePath,
- instanceOfFormWithCustomAutoSendComplete.instanceFilePath,
- instanceOfFormWithCustomAutoSendSubmissionFailed.instanceFilePath
)
)
}
@Test
- fun `if there are multiple versions of one form and only one has auto-send enabled take only instances of that form`() {
- val formWithEnabledAutoSendV1 = buildForm("1", "1", createTempDir().absolutePath, autosend = "false").build()
- val instanceOfFormWithEnabledAutoSendCompleteV1 = buildInstance("1", "1", "instance 2", Instance.STATUS_COMPLETE, null, createTempDir().absolutePath).build()
+ fun `return all finalized forms with autosend when formAutoSend is true`() {
+ formsRepository.save(formWithEnabledAutoSend)
+ formsRepository.save(formWithoutSpecifiedAutoSend)
+ formsRepository.save(formWithDisabledAutoSend)
+ formsRepository.save(formWithCustomAutoSend)
- val formWithEnabledAutoSendV2 = buildForm("1", "2", createTempDir().absolutePath, autosend = "true").build()
- val instanceOfFormWithEnabledAutoSendCompleteV2 = buildInstance("1", "2", "instance 2", Instance.STATUS_COMPLETE, null, createTempDir().absolutePath).build()
+ instancesRepository.apply {
+ save(instanceOfFormWithEnabledAutoSendIncomplete)
+ save(instanceOfFormWithEnabledAutoSendComplete)
+ save(instanceOfFormWithEnabledAutoSendSubmissionFailed)
+ save(instanceOfFormWithEnabledAutoSendSubmitted)
- formsRepository.save(formWithEnabledAutoSendV1)
- formsRepository.save(formWithEnabledAutoSendV2)
+ save(instanceOfFormWithoutSpecifiedAutoSendIncomplete)
+ save(instanceOfFormWithoutSpecifiedAutoSendComplete)
+ save(instanceOfFormWithoutSpecifiedAutoSendSubmissionFailed)
+ save(instanceOfFormWithoutSpecifiedAutoSendSubmitted)
- instancesRepository.apply {
- save(instanceOfFormWithEnabledAutoSendCompleteV1)
- save(instanceOfFormWithEnabledAutoSendCompleteV2)
- }
+ save(instanceOfFormWithDisabledAutoSendIncomplete)
+ save(instanceOfFormWithDisabledAutoSendComplete)
+ save(instanceOfFormWithDisabledAutoSendSubmissionFailed)
+ save(instanceOfFormWithDisabledAutoSendSubmitted)
- val settingsProvider = InMemSettingsProvider().also {
- it.getUnprotectedSettings()
- .save(ProjectKeys.KEY_AUTOSEND, AutoSend.WIFI_ONLY.getValue(application))
+ save(instanceOfFormWithCustomAutoSendIncomplete)
+ save(instanceOfFormWithCustomAutoSendComplete)
+ save(instanceOfFormWithCustomAutoSendSubmissionFailed)
+ save(instanceOfFormWithCustomAutoSendSubmitted)
}
val instancesToSend = InstanceAutoSendFetcher.getInstancesToAutoSend(
- application,
instancesRepository,
formsRepository,
- settingsProvider
+ formAutoSend = true
)
+
assertThat(
instancesToSend.map { it.instanceFilePath },
contains(
- instanceOfFormWithEnabledAutoSendCompleteV2.instanceFilePath
+ instanceOfFormWithEnabledAutoSendComplete.instanceFilePath,
+ instanceOfFormWithEnabledAutoSendSubmissionFailed.instanceFilePath,
)
)
}
diff --git a/collect_app/src/test/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragmentTest.kt b/collect_app/src/test/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragmentTest.kt
index d06804cffae..bf04357f08a 100644
--- a/collect_app/src/test/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragmentTest.kt
+++ b/collect_app/src/test/java/org/odk/collect/android/preferences/screens/FormManagementPreferencesFragmentTest.kt
@@ -455,7 +455,7 @@ class FormManagementPreferencesFragmentTest {
scenario.onFragment { fragment: FormManagementPreferencesFragment ->
fragment.findPreference(ProjectKeys.KEY_AUTOSEND)!!.value = AutoSend.WIFI_ONLY.getValue(context)
}
- verify(instanceSubmitScheduler).scheduleSubmit(projectID)
+ verify(instanceSubmitScheduler).scheduleAutoSend(projectID)
}
@Test
@@ -464,6 +464,6 @@ class FormManagementPreferencesFragmentTest {
scenario.onFragment { fragment: FormManagementPreferencesFragment ->
fragment.findPreference(ProjectKeys.KEY_AUTOSEND)!!.value = AutoSend.OFF.getValue(context)
}
- verify(instanceSubmitScheduler, never()).scheduleSubmit(projectID)
+ verify(instanceSubmitScheduler, never()).scheduleAutoSend(projectID)
}
}
diff --git a/mapbox/build.gradle.kts b/mapbox/build.gradle.kts
index 48f2867203f..ee99e953e4e 100644
--- a/mapbox/build.gradle.kts
+++ b/mapbox/build.gradle.kts
@@ -44,6 +44,7 @@ dependencies {
implementation(project(":settings"))
implementation(project(":shared"))
implementation(project(":strings"))
+ implementation(project(":async"))
implementation(Dependencies.play_services_location)
implementation(Dependencies.androidx_preference_ktx)
implementation(Dependencies.mapbox_android_sdk)
diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt
index 780258d433b..9895300e904 100644
--- a/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt
+++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt
@@ -12,7 +12,7 @@ import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.loader.MapboxMapsInitializer
import org.odk.collect.androidshared.data.getState
-import org.odk.collect.androidshared.network.NetworkStateProvider
+import org.odk.collect.async.network.NetworkStateProvider
import org.odk.collect.settings.SettingsProvider
import org.odk.collect.settings.keys.MetaKeys
import org.odk.collect.shared.injection.ObjectProviderHost
diff --git a/test-forms/src/main/resources/forms/one-question-autosend-disabled.xml b/test-forms/src/main/resources/forms/one-question-autosend-disabled.xml
new file mode 100644
index 00000000000..30ef523a4c5
--- /dev/null
+++ b/test-forms/src/main/resources/forms/one-question-autosend-disabled.xml
@@ -0,0 +1,20 @@
+
+
+
+ One Question Autosend Disabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+