diff --git a/buildSrc/src/main/java/dependencies/Dependencies.kt b/buildSrc/src/main/java/dependencies/Dependencies.kt index 5def5c71b8b..680b88b2a29 100644 --- a/buildSrc/src/main/java/dependencies/Dependencies.kt +++ b/buildSrc/src/main/java/dependencies/Dependencies.kt @@ -43,7 +43,7 @@ object Dependencies { const val rarepebble_colorpicker = "com.github.martin-stone:hsv-alpha-color-picker-android:3.0.1" const val commons_io = "commons-io:commons-io:2.5" // Commons 2.6+ introduce java.nio usage that we can't access until our minSdkVersion >= 26 (https://developer.android.com/reference/java/io/File#toPath()) const val opencsv = "com.opencsv:opencsv:5.7.1" - const val javarosa = "org.getodk:javarosa:4.1.0" + const val javarosa = "org.getodk:javarosa:4.2.0-SNAPSHOT" const val javarosa_local = "org.getodk:javarosa:local" const val karumi_dexter = "com.karumi:dexter:6.2.3" const val zxing_android_embedded = "com.journeyapps:zxing-android-embedded:4.3.0" diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java index e0253031479..1ddcdf91dd8 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java @@ -16,11 +16,15 @@ package org.odk.collect.android.instrumented.forms; +import static junit.framework.Assert.assertEquals; + import android.app.Application; import androidx.test.core.app.ApplicationProvider; import org.javarosa.core.model.FormDef; +import org.javarosa.form.api.FormEntryController; +import org.javarosa.form.api.FormEntryModel; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -46,8 +50,6 @@ import timber.log.Timber; -import static junit.framework.Assert.assertEquals; - /** * This test has been created in order to check indices while navigating through a form. * It's especially important while navigating through a form that contains nested groups and if we @@ -59,6 +61,13 @@ @RunWith(Parameterized.class) public class FormNavigationTest { + private final FormLoaderTask.FormEntryControllerFactory formEntryControllerFactory = new FormLoaderTask.FormEntryControllerFactory() { + @Override + public FormEntryController create(FormDef formDef) { + return new FormEntryController(new FormEntryModel(formDef)); + } + }; + @Rule public RuleChain copyFormChain = TestRuleChain.chain() .around(new RunnableRule(() -> { @@ -120,7 +129,7 @@ private void testIndices(String formName, String[] expectedIndices) throws Execu Timber.i(e); } - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath(formName), null, null); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath(formName), null, null, formEntryControllerFactory); formLoaderTask.setFormLoaderListener(new FormLoaderListener() { @Override public void loadingComplete(FormLoaderTask task, FormDef fd, String warningMsg) { diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java index 69d4457c333..dc29b71aee0 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java @@ -1,18 +1,21 @@ package org.odk.collect.android.instrumented.forms; +import org.javarosa.core.model.FormDef; import org.javarosa.core.reference.RootTranslator; +import org.javarosa.form.api.FormEntryController; +import org.javarosa.form.api.FormEntryModel; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; -import org.odk.collect.android.support.rules.CollectTestRule; -import org.odk.collect.android.support.rules.TestRuleChain; -import org.odk.collect.android.utilities.FormUtils; import org.odk.collect.android.storage.StoragePathProvider; import org.odk.collect.android.storage.StorageSubdirectory; +import org.odk.collect.android.support.rules.CollectTestRule; +import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.android.tasks.FormLoaderTask; import org.odk.collect.android.utilities.FileUtils; +import org.odk.collect.android.utilities.FormUtils; import java.io.File; import java.util.List; @@ -21,6 +24,13 @@ public class FormUtilsTest { private static final String BASIC_FORM = "basic.xml"; private final CollectTestRule rule = new CollectTestRule(); + private final FormLoaderTask.FormEntryControllerFactory formEntryControllerFactory = new FormLoaderTask.FormEntryControllerFactory() { + @Override + public FormEntryController create(FormDef formDef) { + return new FormEntryController(new FormEntryModel(formDef)); + } + }; + @Rule public RuleChain copyFormChain = TestRuleChain.chain() .around(rule); @@ -38,7 +48,7 @@ public void setUp() { public void sessionRootTranslatorOrderDoesNotMatter() throws Exception { final String formPath = new StoragePathProvider().getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + BASIC_FORM; // Load the form in order to populate the ReferenceManager - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); formLoaderTask.execute(formPath).get(); final File formXml = new File(formPath); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java index 928c10b4bb2..af2476aef5b 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java @@ -1,5 +1,15 @@ package org.odk.collect.android.instrumented.tasks; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +import android.app.Application; + +import androidx.test.core.app.ApplicationProvider; + +import org.javarosa.core.model.FormDef; +import org.javarosa.form.api.FormEntryController; +import org.javarosa.form.api.FormEntryModel; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -12,6 +22,7 @@ import org.odk.collect.android.support.rules.RunnableRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.android.tasks.FormLoaderTask; +import org.odk.collect.android.tasks.FormLoaderTask.FormEntryControllerFactory; import org.odk.collect.projects.Project; import java.io.File; @@ -19,13 +30,6 @@ import java.util.Arrays; import java.util.Collections; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.notNullValue; - -import android.app.Application; - -import androidx.test.core.app.ApplicationProvider; - public class FormLoaderTaskTest { private final StoragePathProvider storagePathProvider = new StoragePathProvider(); @@ -35,6 +39,13 @@ public class FormLoaderTaskTest { private static final String SIMPLE_SEARCH_EXTERNAL_CSV_FILE = "simple-search-external-csv-fruits.csv"; private static final String SIMPLE_SEARCH_EXTERNAL_DB_FILE = "simple-search-external-csv-fruits.db"; + private final FormEntryControllerFactory formEntryControllerFactory = new FormEntryControllerFactory() { + @Override + public FormEntryController create(FormDef formDef) { + return new FormEntryController(new FormEntryModel(formDef)); + } + }; + @Rule public RuleChain copyFormChain = TestRuleChain.chain() .around(new RunnableRule(() -> { @@ -55,7 +66,7 @@ public class FormLoaderTaskTest { @Test public void loadFormWithSecondaryCSV() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SECONDARY_INSTANCE_EXTERNAL_CSV_FORM; - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); Assert.assertNotNull(wrapper); } @@ -64,7 +75,7 @@ public void loadFormWithSecondaryCSV() throws Exception { @Test public void loadSearchFromExternalCSV() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SIMPLE_SEARCH_EXTERNAL_CSV_FORM; - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); assertThat(wrapper, notNullValue()); } @@ -72,7 +83,7 @@ public void loadSearchFromExternalCSV() throws Exception { @Test public void loadSearchFromexternalCsvLeavesFileUnchanged() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SIMPLE_SEARCH_EXTERNAL_CSV_FORM; - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); Assert.assertNotNull(wrapper); Assert.assertNotNull(wrapper.getController()); @@ -87,7 +98,7 @@ public void loadSearchFromexternalCsvLeavesFileUnchanged() throws Exception { public void loadSearchFromExternalCSVmultipleTimes() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SIMPLE_SEARCH_EXTERNAL_CSV_FORM; // initial load with side effects - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); Assert.assertNotNull(wrapper); Assert.assertNotNull(wrapper.getController()); @@ -98,7 +109,7 @@ public void loadSearchFromExternalCSVmultipleTimes() throws Exception { long dbLastModified = dbFile.lastModified(); // subsequent load should succeed despite side effects from import - formLoaderTask = new FormLoaderTask(formPath, null, null); + formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); wrapper = formLoaderTask.execute(formPath).get(); Assert.assertNotNull(wrapper); Assert.assertNotNull(wrapper.getController()); diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java index 97027c0fb87..e1f7ce55101 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java @@ -359,6 +359,9 @@ enum AnimationType { @Inject public AudioHelperFactory audioHelperFactory; + @Inject + public FormLoaderTask.FormEntryControllerFactory formEntryControllerFactory; + private final LocationProvidersReceiver locationProvidersReceiver = new LocationProvidersReceiver(); private SwipeHandler swipeHandler; @@ -634,7 +637,7 @@ private void loadForm() { formEntryViewModel.refresh(); } else { Timber.w("Reloading form and restoring state."); - formLoaderTask = new FormLoaderTask(instancePath, startingXPath, waitingXPath); + formLoaderTask = new FormLoaderTask(instancePath, startingXPath, waitingXPath, formEntryControllerFactory); showIfNotShowing(FormLoadingDialogFragment.class, getSupportFragmentManager()); formLoaderTask.execute(formPath); } @@ -713,7 +716,7 @@ private void loadFromIntent(Intent intent) { return; } - formLoaderTask = new FormLoaderTask(instancePath, null, null); + formLoaderTask = new FormLoaderTask(instancePath, null, null, formEntryControllerFactory); formLoaderTask.setFormLoaderListener(this); showIfNotShowing(FormLoadingDialogFragment.class, getSupportFragmentManager()); formLoaderTask.execute(formPath); diff --git a/collect_app/src/main/java/org/odk/collect/android/formmanagement/CollectFormEntryControllerFactory.kt b/collect_app/src/main/java/org/odk/collect/android/formmanagement/CollectFormEntryControllerFactory.kt new file mode 100644 index 00000000000..eb31fa179f3 --- /dev/null +++ b/collect_app/src/main/java/org/odk/collect/android/formmanagement/CollectFormEntryControllerFactory.kt @@ -0,0 +1,22 @@ +package org.odk.collect.android.formmanagement + +import org.javarosa.core.model.FormDef +import org.javarosa.entities.EntityFormFinalizationProcessor +import org.javarosa.form.api.FormEntryController +import org.javarosa.form.api.FormEntryModel +import org.odk.collect.android.tasks.FormLoaderTask.FormEntryControllerFactory +import org.odk.collect.settings.keys.ProjectKeys +import org.odk.collect.shared.settings.Settings + +class CollectFormEntryControllerFactory constructor(private val settings: Settings) : + FormEntryControllerFactory { + override fun create(formDef: FormDef): FormEntryController { + return FormEntryController(FormEntryModel(formDef)).also { + it.addPostProcessor(EntityFormFinalizationProcessor()) + + if (!settings.getBoolean(ProjectKeys.KEY_PREDICATE_CACHING)) { + it.disablePredicateCaching() + } + } + } +} 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 03c9eb354eb..bd488b36675 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 @@ -50,6 +50,7 @@ import org.odk.collect.android.formentry.media.AudioHelperFactory; import org.odk.collect.android.formentry.media.ScreenContextAudioHelperFactory; import org.odk.collect.android.formlists.blankformlist.BlankFormListViewModel; +import org.odk.collect.android.formmanagement.CollectFormEntryControllerFactory; import org.odk.collect.android.formmanagement.FormDownloader; import org.odk.collect.android.formmanagement.FormMetadataParser; import org.odk.collect.android.formmanagement.FormSourceProvider; @@ -84,6 +85,7 @@ import org.odk.collect.android.projects.ProjectDependencyProviderFactory; import org.odk.collect.android.storage.StoragePathProvider; import org.odk.collect.android.storage.StorageSubdirectory; +import org.odk.collect.android.tasks.FormLoaderTask; import org.odk.collect.android.utilities.AdminPasswordProvider; import org.odk.collect.android.utilities.AndroidUserAgent; import org.odk.collect.android.utilities.ChangeLockProvider; @@ -633,4 +635,9 @@ public BlankFormListViewModel.Factory providesBlankFormListViewModel(FormsReposi public ImageCompressionController providesImageCompressorManager() { return new ImageCompressionController(ImageCompressor.INSTANCE); } + + @Provides + public FormLoaderTask.FormEntryControllerFactory formEntryControllerFactory(SettingsProvider settingsProvider) { + return new CollectFormEntryControllerFactory(settingsProvider.getUnprotectedSettings()); + } } diff --git a/collect_app/src/main/java/org/odk/collect/android/preferences/Defaults.kt b/collect_app/src/main/java/org/odk/collect/android/preferences/Defaults.kt index c29f98c33b7..cf835caed05 100644 --- a/collect_app/src/main/java/org/odk/collect/android/preferences/Defaults.kt +++ b/collect_app/src/main/java/org/odk/collect/android/preferences/Defaults.kt @@ -55,6 +55,8 @@ object Defaults { hashMap[ProjectKeys.KEY_USGS_MAP_STYLE] = "topographic" hashMap[ProjectKeys.KEY_GOOGLE_MAP_STYLE] = GoogleMap.MAP_TYPE_NORMAL.toString() hashMap[ProjectKeys.KEY_MAPBOX_MAP_STYLE] = "mapbox://styles/mapbox/streets-v11" + // experimental_preferences.xml + hashMap[ProjectKeys.KEY_PREDICATE_CACHING] = false return hashMap } diff --git a/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java b/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java index 6f7f565dc9a..ebe38699699 100644 --- a/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java +++ b/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java @@ -22,6 +22,8 @@ import android.database.SQLException; import android.os.AsyncTask; +import androidx.annotation.NonNull; + import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; @@ -32,9 +34,7 @@ import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.model.instance.utils.DefaultAnswerResolver; import org.javarosa.core.reference.ReferenceManager; -import org.javarosa.entities.EntityFormFinalizationProcessor; import org.javarosa.form.api.FormEntryController; -import org.javarosa.form.api.FormEntryModel; import org.javarosa.xform.parse.XFormParser; import org.javarosa.xform.util.XFormUtils; import org.javarosa.xpath.XPathTypeMismatchException; @@ -81,6 +81,7 @@ public class FormLoaderTask extends AsyncTask + + Crash app Force an uncaught exception causing the app to crash + Predicate caching