diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java index 61bb191eff0..933ebdde43f 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java @@ -7,6 +7,7 @@ import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import org.odk.collect.android.R; +import org.odk.collect.android.support.CollectHelpers; import org.odk.collect.android.support.TestDependencies; import org.odk.collect.android.support.pages.MainMenuPage; import org.odk.collect.android.support.pages.OkDialog; @@ -15,6 +16,7 @@ import org.odk.collect.android.support.rules.CollectTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.androidtest.RecordedIntentsRule; +import org.odk.collect.projects.Project; @RunWith(AndroidJUnit4.class) public class SendFinalizedFormTest { @@ -101,12 +103,20 @@ public void whenDeleteAfterSendIsEnabled_deletesFilledForm() { @Test public void whenGoogleUsedAsServer_sendsSubmissionToSheet() { - testDependencies.googleAccountPicker.setDeviceAccount("dani@davey.com"); - testDependencies.googleApi.setAccount("dani@davey.com"); + CollectHelpers.addGDProject( + new Project.New( + "GD Project", + "G", + "#3e9fcc" + ), + "dani@davey.com", + testDependencies + ); rule.startAtMainMenu() - .setGoogleAccount("dani@davey.com") - .copyForm("one-question-google.xml") + .openProjectSettingsDialog() + .selectProject("GD Project") + .copyForm("one-question-google.xml", null, false, "GD Project") .startBlankForm("One Question Google") .answerQuestion("what is your age", "47") .swipeToEndScreen() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt index a2faf394a18..064b2f5ad13 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt @@ -35,22 +35,6 @@ class AddNewProjectTest { .assertInactiveProject("Demo project", "demo.getodk.org") } - @Test - fun addingGdriveProjectManually_addsNewProject() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) - - rule.startAtMainMenu() - .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) - - .openProjectSettingsDialog() - .assertCurrentProject(googleAccount, "$googleAccount / Google Drive") - .assertInactiveProject("Demo project", "demo.getodk.org") - } - @Test fun addingProjectFromQrCode_addsNewProject() { val page = rule.startAtMainMenu() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationBannerTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationTest.kt similarity index 60% rename from collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationBannerTest.kt rename to collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationTest.kt index 38850bda0ed..617ed7c8b04 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationBannerTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationTest.kt @@ -9,14 +9,16 @@ import org.junit.Test import org.junit.rules.RuleChain import org.odk.collect.android.R import org.odk.collect.android.activities.WebViewActivity +import org.odk.collect.android.support.CollectHelpers import org.odk.collect.android.support.TestDependencies import org.odk.collect.android.support.pages.MainMenuPage import org.odk.collect.android.support.rules.CollectTestRule import org.odk.collect.android.support.rules.TestRuleChain import org.odk.collect.androidtest.RecordedIntentsRule +import org.odk.collect.projects.Project -class GoogleDriveDeprecationBannerTest { - val rule = CollectTestRule() +class GoogleDriveDeprecationTest { + private val rule = CollectTestRule() private val testDependencies = TestDependencies() @get:Rule @@ -24,6 +26,18 @@ class GoogleDriveDeprecationBannerTest { .around(RecordedIntentsRule()) .around(rule) + private val gdProject1 = Project.New( + "GD Project 1", + "G", + "#3e9fcc" + ) + + private val gdProject2 = Project.New( + "GD Project 2", + "G", + "#3e9fcc" + ) + @Test fun bannerIsNotVisibleInNonGoogleDriveProjects() { rule @@ -33,27 +47,21 @@ class GoogleDriveDeprecationBannerTest { @Test fun bannerIsVisibleInGoogleDriveProjects() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .assertText(R.string.google_drive_deprecation_message) } @Test fun forumThreadIsOpenedAfterClickingLearnMore() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .clickOnString(R.string.learn_more_button_text) intended( @@ -66,14 +74,11 @@ class GoogleDriveDeprecationBannerTest { @Test fun dismissButtonIsVisibleOnlyAfterClickingLearnMore() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .assertTextDoesNotExist(R.string.dismiss_button_text) .clickOnString(R.string.learn_more_button_text) .pressBack(MainMenuPage()) @@ -82,14 +87,11 @@ class GoogleDriveDeprecationBannerTest { @Test fun afterClickingDismissTheBannerDisappears() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .clickOnString(R.string.learn_more_button_text) .pressBack(MainMenuPage()) .clickOnString(R.string.dismiss_button_text) @@ -100,22 +102,44 @@ class GoogleDriveDeprecationBannerTest { @Test fun dismissingTheBannerInOneProjectDoesNotAffectOtherProjects() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) + CollectHelpers.addGDProject(gdProject2, "john@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .clickOnString(R.string.learn_more_button_text) .pressBack(MainMenuPage()) .clickOnString(R.string.dismiss_button_text) .assertTextDoesNotExist(R.string.google_drive_deprecation_message) .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount, true) + .selectProject(gdProject2.name) .assertText(R.string.google_drive_deprecation_message) } + + @Test + fun additionalWarningShouldNotBeDisplayedWhenRemovingNonGDProject() { + rule + .startAtMainMenu() + .openProjectSettingsDialog() + .clickSettings() + .clickProjectManagement() + .clickOnDeleteProject() + .assertTextDoesNotExist(R.string.delete_google_drive_project_confirm_message) + } + + @Test + fun additionalWarningShouldBeDisplayedWhenRemovingGDProject() { + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) + + rule + .startAtMainMenu() + .openProjectSettingsDialog() + .selectProject(gdProject1.name) + .openProjectSettingsDialog() + .clickSettings() + .clickProjectManagement() + .clickOnDeleteProject() + .assertText(R.string.delete_google_drive_project_confirm_message) + } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt index b8d640b2fe1..9a1a80f8338 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt @@ -78,22 +78,4 @@ class FormManagementSettingsTest { assertThat(deferredTasks[0].tag, `is`(previouslyDownloadedTag)) assertThat(deferredTasks[0].repeatPeriod, `is`(1000L * 60 * 60)) } - - @Test - fun whenGoogleDriveUsingAsServer_disablesPrefsAndOnlyAllowsManualUpdates() { - testDependencies.googleAccountPicker.setDeviceAccount("steph@curry.basket") - - MainMenuPage().assertOnPage() - .enablePreviouslyDownloadedOnlyUpdates() // Enabled a different mode before setting up Google - .setGoogleAccount("steph@curry.basket") - .openProjectSettingsDialog() - .clickSettings() - .clickFormManagement() - .assertDisabled(R.string.form_update_mode_title) - .assertDisabled(R.string.form_update_frequency_title) - .assertDisabled(R.string.automatic_download) - .assertText(R.string.manual) - - assertThat(testDependencies.scheduler.deferredTasks.size, `is`(0)) - } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java index f8433cbcbf4..3f33352ee19 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java @@ -1,8 +1,5 @@ package org.odk.collect.android.feature.settings; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; @@ -10,11 +7,9 @@ import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import org.odk.collect.android.R; -import org.odk.collect.android.gdrive.sheets.DriveHelper; 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.ServerSettingsPage; import org.odk.collect.android.support.rules.CollectTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.androidtest.RecordedIntentsRule; @@ -61,32 +56,13 @@ public void whenUsingODKServer_canAddCredentialsForServer() { .clickOKOnDialog(new MainMenuPage()); } - /** - * This test could definitely be extended to cover form download/submit with the creation - * of a stub - * {@link DriveHelper} and - * {@link org.odk.collect.android.gdrive.GoogleAccountsManager} - */ @Test - public void selectingGoogleAccount_showsGoogleAccountSettings() { + public void selectingServerTypeIsDisabled() { new MainMenuPage().assertOnPage() .openProjectSettingsDialog() .clickSettings() .clickServerSettings() .clickOnServerType() - .clickOnButtonInDialog(R.string.server_platform_google_sheets, new ServerSettingsPage()) - .assertText(R.string.selected_google_account_text) - .assertText(R.string.google_sheets_url); - } - - @Test - public void selectingGoogleAccount_disablesAutomaticUpdates() { - MainMenuPage mainMenu = new MainMenuPage().assertOnPage() - .enablePreviouslyDownloadedOnlyUpdates(); - assertThat(testDependencies.scheduler.getDeferredTasks().size(), is(1)); - - testDependencies.googleAccountPicker.setDeviceAccount("steph@curry.basket"); - mainMenu.setGoogleAccount("steph@curry.basket"); - assertThat(testDependencies.scheduler.getDeferredTasks().size(), is(0)); + .assertTextDoesNotExist(R.string.cancel); } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt index b82fef518f3..f8c9fe344a1 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt @@ -1,10 +1,14 @@ package org.odk.collect.android.support +import android.app.Application import androidx.test.core.app.ApplicationProvider import org.odk.collect.android.application.Collect +import org.odk.collect.android.injection.DaggerUtils import org.odk.collect.android.injection.config.AppDependencyComponent import org.odk.collect.android.injection.config.AppDependencyModule import org.odk.collect.android.injection.config.DaggerAppDependencyComponent +import org.odk.collect.projects.Project +import org.odk.collect.settings.keys.ProjectKeys object CollectHelpers { fun overrideAppDependencyModule(appDependencyModule: AppDependencyModule): AppDependencyComponent { @@ -16,4 +20,23 @@ object CollectHelpers { application.component = testComponent return testComponent } + + @JvmStatic + fun addGDProject(gdProject: Project.New, accountName: String, testDependencies: TestDependencies) { + testDependencies.googleAccountPicker.setDeviceAccount(accountName) + testDependencies.googleApi.setAccount(accountName) + + val project = DaggerUtils + .getComponent(ApplicationProvider.getApplicationContext()) + .projectsRepository() + .save(gdProject) + + DaggerUtils + .getComponent(ApplicationProvider.getApplicationContext()) + .settingsProvider().getUnprotectedSettings(project.uuid) + .also { + it.save(ProjectKeys.KEY_PROTOCOL, ProjectKeys.PROTOCOL_GOOGLE_SHEETS) + it.save(ProjectKeys.KEY_SELECTED_GOOGLE_ACCOUNT, accountName) + } + } } 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 e46231032ad..bb7e879154f 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 @@ -4,8 +4,6 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.intent.Intents.intending; -import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.isClickable; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; @@ -14,11 +12,6 @@ import static org.hamcrest.core.AllOf.allOf; import static org.hamcrest.core.StringContains.containsString; -import android.accounts.AccountManager; -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Intent; - import org.odk.collect.android.R; import org.odk.collect.android.support.WaitFor; @@ -170,22 +163,6 @@ public MainMenuPage enableAutoSend() { .pressBack(new MainMenuPage()); } - public MainMenuPage setGoogleAccount(String account) { - Intent data = new Intent(); - data.putExtra(AccountManager.KEY_ACCOUNT_NAME, account); - Instrumentation.ActivityResult activityResult = new Instrumentation.ActivityResult(Activity.RESULT_OK, data); - intending(hasAction("com.google.android.gms.common.account.CHOOSE_ACCOUNT")).respondWith(activityResult); - - return openProjectSettingsDialog() - .clickSettings() - .clickServerSettings() - .clickOnServerType() - .clickOnString(R.string.server_platform_google_sheets) - .clickOnString(R.string.selected_google_account_text) - .pressBack(new ProjectSettingsPage()) - .pressBack(new MainMenuPage()); - } - public MainMenuPage addAndSwitchToProject(String serverUrl) { return openProjectSettingsDialog() .clickAddProject() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt index aeebbcd63f7..d3896b8ed94 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt @@ -1,14 +1,7 @@ package org.odk.collect.android.support.pages -import android.accounts.AccountManager -import android.app.Activity -import android.app.Instrumentation -import android.content.Intent import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.scrollTo -import androidx.test.espresso.intent.Intents.intending -import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.matcher.ViewMatchers.withText import org.odk.collect.android.R @@ -33,21 +26,6 @@ class ManualProjectCreatorDialogPage : Page() { return this } - fun openGooglePickerAndSelect(googleAccount: String, duplicate: Boolean = false): MainMenuPage { - val data = Intent() - data.putExtra(AccountManager.KEY_ACCOUNT_NAME, googleAccount) - val activityResult = Instrumentation.ActivityResult(Activity.RESULT_OK, data) - intending(hasAction("com.google.android.gms.common.account.CHOOSE_ACCOUNT")).respondWith(activityResult) - - onView(withText(R.string.gdrive_configure)).perform(scrollTo(), click()) - - if (duplicate) { - addDuplicateProject() - } - - return MainMenuPage().assertOnPage() - } - fun addProject(): MainMenuPage { onView(withText(R.string.add)).perform(click()) return MainMenuPage().assertOnPage() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt index 073c18b100d..cd656318808 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt @@ -19,6 +19,11 @@ class ProjectManagementPage : Page() { return QRCodePage().assertOnPage() } + fun clickOnDeleteProject(): ProjectManagementPage { + scrollToRecyclerViewItemAndClickText(R.string.delete_project) + return this + } + fun deleteProject(): MainMenuPage { scrollToRecyclerViewItemAndClickText(R.string.delete_project) clickOnString(R.string.delete_project_yes) diff --git a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt index 106f711769a..a7ae00898b3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt +++ b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt @@ -11,6 +11,7 @@ import org.odk.collect.android.analytics.AnalyticsEvents import org.odk.collect.projects.Project.Saved import org.odk.collect.qrcode.QRCodeDecoder import org.odk.collect.settings.ODKAppSettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import java.io.FileNotFoundException import java.io.InputStream @@ -32,15 +33,18 @@ class QRCodeActivityResultDelegate( } try { val response = qrCodeDecoder.decode(imageStream) - if (settingsImporter.fromJSON(response, project)) { - log(AnalyticsEvents.RECONFIGURE_PROJECT) - showToast(R.string.successfully_imported_settings) - ActivityUtils.startActivityAndCloseAllOthers( - activity, - MainMenuActivity::class.java - ) - } else { - showToast(R.string.invalid_qrcode) + + when (settingsImporter.fromJSON(response, project)) { + SettingsImportingResult.SUCCESS -> { + log(AnalyticsEvents.RECONFIGURE_PROJECT) + showToast(R.string.successfully_imported_settings) + ActivityUtils.startActivityAndCloseAllOthers( + activity, + MainMenuActivity::class.java + ) + } + SettingsImportingResult.INVALID_SETTINGS -> showToast(R.string.invalid_qrcode) + SettingsImportingResult.GD_PROJECT -> showToast(R.string.settings_with_gd_protocol) } } catch (e: QRCodeDecoder.QRCodeInvalidException) { showToast(R.string.invalid_qrcode) diff --git a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt index 4188c1adcb7..311031eeab3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt @@ -15,6 +15,7 @@ import org.odk.collect.android.storage.StoragePathProvider import org.odk.collect.androidshared.ui.ToastUtils.showLongToast import org.odk.collect.androidshared.utils.CompressionUtils import org.odk.collect.settings.ODKAppSettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import java.io.File import java.io.IOException import java.util.zip.DataFormatException @@ -40,27 +41,29 @@ class QRCodeScannerFragment : BarCodeScannerFragment() { override fun handleScanningResult(result: BarcodeResult) { val oldProjectName = currentProjectProvider.getCurrentProject().name - val importSuccess = settingsImporter.fromJSON( + val settingsImportingResult = settingsImporter.fromJSON( CompressionUtils.decompress(result.text), currentProjectProvider.getCurrentProject() ) - if (importSuccess) { - Analytics.log(AnalyticsEvents.RECONFIGURE_PROJECT) + when (settingsImportingResult) { + SettingsImportingResult.SUCCESS -> { + Analytics.log(AnalyticsEvents.RECONFIGURE_PROJECT) - val newProjectName = currentProjectProvider.getCurrentProject().name - if (newProjectName != oldProjectName) { - File(storagePathProvider.getProjectRootDirPath() + File.separator + oldProjectName).delete() - File(storagePathProvider.getProjectRootDirPath() + File.separator + newProjectName).createNewFile() - } + val newProjectName = currentProjectProvider.getCurrentProject().name + if (newProjectName != oldProjectName) { + File(storagePathProvider.getProjectRootDirPath() + File.separator + oldProjectName).delete() + File(storagePathProvider.getProjectRootDirPath() + File.separator + newProjectName).createNewFile() + } - showLongToast(requireContext(), getString(R.string.successfully_imported_settings)) - ActivityUtils.startActivityAndCloseAllOthers( - requireActivity(), - MainMenuActivity::class.java - ) - } else { - showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + showLongToast(requireContext(), getString(R.string.successfully_imported_settings)) + ActivityUtils.startActivityAndCloseAllOthers( + requireActivity(), + MainMenuActivity::class.java + ) + } + SettingsImportingResult.INVALID_SETTINGS -> showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + SettingsImportingResult.GD_PROJECT -> showLongToast(requireContext(), getString(R.string.settings_with_gd_protocol)) } } diff --git a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt index ad04c520ae5..7f1f7aabce2 100644 --- a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt @@ -20,6 +20,7 @@ import org.odk.collect.android.projects.DeleteProjectResult import org.odk.collect.android.projects.ProjectDeleter import org.odk.collect.androidshared.ui.ToastUtils import org.odk.collect.androidshared.ui.multiclicksafe.MultiClickGuard +import org.odk.collect.settings.keys.ProjectKeys import javax.inject.Inject class ProjectManagementPreferencesFragment : @@ -65,12 +66,17 @@ class ProjectManagementPreferencesFragment : val pref = Intent(activity, QRCodeTabsActivity::class.java) startActivity(pref) } - DELETE_PROJECT_KEY -> MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.delete_project) - .setMessage(R.string.delete_project_confirm_message) - .setNegativeButton(R.string.delete_project_no) { _: DialogInterface?, _: Int -> } - .setPositiveButton(R.string.delete_project_yes) { _: DialogInterface?, _: Int -> deleteProject() } - .show() + DELETE_PROJECT_KEY -> { + val isGDProject = ProjectKeys.PROTOCOL_GOOGLE_SHEETS == settingsProvider.getUnprotectedSettings().getString(ProjectKeys.KEY_PROTOCOL) + val message = if (isGDProject) R.string.delete_google_drive_project_confirm_message else R.string.delete_project_confirm_message + + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.delete_project) + .setMessage(message) + .setNegativeButton(R.string.delete_project_no) { _: DialogInterface?, _: Int -> } + .setPositiveButton(R.string.delete_project_yes) { _: DialogInterface?, _: Int -> deleteProject() } + .show() + } } return true } diff --git a/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt b/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt index e5abccfe4fc..997a28ad381 100644 --- a/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt +++ b/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt @@ -1,15 +1,10 @@ package org.odk.collect.android.projects -import android.accounts.AccountManager -import android.app.Activity import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.Toolbar import androidx.core.widget.doOnTextChanged import org.odk.collect.analytics.Analytics @@ -19,18 +14,14 @@ import org.odk.collect.android.activities.MainMenuActivity import org.odk.collect.android.analytics.AnalyticsEvents import org.odk.collect.android.configure.qr.AppConfigurationGenerator import org.odk.collect.android.databinding.ManualProjectCreatorDialogLayoutBinding -import org.odk.collect.android.gdrive.GoogleAccountsManager import org.odk.collect.android.injection.DaggerUtils import org.odk.collect.android.projects.DuplicateProjectConfirmationKeys.MATCHING_PROJECT import org.odk.collect.android.projects.DuplicateProjectConfirmationKeys.SETTINGS_JSON import org.odk.collect.android.utilities.SoftKeyboardController -import org.odk.collect.androidshared.system.IntentLauncher import org.odk.collect.androidshared.ui.DialogFragmentUtils import org.odk.collect.androidshared.ui.ToastUtils import org.odk.collect.androidshared.utils.Validator import org.odk.collect.material.MaterialFullScreenDialogFragment -import org.odk.collect.permissions.PermissionListener -import org.odk.collect.permissions.PermissionsProvider import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.SettingsProvider import javax.inject.Inject @@ -51,55 +42,16 @@ class ManualProjectCreatorDialog : @Inject lateinit var currentProjectProvider: CurrentProjectProvider - @Inject - lateinit var permissionsProvider: PermissionsProvider - - @Inject - lateinit var googleAccountsManager: GoogleAccountsManager - @Inject lateinit var projectsRepository: ProjectsRepository @Inject lateinit var settingsProvider: SettingsProvider - @Inject - lateinit var intentLauncher: IntentLauncher - lateinit var settingsConnectionMatcher: SettingsConnectionMatcher private lateinit var binding: ManualProjectCreatorDialogLayoutBinding - val googleAccountResultLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - val resultData = result.data - - if (result.resultCode == Activity.RESULT_OK && resultData != null && resultData.extras != null) { - val accountName = resultData.getStringExtra(AccountManager.KEY_ACCOUNT_NAME) - googleAccountsManager.selectAccount(accountName) - - val settingsJson = - appConfigurationGenerator.getAppConfigurationAsJsonWithGoogleDriveDetails( - accountName - ) - - settingsConnectionMatcher.getProjectWithMatchingConnection(settingsJson) - ?.let { uuid -> - val confirmationArgs = Bundle() - confirmationArgs.putString(SETTINGS_JSON, settingsJson) - confirmationArgs.putString(MATCHING_PROJECT, uuid) - DialogFragmentUtils.showIfNotShowing( - DuplicateProjectConfirmationDialog::class.java, - confirmationArgs, - childFragmentManager - ) - } ?: run { - Analytics.log(AnalyticsEvents.GOOGLE_ACCOUNT_PROJECT) - createProject(settingsJson) - } - } - } - override fun onAttach(context: Context) { super.onAttach(context) DaggerUtils.getComponent(context).inject(this) @@ -134,10 +86,6 @@ class ManualProjectCreatorDialog : binding.addButton.setOnClickListener { handleAddingNewProject() } - - binding.gdrive.setOnClickListener { - configureGoogleAccount() - } } override fun onCloseClicked() { @@ -182,26 +130,6 @@ class ManualProjectCreatorDialog : } } - private fun configureGoogleAccount() { - permissionsProvider.requestGetAccountsPermission( - requireActivity(), - object : PermissionListener { - override fun granted() { - val intent: Intent = googleAccountsManager.accountChooserIntent - intentLauncher.launchForResult(googleAccountResultLauncher, intent) { - ToastUtils.showShortToast( - requireContext(), - getString( - R.string.activity_not_found, - getString(R.string.choose_account) - ) - ) - } - } - } - ) - } - override fun createProject(settingsJson: String) { projectCreator.createNewProject(settingsJson) ActivityUtils.startActivityAndCloseAllOthers(activity, MainMenuActivity::class.java) diff --git a/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt b/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt index b0b16c1ee70..2f967e13ce3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt +++ b/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt @@ -4,6 +4,7 @@ import org.odk.collect.projects.Project import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.importing.SettingsImportingResult class ProjectCreator( private val projectsRepository: ProjectsRepository, @@ -12,18 +13,18 @@ class ProjectCreator( private val settingsProvider: SettingsProvider ) { - fun createNewProject(settingsJson: String): Boolean { + fun createNewProject(settingsJson: String): SettingsImportingResult { val savedProject = projectsRepository.save(Project.New("", "", "")) - val settingsImportedSuccessfully = settingsImporter.fromJSON(settingsJson, savedProject) + val settingsImportingResult = settingsImporter.fromJSON(settingsJson, savedProject) - return if (settingsImportedSuccessfully) { + return if (settingsImportingResult == SettingsImportingResult.SUCCESS) { currentProjectProvider.setCurrentProject(savedProject.uuid) - true + settingsImportingResult } else { settingsProvider.getUnprotectedSettings(savedProject.uuid).clear() settingsProvider.getProtectedSettings(savedProject.uuid).clear() projectsRepository.delete(savedProject.uuid) - false + settingsImportingResult } } } diff --git a/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt b/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt index 8f442972e9c..97f2c20c251 100644 --- a/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt +++ b/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt @@ -35,6 +35,7 @@ import org.odk.collect.projects.ProjectsRepository import org.odk.collect.qrcode.QRCodeDecoder import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.importing.SettingsImportingResult import timber.log.Timber import javax.inject.Inject @@ -274,21 +275,21 @@ class QrCodeProjectCreatorDialog : } override fun createProject(settingsJson: String) { - val projectCreatedSuccessfully = projectCreator.createNewProject(settingsJson) - - if (projectCreatedSuccessfully) { - Analytics.log(AnalyticsEvents.QR_CREATE_PROJECT) - - ActivityUtils.startActivityAndCloseAllOthers(activity, MainMenuActivity::class.java) - ToastUtils.showLongToast( - requireContext(), - getString( - R.string.switched_project, - currentProjectProvider.getCurrentProject().name + when (projectCreator.createNewProject(settingsJson)) { + SettingsImportingResult.SUCCESS -> { + Analytics.log(AnalyticsEvents.QR_CREATE_PROJECT) + + ActivityUtils.startActivityAndCloseAllOthers(activity, MainMenuActivity::class.java) + ToastUtils.showLongToast( + requireContext(), + getString( + R.string.switched_project, + currentProjectProvider.getCurrentProject().name + ) ) - ) - } else { - ToastUtils.showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + } + SettingsImportingResult.INVALID_SETTINGS -> ToastUtils.showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + SettingsImportingResult.GD_PROJECT -> ToastUtils.showLongToast(requireContext(), getString(R.string.settings_with_gd_protocol)) } } diff --git a/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml b/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml index a7a53be2fc8..ce1370e5b6c 100644 --- a/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml +++ b/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml @@ -96,7 +96,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/margin_standard" - app:layout_constraintBottom_toTopOf="@+id/gdrive" app:layout_constraintTop_toBottomOf="@+id/password"> - - - - - - - diff --git a/collect_app/src/main/res/xml/server_preferences.xml b/collect_app/src/main/res/xml/server_preferences.xml index a91a3b261b5..1dfbf741061 100644 --- a/collect_app/src/main/res/xml/server_preferences.xml +++ b/collect_app/src/main/res/xml/server_preferences.xml @@ -8,5 +8,6 @@ android:entryValues="@array/protocol_entry_values" android:key="protocol" android:title="@string/type" + android:enabled="false" app:iconSpaceReserved="false" /> \ No newline at end of file diff --git a/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt b/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt index ceb01c46e65..fec1400cb3f 100644 --- a/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt @@ -20,6 +20,7 @@ import org.odk.collect.android.support.CollectHelpers import org.odk.collect.projects.Project.Saved import org.odk.collect.qrcode.QRCodeDecoder import org.odk.collect.settings.ODKAppSettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import org.robolectric.Robolectric import org.robolectric.Shadows import org.robolectric.shadows.ShadowToast @@ -63,7 +64,7 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.register("qr", "data") - whenever(settingsImporter.fromJSON("data", project)).thenReturn(true) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.SUCCESS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } @@ -77,10 +78,21 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.register("qr", "data") - whenever(settingsImporter.fromJSON("data", project)).thenReturn(false) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } + @Test + fun forSelectPhotoWithGoogleDriveProtocol_whenImporting_showsInvalidToast() { + val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) + val data = intentWithData("file://qr", "qr") + fakeQRDecoder.register("qr", "data") + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.GD_PROJECT) + delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) + + assertThat(ShadowToast.getTextOfLatestToast(), equalTo(context.getString(R.string.settings_with_gd_protocol))) + } + @Test fun forSelectPhoto_whenQRCodeDecodeFailsWithInvalid_showsInvalidToast() { importSettingsFromQrCode_withInvalidQrCode() @@ -91,7 +103,7 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.failsWith(QRCodeDecoder.QRCodeInvalidException()) - whenever(settingsImporter.fromJSON("data", project)).thenReturn(false) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } @@ -105,7 +117,7 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.failsWith(QRCodeDecoder.QRCodeNotFoundException()) - whenever(settingsImporter.fromJSON("data", project)).thenReturn(false) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt index b72ceaeef0b..4e003033092 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt @@ -1,12 +1,9 @@ package org.odk.collect.android.projects -import android.content.Context -import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.pressBack import androidx.test.espresso.action.ViewActions.replaceText -import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers @@ -29,13 +26,11 @@ import org.odk.collect.android.activities.MainMenuActivity import org.odk.collect.android.injection.config.AppDependencyModule import org.odk.collect.android.support.CollectHelpers import org.odk.collect.android.support.Matchers.isPasswordHidden -import org.odk.collect.androidshared.system.IntentLauncher import org.odk.collect.fragmentstest.FragmentScenarioLauncherRule import org.odk.collect.projects.Project import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider -import org.odk.collect.testshared.ErrorIntentLauncher import org.robolectric.shadows.ShadowToast @RunWith(AndroidJUnit4::class) @@ -163,18 +158,4 @@ class ManualProjectCreatorDialogTest { Intents.release() } } - - @Test - fun `If activity to choose google account is not found the app should not crash`() { - CollectHelpers.overrideAppDependencyModule(object : AppDependencyModule() { - override fun providesIntentLauncher(): IntentLauncher { - return ErrorIntentLauncher() - } - }) - - launcherRule.launch(ManualProjectCreatorDialog::class.java) - onView(withText(R.string.gdrive_configure)).inRoot(isDialog()).perform(scrollTo(), click()) - val context = ApplicationProvider.getApplicationContext() - assertThat(ShadowToast.getTextOfLatestToast(), `is`(context.getString(R.string.activity_not_found, context.getString(R.string.choose_account)))) - } } diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt index 093cdfde606..3202ef38235 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt @@ -12,6 +12,7 @@ import org.odk.collect.projects.Project import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.importing.SettingsImportingResult import org.odk.collect.shared.settings.Settings class ProjectCreatorTest { @@ -55,24 +56,40 @@ class ProjectCreatorTest { } @Test - fun `When importing settings failed createNewProject() should return false`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(false) + fun `When importing settings failed createNewProject() should return 'INVALID_SETTINGS'`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) projectCreator.createNewProject(json) - assertThat(projectCreator.createNewProject(json), `is`(false)) + assertThat(projectCreator.createNewProject(json), `is`(SettingsImportingResult.INVALID_SETTINGS)) } @Test - fun `When importing settings succeeded createNewProject() should return true`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(true) + fun `When importing settings contain GD protocol createNewProject() should return 'GD_PROJECT'`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.GD_PROJECT) projectCreator.createNewProject(json) - assertThat(projectCreator.createNewProject(json), `is`(true)) + assertThat(projectCreator.createNewProject(json), `is`(SettingsImportingResult.GD_PROJECT)) + } + + @Test + fun `When importing settings succeeded createNewProject() should return 'SUCCESS'`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.SUCCESS) + + projectCreator.createNewProject(json) + assertThat(projectCreator.createNewProject(json), `is`(SettingsImportingResult.SUCCESS)) } @Test fun `When importing settings failed should created project be deleted`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(false) + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) + + projectCreator.createNewProject(json) + verify(projectsRepository).delete(savedProject.uuid) + } + + @Test + fun `When importing settings contain GD protocol should created project be deleted`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.GD_PROJECT) projectCreator.createNewProject(json) verify(projectsRepository).delete(savedProject.uuid) @@ -80,7 +97,7 @@ class ProjectCreatorTest { @Test fun `When importing settings failed should prefs be cleared`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(false) + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) projectCreator.createNewProject(json) @@ -90,7 +107,7 @@ class ProjectCreatorTest { @Test fun `New project id should be set`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(true) + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.SUCCESS) projectCreator.createNewProject(json) verify(currentProjectProvider).setCurrentProject("1") diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt index 64e6205db11..e3fb2ac2fa9 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt @@ -180,4 +180,41 @@ class QrCodeProjectCreatorDialogTest { ) verifyNoInteractions(projectCreator) } + + @Test + fun `When QR code contains GD protocol a toast should be displayed`() { + val projectCreator = mock() + + CollectHelpers.overrideAppDependencyModule(object : AppDependencyModule() { + override fun providesBarcodeViewDecoder(): BarcodeViewDecoder { + val barcodeResult = mock { + `when`(it.text).thenReturn( + CompressionUtils.compress( + "{\n" + + " \"general\": {\n" + + " \"protocol\" : \"google_sheets\"" + + " },\n" + + " \"admin\": {\n" + + " }\n" + + "}" + ) + ) + } + + return mock { + `when`(it.waitForBarcode(any())).thenReturn(MutableLiveData(barcodeResult)) + } + } + }) + + launcherRule.launch(QrCodeProjectCreatorDialog::class.java) + assertThat( + ShadowToast.getTextOfLatestToast(), + `is`( + ApplicationProvider.getApplicationContext() + .getString(R.string.settings_with_gd_protocol) + ) + ) + verifyNoInteractions(projectCreator) + } } diff --git a/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt b/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt index bf4c3a777b9..16e73b73ed5 100644 --- a/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt +++ b/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt @@ -6,6 +6,7 @@ import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.importing.ProjectDetailsCreatorImpl import org.odk.collect.settings.importing.SettingsChangeHandler import org.odk.collect.settings.importing.SettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import org.odk.collect.settings.validation.JsonSchemaSettingsValidator class ODKAppSettingsImporter( @@ -29,11 +30,11 @@ class ODKAppSettingsImporter( ProjectDetailsCreatorImpl(projectColors, generalDefaults) ) - fun fromJSON(json: String, project: Project.Saved): Boolean { + fun fromJSON(json: String, project: Project.Saved): SettingsImportingResult { return try { settingsImporter.fromJSON(json, project, deviceUnsupportedSettings) } catch (e: Throwable) { - false + SettingsImportingResult.INVALID_SETTINGS } } } diff --git a/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt index 4de64b11fc5..07459bafb06 100644 --- a/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt +++ b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt @@ -20,9 +20,9 @@ internal class SettingsImporter( private val projectDetailsCreator: ProjectDetailsCreator ) { - fun fromJSON(json: String, project: Project.Saved, deviceUnsupportedSettings: JSONObject): Boolean { + fun fromJSON(json: String, project: Project.Saved, deviceUnsupportedSettings: JSONObject): SettingsImportingResult { if (!settingsValidator.isValid(json)) { - return false + return SettingsImportingResult.INVALID_SETTINGS } val generalSettings = settingsProvider.getUnprotectedSettings(project.uuid) @@ -33,6 +33,10 @@ internal class SettingsImporter( val jsonObject = JSONObject(json) + if (isGDProject(jsonObject)) { + return SettingsImportingResult.GD_PROJECT + } + // Import unprotected settings importToPrefs(jsonObject, AppConfigurationKeys.GENERAL, generalSettings, deviceUnsupportedSettings) @@ -65,7 +69,13 @@ internal class SettingsImporter( settingsChangedHandler.onSettingsChanged(project.uuid) - return true + return SettingsImportingResult.SUCCESS + } + + private fun isGDProject(jsonObject: JSONObject): Boolean { + val generalSettings = jsonObject.getJSONObject(AppConfigurationKeys.GENERAL) + return generalSettings.has(ProjectKeys.KEY_PROTOCOL) && + generalSettings.get(ProjectKeys.KEY_PROTOCOL) == ProjectKeys.PROTOCOL_GOOGLE_SHEETS } private fun importToPrefs( diff --git a/settings/src/main/java/org/odk/collect/settings/importing/SettingsImportingResult.kt b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImportingResult.kt new file mode 100644 index 00000000000..f8f78519ae7 --- /dev/null +++ b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImportingResult.kt @@ -0,0 +1,7 @@ +package org.odk.collect.settings.importing + +enum class SettingsImportingResult { + SUCCESS, + INVALID_SETTINGS, + GD_PROJECT +} diff --git a/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt b/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt index f61760b6f10..a95fe4b43e1 100644 --- a/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt +++ b/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt @@ -10,6 +10,7 @@ import org.mockito.kotlin.whenever import org.odk.collect.projects.InMemProjectsRepository import org.odk.collect.projects.Project import org.odk.collect.settings.importing.SettingsChangeHandler +import org.odk.collect.settings.importing.SettingsImportingResult import org.odk.collect.settings.support.SettingsUtils.assertSettingsEmpty import java.lang.RuntimeException @@ -39,7 +40,7 @@ class ODKAppSettingsImporterTest { "}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(true)) + assertThat(result, equalTo(SettingsImportingResult.SUCCESS)) } @Test @@ -48,7 +49,7 @@ class ODKAppSettingsImporterTest { "{ \"admin\": {}}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) assertSettingsEmpty(settingsProvider.getProtectedSettings()) } @@ -59,7 +60,7 @@ class ODKAppSettingsImporterTest { "{ \"general\": {}}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) assertSettingsEmpty(settingsProvider.getProtectedSettings()) } @@ -70,7 +71,7 @@ class ODKAppSettingsImporterTest { "{\"general\":{*},\"admin\":{}}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) assertSettingsEmpty(settingsProvider.getProtectedSettings()) } @@ -88,6 +89,23 @@ class ODKAppSettingsImporterTest { "}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) + } + + @Test + fun `rejects JSON with google_sheets protocol`() { + val result = settingsImporter.fromJSON( + "{\n" + + " \"general\": {\n" + + " \"protocol\" : \"google_sheets\"" + + " },\n" + + " \"admin\": {\n" + + " }\n" + + "}", + projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) + ) + assertThat(result, equalTo(SettingsImportingResult.GD_PROJECT)) + assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) + assertSettingsEmpty(settingsProvider.getProtectedSettings()) } } diff --git a/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt b/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt index 24aef318005..5bed761c6f4 100644 --- a/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt +++ b/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt @@ -66,9 +66,9 @@ class SettingsImporterTest { } @Test - fun whenJSONSettingsAreInvalid_returnsFalse() { + fun `when JSON settings are invalid returns 'INVALID_SETTINGS'`() { whenever(settingsValidator.isValid(emptySettings())).thenReturn(false) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(false)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.INVALID_SETTINGS)) } @Test @@ -86,7 +86,7 @@ class SettingsImporterTest { JSONObject().put("key3", 5) ) - assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertThat(generalSettings.contains("key3"), `is`(false)) assertThat(adminSettings.contains("key3"), `is`(false)) @@ -122,7 +122,7 @@ class SettingsImporterTest { importer.fromJSON( json.toString(), currentProject, deviceUnsupportedSettings ), - `is`(true) + `is`(SettingsImportingResult.SUCCESS) ) assertThat(generalSettings.contains("key3"), `is`(true)) @@ -133,7 +133,7 @@ class SettingsImporterTest { @Test fun `for supported settings that do not exist in json save defaults`() { - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertSettings( generalSettings, "key1", "default", @@ -160,7 +160,7 @@ class SettingsImporterTest { JSONObject().put("key1", 6) ) - assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertSettings( generalSettings, "key1", "default", @@ -183,7 +183,7 @@ class SettingsImporterTest { adminSettings, "key1", 0 ) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertSettings( generalSettings, "key1", "default", @@ -213,7 +213,7 @@ class SettingsImporterTest { projectsRepository, projectDetailsCreator ) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) } @Test // Migrations might use old keys that are "unknown" to the app @@ -239,7 +239,7 @@ class SettingsImporterTest { projectsRepository, projectDetailsCreator ) - assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) } @Test @@ -254,7 +254,7 @@ class SettingsImporterTest { projectsRepository, projectDetailsCreator ) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) verify(settingsChangeHandler).onSettingsChanged("1") verifyNoMoreInteractions(settingsChangeHandler) } @@ -315,25 +315,14 @@ class SettingsImporterTest { } @Test - fun `when protocol is Google Drive and project name not set, project name falls back to Google account`() { - val generalJson = JSONObject() - .put(ProjectKeys.KEY_PROTOCOL, ProjectKeys.PROTOCOL_GOOGLE_SHEETS) - .put(ProjectKeys.KEY_SELECTED_GOOGLE_ACCOUNT, "foo@bar.baz") + fun `when protocol is Google Drive returns GD_PROJECT`() { + val generalJson = JSONObject().put(ProjectKeys.KEY_PROTOCOL, ProjectKeys.PROTOCOL_GOOGLE_SHEETS) + val settings = JSONObject() .put(AppConfigurationKeys.GENERAL, generalJson) .put(AppConfigurationKeys.ADMIN, JSONObject()) - whenever( - projectDetailsCreator.createProjectFromDetails( - any(), - any(), - any(), - any() - ) - ).thenReturn(Project.New("A", "B", "C")) - - importer.fromJSON(settings.toString(), currentProject, JSONObject()) - verify(projectDetailsCreator).createProjectFromDetails("", "", "", "foo@bar.baz") + assertThat(importer.fromJSON(settings.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.GD_PROJECT)) } private fun emptySettings(): String { diff --git a/strings/src/main/res/values-cs/strings.xml b/strings/src/main/res/values-cs/strings.xml index 52a6f56cf44..726b4e3dafa 100644 --- a/strings/src/main/res/values-cs/strings.xml +++ b/strings/src/main/res/values-cs/strings.xml @@ -196,7 +196,6 @@ Soubor SVG neexistuje! Udělat fotku Vyberte obrázek - Zvolte účet Vybraný soubor není platný obrázek Klepnutím na obrazovku udělejte snímek Přední fotoaparát není v tomto zařízení k dispozici @@ -932,10 +931,6 @@ Ručně zadejte podrobnosti projektu Po přidání projektu jej můžete nakonfigurovat v části Nastavení - - Můj projekt používá Google Drive. - - Konfigurovat Ještě nemáte žádný projekt? diff --git a/strings/src/main/res/values-de/strings.xml b/strings/src/main/res/values-de/strings.xml index a2f3a4bbafa..01dc44eeada 100644 --- a/strings/src/main/res/values-de/strings.xml +++ b/strings/src/main/res/values-de/strings.xml @@ -198,7 +198,6 @@ SVG-Datei existiert nicht! Foto machen Bild auswählen - Konto auswählen Ausgewählte Datei ist kein gültiges Bild. Bildschirm berühren um ein Foto aufzunehmen Frontkamera bei diesem Gerät nicht verfügbar @@ -928,10 +927,6 @@ Projektdetails manuell eingeben Nachdem Sie Ihr Projekt hinzugefügt haben, können Sie es in den konfigurieren. - - Mein Projekt benutzt Google Drive. - - Konfigurieren Noch kein eigenes Projekt? diff --git a/strings/src/main/res/values-es/strings.xml b/strings/src/main/res/values-es/strings.xml index 0cb191ec186..18ddf04884f 100644 --- a/strings/src/main/res/values-es/strings.xml +++ b/strings/src/main/res/values-es/strings.xml @@ -196,7 +196,6 @@ Archivo SVG no existe Tomar la Foto Escoja la Imagen - Elegir cuenta El archivo seleccionado no es una imagen válida Toca la pantalla para tomar una foto Este dispositivo no tiene Cámara frontal disponible @@ -930,10 +929,6 @@ Ingrese manualmente los detalles del proyecto Después de agregar su proyecto, puede configurarlo en Configuración - - Mi proyecto usa Google Drive. - - Configurar ¿Aún no tienes un proyecto? diff --git a/strings/src/main/res/values-fa/strings.xml b/strings/src/main/res/values-fa/strings.xml index 6a96440e06f..ecd3a75fe57 100644 --- a/strings/src/main/res/values-fa/strings.xml +++ b/strings/src/main/res/values-fa/strings.xml @@ -196,7 +196,6 @@ فایل SVG موجود نیست! گرفتن عکس انتخاب تصویر - حساب را انتخاب کنید فایل انتخاب شده تصویر قابل اعتبار ندارد برای گرفتن عکس روی صفحه ضربه بزنید این دستگاه دوربین جلو موجود نیست @@ -880,10 +879,6 @@ جزئیات پروژه را به صورت دستی وارد کنید پس از اینکه پروژه خود را اضافه کردید، می توانید آن را در تنظیمات تنظیم کنید - - پروژه من از Google Drive استفاده می کند. - - تنظیم کردن هنوز پروژه ای ندارید؟ diff --git a/strings/src/main/res/values-fi/strings.xml b/strings/src/main/res/values-fi/strings.xml index 8a465821a1a..badad9fd242 100644 --- a/strings/src/main/res/values-fi/strings.xml +++ b/strings/src/main/res/values-fi/strings.xml @@ -198,7 +198,6 @@ SVG-tiedosto puuttuu! Ota kuva Valitse kuva - Valitse tili Valittu tiedosto ei ole kelvollinen kuva Napsauta näyttöä ottaaksesi kuvan Etukamera ei ole käytettävissä tällä laitteella @@ -928,10 +927,6 @@ Syötä projektin yksityiskohdat manuaalisesti Lisättyäsi projektin voit konfiguroida sen Asetuksissa. - - Projektini käyttää Google Drive -palvelua. - - Asetukset Puuttuuko projekti vielä? diff --git a/strings/src/main/res/values-fr/strings.xml b/strings/src/main/res/values-fr/strings.xml index c0b245165cd..58c8f114d5f 100644 --- a/strings/src/main/res/values-fr/strings.xml +++ b/strings/src/main/res/values-fr/strings.xml @@ -198,7 +198,6 @@ Le fichier SVG n\'existe pas ! Prendre une photo Choisir une Image - Choisir compte Le fichier choisi n\'est pas une image valide Toucher l\'écran pour prendre une photo Caméra frontale non disponible sur cet appareil @@ -931,10 +930,6 @@ Saisir les détails du projet Après avoir ajouté un projet, vous pouvez le configurer dans les Paramètres - - Mon projet utilise Google Drive. - - Configurer Pas encore de projet? diff --git a/strings/src/main/res/values-in/strings.xml b/strings/src/main/res/values-in/strings.xml index 8f19c36e9c5..e6264df64d2 100644 --- a/strings/src/main/res/values-in/strings.xml +++ b/strings/src/main/res/values-in/strings.xml @@ -194,7 +194,6 @@ Berkas SVG tidak ada. Ambil Foto Pilih Gambar - Pilih akun Berkas terpilih bukan gambar yang valid Sentuh layar untuk ambil gambar Kamera depan tidak tersedia dalam perangkat ini. @@ -892,10 +891,6 @@ Konfigurasi dengan QR kode Masukkan rincian proyek secara manual - - Proyek saya menggunakan Google Drive - - Konfigurasi Belum memliki proyek? diff --git a/strings/src/main/res/values-it/strings.xml b/strings/src/main/res/values-it/strings.xml index 3870aee6dd0..aa338d2477b 100644 --- a/strings/src/main/res/values-it/strings.xml +++ b/strings/src/main/res/values-it/strings.xml @@ -194,7 +194,6 @@ Il file SVG non esiste! Scatta Foto Seleziona Foto - Scegli l\'account Il file selezionato non è una valida immagine Tocca lo schermo per scattare una foto Fotocamera frontale non disponibile in questo dispositivo @@ -917,10 +916,6 @@ Inserisci manualmente i dettagli del progetto Dopo aver aggiunto il progetto, puoi configurarlo in Impostazioni - - Il mio progetto utilizza Google Drive. - - Configurare Non hai ancora un Progetto? diff --git a/strings/src/main/res/values-pt/strings.xml b/strings/src/main/res/values-pt/strings.xml index 151c5e1e885..9bcc97a7980 100644 --- a/strings/src/main/res/values-pt/strings.xml +++ b/strings/src/main/res/values-pt/strings.xml @@ -194,7 +194,6 @@ O arquivo SVG não existe! Tirar Foto Escolher Imagem - Escolher conta O arquivo selecionado não é uma imagem válida Toque na tela para tirar uma fotografia A câmera da frente não está disponível neste dispositivo @@ -917,10 +916,6 @@ Entrar com os detalhes do projeto manualmente Após adicionar o seu projeto, você pode configurá-lo em Configurações - - Meu projeto usa o Google Drive - - Configurar Não tem ainda um projeto? diff --git a/strings/src/main/res/values-rw/strings.xml b/strings/src/main/res/values-rw/strings.xml index f5123bb4807..fa813df5fb5 100644 --- a/strings/src/main/res/values-rw/strings.xml +++ b/strings/src/main/res/values-rw/strings.xml @@ -195,7 +195,6 @@ N\'ukomea guhura n\'iki kibazo, wakigeza kuwa gusabye gukusanya amakuru.Ububiko bwa SVG ntabwo buboneka! Fata ifoto Hitamo Ishusho - Hitamo konti Ifoto wahisemo mububiko si ifoto yukuri Kora kuri screen ufate ifoto Ifoto y\'imbere ntabwo iboneka kuri iki gikoresho @@ -862,10 +861,6 @@ Birashoboka ko ari zimwe zashyizwemo mubihe bitandukanye cgw ziturutse ahatanduk Byihuze hifashishijwe kode ya QR Ukoresheje amaboko injiza amakuru kuri project - - Poroje yanjye ikoresha Google Drive - - Hindura Nta poroje ufitemo diff --git a/strings/src/main/res/values-sw/strings.xml b/strings/src/main/res/values-sw/strings.xml index a6061fa0aee..61de1a20bc9 100644 --- a/strings/src/main/res/values-sw/strings.xml +++ b/strings/src/main/res/values-sw/strings.xml @@ -194,7 +194,6 @@ Kabrasha la SVG halipo Chukua picha Chagua picha - Chagua akaunti Faili lillilochaguliwa sio picha sahihi Bofya kioo kuchukua picha Kamera ya mbele haipo katika kifaa hiki @@ -749,8 +748,6 @@ select the wording and where to put the \n for maximum impact in your language.--> - - Mradi wangu unatumia Google Drive. diff --git a/strings/src/main/res/values-tr/strings.xml b/strings/src/main/res/values-tr/strings.xml index d905c510b39..2e3a4c71f5d 100644 --- a/strings/src/main/res/values-tr/strings.xml +++ b/strings/src/main/res/values-tr/strings.xml @@ -396,10 +396,6 @@ QR kod ile kurulum yap Proje detaylarını manuel ekle - - Projem Google Drive kullanıyor. - - Ayarla Hala projeniz yok mu? diff --git a/strings/src/main/res/values-zh/strings.xml b/strings/src/main/res/values-zh/strings.xml index 9185932b345..24dd9b86c9a 100644 --- a/strings/src/main/res/values-zh/strings.xml +++ b/strings/src/main/res/values-zh/strings.xml @@ -198,7 +198,6 @@ SVG文件不存在! 照相 选择图片 - 选择帐户 所选文件不是有效图像 点击屏幕拍照 前置摄像头在此设备上不可用 @@ -926,10 +925,6 @@ 手动输入项目详细信息 添加项目后,可以在“设置”中对其进行配置 - - 我的项目使用Google Drive。 - - 配置 还没有项目? diff --git a/strings/src/main/res/values/strings.xml b/strings/src/main/res/values/strings.xml index cc79bb7989d..26c7240eded 100644 --- a/strings/src/main/res/values/strings.xml +++ b/strings/src/main/res/values/strings.xml @@ -254,7 +254,6 @@ Take Picture Choose Image - Choose account Selected file is not a valid image Tap the screen to take a picture Front camera not available on this device @@ -1049,6 +1048,7 @@ QR code does not contain valid settings QR Code not found in the selected image Current settings are corrupt. From project management settings, reset settings or import working ones. + Google Drive/Sheets projects can no longer be created Switch to %s All blank forms, submissions and settings will be permanently deleted. + This is a Google Drive/Sheets project. Once you delete it, you will not be able to create it again.\n\nAll blank forms, submissions and settings will be permanently deleted. Yes No Project can\'t be deleted @@ -1133,10 +1134,6 @@ Manually enter project details After you add your project, you can configure it in Settings - - My project uses Google Drive. - - Configure Don\'t have a project yet?