From 0e3ae3fff08c8f1c358f039b003a167cb96bf938 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 30 Apr 2024 16:41:49 +0100 Subject: [PATCH 01/24] Isolate deprecated network info code --- .../network/ConnectivityProvider.kt | 16 ++++++---- .../network/NetworkStateProvider.kt | 4 +-- .../autosend/AutoSendSettingsProvider.kt | 8 ++--- .../autosend/AutoSendSettingsProviderTest.kt | 29 +++++++------------ 4 files changed, 28 insertions(+), 29 deletions(-) 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 index 39faf56766e..020dd81848b 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt @@ -2,17 +2,23 @@ package org.odk.collect.androidshared.network import android.content.Context import android.net.ConnectivityManager -import android.net.NetworkInfo +import org.odk.collect.async.Scheduler class ConnectivityProvider(private val context: Context) : NetworkStateProvider { override val isDeviceOnline: Boolean get() { - val networkInfo = networkInfo - return networkInfo != null && networkInfo.isConnected + val activeNetworkInfo = connectivityManager.activeNetworkInfo + return activeNetworkInfo != null && activeNetworkInfo.isConnected } - override val networkInfo: NetworkInfo? - get() = connectivityManager.activeNetworkInfo + override val currentNetwork: Scheduler.NetworkType? + get() { + return when (connectivityManager.activeNetworkInfo?.type) { + ConnectivityManager.TYPE_WIFI -> Scheduler.NetworkType.WIFI + ConnectivityManager.TYPE_MOBILE -> Scheduler.NetworkType.CELLULAR + else -> null + } + } 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 index d3b9e983c58..8a2b4db3a8f 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt @@ -1,8 +1,8 @@ package org.odk.collect.androidshared.network -import android.net.NetworkInfo +import org.odk.collect.async.Scheduler interface NetworkStateProvider { val isDeviceOnline: Boolean - val networkInfo: NetworkInfo? + val currentNetwork: Scheduler.NetworkType? } 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..837579b282d 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.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/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..4a60a6e1b69 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 @@ -12,6 +10,7 @@ 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.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) From dde08ad10a2f2d9edccf94a384fb30e3a4ab109c Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 30 Apr 2024 17:25:44 +0100 Subject: [PATCH 02/24] Make TestScheduler network aware to reveal failing autosend tests --- .../formmanagement/BulkFinalizationTest.kt | 5 +++- .../instancemanagement/AutoSendTest.kt | 26 +++++++++++++++--- .../settings/ConfigureWithQRCodeTest.java | 25 +++++++---------- .../support/FakeNetworkStateProvider.kt | 25 +++++++++++++++++ .../android/support/TestDependencies.java | 3 ++- .../collect/android/support/TestScheduler.kt | 27 ++++++++++++------- .../android/support/pages/MainMenuPage.java | 4 +-- 7 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 collect_app/src/androidTest/java/org/odk/collect/android/support/FakeNetworkStateProvider.kt 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..acdad2ac6df 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") 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..d1138394088 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,22 @@ class AutoSendTest { } @Test - fun whenFormHasAutoSend_fillingAndFinalizingForm_notifiesUserWhenSendingFails() { + 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) 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..2ac0de10d5d --- /dev/null +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeNetworkStateProvider.kt @@ -0,0 +1,25 @@ +package org.odk.collect.android.support + +import org.odk.collect.androidshared.network.NetworkStateProvider +import org.odk.collect.async.Scheduler + +class FakeNetworkStateProvider : NetworkStateProvider { + + private var online = true + private var type: Scheduler.NetworkType? = Scheduler.NetworkType.WIFI + + fun goOnline(networkType: Scheduler.NetworkType) { + online = true + type = networkType + } + + fun goOffline() { + online = false + type = null + } + + override val isDeviceOnline: Boolean + get() = online + 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..cccc4760708 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 @@ -16,7 +16,8 @@ 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(); 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..22d96a5df2c 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 @@ -7,6 +7,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Runnable import kotlinx.coroutines.flow.Flow +import org.odk.collect.androidshared.network.NetworkStateProvider import org.odk.collect.async.Cancellable import org.odk.collect.async.CoroutineAndWorkManagerScheduler import org.odk.collect.async.Scheduler @@ -15,7 +16,7 @@ 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,7 @@ class TestScheduler : Scheduler, CoroutineDispatcher() { inputData: Map, networkConstraint: Scheduler.NetworkType? ) { - deferredTasks.add(DeferredTask(tag, spec, null, inputData)) + deferredTasks.add(DeferredTask(tag, spec, null, inputData, networkConstraint)) } override fun networkDeferredRepeat( @@ -65,7 +66,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 +78,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 +136,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()); From f1bca3e83e5b3e4b50d811607f72b2d889205ba1 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 12:15:30 +0100 Subject: [PATCH 03/24] Fix form level auto send --- .../instancemanagement/AutoSendTest.kt | 20 +++++++++ .../activities/FormEntryViewModelFactory.kt | 7 ++- .../activities/FormFillingActivity.java | 7 +-- .../backgroundwork/AutoSendFormTaskSpec.kt | 43 +++++++++++++++++++ .../FormUpdateAndInstanceSubmitScheduler.java | 12 ++++++ .../InstanceSubmitScheduler.java | 2 + .../android/backgroundwork/TaskData.kt | 1 + .../formentry/saving/FormSaveViewModel.java | 11 ++++- .../formhierarchy/FormHierarchyActivity.java | 7 ++- .../config/AppDependencyComponent.java | 3 ++ .../InstancesDataService.kt | 21 +++++++++ .../forms/one-question-autosend-disabled.xml | 20 +++++++++ 12 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendFormTaskSpec.kt create mode 100644 test-forms/src/main/resources/forms/one-question-autosend-disabled.xml 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 d1138394088..6dffe359bde 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 @@ -153,6 +153,26 @@ class AutoSendTest { ) } + @Test + fun whenFormHasAutoSendDisabled_fillingAndFinalizingForm_doesNotSendForm_regardlessOfSetting() { + testDependencies.server.alwaysReturnError() + + 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/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt index 679f8a51723..062767d8181 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 @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.savedstate.SavedStateRegistryOwner import org.javarosa.core.model.actions.recordaudio.RecordAudioActions import org.javarosa.core.model.instance.TreeReference +import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler import org.odk.collect.android.entities.EntitiesRepositoryProvider import org.odk.collect.android.formentry.BackgroundAudioViewModel import org.odk.collect.android.formentry.BackgroundAudioViewModel.RecordAudioActionRegistry @@ -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 instanceSubmitScheduler: InstanceSubmitScheduler ) : 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), + instanceSubmitScheduler ) } 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..ab3b45f5391 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 @@ -433,7 +433,8 @@ public void onCreate(Bundle savedInstanceState) { instancesRepositoryProvider, new SavepointsRepositoryProvider(this, storagePathProvider), new QRCodeCreatorImpl(), - new HtmlPrinter() + new HtmlPrinter(), + instanceSubmitScheduler ); 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/backgroundwork/AutoSendFormTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendFormTaskSpec.kt new file mode 100644 index 00000000000..8c3afc724ed --- /dev/null +++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendFormTaskSpec.kt @@ -0,0 +1,43 @@ +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 AutoSendFormTaskSpec : TaskSpec { + + @Inject + lateinit var instancesDataService: InstancesDataService + + override val maxRetries: Int? = null + override val backoffPolicy = BackoffPolicy.EXPONENTIAL + override val backoffDelay: Long = 60_000 + + override fun getTask( + context: Context, + inputData: Map, + isLastUniqueExecution: Boolean + ): Supplier { + DaggerUtils.getComponent(context).inject(this) + + return Supplier { + val projectId = inputData[TaskData.DATA_PROJECT_ID]!! + val instanceId = inputData[TaskData.DATA_INSTANCE_ID]!!.toLong() + instancesDataService.sendInstances(projectId, listOf(instanceId)) + true + } + } + + override fun getWorkManagerAdapter(): Class { + return Adapter::class.java + } + + class Adapter(context: Context, workerParams: WorkerParameters) : + WorkerAdapter(AutoSendFormTaskSpec(), 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..0d46a79b4c2 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 @@ -83,6 +83,14 @@ public void scheduleSubmit(String projectId) { scheduler.networkDeferred(getAutoSendTag(projectId), new AutoSendTaskSpec(), inputData, networkType); } + @Override + public void scheduleSubmit(String projectId, Long instanceId) { + HashMap inputData = new HashMap<>(); + inputData.put(TaskData.DATA_PROJECT_ID, projectId); + inputData.put(TaskData.DATA_INSTANCE_ID, instanceId.toString()); + scheduler.networkDeferred(getAutoSendFormTag(projectId), new AutoSendFormTaskSpec(), inputData, null); + } + @Override public void cancelSubmit(String projectId) { scheduler.cancelDeferred(getAutoSendTag(projectId)); @@ -93,6 +101,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..3ee8a9d90fb 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 @@ -4,5 +4,7 @@ public interface InstanceSubmitScheduler { void scheduleSubmit(String projectId); + void scheduleSubmit(String projectId, Long instanceId); + void cancelSubmit(String projectId); } 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..84460388517 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_INSTANCE_ID = "instanceId" } 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..429f05a549c 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 @@ -17,6 +17,7 @@ import org.apache.commons.io.IOUtils; import org.javarosa.form.api.FormEntryController; import org.odk.collect.android.application.Collect; +import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler; import org.odk.collect.android.dao.helpers.InstancesDaoHelper; import org.odk.collect.android.dynamicpreload.ExternalDataManager; import org.odk.collect.android.formentry.FormSession; @@ -90,12 +91,13 @@ public class FormSaveViewModel extends ViewModel implements MaterialProgressDial private Form form; private Instance instance; private final Cancellable formSessionObserver; + private InstanceSubmitScheduler instanceSubmitScheduler; 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, InstanceSubmitScheduler instanceSubmitScheduler ) { 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.instanceSubmitScheduler = instanceSubmitScheduler; if (stateHandle.get(ORIGINAL_FILES) != null) { originalFiles = stateHandle.get(ORIGINAL_FILES); @@ -268,6 +271,12 @@ 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()); + + if (form.getAutoSend() != null && form.getAutoSend().equals("true")) { + instanceSubmitScheduler.scheduleSubmit(projectsDataService.getCurrentProject().getUuid(), instance.getDbId()); + } else { + instanceSubmitScheduler.scheduleSubmit(projectsDataService.getCurrentProject().getUuid()); + } } 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..7b2454c968c 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 @@ -47,6 +47,7 @@ import org.odk.collect.analytics.Analytics; import org.odk.collect.android.R; import org.odk.collect.android.activities.FormEntryViewModelFactory; +import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler; import org.odk.collect.android.entities.EntitiesRepositoryProvider; import org.odk.collect.android.exception.JavaRosaException; import org.odk.collect.android.formentry.FormEntryViewModel; @@ -195,6 +196,9 @@ public class FormHierarchyActivity extends LocalizedActivity implements DeleteRe @Inject public StoragePathProvider storagePathProvider; + @Inject + public InstanceSubmitScheduler instanceSubmitScheduler; + 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(), + instanceSubmitScheduler ); this.getSupportFragmentManager().setFragmentFactory(new FragmentFactoryBuilder() 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..75d6f2d3c2c 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,6 +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.AutoSendFormTaskSpec; import org.odk.collect.android.backgroundwork.AutoSendTaskSpec; import org.odk.collect.android.backgroundwork.AutoUpdateTaskSpec; import org.odk.collect.android.backgroundwork.SyncFormsTaskSpec; @@ -261,6 +262,8 @@ interface Builder { void inject(DownloadFormListTask downloadFormListTask); + void inject(AutoSendFormTaskSpec autoSendFormTaskSpec); + OpenRosaHttpInterface openRosaHttpInterface(); ReferenceManager referenceManager(); 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..0da2fa62804 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 @@ -178,6 +178,27 @@ class InstancesDataService( } } + fun sendInstances(projectId: String, instanceIds: List) { + val projectDependencyProvider = + projectDependencyProviderFactory.create(projectId) + + val instanceSubmitter = InstanceSubmitter( + projectDependencyProvider.formsRepository, + projectDependencyProvider.generalSettings, + propertyManager, + httpInterface, + projectDependencyProvider.instancesRepository + ) + + val instances = instanceIds.map { + projectDependencyProvider.instancesRepository.get(it)!! + } + + val results = instanceSubmitter.submitInstances(instances) + notifier.onSubmission(results, projectDependencyProvider.projectId) + update(projectId) + } + fun autoSendInstances(projectId: String): Boolean { val projectDependencyProvider = projectDependencyProviderFactory.create(projectId) 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 + + + + + + + + + + + + + + + + From 8ae1ec2d5b1b641abb3fdbf2f7cd3ad3fa1c91a6 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 12:48:27 +0100 Subject: [PATCH 04/24] Make sure form level auto send works for multiple forms --- .../instancemanagement/AutoSendTest.kt | 22 +++++++++++++++++++ .../collect/android/support/TestScheduler.kt | 1 + .../FormUpdateAndInstanceSubmitScheduler.java | 6 ++--- 3 files changed, 26 insertions(+), 3 deletions(-) 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 6dffe359bde..67427bb3707 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 @@ -120,6 +120,28 @@ class AutoSendTest { ).pressBack(MainMenuPage()) } + @Test + 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() 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 22d96a5df2c..1fb4f4f1773 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 @@ -56,6 +56,7 @@ class TestScheduler(private val networkStateProvider: NetworkStateProvider) : Sc inputData: Map, networkConstraint: Scheduler.NetworkType? ) { + cancelDeferred(tag) deferredTasks.add(DeferredTask(tag, spec, null, inputData, networkConstraint)) } 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 0d46a79b4c2..81e2d43004c 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 @@ -88,7 +88,7 @@ public void scheduleSubmit(String projectId, Long instanceId) { HashMap inputData = new HashMap<>(); inputData.put(TaskData.DATA_PROJECT_ID, projectId); inputData.put(TaskData.DATA_INSTANCE_ID, instanceId.toString()); - scheduler.networkDeferred(getAutoSendFormTag(projectId), new AutoSendFormTaskSpec(), inputData, null); + scheduler.networkDeferred(getAutoSendFormTag(projectId, instanceId), new AutoSendFormTaskSpec(), inputData, null); } @Override @@ -101,8 +101,8 @@ public String getAutoSendTag(String projectId) { return "AutoSendWorker:" + projectId; } - public String getAutoSendFormTag(String projectId) { - return "auto_send_form:" + projectId; + public String getAutoSendFormTag(String projectId, Long instanceId) { + return "auto_send_form:" + projectId + ":" + instanceId; } @NotNull From 77b9edb18dc86d54db9e01949e74ea8cb1d609a6 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 13:03:31 +0100 Subject: [PATCH 05/24] Simplify auto send logic --- .../FormUpdateAndInstanceSubmitScheduler.java | 12 ++-- .../injection/config/AppDependencyModule.java | 2 +- .../InstancesDataService.kt | 10 +--- .../autosend/InstanceAutoSendFetcher.kt | 13 +---- ...ormUpdateAndInstanceSubmitSchedulerTest.kt | 11 ++++ .../audit/FormSaveViewModelTest.java | 8 +-- .../InstancesDataServiceTest.kt | 6 +- .../autosend/InstanceAutoSendFetcherTest.kt | 57 +------------------ 8 files changed, 33 insertions(+), 86 deletions(-) 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 81e2d43004c..005972b2cc3 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 @@ -69,18 +69,22 @@ public void cancelUpdates(String projectId) { @Override public void scheduleSubmit(String projectId) { - Scheduler.NetworkType networkType = null; + 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 AutoSendTaskSpec(), inputData, networkConstraint); } @Override 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..dcc7ead4828 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 @@ -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 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 0da2fa62804..d1529175930 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,13 @@ 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.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 +28,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) @@ -216,10 +214,8 @@ class InstancesDataService( ).withLock { acquiredLock: Boolean -> if (acquiredLock) { val toUpload = InstanceAutoSendFetcher.getInstancesToAutoSend( - application, projectDependencyProvider.instancesRepository, - projectDependencyProvider.formsRepository, - projectDependencyProvider.settingsProvider + projectDependencyProvider.formsRepository ) if (toUpload.isNotEmpty()) { 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..84b8c9619e1 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,32 +1,23 @@ package org.odk.collect.android.instancemanagement.autosend -import android.app.Application 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 + formsRepository: FormsRepository ): List { val allFinalizedForms = instancesRepository.getAllByStatus( Instance.STATUS_COMPLETE, Instance.STATUS_SUBMISSION_FAILED ) - val autoSendSetting = - settingsProvider.getUnprotectedSettings().getAutoSend(application) - return allFinalizedForms.filter { formsRepository.getLatestByFormIdAndVersion(it.formId, it.formVersion)?.let { form -> - form.shouldFormBeSentAutomatically(autoSendSetting != AutoSend.OFF) + form.autoSend == null } ?: false } } 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..4fd0f99afdd 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 @@ -127,6 +128,16 @@ class FormUpdateAndInstanceSubmitSchedulerTest { ) } + @Test + fun `scheduleSubmit does nothing if auto send is disabled`() { + settingsProvider.getUnprotectedSettings("myProject") + .save(ProjectKeys.KEY_AUTOSEND, "off") + val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) + + manager.scheduleSubmit("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/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/instancemanagement/InstancesDataServiceTest.kt b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/InstancesDataServiceTest.kt index 13358a88773..8b8f51be36e 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, 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..0001dcba000 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,16 @@ 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() - - val formWithEnabledAutoSendV2 = buildForm("1", "2", createTempDir().absolutePath, autosend = "true").build() - val instanceOfFormWithEnabledAutoSendCompleteV2 = buildInstance("1", "2", "instance 2", Instance.STATUS_COMPLETE, null, createTempDir().absolutePath).build() - - formsRepository.save(formWithEnabledAutoSendV1) - formsRepository.save(formWithEnabledAutoSendV2) - - instancesRepository.apply { - save(instanceOfFormWithEnabledAutoSendCompleteV1) - save(instanceOfFormWithEnabledAutoSendCompleteV2) - } - - val settingsProvider = InMemSettingsProvider().also { - it.getUnprotectedSettings() - .save(ProjectKeys.KEY_AUTOSEND, AutoSend.WIFI_ONLY.getValue(application)) - } - - val instancesToSend = InstanceAutoSendFetcher.getInstancesToAutoSend( - application, - instancesRepository, - formsRepository, - settingsProvider - ) - assertThat( - instancesToSend.map { it.instanceFilePath }, - contains( - instanceOfFormWithEnabledAutoSendCompleteV2.instanceFilePath ) ) } From 80a35a5b3303f075e63ffa0ad20f1bad6160bd4d Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 15:14:25 +0100 Subject: [PATCH 06/24] Remove extra send method --- .../backgroundwork/AutoSendTaskSpec.kt | 2 +- .../FormUpdateAndInstanceSubmitScheduler.java | 2 +- ...endFormTaskSpec.kt => SendFormTaskSpec.kt} | 4 +- .../config/AppDependencyComponent.java | 4 +- .../InstancesDataService.kt | 37 ++++++------------- .../backgroundwork/AutoSendTaskSpecTest.kt | 4 +- .../InstancesDataServiceTest.kt | 12 +++--- 7 files changed, 25 insertions(+), 40 deletions(-) rename collect_app/src/main/java/org/odk/collect/android/backgroundwork/{AutoSendFormTaskSpec.kt => SendFormTaskSpec.kt} (92%) 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/AutoSendTaskSpec.kt index 6fbe7700c52..79266494f76 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/AutoSendTaskSpec.kt @@ -36,7 +36,7 @@ class AutoSendTaskSpec : TaskSpec { return Supplier { val projectId = inputData[TaskData.DATA_PROJECT_ID] if (projectId != null) { - instancesDataService.autoSendInstances(projectId) + instancesDataService.sendInstances(projectId) } else { throw IllegalArgumentException("No project ID provided!") } 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 005972b2cc3..41adb370c3c 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 @@ -92,7 +92,7 @@ public void scheduleSubmit(String projectId, Long instanceId) { HashMap inputData = new HashMap<>(); inputData.put(TaskData.DATA_PROJECT_ID, projectId); inputData.put(TaskData.DATA_INSTANCE_ID, instanceId.toString()); - scheduler.networkDeferred(getAutoSendFormTag(projectId, instanceId), new AutoSendFormTaskSpec(), inputData, null); + scheduler.networkDeferred(getAutoSendFormTag(projectId, instanceId), new SendFormTaskSpec(), inputData, null); } @Override diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendFormTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormTaskSpec.kt similarity index 92% rename from collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendFormTaskSpec.kt rename to collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormTaskSpec.kt index 8c3afc724ed..be1e51b1fd6 100644 --- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/AutoSendFormTaskSpec.kt +++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormTaskSpec.kt @@ -10,7 +10,7 @@ import org.odk.collect.async.WorkerAdapter import java.util.function.Supplier import javax.inject.Inject -class AutoSendFormTaskSpec : TaskSpec { +class SendFormTaskSpec : TaskSpec { @Inject lateinit var instancesDataService: InstancesDataService @@ -39,5 +39,5 @@ class AutoSendFormTaskSpec : TaskSpec { } class Adapter(context: Context, workerParams: WorkerParameters) : - WorkerAdapter(AutoSendFormTaskSpec(), context, workerParams) + WorkerAdapter(SendFormTaskSpec(), context, workerParams) } 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 75d6f2d3c2c..2b60d9d844f 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.AutoSendFormTaskSpec; +import org.odk.collect.android.backgroundwork.SendFormTaskSpec; import org.odk.collect.android.backgroundwork.AutoSendTaskSpec; import org.odk.collect.android.backgroundwork.AutoUpdateTaskSpec; import org.odk.collect.android.backgroundwork.SyncFormsTaskSpec; @@ -262,7 +262,7 @@ interface Builder { void inject(DownloadFormListTask downloadFormListTask); - void inject(AutoSendFormTaskSpec autoSendFormTaskSpec); + void inject(SendFormTaskSpec sendFormTaskSpec); OpenRosaHttpInterface openRosaHttpInterface(); 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 d1529175930..ca390df9035 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 @@ -176,28 +176,7 @@ class InstancesDataService( } } - fun sendInstances(projectId: String, instanceIds: List) { - val projectDependencyProvider = - projectDependencyProviderFactory.create(projectId) - - val instanceSubmitter = InstanceSubmitter( - projectDependencyProvider.formsRepository, - projectDependencyProvider.generalSettings, - propertyManager, - httpInterface, - projectDependencyProvider.instancesRepository - ) - - val instances = instanceIds.map { - projectDependencyProvider.instancesRepository.get(it)!! - } - - val results = instanceSubmitter.submitInstances(instances) - notifier.onSubmission(results, projectDependencyProvider.projectId) - update(projectId) - } - - fun autoSendInstances(projectId: String): Boolean { + fun sendInstances(projectId: String, instanceIds: List? = null): Boolean { val projectDependencyProvider = projectDependencyProviderFactory.create(projectId) @@ -213,10 +192,16 @@ class InstancesDataService( projectDependencyProvider.projectId ).withLock { acquiredLock: Boolean -> if (acquiredLock) { - val toUpload = InstanceAutoSendFetcher.getInstancesToAutoSend( - projectDependencyProvider.instancesRepository, - projectDependencyProvider.formsRepository - ) + val toUpload = if (instanceIds != null) { + instanceIds.map { + projectDependencyProvider.instancesRepository.get(it)!! + } + } else { + InstanceAutoSendFetcher.getInstancesToAutoSend( + projectDependencyProvider.instancesRepository, + projectDependencyProvider.formsRepository + ) + } if (toUpload.isNotEmpty()) { val results = instanceSubmitter.submitInstances(toUpload) 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/AutoSendTaskSpecTest.kt index 6cf2c3091bb..65880ce73f0 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/AutoSendTaskSpecTest.kt @@ -54,7 +54,7 @@ class AutoSendTaskSpecTest { @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() @@ -64,7 +64,7 @@ class AutoSendTaskSpecTest { @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() 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 8b8f51be36e..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 @@ -92,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()) @@ -114,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)) } } From 4cd6530b9f047ec9ef4937fe12dfb85b3fa1e36d Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 16:36:14 +0100 Subject: [PATCH 07/24] Use one task spec for sending forms --- .../FormUpdateAndInstanceSubmitScheduler.java | 4 +- .../backgroundwork/SendFormTaskSpec.kt | 43 ------------------- ...toSendTaskSpec.kt => SendFormsTaskSpec.kt} | 11 +++-- .../config/AppDependencyComponent.java | 7 +-- ...ormUpdateAndInstanceSubmitSchedulerTest.kt | 6 +-- ...skSpecTest.kt => SendFormsTaskSpecTest.kt} | 8 ++-- 6 files changed, 19 insertions(+), 60 deletions(-) delete mode 100644 collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormTaskSpec.kt rename collect_app/src/main/java/org/odk/collect/android/backgroundwork/{AutoSendTaskSpec.kt => SendFormsTaskSpec.kt} (81%) rename collect_app/src/test/java/org/odk/collect/android/backgroundwork/{AutoSendTaskSpecTest.kt => SendFormsTaskSpecTest.kt} (93%) 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 41adb370c3c..8ea2ef47555 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 @@ -84,7 +84,7 @@ public void scheduleSubmit(String projectId) { HashMap inputData = new HashMap<>(); inputData.put(TaskData.DATA_PROJECT_ID, projectId); - scheduler.networkDeferred(getAutoSendTag(projectId), new AutoSendTaskSpec(), inputData, networkConstraint); + scheduler.networkDeferred(getAutoSendTag(projectId), new SendFormsTaskSpec(), inputData, networkConstraint); } @Override @@ -92,7 +92,7 @@ public void scheduleSubmit(String projectId, Long instanceId) { HashMap inputData = new HashMap<>(); inputData.put(TaskData.DATA_PROJECT_ID, projectId); inputData.put(TaskData.DATA_INSTANCE_ID, instanceId.toString()); - scheduler.networkDeferred(getAutoSendFormTag(projectId, instanceId), new SendFormTaskSpec(), inputData, null); + scheduler.networkDeferred(getAutoSendFormTag(projectId, instanceId), new SendFormsTaskSpec(), inputData, null); } @Override diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormTaskSpec.kt deleted file mode 100644 index be1e51b1fd6..00000000000 --- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormTaskSpec.kt +++ /dev/null @@ -1,43 +0,0 @@ -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 SendFormTaskSpec : TaskSpec { - - @Inject - lateinit var instancesDataService: InstancesDataService - - override val maxRetries: Int? = null - override val backoffPolicy = BackoffPolicy.EXPONENTIAL - override val backoffDelay: Long = 60_000 - - override fun getTask( - context: Context, - inputData: Map, - isLastUniqueExecution: Boolean - ): Supplier { - DaggerUtils.getComponent(context).inject(this) - - return Supplier { - val projectId = inputData[TaskData.DATA_PROJECT_ID]!! - val instanceId = inputData[TaskData.DATA_INSTANCE_ID]!!.toLong() - instancesDataService.sendInstances(projectId, listOf(instanceId)) - true - } - } - - override fun getWorkManagerAdapter(): Class { - return Adapter::class.java - } - - class Adapter(context: Context, workerParams: WorkerParameters) : - WorkerAdapter(SendFormTaskSpec(), context, workerParams) -} 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 81% 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 79266494f76..cf13497cfe4 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 @@ -23,7 +23,7 @@ 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 @@ -35,8 +35,13 @@ class AutoSendTaskSpec : TaskSpec { DaggerUtils.getComponent(context).inject(this) return Supplier { val projectId = inputData[TaskData.DATA_PROJECT_ID] + val instanceId = inputData[TaskData.DATA_INSTANCE_ID]?.toLong() if (projectId != null) { - instancesDataService.sendInstances(projectId) + if (instanceId != null) { + instancesDataService.sendInstances(projectId, listOf(instanceId)) + } else { + instancesDataService.sendInstances(projectId) + } } else { throw IllegalArgumentException("No project ID provided!") } @@ -48,5 +53,5 @@ class AutoSendTaskSpec : TaskSpec { } class Adapter(context: Context, workerParams: WorkerParameters) : - WorkerAdapter(AutoSendTaskSpec(), context, workerParams) + WorkerAdapter(SendFormsTaskSpec(), context, workerParams) } 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 2b60d9d844f..5ca03856628 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,8 +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.SendFormTaskSpec; -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; @@ -170,7 +169,7 @@ interface Builder { void inject(ShowQRCodeFragment showQRCodeFragment); - void inject(AutoSendTaskSpec autoSendTaskSpec); + void inject(SendFormsTaskSpec sendFormsTaskSpec); void inject(AdminPasswordDialogFragment adminPasswordDialogFragment); @@ -262,8 +261,6 @@ interface Builder { void inject(DownloadFormListTask downloadFormListTask); - void inject(SendFormTaskSpec sendFormTaskSpec); - OpenRosaHttpInterface openRosaHttpInterface(); ReferenceManager referenceManager(); 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 4fd0f99afdd..43e64d627b8 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 @@ -92,7 +92,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { manager.scheduleSubmit("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), - any(), + any(), eq(mapOf(TaskData.DATA_PROJECT_ID to "myProject")), eq(null) ) @@ -107,7 +107,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { manager.scheduleSubmit("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), - any(), + any(), eq(mapOf(TaskData.DATA_PROJECT_ID to "myProject")), eq(Scheduler.NetworkType.WIFI) ) @@ -122,7 +122,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { manager.scheduleSubmit("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), - any(), + any(), eq(mapOf(TaskData.DATA_PROJECT_ID to "myProject")), eq(Scheduler.NetworkType.CELLULAR) ) 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 93% 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 65880ce73f0..99f3d0da418 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 @@ -49,7 +49,7 @@ class AutoSendTaskSpecTest { @Test fun `maxRetries should not be limited`() { - assertThat(AutoSendTaskSpec().maxRetries, equalTo(null)) + assertThat(SendFormsTaskSpec().maxRetries, equalTo(null)) } @Test @@ -57,7 +57,7 @@ class AutoSendTaskSpecTest { 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)) } @@ -67,7 +67,7 @@ class AutoSendTaskSpecTest { 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)) } From 4889cd7113fba6da838568edfb586c4910fef961 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 17:11:24 +0100 Subject: [PATCH 08/24] Support auto send forms in bulk finalization --- .../formmanagement/BulkFinalizationTest.kt | 20 +++++++++++++++++ .../activities/FormEntryViewModelFactory.kt | 6 ++--- .../activities/FormFillingActivity.java | 6 ++--- .../formentry/saving/FormSaveViewModel.java | 14 +++++------- .../formhierarchy/FormHierarchyActivity.java | 6 ++--- .../InstancesDataService.kt | 22 ++++++++++++++++++- 6 files changed, 55 insertions(+), 19 deletions(-) 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 acdad2ac6df..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 @@ -193,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/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt index 062767d8181..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 @@ -6,7 +6,6 @@ import androidx.lifecycle.ViewModel import androidx.savedstate.SavedStateRegistryOwner import org.javarosa.core.model.actions.recordaudio.RecordAudioActions import org.javarosa.core.model.instance.TreeReference -import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler import org.odk.collect.android.entities.EntitiesRepositoryProvider import org.odk.collect.android.formentry.BackgroundAudioViewModel import org.odk.collect.android.formentry.BackgroundAudioViewModel.RecordAudioActionRegistry @@ -20,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 @@ -57,7 +57,7 @@ class FormEntryViewModelFactory( private val savepointsRepositoryProvider: SavepointsRepositoryProvider, private val qrCodeCreator: QRCodeCreator, private val htmlPrinter: HtmlPrinter, - private val instanceSubmitScheduler: InstanceSubmitScheduler + private val instancesDataService: InstancesDataService ) : AbstractSavedStateViewModelFactory(owner, null) { override fun create( @@ -89,7 +89,7 @@ class FormEntryViewModelFactory( entitiesRepositoryProvider.get(projectId), instancesRepositoryProvider.get(projectId), savepointsRepositoryProvider.get(projectId), - instanceSubmitScheduler + 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 ab3b45f5391..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; @@ -434,7 +434,7 @@ public void onCreate(Bundle savedInstanceState) { new SavepointsRepositoryProvider(this, storagePathProvider), new QRCodeCreatorImpl(), new HtmlPrinter(), - instanceSubmitScheduler + instancesDataService ); this.getSupportFragmentManager().setFragmentFactory(new FragmentFactoryBuilder() 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 429f05a549c..c15e241c115 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 @@ -17,12 +17,12 @@ import org.apache.commons.io.IOUtils; import org.javarosa.form.api.FormEntryController; import org.odk.collect.android.application.Collect; -import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler; import org.odk.collect.android.dao.helpers.InstancesDaoHelper; import org.odk.collect.android.dynamicpreload.ExternalDataManager; 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; @@ -91,13 +91,13 @@ public class FormSaveViewModel extends ViewModel implements MaterialProgressDial private Form form; private Instance instance; private final Cancellable formSessionObserver; - private InstanceSubmitScheduler instanceSubmitScheduler; + 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, InstanceSubmitScheduler instanceSubmitScheduler + SavepointsRepository savepointsRepository, InstancesDataService instancesDataService ) { this.stateHandle = stateHandle; this.clock = clock; @@ -109,7 +109,7 @@ public FormSaveViewModel(SavedStateHandle stateHandle, Supplier clock, For this.entitiesRepository = entitiesRepository; this.instancesRepository = instancesRepository; this.savepointsRepository = savepointsRepository; - this.instanceSubmitScheduler = instanceSubmitScheduler; + this.instancesDataService = instancesDataService; if (stateHandle.get(ORIGINAL_FILES) != null) { originalFiles = stateHandle.get(ORIGINAL_FILES); @@ -272,11 +272,7 @@ private void handleTaskResult(SaveToDiskResult taskResult, SaveRequest saveReque formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_EXIT, false, clock.get()); formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_FINALIZE, true, clock.get()); - if (form.getAutoSend() != null && form.getAutoSend().equals("true")) { - instanceSubmitScheduler.scheduleSubmit(projectsDataService.getCurrentProject().getUuid(), instance.getDbId()); - } else { - instanceSubmitScheduler.scheduleSubmit(projectsDataService.getCurrentProject().getUuid()); - } + instancesDataService.instanceFinalized(projectsDataService.getCurrentProject().getUuid(), instance.getDbId()); } 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 7b2454c968c..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 @@ -47,7 +47,6 @@ import org.odk.collect.analytics.Analytics; import org.odk.collect.android.R; import org.odk.collect.android.activities.FormEntryViewModelFactory; -import org.odk.collect.android.backgroundwork.InstanceSubmitScheduler; import org.odk.collect.android.entities.EntitiesRepositoryProvider; import org.odk.collect.android.exception.JavaRosaException; import org.odk.collect.android.formentry.FormEntryViewModel; @@ -55,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; @@ -197,7 +197,7 @@ public class FormHierarchyActivity extends LocalizedActivity implements DeleteRe public StoragePathProvider storagePathProvider; @Inject - public InstanceSubmitScheduler instanceSubmitScheduler; + public InstancesDataService instancesDataService; protected final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { @Override @@ -235,7 +235,7 @@ public void onCreate(Bundle savedInstanceState) { new SavepointsRepositoryProvider(this, storagePathProvider), new QRCodeCreatorImpl(), new HtmlPrinter(), - instanceSubmitScheduler + instancesDataService ); this.getSupportFragmentManager().setFragmentFactory(new FragmentFactoryBuilder() 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 ca390df9035..33d8c4344d2 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 @@ -120,6 +120,7 @@ class InstancesDataService( if (finalizedInstance == null) { result.copy(failureCount = result.failureCount + 1) } else { + instanceFinalized(projectId, finalizedInstance.dbId) result } } @@ -131,7 +132,6 @@ class InstancesDataService( } update(projectId) - instanceSubmitScheduler.scheduleSubmit(projectId) return result.copy(successCount = instances.size - result.failureCount) } @@ -218,6 +218,26 @@ class InstancesDataService( } } + fun instanceFinalized(projectId: String, instanceId: Long? = null) { + if (instanceId != null) { + val projectDependencyProvider = projectDependencyProviderFactory.create(projectId) + val formsRepository = projectDependencyProvider.formsRepository + val instancesRepository = projectDependencyProvider.instancesRepository + + val instance = instancesRepository.get(instanceId)!! + val form = + formsRepository.getLatestByFormIdAndVersion(instance.formId, instance.formVersion)!! + + if (form.autoSend != null && form.autoSend == "true") { + instanceSubmitScheduler.scheduleSubmit(projectId, instance.dbId) + } else { + instanceSubmitScheduler.scheduleSubmit(projectId) + } + } else { + instanceSubmitScheduler.scheduleSubmit(projectId) + } + } + companion object { private const val EDITABLE_COUNT_KEY = "instancesEditableCount" private const val SENDABLE_COUNT_KEY = "instancesSendableCount" From 7e902212f20d6b1189d482470e2a6f0628cfa477 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 17:15:03 +0100 Subject: [PATCH 09/24] Rename method --- .../FormUpdateAndInstanceSubmitScheduler.java | 2 +- .../android/backgroundwork/InstanceSubmitScheduler.java | 2 +- .../android/instancemanagement/InstancesDataService.kt | 4 ++-- .../screens/FormManagementPreferencesFragment.java | 2 +- .../FormUpdateAndInstanceSubmitSchedulerTest.kt | 8 ++++---- .../screens/FormManagementPreferencesFragmentTest.kt | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) 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 8ea2ef47555..cc54f8a69e0 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,7 +68,7 @@ public void cancelUpdates(String projectId) { } @Override - public void scheduleSubmit(String projectId) { + public void scheduleSubmitIfNeeded(String projectId) { Scheduler.NetworkType networkConstraint; Settings settings = settingsProvider.getUnprotectedSettings(projectId); AutoSend autoSendSetting = StringIdEnumUtils.getAutoSend(settings, application); 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 3ee8a9d90fb..50fd93da93b 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,7 @@ public interface InstanceSubmitScheduler { - void scheduleSubmit(String projectId); + void scheduleSubmitIfNeeded(String projectId); void scheduleSubmit(String projectId, Long instanceId); 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 33d8c4344d2..7975a01b394 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 @@ -231,10 +231,10 @@ class InstancesDataService( if (form.autoSend != null && form.autoSend == "true") { instanceSubmitScheduler.scheduleSubmit(projectId, instance.dbId) } else { - instanceSubmitScheduler.scheduleSubmit(projectId) + instanceSubmitScheduler.scheduleSubmitIfNeeded(projectId) } } else { - instanceSubmitScheduler.scheduleSubmit(projectId) + instanceSubmitScheduler.scheduleSubmitIfNeeded(projectId) } } 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..87f95b34223 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.scheduleSubmitIfNeeded(projectsDataService.getCurrentProject().getUuid()); } } 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 43e64d627b8..f80505eae3b 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 @@ -89,7 +89,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "wifi_and_cellular") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmit("myProject") + manager.scheduleSubmitIfNeeded("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), any(), @@ -104,7 +104,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "wifi_only") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmit("myProject") + manager.scheduleSubmitIfNeeded("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), any(), @@ -119,7 +119,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "cellular_only") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmit("myProject") + manager.scheduleSubmitIfNeeded("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), any(), @@ -134,7 +134,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "off") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmit("myProject") + manager.scheduleSubmitIfNeeded("myProject") verifyNoInteractions(scheduler) } 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..039809ae089 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).scheduleSubmitIfNeeded(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()).scheduleSubmitIfNeeded(projectID) } } From c862c75271dcd853856de7a9cda9a8daabd15b9f Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 1 May 2024 19:23:20 +0100 Subject: [PATCH 10/24] Simplify method signature --- .../formentry/saving/FormSaveViewModel.java | 2 +- .../InstancesDataService.kt | 21 +++++-------------- 2 files changed, 6 insertions(+), 17 deletions(-) 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 c15e241c115..1e9e76a629d 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 @@ -272,7 +272,7 @@ private void handleTaskResult(SaveToDiskResult taskResult, SaveRequest saveReque 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(), instance.getDbId()); + instancesDataService.instanceFinalized(projectsDataService.getCurrentProject().getUuid(), form, instance); } else { formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_EXIT, true, clock.get()); } 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 7975a01b394..a25b2b25e7f 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 @@ -15,6 +15,7 @@ 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.AppState +import org.odk.collect.forms.Form import org.odk.collect.forms.instances.Instance import org.odk.collect.metadata.PropertyManager import java.io.File @@ -120,7 +121,7 @@ class InstancesDataService( if (finalizedInstance == null) { result.copy(failureCount = result.failureCount + 1) } else { - instanceFinalized(projectId, finalizedInstance.dbId) + instanceFinalized(projectId, form, instance) result } } @@ -218,21 +219,9 @@ class InstancesDataService( } } - fun instanceFinalized(projectId: String, instanceId: Long? = null) { - if (instanceId != null) { - val projectDependencyProvider = projectDependencyProviderFactory.create(projectId) - val formsRepository = projectDependencyProvider.formsRepository - val instancesRepository = projectDependencyProvider.instancesRepository - - val instance = instancesRepository.get(instanceId)!! - val form = - formsRepository.getLatestByFormIdAndVersion(instance.formId, instance.formVersion)!! - - if (form.autoSend != null && form.autoSend == "true") { - instanceSubmitScheduler.scheduleSubmit(projectId, instance.dbId) - } else { - instanceSubmitScheduler.scheduleSubmitIfNeeded(projectId) - } + fun instanceFinalized(projectId: String, form: Form, instance: Instance) { + if (form.autoSend != null && form.autoSend == "true") { + instanceSubmitScheduler.scheduleSubmit(projectId, instance.dbId) } else { instanceSubmitScheduler.scheduleSubmitIfNeeded(projectId) } From e2a81c6366c2ccca2f98ff7eba211010435420fc Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 2 May 2024 10:34:25 +0100 Subject: [PATCH 11/24] Don't queue multiple form level auto send jobs --- .../FormUpdateAndInstanceSubmitScheduler.java | 12 ++++++------ .../InstanceSubmitScheduler.java | 4 ++-- .../backgroundwork/SendFormsTaskSpec.kt | 6 +++--- .../collect/android/backgroundwork/TaskData.kt | 2 +- .../formentry/saving/FormSaveViewModel.java | 2 +- .../instancemanagement/InstancesDataService.kt | 18 ++++++++++-------- .../FormManagementPreferencesFragment.java | 2 +- ...FormUpdateAndInstanceSubmitSchedulerTest.kt | 8 ++++---- .../FormManagementPreferencesFragmentTest.kt | 4 ++-- 9 files changed, 30 insertions(+), 28 deletions(-) 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 cc54f8a69e0..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,7 +68,7 @@ public void cancelUpdates(String projectId) { } @Override - public void scheduleSubmitIfNeeded(String projectId) { + public void scheduleAutoSend(String projectId) { Scheduler.NetworkType networkConstraint; Settings settings = settingsProvider.getUnprotectedSettings(projectId); AutoSend autoSendSetting = StringIdEnumUtils.getAutoSend(settings, application); @@ -88,11 +88,11 @@ public void scheduleSubmitIfNeeded(String projectId) { } @Override - public void scheduleSubmit(String projectId, Long instanceId) { + public void scheduleFormAutoSend(String projectId) { HashMap inputData = new HashMap<>(); inputData.put(TaskData.DATA_PROJECT_ID, projectId); - inputData.put(TaskData.DATA_INSTANCE_ID, instanceId.toString()); - scheduler.networkDeferred(getAutoSendFormTag(projectId, instanceId), new SendFormsTaskSpec(), inputData, null); + inputData.put(TaskData.DATA_FORM_AUTO_SEND, ""); + scheduler.networkDeferred(getAutoSendFormTag(projectId), new SendFormsTaskSpec(), inputData, null); } @Override @@ -105,8 +105,8 @@ public String getAutoSendTag(String projectId) { return "AutoSendWorker:" + projectId; } - public String getAutoSendFormTag(String projectId, Long instanceId) { - return "auto_send_form:" + projectId + ":" + instanceId; + public String getAutoSendFormTag(String projectId) { + return "auto_send_form:" + projectId; } @NotNull 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 50fd93da93b..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,9 +2,9 @@ public interface InstanceSubmitScheduler { - void scheduleSubmitIfNeeded(String projectId); + void scheduleAutoSend(String projectId); - void scheduleSubmit(String projectId, Long instanceId); + void scheduleFormAutoSend(String projectId); void cancelSubmit(String projectId); } diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt index cf13497cfe4..1a40373750e 100644 --- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt +++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt @@ -35,10 +35,10 @@ class SendFormsTaskSpec : TaskSpec { DaggerUtils.getComponent(context).inject(this) return Supplier { val projectId = inputData[TaskData.DATA_PROJECT_ID] - val instanceId = inputData[TaskData.DATA_INSTANCE_ID]?.toLong() + val formAutoSend = inputData[TaskData.DATA_FORM_AUTO_SEND] != null if (projectId != null) { - if (instanceId != null) { - instancesDataService.sendInstances(projectId, listOf(instanceId)) + if (formAutoSend) { + instancesDataService.sendInstances(projectId, formAutoSendOnly = true) } else { instancesDataService.sendInstances(projectId) } 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 84460388517..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,5 +2,5 @@ package org.odk.collect.android.backgroundwork object TaskData { const val DATA_PROJECT_ID = "projectId" - const val DATA_INSTANCE_ID = "instanceId" + 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 1e9e76a629d..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 @@ -272,7 +272,7 @@ private void handleTaskResult(SaveToDiskResult taskResult, SaveRequest saveReque 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, instance); + 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/instancemanagement/InstancesDataService.kt b/collect_app/src/main/java/org/odk/collect/android/instancemanagement/InstancesDataService.kt index a25b2b25e7f..a6c2e7ed48a 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 @@ -121,7 +121,7 @@ class InstancesDataService( if (finalizedInstance == null) { result.copy(failureCount = result.failureCount + 1) } else { - instanceFinalized(projectId, form, instance) + instanceFinalized(projectId, form) result } } @@ -177,7 +177,7 @@ class InstancesDataService( } } - fun sendInstances(projectId: String, instanceIds: List? = null): Boolean { + fun sendInstances(projectId: String, formAutoSendOnly: Boolean = false): Boolean { val projectDependencyProvider = projectDependencyProviderFactory.create(projectId) @@ -193,9 +193,11 @@ class InstancesDataService( projectDependencyProvider.projectId ).withLock { acquiredLock: Boolean -> if (acquiredLock) { - val toUpload = if (instanceIds != null) { - instanceIds.map { - projectDependencyProvider.instancesRepository.get(it)!! + val toUpload = if (formAutoSendOnly) { + projectDependencyProvider.instancesRepository.all.filter { + projectDependencyProvider.formsRepository.getLatestByFormIdAndVersion(it.formId, it.formVersion)?.let { form -> + form.autoSend != null && form.autoSend == "true" + } ?: false } } else { InstanceAutoSendFetcher.getInstancesToAutoSend( @@ -219,11 +221,11 @@ class InstancesDataService( } } - fun instanceFinalized(projectId: String, form: Form, instance: Instance) { + fun instanceFinalized(projectId: String, form: Form) { if (form.autoSend != null && form.autoSend == "true") { - instanceSubmitScheduler.scheduleSubmit(projectId, instance.dbId) + instanceSubmitScheduler.scheduleFormAutoSend(projectId) } else { - instanceSubmitScheduler.scheduleSubmitIfNeeded(projectId) + instanceSubmitScheduler.scheduleAutoSend(projectId) } } 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 87f95b34223..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.scheduleSubmitIfNeeded(projectsDataService.getCurrentProject().getUuid()); + instanceSubmitScheduler.scheduleAutoSend(projectsDataService.getCurrentProject().getUuid()); } } 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 f80505eae3b..7b102fa8ab8 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 @@ -89,7 +89,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "wifi_and_cellular") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmitIfNeeded("myProject") + manager.scheduleAutoSend("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), any(), @@ -104,7 +104,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "wifi_only") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmitIfNeeded("myProject") + manager.scheduleAutoSend("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), any(), @@ -119,7 +119,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "cellular_only") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmitIfNeeded("myProject") + manager.scheduleAutoSend("myProject") verify(scheduler).networkDeferred( eq("AutoSendWorker:myProject"), any(), @@ -134,7 +134,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { .save(ProjectKeys.KEY_AUTOSEND, "off") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) - manager.scheduleSubmitIfNeeded("myProject") + manager.scheduleAutoSend("myProject") verifyNoInteractions(scheduler) } 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 039809ae089..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).scheduleSubmitIfNeeded(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()).scheduleSubmitIfNeeded(projectID) + verify(instanceSubmitScheduler, never()).scheduleAutoSend(projectID) } } From b4d5fdeebded112ab1204905cb6a9a56588f1548 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 2 May 2024 10:46:09 +0100 Subject: [PATCH 12/24] Make sure form level auto send doesn't resend forms --- .../backgroundwork/SendFormsTaskSpec.kt | 2 +- .../InstancesDataService.kt | 19 +++----- .../autosend/InstanceAutoSendFetcher.kt | 15 +++++-- .../autosend/InstanceAutoSendFetcherTest.kt | 44 +++++++++++++++++++ 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt index 1a40373750e..3b507577154 100644 --- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt +++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt @@ -38,7 +38,7 @@ class SendFormsTaskSpec : TaskSpec { val formAutoSend = inputData[TaskData.DATA_FORM_AUTO_SEND] != null if (projectId != null) { if (formAutoSend) { - instancesDataService.sendInstances(projectId, formAutoSendOnly = true) + instancesDataService.sendInstances(projectId, formAutoSend = true) } else { instancesDataService.sendInstances(projectId) } 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 a6c2e7ed48a..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 @@ -177,7 +177,7 @@ class InstancesDataService( } } - fun sendInstances(projectId: String, formAutoSendOnly: Boolean = false): Boolean { + fun sendInstances(projectId: String, formAutoSend: Boolean = false): Boolean { val projectDependencyProvider = projectDependencyProviderFactory.create(projectId) @@ -193,18 +193,11 @@ class InstancesDataService( projectDependencyProvider.projectId ).withLock { acquiredLock: Boolean -> if (acquiredLock) { - val toUpload = if (formAutoSendOnly) { - projectDependencyProvider.instancesRepository.all.filter { - projectDependencyProvider.formsRepository.getLatestByFormIdAndVersion(it.formId, it.formVersion)?.let { form -> - form.autoSend != null && form.autoSend == "true" - } ?: false - } - } else { - InstanceAutoSendFetcher.getInstancesToAutoSend( - projectDependencyProvider.instancesRepository, - projectDependencyProvider.formsRepository - ) - } + val toUpload = InstanceAutoSendFetcher.getInstancesToAutoSend( + projectDependencyProvider.instancesRepository, + projectDependencyProvider.formsRepository, + formAutoSend + ) if (toUpload.isNotEmpty()) { val results = instanceSubmitter.submitInstances(toUpload) 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 84b8c9619e1..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,5 +1,6 @@ package org.odk.collect.android.instancemanagement.autosend +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 @@ -8,17 +9,23 @@ object InstanceAutoSendFetcher { fun getInstancesToAutoSend( instancesRepository: InstancesRepository, - formsRepository: FormsRepository + formsRepository: FormsRepository, + formAutoSend: Boolean = false ): List { val allFinalizedForms = instancesRepository.getAllByStatus( Instance.STATUS_COMPLETE, Instance.STATUS_SUBMISSION_FAILED ) + 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.autoSend == null - } ?: false + formsRepository.getLatestByFormIdAndVersion(it.formId, it.formVersion) + ?.let { form -> filter(form) } ?: false } } } 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 0001dcba000..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 @@ -88,4 +88,48 @@ class InstanceAutoSendFetcherTest { ) ) } + + @Test + fun `return all finalized forms with autosend when formAutoSend is true`() { + formsRepository.save(formWithEnabledAutoSend) + formsRepository.save(formWithoutSpecifiedAutoSend) + formsRepository.save(formWithDisabledAutoSend) + formsRepository.save(formWithCustomAutoSend) + + instancesRepository.apply { + save(instanceOfFormWithEnabledAutoSendIncomplete) + save(instanceOfFormWithEnabledAutoSendComplete) + save(instanceOfFormWithEnabledAutoSendSubmissionFailed) + save(instanceOfFormWithEnabledAutoSendSubmitted) + + save(instanceOfFormWithoutSpecifiedAutoSendIncomplete) + save(instanceOfFormWithoutSpecifiedAutoSendComplete) + save(instanceOfFormWithoutSpecifiedAutoSendSubmissionFailed) + save(instanceOfFormWithoutSpecifiedAutoSendSubmitted) + + save(instanceOfFormWithDisabledAutoSendIncomplete) + save(instanceOfFormWithDisabledAutoSendComplete) + save(instanceOfFormWithDisabledAutoSendSubmissionFailed) + save(instanceOfFormWithDisabledAutoSendSubmitted) + + save(instanceOfFormWithCustomAutoSendIncomplete) + save(instanceOfFormWithCustomAutoSendComplete) + save(instanceOfFormWithCustomAutoSendSubmissionFailed) + save(instanceOfFormWithCustomAutoSendSubmitted) + } + + val instancesToSend = InstanceAutoSendFetcher.getInstancesToAutoSend( + instancesRepository, + formsRepository, + formAutoSend = true + ) + + assertThat( + instancesToSend.map { it.instanceFilePath }, + contains( + instanceOfFormWithEnabledAutoSendComplete.instanceFilePath, + instanceOfFormWithEnabledAutoSendSubmissionFailed.instanceFilePath, + ) + ) + } } From 6c8398a2ef53a3381fe4e8b4cf21348d7b01d130 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 3 May 2024 10:52:17 +0100 Subject: [PATCH 13/24] Simplify TaskSpec and make sure jobs are rescheduled to account for name changes etc --- .../async/CoroutineAndWorkManagerScheduler.kt | 16 +++-- .../java/org/odk/collect/async/TaskSpec.kt | 6 -- .../{WorkerAdapter.kt => TaskSpecWorker.kt} | 16 +++-- ...atesUpgrade.kt => ScheduledWorkUpgrade.kt} | 11 ++- .../upgrade/UpgradeInitializer.kt | 6 +- .../backgroundwork/AutoUpdateTaskSpec.kt | 9 --- .../backgroundwork/SendFormsTaskSpec.kt | 9 --- .../backgroundwork/SyncFormsTaskSpec.kt | 9 --- .../injection/config/AppDependencyModule.java | 10 +-- .../initialization/FormUpdatesUpgradeTest.kt | 35 ---------- .../ScheduledWorkUpgradeTest.kt | 67 +++++++++++++++++++ 11 files changed, 106 insertions(+), 88 deletions(-) rename async/src/main/java/org/odk/collect/async/{WorkerAdapter.kt => TaskSpecWorker.kt} (55%) rename collect_app/src/main/java/org/odk/collect/android/application/initialization/{FormUpdatesUpgrade.kt => ScheduledWorkUpgrade.kt} (57%) delete mode 100644 collect_app/src/test/java/org/odk/collect/android/application/initialization/FormUpdatesUpgradeTest.kt create mode 100644 collect_app/src/test/java/org/odk/collect/android/application/initialization/ScheduledWorkUpgradeTest.kt 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..9a067c6a375 100644 --- a/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt +++ b/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt @@ -28,10 +28,12 @@ class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, back .setRequiredNetworkType(constraintNetworkType) .build() - val workManagerInputData = Data.Builder().putAll(inputData).build() + val workManagerInputData = Data.Builder() + .putString(TaskSpecWorker.TASK_SPEC_CLASS, spec.javaClass.name) + .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) @@ -45,10 +47,12 @@ class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, back .setRequiredNetworkType(NetworkType.CONNECTED) .build() - val workManagerInputData = Data.Builder().putAll(inputData).build() + val workManagerInputData = Data.Builder() + .putString(TaskSpecWorker.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) 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/WorkerAdapter.kt b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt similarity index 55% rename from async/src/main/java/org/odk/collect/async/WorkerAdapter.kt rename to async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt index fc65d1575e0..26857fc2fe6 100644 --- a/async/src/main/java/org/odk/collect/async/WorkerAdapter.kt +++ b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt @@ -4,15 +4,18 @@ import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters -abstract class WorkerAdapter( - private val spec: TaskSpec, +class TaskSpecWorker( context: Context, workerParams: WorkerParameters ) : Worker(context, workerParams) { override fun doWork(): Result { + val specClass = inputData.getString(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()).get() + val completed = + spec.getTask(applicationContext, stringInputData, isLastUniqueExecution(spec)).get() val maxRetries = spec.maxRetries return if (completed) { @@ -24,5 +27,10 @@ abstract class WorkerAdapter( } } - private fun isLastUniqueExecution() = spec.maxRetries?.let { runAttemptCount >= it } ?: true + private fun isLastUniqueExecution(spec: TaskSpec) = + spec.maxRetries?.let { runAttemptCount >= it } ?: true + + companion object { + const val TASK_SPEC_CLASS = "taskSpecClass" + } } 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/SendFormsTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt index 3b507577154..f4672ceafdd 100644 --- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt +++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt @@ -15,11 +15,9 @@ 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 @@ -47,11 +45,4 @@ class SendFormsTaskSpec : TaskSpec { } } } - - override fun getWorkManagerAdapter(): Class { - return Adapter::class.java - } - - class Adapter(context: Context, workerParams: WorkerParameters) : - WorkerAdapter(SendFormsTaskSpec(), 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/injection/config/AppDependencyModule.java b/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java index dcc7ead4828..cdd6adc4dfa 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; @@ -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/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) + } +} From 4d8c8410e3af08f99bdea7a03bd958b0ba33a950 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 3 May 2024 10:58:19 +0100 Subject: [PATCH 14/24] Simplify NetworkStateProvider API --- .../network/ConnectivityProvider.kt | 19 +++++++++---------- .../network/NetworkStateProvider.kt | 6 +++++- .../async/CoroutineAndWorkManagerScheduler.kt | 2 +- .../java/org/odk/collect/async/Scheduler.kt | 3 ++- 4 files changed, 17 insertions(+), 13 deletions(-) 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 index 020dd81848b..783966df725 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt @@ -5,18 +5,17 @@ import android.net.ConnectivityManager import org.odk.collect.async.Scheduler class ConnectivityProvider(private val context: Context) : NetworkStateProvider { - override val isDeviceOnline: Boolean - get() { - val activeNetworkInfo = connectivityManager.activeNetworkInfo - return activeNetworkInfo != null && activeNetworkInfo.isConnected - } - override val currentNetwork: Scheduler.NetworkType? get() { - return when (connectivityManager.activeNetworkInfo?.type) { - ConnectivityManager.TYPE_WIFI -> Scheduler.NetworkType.WIFI - ConnectivityManager.TYPE_MOBILE -> Scheduler.NetworkType.CELLULAR - else -> null + return if (connectivityManager.activeNetworkInfo?.isConnected == true) { + when (connectivityManager.activeNetworkInfo?.type) { + ConnectivityManager.TYPE_WIFI -> Scheduler.NetworkType.WIFI + ConnectivityManager.TYPE_MOBILE -> Scheduler.NetworkType.CELLULAR + null -> null + else -> Scheduler.NetworkType.OTHER + } + } else { + null } } 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 index 8a2b4db3a8f..d775213f8b9 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt @@ -3,6 +3,10 @@ package org.odk.collect.androidshared.network import org.odk.collect.async.Scheduler interface NetworkStateProvider { - val isDeviceOnline: Boolean val currentNetwork: Scheduler.NetworkType? + + val isDeviceOnline: Boolean + get() { + return currentNetwork != null + } } 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 9a067c6a375..45179cf962c 100644 --- a/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt +++ b/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt @@ -21,7 +21,7 @@ class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, back val constraintNetworkType = when (networkConstraint) { Scheduler.NetworkType.WIFI -> NetworkType.UNMETERED Scheduler.NetworkType.CELLULAR -> NetworkType.METERED - null -> NetworkType.CONNECTED + else -> NetworkType.CONNECTED } val constraints = Constraints.Builder() 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 } } From 1d88a3b68770f870b221c3be1df12587df43e9cd Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 3 May 2024 11:23:01 +0100 Subject: [PATCH 15/24] Retry cellular tasks if connected to a metered non-cellular connection --- async/src/main/AndroidManifest.xml | 3 +- .../async/CoroutineAndWorkManagerScheduler.kt | 50 +++++++++++++++---- .../org/odk/collect/async/TaskSpecWorker.kt | 13 ++++- .../async}/network/ConnectivityProvider.kt | 2 +- .../async}/network/NetworkStateProvider.kt | 2 +- .../support/FakeNetworkStateProvider.kt | 7 +-- .../collect/android/support/TestScheduler.kt | 2 +- .../activities/FormDownloadListActivity.java | 2 +- .../collect/android/application/Collect.java | 2 +- .../backgroundwork/SendFormsTaskSpec.kt | 2 +- .../blankformlist/BlankFormListActivity.kt | 2 +- .../BlankFormListMenuProvider.kt | 2 +- .../config/AppDependencyComponent.java | 2 +- .../injection/config/AppDependencyModule.java | 4 +- .../autosend/AutoSendSettingsProvider.kt | 2 +- .../send/InstanceUploaderListActivity.java | 2 +- .../BlankFormListMenuProviderTest.kt | 2 +- .../autosend/AutoSendSettingsProviderTest.kt | 2 +- 18 files changed, 69 insertions(+), 34 deletions(-) rename {androidshared/src/main/java/org/odk/collect/androidshared => async/src/main/java/org/odk/collect/async}/network/ConnectivityProvider.kt (95%) rename {androidshared/src/main/java/org/odk/collect/androidshared => async/src/main/java/org/odk/collect/async}/network/NetworkStateProvider.kt (82%) 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 45179cf962c..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,11 +13,24 @@ 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 @@ -29,7 +42,11 @@ class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, back .build() val workManagerInputData = Data.Builder() - .putString(TaskSpecWorker.TASK_SPEC_CLASS, spec.javaClass.name) + .putString(TaskSpecWorker.DATA_TASK_SPEC_CLASS, spec.javaClass.name) + .putBoolean( + TaskSpecWorker.DATA_CELLULAR_ONLY, + networkConstraint == Scheduler.NetworkType.CELLULAR + ) .putAll(inputData) .build() @@ -42,17 +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() - .putString(TaskSpecWorker.TASK_SPEC_CLASS, spec.javaClass.name) + .putString(TaskSpecWorker.DATA_TASK_SPEC_CLASS, spec.javaClass.name) .putAll(inputData) .build() - val builder = PeriodicWorkRequest.Builder(TaskSpecWorker::class.java, repeatPeriod, TimeUnit.MILLISECONDS) + val builder = PeriodicWorkRequest.Builder( + TaskSpecWorker::class.java, + repeatPeriod, + TimeUnit.MILLISECONDS + ) .addTag(tag) .setInputData(workManagerInputData) .setConstraints(constraints) @@ -63,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/TaskSpecWorker.kt b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt index 26857fc2fe6..873f88be48a 100644 --- a/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt +++ b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt @@ -3,14 +3,22 @@ package org.odk.collect.async import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters +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 specClass = inputData.getString(TASK_SPEC_CLASS)!! + val cellularOnly = inputData.getBoolean(DATA_CELLULAR_ONLY, false) + if (cellularOnly && connectivityProvider.currentNetwork != Scheduler.NetworkType.CELLULAR) { + 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() } @@ -31,6 +39,7 @@ class TaskSpecWorker( spec.maxRetries?.let { runAttemptCount >= it } ?: true companion object { - const val TASK_SPEC_CLASS = "taskSpecClass" + const val DATA_TASK_SPEC_CLASS = "taskSpecClass" + const val DATA_CELLULAR_ONLY = "cellularOnly" } } diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt b/async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt similarity index 95% rename from androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt rename to async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt index 783966df725..a789e868c90 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/network/ConnectivityProvider.kt +++ b/async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt @@ -1,4 +1,4 @@ -package org.odk.collect.androidshared.network +package org.odk.collect.async.network import android.content.Context import android.net.ConnectivityManager diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt b/async/src/main/java/org/odk/collect/async/network/NetworkStateProvider.kt similarity index 82% rename from androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt rename to async/src/main/java/org/odk/collect/async/network/NetworkStateProvider.kt index d775213f8b9..410454a5106 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/network/NetworkStateProvider.kt +++ b/async/src/main/java/org/odk/collect/async/network/NetworkStateProvider.kt @@ -1,4 +1,4 @@ -package org.odk.collect.androidshared.network +package org.odk.collect.async.network import org.odk.collect.async.Scheduler 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 index 2ac0de10d5d..395e59f4db8 100644 --- 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 @@ -1,25 +1,20 @@ package org.odk.collect.android.support -import org.odk.collect.androidshared.network.NetworkStateProvider import org.odk.collect.async.Scheduler +import org.odk.collect.async.network.NetworkStateProvider class FakeNetworkStateProvider : NetworkStateProvider { - private var online = true private var type: Scheduler.NetworkType? = Scheduler.NetworkType.WIFI fun goOnline(networkType: Scheduler.NetworkType) { - online = true type = networkType } fun goOffline() { - online = false type = null } - override val isDeviceOnline: Boolean - get() = online override val currentNetwork: Scheduler.NetworkType? get() = type } 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 1fb4f4f1773..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 @@ -7,11 +7,11 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Runnable import kotlinx.coroutines.flow.Flow -import org.odk.collect.androidshared.network.NetworkStateProvider 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 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/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/backgroundwork/SendFormsTaskSpec.kt b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt index f4672ceafdd..6cedcb5aeb9 100644 --- a/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt +++ b/collect_app/src/main/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpec.kt @@ -25,7 +25,7 @@ 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 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 5ca03856628..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 @@ -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; 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 cdd6adc4dfa..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 @@ -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; 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 837579b282d..c8997f40c08 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,7 +1,7 @@ package org.odk.collect.android.instancemanagement.autosend import android.app.Application -import org.odk.collect.androidshared.network.NetworkStateProvider +import org.odk.collect.async.network.NetworkStateProvider import org.odk.collect.async.Scheduler import org.odk.collect.settings.SettingsProvider import org.odk.collect.settings.enums.AutoSend 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/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/autosend/AutoSendSettingsProviderTest.kt b/collect_app/src/test/java/org/odk/collect/android/instancemanagement/autosend/AutoSendSettingsProviderTest.kt index 4a60a6e1b69..e3f13150a56 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 @@ -9,7 +9,7 @@ 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.network.NetworkStateProvider import org.odk.collect.async.Scheduler import org.odk.collect.projects.Project import org.odk.collect.settings.InMemSettingsProvider From 8b1f171e03cc7603247074c2b3f6334d0eb2f50b Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 3 May 2024 11:45:22 +0100 Subject: [PATCH 16/24] Rework test --- ...erAdapterTest.kt => TaskSpecWorkerTest.kt} | 119 ++++++++++++------ .../autosend/AutoSendSettingsProvider.kt | 2 +- .../autosend/AutoSendSettingsProviderTest.kt | 2 +- 3 files changed, 81 insertions(+), 42 deletions(-) rename async/src/test/java/org/odk/collect/async/{WorkerAdapterTest.kt => TaskSpecWorkerTest.kt} (51%) 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..b28f0a2dbcd 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)) +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/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 c8997f40c08..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 org.odk.collect.async.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 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 e3f13150a56..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 @@ -9,8 +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.async.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 From 8ca2b49da50e72d23a1798b755bd3a2d60a563c3 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 3 May 2024 12:03:34 +0100 Subject: [PATCH 17/24] Remove test mirroring implementation --- .../collect/android/backgroundwork/SendFormsTaskSpecTest.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/collect_app/src/test/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpecTest.kt b/collect_app/src/test/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpecTest.kt index 99f3d0da418..915ef9b1192 100644 --- a/collect_app/src/test/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpecTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/backgroundwork/SendFormsTaskSpecTest.kt @@ -47,11 +47,6 @@ class SendFormsTaskSpecTest { projectId = CollectHelpers.setupDemoProject() } - @Test - fun `maxRetries should not be limited`() { - assertThat(SendFormsTaskSpec().maxRetries, equalTo(null)) - } - @Test fun `returns false if sending instances fails`() { whenever(instancesDataService.sendInstances(projectId)).doReturn(false) From 57c459cdf0000419c0cf4eb54326789259a7a909 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 7 May 2024 17:37:35 +0100 Subject: [PATCH 18/24] Make sure network state provider is shared properly --- .../org/odk/collect/android/support/TestDependencies.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 cccc4760708..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,6 +12,7 @@ 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 { @@ -35,4 +37,9 @@ public Scheduler providesScheduler(WorkManager workManager) { public BarcodeViewDecoder providesBarcodeViewDecoder() { return stubBarcodeViewDecoder; } + + @Override + public NetworkStateProvider providesNetworkStateProvider(Context context) { + return networkStateProvider; + } } From 78a1bda04c6c1e412514199605118bdfa4023cbd Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 7 May 2024 17:55:43 +0100 Subject: [PATCH 19/24] Set user property when user runs into metered wifi --- .../src/main/java/org/odk/collect/analytics/Analytics.kt | 4 ++++ async/build.gradle.kts | 3 +++ async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt | 2 ++ 3 files changed, 9 insertions(+) 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/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/java/org/odk/collect/async/TaskSpecWorker.kt b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt index 873f88be48a..c3e41880436 100644 --- a/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt +++ b/async/src/main/java/org/odk/collect/async/TaskSpecWorker.kt @@ -3,6 +3,7 @@ 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( @@ -15,6 +16,7 @@ class TaskSpecWorker( 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() } From 8dd91aa83d38a7c1482dae8cdd4900e685089991 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 10 Jun 2024 11:10:49 +0100 Subject: [PATCH 20/24] Remove unneeded line from test --- .../collect/android/feature/instancemanagement/AutoSendTest.kt | 2 -- 1 file changed, 2 deletions(-) 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 67427bb3707..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 @@ -177,8 +177,6 @@ class AutoSendTest { @Test fun whenFormHasAutoSendDisabled_fillingAndFinalizingForm_doesNotSendForm_regardlessOfSetting() { - testDependencies.server.alwaysReturnError() - val mainMenuPage = rule.startAtMainMenu() .setServer(testDependencies.server.url) .enableAutoSend( From 5a900990cbe6e01cc6bbb842f17bd3813d5c8451 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 10 Jun 2024 11:15:19 +0100 Subject: [PATCH 21/24] Fix import --- mapbox/build.gradle.kts | 1 + .../java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 From f08bf48c2a582d7c163f71b1958a1384686fbfa8 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 10 Jun 2024 11:24:13 +0100 Subject: [PATCH 22/24] Update test names --- .../FormUpdateAndInstanceSubmitSchedulerTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 7b102fa8ab8..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 @@ -84,7 +84,7 @@ 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) @@ -99,7 +99,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { } @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) @@ -114,7 +114,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { } @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) @@ -129,7 +129,7 @@ class FormUpdateAndInstanceSubmitSchedulerTest { } @Test - fun `scheduleSubmit does nothing if auto send is disabled`() { + fun `scheduleAutoSend does nothing if auto send is disabled`() { settingsProvider.getUnprotectedSettings("myProject") .save(ProjectKeys.KEY_AUTOSEND, "off") val manager = FormUpdateAndInstanceSubmitScheduler(scheduler, settingsProvider, application) From 52cc9b15c3d11cc186275d48c0e31e89a259bb96 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 10 Jun 2024 11:27:53 +0100 Subject: [PATCH 23/24] Remove impossible when clause --- .../java/org/odk/collect/async/network/ConnectivityProvider.kt | 1 - 1 file changed, 1 deletion(-) 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 index a789e868c90..526ad50c363 100644 --- a/async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt +++ b/async/src/main/java/org/odk/collect/async/network/ConnectivityProvider.kt @@ -11,7 +11,6 @@ class ConnectivityProvider(private val context: Context) : NetworkStateProvider when (connectivityManager.activeNetworkInfo?.type) { ConnectivityManager.TYPE_WIFI -> Scheduler.NetworkType.WIFI ConnectivityManager.TYPE_MOBILE -> Scheduler.NetworkType.CELLULAR - null -> null else -> Scheduler.NetworkType.OTHER } } else { From 64355c8a17d05832702b2b77bebe3a36d82dbe2d Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 10 Jun 2024 11:35:17 +0100 Subject: [PATCH 24/24] Make test helper private --- async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt b/async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt index b28f0a2dbcd..df69f12ee0f 100644 --- a/async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt +++ b/async/src/test/java/org/odk/collect/async/TaskSpecWorkerTest.kt @@ -114,7 +114,7 @@ class TaskSpecWorkerTest { } } -class TestTaskSpec : TaskSpec { +private class TestTaskSpec : TaskSpec { companion object {