Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove grace period for v2023.3 #5728

Merged
merged 7 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.odk.collect.android.support.pages.FormEntryPage;
import org.odk.collect.android.support.pages.MainMenuPage;
import org.odk.collect.android.support.pages.SaveOrDiscardFormDialog;
import org.odk.collect.android.support.pages.SendFinalizedFormPage;
import org.odk.collect.android.support.rules.CollectTestRule;
import org.odk.collect.android.support.rules.TestRuleChain;
import org.odk.collect.audiorecorder.recording.AudioRecorder;
Expand Down Expand Up @@ -176,10 +177,16 @@ public void whenRecordAudioPermissionNotGranted_openingForm_andDenyingPermission
@Test
public void viewForm_doesNotRecordAudio() {
rule.startAtMainMenu()
.setServer(testDependencies.server.getURL())
.copyForm("one-question-background-audio.xml")
.startBlankForm("One Question")
.fillOutAndFinalize(new FormEntryPage.QuestionAndAnswer("what is your age", "17"))
.clickSendFinalizedForm(1)
.clickSelectAll()
.clickSendSelected()
.clickOK(new SendFinalizedFormPage())
.pressBack(new MainMenuPage())
.clickViewSentForm(1)
.clickOnForm("One Question");

assertThat(stubAudioRecorderViewModel.isRecording(), is(false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package org.odk.collect.android.feature.formentry
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.odk.collect.android.R
import org.odk.collect.android.support.pages.MainMenuPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain
Expand All @@ -30,7 +29,7 @@ class FormSavedSnackbarTest {
}

@Test
fun whenDraftFinalized_displaySnackbarWithViewAction() {
fun whenDraftFinalized_displaySnackbarWithViewActionThatOpensFormForEdit() {
rule.startAtMainMenu()
.copyForm("one-question.xml")
.startBlankForm("One Question")
Expand All @@ -43,10 +42,10 @@ class FormSavedSnackbarTest {
.clickFinalize()
.assertText(org.odk.collect.strings.R.string.form_saved)
.clickOnString(org.odk.collect.strings.R.string.view_form)
.clickOKOnDialog()
.assertText("25")
.assertTextDoesNotExist(org.odk.collect.strings.R.string.jump_to_beginning)
.assertTextDoesNotExist(org.odk.collect.strings.R.string.jump_to_end)
.assertText(org.odk.collect.strings.R.string.exit)
.assertText(org.odk.collect.strings.R.string.jump_to_beginning)
.assertText(org.odk.collect.strings.R.string.jump_to_end)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.odk.collect.android.support.pages.FormEntryPage.QuestionAndAnswer
import org.odk.collect.android.support.pages.MainMenuPage
import org.odk.collect.android.support.pages.OkDialog
import org.odk.collect.android.support.pages.ProjectSettingsPage
import org.odk.collect.android.support.pages.SaveOrDiscardFormDialog
import org.odk.collect.android.support.pages.SendFinalizedFormPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain.chain
Expand All @@ -29,13 +30,63 @@ class SendFinalizedFormTest {
.around(rule)

@Test
fun canViewFormsBeforeSending() {
fun canEditAndFinalizeFormsBeforeSending() {
rule.withProject(testDependencies.server.url)
.copyForm("one-question.xml", testDependencies.server.hostName)
.startBlankForm("One Question")
.fillOutAndFinalize(QuestionAndAnswer("what is your age", "52"))
.closeSnackbar() // Make sure we don't get a false positive from this later

.clickSendFinalizedForm(1)
.clickOnFormToEdit("One Question")
.clickGoToStart()
.answerQuestion("what is your age", "53")
.swipeToEndScreen()
.clickFinalize()
.checkIsSnackbarWithMessageDisplayed(org.odk.collect.strings.R.string.form_saved)

.clickSendFinalizedForm(1)
.clickOnFormToEdit("One Question")
.assertText("53")
}

@Test
fun canEditAndConvertToDraftFormsBeforeSending() {
rule.withProject(testDependencies.server.url)
.copyForm("one-question.xml", testDependencies.server.hostName)
.startBlankForm("One Question")
.fillOutAndFinalize(QuestionAndAnswer("what is your age", "52"))
.closeSnackbar() // Make sure we don't get a false positive from this later

.clickSendFinalizedForm(1)
.clickOnFormToEdit("One Question")
.clickGoToStart()
.answerQuestion("what is your age", "53")
.swipeToEndScreen()
.clickSaveAsDraft()
.checkIsSnackbarWithMessageDisplayed(org.odk.collect.strings.R.string.form_saved_as_draft)

.clickEditSavedForm(1)
.clickOnForm("One Question")
.assertText("53")
}

@Test
fun canEditAFormAndLeaveFinalizedBeforeSending() {
rule.withProject(testDependencies.server.url)
.copyForm("one-question.xml", testDependencies.server.hostName)
.startBlankForm("One Question")
.fillOutAndFinalize(QuestionAndAnswer("what is your age", "52"))

.clickSendFinalizedForm(1)
.clickOnFormToEdit("One Question")
.clickGoToStart()
.answerQuestion("what is your age", "53")
.pressBack(SaveOrDiscardFormDialog(MainMenuPage()))
.clickDiscardChanges()

.clickSendFinalizedForm(1)
.clickOnFormToEdit("One Question")
.assertText("52")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public ViewFormPage clickOnForm(String formLabel) {
return new ViewFormPage(formLabel).assertOnPage();
}

public FormHierarchyPage clickOnFormToEdit(String formLabel) {
clickOnText(formLabel);
clickOKOnDialog();
return new FormHierarchyPage(formLabel).assertOnPage();
}

public OkDialog clickSendSelected() {
clickOnText(getTranslatedString(org.odk.collect.strings.R.string.send_selected_data));
return new OkDialog();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
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;
import org.odk.collect.android.external.FormsContract;
Expand Down Expand Up @@ -1039,7 +1038,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
return true;
} else if (item.getItemId() == R.id.menu_save) {
// don't exit
saveForm(false, InstancesDaoHelper.isInstanceComplete(getFormController()), null, true);
saveForm(false, false, null, true);
return true;
}

Expand Down Expand Up @@ -1966,7 +1965,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
}

QuitFormDialog.show(this, formSaveViewModel, formEntryViewModel, settingsProvider, () -> {
saveForm(true, InstancesDaoHelper.isInstanceComplete(getFormController()), null, true);
saveForm(true, false, null, true);
});
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import android.widget.ListView;
import android.widget.ProgressBar;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
Expand Down Expand Up @@ -134,6 +136,11 @@ public class InstanceUploaderListActivity extends LocalizedActivity implements
private SearchView searchView;
private String savedFilterText;

private final ActivityResultLauncher<Intent> formLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
setResult(RESULT_OK, result.getData());
finish();
});

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand Down Expand Up @@ -393,7 +400,7 @@ public void onItemClick(AdapterView<?> parent, View view, int position, long row
} else {
long instanceId = c.getLong(c.getColumnIndex(DatabaseInstanceColumns._ID));
Intent intent = FormFillingIntentFactory.editInstanceIntent(this, currentProjectProvider.getCurrentProject().getUuid(), instanceId);
startActivity(intent);
formLauncher.launch(intent);
}
}

Expand All @@ -413,14 +420,14 @@ protected void onSaveInstanceState(Bundle outState) {

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == RESULT_CANCELED) {
multiSelectViewModel.unselectAll();
return;
}

switch (requestCode) {
// returns with a form path, start entry
case INSTANCE_UPLOADER:
if (resultCode == RESULT_CANCELED) {
multiSelectViewModel.unselectAll();
return;
}

if (intent.getBooleanExtra(FormFillingActivity.KEY_SUCCESS, false)) {
listView.clearChoices();
if (listAdapter.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@
package org.odk.collect.android.dao.helpers;

import org.odk.collect.android.application.Collect;
import org.odk.collect.android.javarosawrapper.FormController;
import org.odk.collect.android.utilities.InstancesRepositoryProvider;
import org.odk.collect.forms.instances.Instance;
import org.odk.collect.forms.instances.InstancesRepository;

import timber.log.Timber;

/**
* Provides abstractions over database calls for instances.
*
Expand All @@ -34,34 +31,6 @@ private InstancesDaoHelper() {

}

/**
* Checks the database to determine if the current instance being edited has
* already been 'marked completed'. A form can be 'unmarked' complete and
* then resaved.
*
* @return true if form has been marked completed, false otherwise.
* <p>
* TODO: replace with method in {@link InstancesRepository}
* that returns an {@link Instance} object from a path.
*/
public static boolean isInstanceComplete(FormController formController) {
// default to false if we're mid form
boolean complete = false;

if (formController != null && formController.getInstanceFile() != null) {
// Then see if we've already marked this form as complete before
String path = formController.getInstanceFile().getAbsolutePath();
Instance instance = new InstancesRepositoryProvider(Collect.getInstance()).get().getOneByPath(path);
if (instance != null && instance.getStatus().equals(Instance.STATUS_COMPLETE)) {
complete = true;
}
} else {
Timber.w("FormController or its instanceFile field has a null value");
}

return complete;
}

// TODO: replace with method in {@link org.odk.collect.android.instances.InstancesRepository}
// that returns an {@link Instance} object from a path.
public static boolean isInstanceAvailable(String path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,33 @@ package org.odk.collect.android.external

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.odk.collect.analytics.Analytics
import org.odk.collect.android.activities.FormFillingActivity
import org.odk.collect.android.analytics.AnalyticsEvents
import org.odk.collect.android.injection.DaggerUtils
import org.odk.collect.android.instancemanagement.InstanceDeleter
import org.odk.collect.android.instancemanagement.canBeEdited
import org.odk.collect.android.projects.CurrentProjectProvider
import org.odk.collect.android.utilities.ApplicationConstants
import org.odk.collect.android.utilities.ContentUriHelper
import org.odk.collect.android.utilities.FormsRepositoryProvider
import org.odk.collect.android.utilities.InstancesRepositoryProvider
import org.odk.collect.forms.Form
import org.odk.collect.forms.instances.Instance
import org.odk.collect.projects.ProjectsRepository
import org.odk.collect.settings.SettingsProvider
import org.odk.collect.settings.keys.ProjectKeys
import org.odk.collect.settings.keys.ProtectedProjectKeys
import org.odk.collect.strings.localization.LocalizedActivity
import java.io.File
import javax.inject.Inject

/**
* This class serves as a firewall for starting form filling. It should be used to do that
* rather than [FormFillingActivity] directly as it ensures that the required data is valid.
*/
class FormUriActivity : ComponentActivity() {
class FormUriActivity : LocalizedActivity() {

@Inject
lateinit var currentProjectProvider: CurrentProjectProvider
Expand Down Expand Up @@ -64,7 +65,16 @@ class FormUriActivity : ComponentActivity() {
!assertNotNewFormInGoogleDriveProject() -> Unit
!assertFormNotEncrypted() -> Unit
!assertFormFillingNotAlreadyStarted(savedInstanceState) -> Unit
else -> startForm()
else -> if (isFormFinalizedButEditable()) {
MaterialAlertDialogBuilder(this)
.setMessage(org.odk.collect.strings.R.string.edit_finalized_form_warning)
.setPositiveButton(org.odk.collect.strings.R.string.ok) { _, _ -> startForm() }
.setCancelable(false)
.create()
.show()
} else {
startForm()
}
}
}

Expand All @@ -78,20 +88,6 @@ class FormUriActivity : ComponentActivity() {
}
}

private fun assertCurrentProjectUsed(): Boolean {
val projects = projectsRepository.getAll()
val firstProject = projects.first()
val uriProjectId = intent.data?.getQueryParameter("projectId")
val projectId = uriProjectId ?: firstProject.uuid

return if (projectId != currentProjectProvider.getCurrentProject().uuid) {
displayErrorDialog(getString(org.odk.collect.strings.R.string.wrong_project_selected_for_form))
false
} else {
true
}
}

private fun assertNotNewFormInGoogleDriveProject(): Boolean {
val uri = intent.data!!
val uriMimeType = contentResolver.getType(uri)
Expand All @@ -109,6 +105,20 @@ class FormUriActivity : ComponentActivity() {
}
}

private fun assertCurrentProjectUsed(): Boolean {
val projects = projectsRepository.getAll()
val firstProject = projects.first()
val uriProjectId = intent.data?.getQueryParameter("projectId")
val projectId = uriProjectId ?: firstProject.uuid

return if (projectId != currentProjectProvider.getCurrentProject().uuid) {
displayErrorDialog(getString(org.odk.collect.strings.R.string.wrong_project_selected_for_form))
false
} else {
true
}
}

private fun assertValidUri(): Boolean {
val isUriValid = intent.data?.let {
val uriMimeType = contentResolver.getType(it)
Expand Down Expand Up @@ -232,6 +242,18 @@ class FormUriActivity : ComponentActivity() {
return formEditingEnabled
}

private fun isFormFinalizedButEditable(): Boolean {
val uri = intent.data!!
val uriMimeType = contentResolver.getType(uri)

return if (uriMimeType == InstancesContract.CONTENT_ITEM_TYPE) {
val instance = instanceRepositoryProvider.get().get(ContentUriHelper.getIdFromUri(uri))
instance!!.status == Instance.STATUS_COMPLETE && instance.canBeEdited(settingsProvider)
} else {
false
}
}

override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(FORM_FILLING_ALREADY_STARTED, formFillingAlreadyStarted)
super.onSaveInstanceState(outState)
Expand All @@ -241,3 +263,8 @@ class FormUriActivity : ComponentActivity() {
private const val FORM_FILLING_ALREADY_STARTED = "FORM_FILLING_ALREADY_STARTED"
}
}

private fun Instance.canBeEdited(settingsProvider: SettingsProvider): Boolean {
return (this.status == Instance.STATUS_INCOMPLETE || this.status == Instance.STATUS_COMPLETE) &&
settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_EDIT_SAVED)
}
Loading