diff --git a/Corona-Warn-App/config/detekt.yml b/Corona-Warn-App/config/detekt.yml index 16605a8917887ed41ae1933e82c95a3dd0a3cb0a..0e4e6182ac7df20856e94129fe88eae8a0c0921d 100644 --- a/Corona-Warn-App/config/detekt.yml +++ b/Corona-Warn-App/config/detekt.yml @@ -409,7 +409,7 @@ performance: active: true excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt'] SpreadOperator: - active: true + active: false excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt', '**/CertificatePinnerFactory.kt'] UnnecessaryTemporaryInstantiation: active: true @@ -630,4 +630,4 @@ style: WildcardImport: active: true excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt'] - excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*'] \ No newline at end of file + excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*'] diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt index 1af3e9cef629b1badf4e63c9fec84a426e5030ea..829ed1551d57246345e1a7f7457f7be0762e36bf 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt @@ -13,13 +13,13 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragmentInContainer2 import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule +import testhelpers.SCREENSHOT_DELAY_TIME @RunWith(AndroidJUnit4::class) class OnboardingDeltaInteroperabilityFragmentTest : BaseUITest() { diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt index 2ccbcec5eed12e03b90db058d03859b8d8cc1b2c..f8d350f665cd81ffb4de089e9b488f2052af65b1 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt @@ -10,12 +10,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.launchFragmentInContainer2 import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule +import testhelpers.SCREENSHOT_DELAY_TIME @RunWith(AndroidJUnit4::class) class OnboardingTestFragmentTest : BaseUITest() { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt index f57f6374466ad8b768ddd765c0a6846efb5e6047..9e7e02ee68a667d4e349c8363ebc56da907dd9d0 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt @@ -8,6 +8,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.gson.Gson import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -24,6 +25,7 @@ import java.util.UUID class SubmissionTestFragmentViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val tekHistoryStorage: TEKHistoryStorage, + private val submissionRepository: SubmissionRepository, tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory, @BaseGson baseGson: Gson ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt index e66f56ad6fa014a5d86a9348d100fbe0d5113036..4fbdaee7f79dd4a70673342ce99917152ada406b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt @@ -21,6 +21,7 @@ import de.rki.coronawarnapp.exception.reporting.ReportingConstants.ERROR_REPORT_ import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.risk.RiskLevelChangeDetector import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.WatchdogService @@ -53,6 +54,8 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { @Inject lateinit var contactDiaryWorkScheduler: ContactDiaryWorkScheduler @Inject lateinit var notificationHelper: NotificationHelper @Inject lateinit var deviceTimeHandler: DeviceTimeHandler + @Inject lateinit var autoSubmission: AutoSubmission + @LogHistoryTree @Inject lateinit var rollingLogHistory: Timber.Tree override fun onCreate() { @@ -88,6 +91,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { deviceTimeHandler.launch() configChangeDetector.launch() riskLevelChangeDetector.launch() + autoSubmission.setup() } private val activityLifecycleCallback = object : ActivityLifecycleCallbacks { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayFragment.kt index 3b1c9498f487ffbfbc693bc4ef5ccc21ce1112b9..42349daa6e6170199ce52d9032151af796c309b4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayFragment.kt @@ -62,7 +62,8 @@ class ContactDiaryDayFragment : Fragment(R.layout.contact_diary_day_fragment), A } viewModel.uiState.observe2(this) { - binding.contactDiaryDayHeader.title = it.dayText + binding.contactDiaryDayHeader.title = it.dayText(requireContext()) + binding.contentContainer.contentDescription = it.dayText(requireContext()) } viewModel.routeToScreen.observe2(this) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayViewModel.kt index 55f7071c94a9709bcfbdec01e6372b573677e89d..d7312158100c45b10934a2b669b0d6c473bd1811 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/ContactDiaryDayViewModel.kt @@ -1,9 +1,11 @@ package de.rki.coronawarnapp.contactdiary.ui.day +import android.content.Context import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.ui.day.tabs.ContactDiaryDayTab +import de.rki.coronawarnapp.contactdiary.util.getLocale import de.rki.coronawarnapp.contactdiary.util.toFormattedDay import de.rki.coronawarnapp.ui.SingleLiveEvent import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -22,7 +24,7 @@ class ContactDiaryDayViewModel @AssistedInject constructor( val routeToScreen: SingleLiveEvent<ContactDiaryDayNavigationEvents> = SingleLiveEvent() val uiState = displayedDay.map { day -> - UIState(dayText = day.toFormattedDay()) + UIState(dayText = { day.toFormattedDay(it.getLocale()) }) }.asLiveData() fun onCreateButtonClicked(activeTab: ContactDiaryDayTab) { @@ -39,7 +41,7 @@ class ContactDiaryDayViewModel @AssistedInject constructor( } data class UIState( - val dayText: String + val dayText: (Context) -> String ) @AssistedInject.Factory diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListAdapter.kt index d4ddccf8c4fe0496b597decd16bb6d272e42af1c..53ad1fa2d41f1b8a20164d6fa8531dc1727f420b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListAdapter.kt @@ -1,9 +1,11 @@ package de.rki.coronawarnapp.contactdiary.ui.day.tabs.location import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation import de.rki.coronawarnapp.contactdiary.util.SelectableItem +import de.rki.coronawarnapp.contactdiary.util.setClickLabel import de.rki.coronawarnapp.databinding.ContactDiaryLocationListItemBinding import de.rki.coronawarnapp.ui.lists.BaseAdapter import de.rki.coronawarnapp.util.lists.BindableVH @@ -27,6 +29,8 @@ class ContactDiaryLocationListAdapter( override fun onBindBaseVH(holder: CachedLocationViewHolder, position: Int, payloads: MutableList<Any>) { val item = data[position] holder.itemView.setOnClickListener { + it.contentDescription = item.onClickDescription.get(holder.context) + it.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION) onTappedCallback(item) } holder.bind(item) @@ -41,13 +45,14 @@ class ContactDiaryLocationListAdapter( override val onBindData: ContactDiaryLocationListItemBinding.( key: SelectableItem<ContactDiaryLocation>, payloads: List<Any> - ) -> Unit = - { key, _ -> - contactDiaryLocationListLineName.text = key.item.locationName - when (key.selected) { - true -> contactDiaryLocationListLineIcon.setImageResource(R.drawable.ic_selected) - false -> contactDiaryLocationListLineIcon.setImageResource(R.drawable.ic_unselected) - } + ) -> Unit = { key, _ -> + contactDiaryLocationListItemName.text = key.item.locationName + contactDiaryLocationListItem.contentDescription = key.contentDescription.get(context) + contactDiaryLocationListItem.setClickLabel(context.getString(key.clickLabel)) + when (key.selected) { + true -> contactDiaryLocationListItemIcon.setImageResource(R.drawable.ic_selected) + false -> contactDiaryLocationListItemIcon.setImageResource(R.drawable.ic_unselected) } + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListFragment.kt index 0d20b283c16747b49b91383d6fc54cd9b2be710f..ee5cedaaad5e8115464aa21674b2a20c850d34da 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListFragment.kt @@ -34,7 +34,7 @@ class ContactDiaryLocationListFragment : Fragment(R.layout.contact_diary_locatio super.onViewCreated(view, savedInstanceState) val locationListAdapter = ContactDiaryLocationListAdapter { - viewModel.locationSelectionChanged(it) + viewModel.onLocationSelectionChanged(it) } binding.contactDiaryLocationListRecyclerView.apply { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt index 70cf35b55aa35818c833ca8d1305298fc1eda2d2..8adf87a56a285ce1a6661fc1098c5d63f63a6861 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.contactdiary.ui.day.tabs.location import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocationVisit import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository @@ -10,6 +11,7 @@ import de.rki.coronawarnapp.contactdiary.util.SelectableItem import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.ui.toResolvingString import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import kotlinx.coroutines.CoroutineExceptionHandler @@ -34,14 +36,28 @@ class ContactDiaryLocationListViewModel @AssistedInject constructor( val uiList = selectableLocations.combine(dayElement) { locations, dayElement -> locations.map { contactDiaryLocation -> if (dayElement.any { it.contactDiaryLocation.locationId == contactDiaryLocation.locationId }) { - SelectableItem(true, contactDiaryLocation) + SelectableItem( + true, + contactDiaryLocation, + SELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryLocation.locationName), + UNSELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryLocation.locationName), + DESELECT_ACTION_DESCRIPTION, + SELECT_ACTION_DESCRIPTION + ) } else { - SelectableItem(false, contactDiaryLocation) + SelectableItem( + false, + contactDiaryLocation, + UNSELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryLocation.locationName), + SELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryLocation.locationName), + SELECT_ACTION_DESCRIPTION, + DESELECT_ACTION_DESCRIPTION + ) } } }.asLiveData() - fun locationSelectionChanged(item: SelectableItem<ContactDiaryLocation>) = launch(coroutineExceptionHandler) { + fun onLocationSelectionChanged(item: SelectableItem<ContactDiaryLocation>) = launch(coroutineExceptionHandler) { if (!item.selected) { contactDiaryRepository.addLocationVisit( DefaultContactDiaryLocationVisit( @@ -50,18 +66,21 @@ class ContactDiaryLocationListViewModel @AssistedInject constructor( ) ) } else { - val visit = dayElement.first() + val visit = dayElement + .first() .find { it.contactDiaryLocation.locationId == item.item.locationId } visit?.let { contactDiaryRepository.deleteLocationVisit(it) } } } - companion object { - private val TAG = ContactDiaryLocationListViewModel::class.java.simpleName - } - @AssistedInject.Factory interface Factory : CWAViewModelFactory<ContactDiaryLocationListViewModel> { fun create(selectedDay: String): ContactDiaryLocationListViewModel } } + +private val TAG = ContactDiaryLocationListViewModel::class.java.simpleName +private const val SELECTED_CONTENT_DESCRIPTION = R.string.accessibility_location_selected +private const val UNSELECTED_CONTENT_DESCRIPTION = R.string.accessibility_location_unselected +private const val SELECT_ACTION_DESCRIPTION = R.string.accessibility_action_select +private const val DESELECT_ACTION_DESCRIPTION = R.string.accessibility_action_deselect diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListAdapter.kt index 90112537502a0fd8dda51b0ba85c7bd5e46792a3..f8142cb31790c510697b750316d50e77c200de9c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListAdapter.kt @@ -1,9 +1,11 @@ package de.rki.coronawarnapp.contactdiary.ui.day.tabs.person import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson import de.rki.coronawarnapp.contactdiary.util.SelectableItem +import de.rki.coronawarnapp.contactdiary.util.setClickLabel import de.rki.coronawarnapp.databinding.ContactDiaryPersonListItemBinding import de.rki.coronawarnapp.ui.lists.BaseAdapter import de.rki.coronawarnapp.util.lists.BindableVH @@ -27,6 +29,8 @@ class ContactDiaryPersonListAdapter( override fun onBindBaseVH(holder: CachedPersonViewHolder, position: Int, payloads: MutableList<Any>) { val item = data[position] holder.itemView.setOnClickListener { + it.contentDescription = item.onClickDescription.get(holder.context) + it.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION) onTappedCallback(item) } holder.bind(item, payloads) @@ -42,10 +46,12 @@ class ContactDiaryPersonListAdapter( key: SelectableItem<ContactDiaryPerson>, payloads: List<Any> ) -> Unit = { key, _ -> - contactDiaryPersonListLineName.text = key.item.fullName + contactDiaryPersonListItemName.text = key.item.fullName + contactDiaryPersonListItem.contentDescription = key.contentDescription.get(context) + contactDiaryPersonListItem.setClickLabel(context.getString(key.clickLabel)) when (key.selected) { - true -> contactDiaryPersonListLineIcon.setImageResource(R.drawable.ic_selected) - false -> contactDiaryPersonListLineIcon.setImageResource(R.drawable.ic_unselected) + true -> contactDiaryPersonListItemIcon.setImageResource(R.drawable.ic_selected) + false -> contactDiaryPersonListItemIcon.setImageResource(R.drawable.ic_unselected) } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListFragment.kt index 3ccb18d5daced98aa81676e7ea5f149722c2daae..bab1d0324e3b531dde01514248a80889307bdd84 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListFragment.kt @@ -33,8 +33,8 @@ class ContactDiaryPersonListFragment : Fragment(R.layout.contact_diary_person_li override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val personListAdapter = ContactDiaryPersonListAdapter { - viewModel.personSelectionChanged(it) + val personListAdapter = ContactDiaryPersonListAdapter() { + viewModel.onPersonSelectionChanged(it) } binding.contactDiaryPersonListRecyclerView.apply { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt index d1d488276ab5a86965c97d2d43f2b309c4ac60f7..1f88e272985d328121cde26ee4060c4f8da1ccb7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.contactdiary.ui.day.tabs.person import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryPersonEncounter import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository @@ -10,6 +11,7 @@ import de.rki.coronawarnapp.contactdiary.util.SelectableItem import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.ui.toResolvingString import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import kotlinx.coroutines.CoroutineExceptionHandler @@ -34,14 +36,28 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor( val uiList = selectablePersons.combine(dayElement) { persons, dayElement -> persons.map { contactDiaryPerson -> if (dayElement.any { it.contactDiaryPerson.personId == contactDiaryPerson.personId }) { - SelectableItem(true, contactDiaryPerson) + SelectableItem( + true, + contactDiaryPerson, + SELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryPerson.fullName), + UNSELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryPerson.fullName), + DESELECT_ACTION_DESCRIPTION, + SELECT_ACTION_DESCRIPTION + ) } else { - SelectableItem(false, contactDiaryPerson) + SelectableItem( + false, + contactDiaryPerson, + UNSELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryPerson.fullName), + SELECTED_CONTENT_DESCRIPTION.toResolvingString(contactDiaryPerson.fullName), + SELECT_ACTION_DESCRIPTION, + DESELECT_ACTION_DESCRIPTION + ) } } }.asLiveData() - fun personSelectionChanged(item: SelectableItem<ContactDiaryPerson>) = launch(coroutineExceptionHandler) { + fun onPersonSelectionChanged(item: SelectableItem<ContactDiaryPerson>) = launch(coroutineExceptionHandler) { if (!item.selected) { contactDiaryRepository.addPersonEncounter( DefaultContactDiaryPersonEncounter( @@ -56,12 +72,14 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor( } } - companion object { - private val TAG = ContactDiaryPersonListViewModel::class.java.simpleName - } - @AssistedInject.Factory interface Factory : CWAViewModelFactory<ContactDiaryPersonListViewModel> { fun create(selectedDay: String): ContactDiaryPersonListViewModel } } + +private val TAG = ContactDiaryPersonListViewModel::class.java.simpleName +private const val SELECTED_CONTENT_DESCRIPTION = R.string.accessibility_person_selected +private const val UNSELECTED_CONTENT_DESCRIPTION = R.string.accessibility_person_unselected +private const val SELECT_ACTION_DESCRIPTION = R.string.accessibility_action_select +private const val DESELECT_ACTION_DESCRIPTION = R.string.accessibility_action_deselect diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt index 6e232551f64a034b3515566ab72689d9d20f4f34..e9c77914fabb5e74f02964cc10a5a1fe2762614f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt @@ -13,6 +13,7 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditLocationsViewModel.NavigationEvent.ShowDeletionConfirmationDialog import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditLocationsViewModel.NavigationEvent.ShowLocationDetailSheet +import de.rki.coronawarnapp.contactdiary.util.setClickLabel import de.rki.coronawarnapp.databinding.ContactDiaryEditLocationsFragmentBinding import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject @@ -33,7 +34,7 @@ class ContactDiaryEditLocationsFragment : Fragment(R.layout.contact_diary_edit_l private val viewModel: ContactDiaryEditLocationsViewModel by cwaViewModels { viewModelFactory } private val binding: ContactDiaryEditLocationsFragmentBinding by viewBindingLazy() - private val listAdapter = ListAdapter() + private lateinit var listAdapter: ListAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -44,6 +45,8 @@ class ContactDiaryEditLocationsFragment : Fragment(R.layout.contact_diary_edit_l popBackStack() } + binding.deleteButton.setOnClickListener { viewModel.onDeleteAllLocationsClick() } + viewModel.isListVisible.observe2(this) { binding.contactDiaryLocationListNoItemsGroup.isGone = it } @@ -70,10 +73,6 @@ class ContactDiaryEditLocationsFragment : Fragment(R.layout.contact_diary_edit_l } } } - - binding.apply { - deleteButton.setOnClickListener { viewModel.onDeleteAllLocationsClick() } - } } override fun onResume() { @@ -82,6 +81,9 @@ class ContactDiaryEditLocationsFragment : Fragment(R.layout.contact_diary_edit_l } private fun setupRecyclerView() { + listAdapter = ListAdapter(getString(R.string.accessibility_edit)) { + getString(R.string.accessibility_location, it.locationName) + } binding.locationsRecyclerView.adapter = listAdapter } @@ -98,7 +100,10 @@ class ContactDiaryEditLocationsFragment : Fragment(R.layout.contact_diary_edit_l ) } - inner class ListAdapter : RecyclerView.Adapter<ListAdapter.ViewHolder>(), + inner class ListAdapter( + private val clickLabelString: String, + private val getContentDescriptionString: (ContactDiaryLocation) -> String + ) : RecyclerView.Adapter<ListAdapter.ViewHolder>(), AsyncDiffUtilAdapter<ContactDiaryLocation> { override val asyncDiffer: AsyncDiffer<ContactDiaryLocation> = AsyncDiffer(this) @@ -115,9 +120,13 @@ class ContactDiaryEditLocationsFragment : Fragment(R.layout.contact_diary_edit_l override fun onBindViewHolder(viewHolder: ListAdapter.ViewHolder, position: Int) { val location = data[position] - viewHolder.nameTextView.text = location.locationName - viewHolder.itemContainerView.setOnClickListener { - viewModel.onEditLocationClick(location) + with(viewHolder) { + nameTextView.text = location.locationName + itemContainerView.setOnClickListener { + viewModel.onEditLocationClick(location) + } + itemContainerView.contentDescription = getContentDescriptionString(location) + itemContainerView.setClickLabel(clickLabelString) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt index b5f46e4ab7a59c21516259f49a013ab7ca158f65..eaeace7dd4329e2179d3803652d29d0b86f55332 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt @@ -13,6 +13,7 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditPersonsViewModel.NavigationEvent.ShowDeletionConfirmationDialog import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditPersonsViewModel.NavigationEvent.ShowPersonDetailSheet +import de.rki.coronawarnapp.contactdiary.util.setClickLabel import de.rki.coronawarnapp.databinding.ContactDiaryEditPersonsFragmentBinding import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject @@ -33,7 +34,7 @@ class ContactDiaryEditPersonsFragment : Fragment(R.layout.contact_diary_edit_per private val viewModel: ContactDiaryEditPersonsViewModel by cwaViewModels { viewModelFactory } private val binding: ContactDiaryEditPersonsFragmentBinding by viewBindingLazy() - private val listAdapter = ListAdapter() + private lateinit var listAdapter: ListAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -82,6 +83,9 @@ class ContactDiaryEditPersonsFragment : Fragment(R.layout.contact_diary_edit_per } private fun setupRecyclerView() { + listAdapter = ListAdapter(getString(R.string.accessibility_edit)) { + getString(R.string.accessibility_person, it.fullName) + } binding.personsRecyclerView.adapter = listAdapter } @@ -98,7 +102,10 @@ class ContactDiaryEditPersonsFragment : Fragment(R.layout.contact_diary_edit_per ) } - inner class ListAdapter : RecyclerView.Adapter<ListAdapter.ViewHolder>(), + inner class ListAdapter( + private val clickLabelString: String, + private val getContentDescriptionString: (ContactDiaryPerson) -> String + ) : RecyclerView.Adapter<ListAdapter.ViewHolder>(), AsyncDiffUtilAdapter<ContactDiaryPerson> { override val asyncDiffer: AsyncDiffer<ContactDiaryPerson> = AsyncDiffer(this) @@ -115,9 +122,13 @@ class ContactDiaryEditPersonsFragment : Fragment(R.layout.contact_diary_edit_per override fun onBindViewHolder(viewHolder: ListAdapter.ViewHolder, position: Int) { val person = data[position] - viewHolder.nameTextView.text = person.fullName - viewHolder.itemContainerView.setOnClickListener { - viewModel.onEditPersonClick(person) + with(viewHolder) { + nameTextView.text = person.fullName + itemContainerView.setOnClickListener { + viewModel.onEditPersonClick(person) + } + itemContainerView.contentDescription = getContentDescriptionString(person) + itemContainerView.setClickLabel(clickLabelString) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt index 318e441cf2294cede6f37e4d438af3853fd0425f..8df5988087bd2fd580a25c7b4acf04d723fd01bf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt @@ -7,6 +7,8 @@ import androidx.core.app.ShareCompat import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.ui.overview.adapter.ContactDiaryOverviewAdapter +import de.rki.coronawarnapp.contactdiary.util.getLocale +import de.rki.coronawarnapp.contactdiary.util.toFormattedDay import de.rki.coronawarnapp.databinding.ContactDiaryOverviewFragmentBinding import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.doNavigate @@ -26,9 +28,10 @@ class ContactDiaryOverviewFragment : Fragment(R.layout.contact_diary_overview_fr override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val adapter = ContactDiaryOverviewAdapter { - vm.onItemPress(it) - } + val adapter = ContactDiaryOverviewAdapter( + { it.toFormattedDay(requireContext().getLocale()) }, + { vm.onItemPress(it) } + ) setupToolbar() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt index f4f6607ea102ac94ccfa9eed012ddf26535cd12b..048c25a9f2de0dc6f7b82ccee8b40415c5677a13 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt @@ -16,7 +16,8 @@ class ContactDiaryOverviewMenu @Inject constructor( private val navController: NavController get() = contactDiaryOverviewFragment.findNavController() private val vm: ContactDiaryOverviewViewModel by contactDiaryOverviewFragment.cwaViewModels { - contactDiaryOverviewFragment.viewModelFactory } + contactDiaryOverviewFragment.viewModelFactory + } fun setupMenu(toolbar: Toolbar) = toolbar.apply { inflateMenu(R.menu.menu_contact_diary_overview) @@ -29,7 +30,8 @@ class ContactDiaryOverviewMenu @Inject constructor( ) true } - R.id.menu_contact_diary_export_entries -> { vm.onExportPress(context) + R.id.menu_contact_diary_export_entries -> { + vm.onExportPress(context) true } R.id.menu_contact_diary_edit_persons -> { navController.doNavigate( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt index 3a47a02b7aa4db0508ab06cdc61297b43f94ee3a..4875c0734c43571d1d915cd1f1ddb74123cb145b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt @@ -4,10 +4,13 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isGone import androidx.recyclerview.widget.RecyclerView -import de.rki.coronawarnapp.contactdiary.util.toFormattedDay import de.rki.coronawarnapp.databinding.ContactDiaryOverviewListItemBinding +import org.joda.time.LocalDate -class ContactDiaryOverviewAdapter(private val onItemSelectionListener: (ListItem) -> Unit) : +class ContactDiaryOverviewAdapter( + private val dateFormatter: (LocalDate) -> String, + private val onItemSelectionListener: (ListItem) -> Unit +) : RecyclerView.Adapter<ContactDiaryOverviewAdapter.OverviewElementHolder>() { private val elements: MutableList<ListItem> = mutableListOf() @@ -31,7 +34,7 @@ class ContactDiaryOverviewAdapter(private val onItemSelectionListener: (ListItem override fun getItemCount() = elements.size override fun onBindViewHolder(holder: OverviewElementHolder, position: Int) { - holder.bind(elements[position], onItemSelectionListener) + holder.bind(elements[position], dateFormatter, onItemSelectionListener) } class OverviewElementHolder(private val viewDataBinding: ContactDiaryOverviewListItemBinding) : @@ -44,10 +47,10 @@ class ContactDiaryOverviewAdapter(private val onItemSelectionListener: (ListItem fun bind( item: ListItem, + dateFormatter: (LocalDate) -> String, onElementSelectionListener: (ListItem) -> Unit ) { - viewDataBinding.contactDiaryOverviewElementName.text = - item.date.toFormattedDay() + viewDataBinding.contactDiaryOverviewElementName.text = dateFormatter(item.date) viewDataBinding.contactDiaryOverviewElementBody.setOnClickListener { onElementSelectionListener(item) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensions.kt index 0397f5c2cb0d23b75041d24ba46e2d5fc6a857b4..92614ba40b25549db3818376859f031ea424e423 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensions.kt @@ -1,11 +1,16 @@ package de.rki.coronawarnapp.contactdiary.util import android.content.Context +import android.os.Build import android.view.View import android.view.ViewTreeObserver import android.view.inputmethod.InputMethodManager +import androidx.core.view.AccessibilityDelegateCompat +import androidx.core.view.ViewCompat +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.viewpager2.widget.ViewPager2 import org.joda.time.LocalDate +import org.joda.time.format.DateTimeFormat import java.util.Locale fun ViewPager2.registerOnPageChangeCallback(cb: (position: Int) -> Unit) { @@ -16,8 +21,22 @@ fun ViewPager2.registerOnPageChangeCallback(cb: (position: Int) -> Unit) { }) } -// According to tech spec german locale only -fun LocalDate.toFormattedDay(): String = toString("EEEE, dd.MM.yy", Locale.GERMAN) +fun Context.getLocale(): Locale { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + @Suppress("NewApi") + resources.configuration.locales[0] + } else { + @Suppress("DEPRECATION") + resources.configuration.locale + } +} + +fun LocalDate.toFormattedDay(locale: Locale): String { + // Use two different methods to get the final date format (Weekday, Shortdate) + // because the custom pattern of toString() does not localize characters like "/" or "." + return "${toString("EEEE", locale)}, " + + DateTimeFormat.shortDate().withLocale(locale).print(this) +} fun String.formatContactDiaryNameField(maxLength: Int): String { val newName = if (isNotBlank()) { @@ -57,3 +76,16 @@ fun View.focusAndShowKeyboard() { }) } } + +fun View.setClickLabel(label: String) { + ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() { + override fun onInitializeAccessibilityNodeInfo(v: View, info: AccessibilityNodeInfoCompat) { + super.onInitializeAccessibilityNodeInfo(v, info) + info.addAction( + AccessibilityNodeInfoCompat.AccessibilityActionCompat( + AccessibilityNodeInfoCompat.ACTION_CLICK, label + ) + ) + } + }) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/SelectableItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/SelectableItem.kt index bcb816f356014e3f267057683a3aa87acc04bc4f..dcf17cd5fb31dbc2712f4fcbf04d828fc4fef7ea 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/SelectableItem.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/util/SelectableItem.kt @@ -1,9 +1,15 @@ package de.rki.coronawarnapp.contactdiary.util +import androidx.annotation.StringRes import de.rki.coronawarnapp.util.lists.HasStableId +import de.rki.coronawarnapp.util.ui.LazyString data class SelectableItem<T : HasStableId>( val selected: Boolean, val item: T, + val contentDescription: LazyString, + val onClickDescription: LazyString, + @StringRes val clickLabel: Int, + @StringRes val onClickLabel: Int, override val stableId: Long = item.stableId ) : HasStableId diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt index 764ebc69c697f19eb2015bd4fe138f64bbe9f210..c129cbe1e1e3f04f55871a729b111ab677444521 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt @@ -464,7 +464,7 @@ object LocalData { ) } - fun numberOfSuccessfulSubmissions(): Int { + private fun numberOfSuccessfulSubmissions(): Int { return getSharedPreferenceInstance().getInt( CoronaWarnApplication.getAppContext() .getString(R.string.preference_number_successful_submissions), diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt index cb5fd7406cc612452c1c8184e44cb76bb07e8589..b9ef028545fceaa9ca88b78b4092fd86c93067d8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt @@ -9,6 +9,8 @@ import de.rki.coronawarnapp.http.HttpClientDefault import de.rki.coronawarnapp.http.RestrictedConnectionSpecs import de.rki.coronawarnapp.submission.server.SubmissionApiV1 import de.rki.coronawarnapp.submission.server.SubmissionHttpClient +import de.rki.coronawarnapp.submission.task.DefaultKeyConverter +import de.rki.coronawarnapp.submission.task.KeyConverter import de.rki.coronawarnapp.util.di.AppContext import okhttp3.Cache import okhttp3.ConnectionSpec diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt similarity index 84% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt index b59ef194e3fea7519cc81a7548f9247ae1c13350..fb7db369895314f6977e0d6591e79c0b7279c02b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.storage +package de.rki.coronawarnapp.submission import androidx.annotation.VisibleForTesting import de.rki.coronawarnapp.exception.ExceptionCategory @@ -7,13 +7,8 @@ import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.service.submission.SubmissionService -import de.rki.coronawarnapp.submission.SubmissionSettings -import de.rki.coronawarnapp.submission.SubmissionTask +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage -import de.rki.coronawarnapp.task.TaskController -import de.rki.coronawarnapp.task.TaskInfo -import de.rki.coronawarnapp.task.common.DefaultTaskRequest -import de.rki.coronawarnapp.task.submitBlocking import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess @@ -24,7 +19,6 @@ import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import timber.log.Timber import java.util.Date @@ -37,7 +31,6 @@ class SubmissionRepository @Inject constructor( private val submissionService: SubmissionService, @AppScope private val scope: CoroutineScope, private val timeStamper: TimeStamper, - private val taskController: TaskController, private val tekHistoryStorage: TEKHistoryStorage ) { private val testResultReceivedDateFlowInternal = MutableStateFlow(Date()) @@ -52,23 +45,8 @@ class SubmissionRepository @Inject constructor( val hasViewedTestResult = submissionSettings.hasViewedTestResult.flow val currentSymptoms = submissionSettings.symptoms - private fun List<TaskInfo>.isSubmissionTaskRunning() = any { - it.taskState.isActive && it.taskState.request.type == SubmissionTask::class - } - - val isSubmissionRunning = taskController.tasks.map { it.isSubmissionTaskRunning() } - private val testResultFlow = MutableStateFlow<TestResult?>(null) - suspend fun startSubmission() { - Timber.i("Starting submission.") - val result = taskController.submitBlocking(DefaultTaskRequest(type = SubmissionTask::class)) - result.error?.let { - Timber.e(it, "Submission failed.") - it.report(ExceptionCategory.HTTP, prefix = "Submission failed.") - } - } - // to be used by new submission flow screens fun giveConsentToSubmission() { submissionSettings.hasGivenConsent.update { @@ -202,13 +180,17 @@ class SubmissionRepository @Inject constructor( LocalData.isAllowedToSubmitDiagnosisKeys(false) LocalData.isTestResultNotificationSent(false) } -} -private fun deriveUiState(testResult: TestResult?): DeviceUIState = when (testResult) { - TestResult.NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE - TestResult.POSITIVE -> DeviceUIState.PAIRED_POSITIVE - TestResult.PENDING -> DeviceUIState.PAIRED_NO_RESULT - TestResult.REDEEMED -> DeviceUIState.PAIRED_REDEEMED - TestResult.INVALID -> DeviceUIState.PAIRED_ERROR - null -> DeviceUIState.UNPAIRED + private fun deriveUiState(testResult: TestResult?): DeviceUIState = when (testResult) { + TestResult.NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE + TestResult.POSITIVE -> DeviceUIState.PAIRED_POSITIVE + TestResult.PENDING -> DeviceUIState.PAIRED_NO_RESULT + TestResult.REDEEMED -> DeviceUIState.PAIRED_REDEEMED + TestResult.INVALID -> DeviceUIState.PAIRED_ERROR + null -> DeviceUIState.UNPAIRED + } + + companion object { + private const val TAG = "SubmissionRepository" + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt index 4eb80abf9361f52960f8eda81e9614aaa2de6566..486111f0e4c7cc6dc21e1cd88726451e93839cdd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.util.preferences.clearAndNotify import de.rki.coronawarnapp.util.preferences.createFlowPreference import de.rki.coronawarnapp.util.serialization.BaseGson import de.rki.coronawarnapp.util.serialization.adapter.RuntimeTypeAdapterFactory +import org.joda.time.Instant import javax.inject.Inject import javax.inject.Singleton @@ -51,6 +52,36 @@ class SubmissionSettings @Inject constructor( writer = FlowPreference.gsonWriter(gson) ) + val lastSubmissionUserActivityUTC = prefs.createFlowPreference( + key = "submission.user.activity.last", + reader = { key -> + Instant.ofEpochMilli(getLong(key, 0L)) + }, + writer = { key, value -> + putLong(key, value.millis) + } + ) + + val autoSubmissionEnabled = prefs.createFlowPreference( + key = "submission.auto.enabled", + defaultValue = false + ) + + val autoSubmissionAttemptsCount = prefs.createFlowPreference( + key = "submission.auto.attempts.count", + defaultValue = 0 + ) + + val autoSubmissionAttemptsLast = prefs.createFlowPreference( + key = "submission.auto.attempts.last", + reader = { key -> + Instant.ofEpochMilli(getLong(key, 0L)) + }, + writer = { key, value -> + putLong(key, value.millis) + } + ) + fun clear() { prefs.clearAndNotify() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTask.kt deleted file mode 100644 index 1511a337ea20b57b3c9d09fd67349d4e80e95c7b..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTask.kt +++ /dev/null @@ -1,126 +0,0 @@ -package de.rki.coronawarnapp.submission - -import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey -import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException -import de.rki.coronawarnapp.notification.TestResultNotificationService -import de.rki.coronawarnapp.playbook.Playbook -import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage -import de.rki.coronawarnapp.task.Task -import de.rki.coronawarnapp.task.TaskCancellationException -import de.rki.coronawarnapp.task.TaskFactory -import de.rki.coronawarnapp.task.common.DefaultProgress -import de.rki.coronawarnapp.worker.BackgroundWorkScheduler -import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.first -import org.joda.time.Duration -import timber.log.Timber -import javax.inject.Inject -import javax.inject.Provider - -class SubmissionTask @Inject constructor( - private val playbook: Playbook, - private val appConfigProvider: AppConfigProvider, - private val tekHistoryCalculations: ExposureKeyHistoryCalculations, - private val tekHistoryStorage: TEKHistoryStorage, - private val submissionSettings: SubmissionSettings, - private val testResultNotificationService: TestResultNotificationService -) : Task<DefaultProgress, Task.Result> { - - private val internalProgress = ConflatedBroadcastChannel<DefaultProgress>() - override val progress: Flow<DefaultProgress> = internalProgress.asFlow() - - private var isCanceled = false - - override suspend fun run(arguments: Task.Arguments) = try { - Timber.tag(TAG).d("Running with arguments=%s", arguments) - - val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() - Timber.tag(TAG).d("Using registrationToken=$registrationToken") - - val keys: List<TemporaryExposureKey> = tekHistoryStorage.tekData.first().flatMap { it.keys } - val symptoms: Symptoms = submissionSettings.symptoms.value ?: Symptoms.NO_INFO_GIVEN - - val transformedKeys = tekHistoryCalculations.transformToKeyHistoryInExternalFormat( - keys, - symptoms - ) - Timber.tag(TAG).d("Transformed keys with symptoms %s from %s to %s", symptoms, keys, transformedKeys) - - val submissionData = Playbook.SubmissionData( - registrationToken, - transformedKeys, - true, - getSupportedCountries() - ) - - checkCancel() - - Timber.tag(TAG).d("Submitting %s", submissionData) - playbook.submit(submissionData) - - Timber.tag(TAG).d("Submission successful, deleting submission data.") - tekHistoryStorage.clear() - submissionSettings.symptoms.update { null } - - // TODO re-evaluate the necessity of this behavior - BackgroundWorkScheduler.stopWorkScheduler() - LocalData.numberOfSuccessfulSubmissions(1) - - testResultNotificationService.cancelPositiveTestResultNotification() - - object : Task.Result {} - } catch (error: Exception) { - Timber.tag(TAG).e(error) - throw error - } finally { - Timber.i("Finished (isCanceled=$isCanceled).") - internalProgress.close() - } - - private suspend fun getSupportedCountries(): List<String> { - val countries = appConfigProvider.getAppConfig().supportedCountries - return when { - countries.isEmpty() -> { - Timber.w("Country list was empty, corrected") - listOf(FALLBACK_COUNTRY) - } - else -> countries - }.also { Timber.i("Supported countries = $it") } - } - - private fun checkCancel() { - if (isCanceled) throw TaskCancellationException() - } - - override suspend fun cancel() { - Timber.w("cancel() called.") - isCanceled = true - } - - data class Config( - override val executionTimeout: Duration = Duration.standardMinutes(8), // TODO unit-test that not > 9 min - - override val collisionBehavior: TaskFactory.Config.CollisionBehavior = - TaskFactory.Config.CollisionBehavior.ENQUEUE - - ) : TaskFactory.Config - - class Factory @Inject constructor( - private val taskByDagger: Provider<SubmissionTask> - ) : TaskFactory<DefaultProgress, Task.Result> { - - override suspend fun createConfig(): TaskFactory.Config = Config() - override val taskProvider: () -> Task<DefaultProgress, Task.Result> = { - taskByDagger.get() - } - } - - companion object { - private const val FALLBACK_COUNTRY = "DE" - private val TAG: String? = SubmissionTask::class.simpleName - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/auto/AutoSubmission.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/auto/AutoSubmission.kt new file mode 100644 index 0000000000000000000000000000000000000000..648e7f028490c1133befbf688a39bdaa72e048bc --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/auto/AutoSubmission.kt @@ -0,0 +1,141 @@ +package de.rki.coronawarnapp.submission.auto + +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.submission.task.SubmissionTask +import de.rki.coronawarnapp.task.TaskController +import de.rki.coronawarnapp.task.TaskInfo +import de.rki.coronawarnapp.task.common.DefaultTaskRequest +import de.rki.coronawarnapp.task.submitBlocking +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.worker.BackgroundConstants +import kotlinx.coroutines.flow.map +import org.joda.time.Instant +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AutoSubmission @Inject constructor( + private val timeStamper: TimeStamper, + private val submissionSettings: SubmissionSettings, + private val workManager: WorkManager, + private val taskController: TaskController +) { + + private fun List<TaskInfo>.isSubmissionTaskRunning() = any { + it.taskState.isActive && it.taskState.request.type == SubmissionTask::class + } + + val isSubmissionRunning = taskController.tasks.map { it.isSubmissionTaskRunning() } + + fun setup() { + Timber.tag(TAG).v("setup()") + + if (submissionSettings.autoSubmissionEnabled.value) { + Timber.tag(TAG).i("Fresh app start and auto submission is enabled, updating mode.") + + updateMode(Mode.SUBMIT_ASAP) + } else { + Timber.tag(TAG).d("AutoSubmission is disabled.") + } + } + + fun updateMode(newMode: Mode) { + Timber.tag(TAG).i("updateMode(mode=$newMode)") + + when (newMode) { + Mode.DISABLED -> disableAutoSubmission() + Mode.MONITOR -> enableAutoSubmission(lastActivity = timeStamper.nowUTC) + Mode.SUBMIT_ASAP -> enableAutoSubmission(lastActivity = Instant.EPOCH) + } + } + + suspend fun runSubmissionNow() { + Timber.tag(TAG).i("runSubmissionNow()") + + val result = taskController.submitBlocking( + DefaultTaskRequest( + type = SubmissionTask::class, + arguments = SubmissionTask.Arguments(checkUserActivity = false), + originTag = TAG + ) + ) + if (result.isSuccessful) { + Timber.tag(TAG).i("Blocking submission was successful.") + } else { + Timber.tag(TAG).w(result.error, "Blocking submission was not successful, enabling auto submission.") + updateMode(Mode.SUBMIT_ASAP) + } + } + + private fun scheduleWorker() { + Timber.tag(TAG).v("scheduleWorker(): Creating periodic worker request for submission.") + + val request = PeriodicWorkRequestBuilder<SubmissionWorker>(15, TimeUnit.MINUTES).apply { + addTag(AUTOSUBMISSION_WORKER_TAG) + setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BackgroundConstants.BACKOFF_INITIAL_DELAY, TimeUnit.MINUTES) + setConstraints( + Constraints.Builder().apply { + setRequiredNetworkType(NetworkType.CONNECTED) + }.build() + ) + }.build() + + workManager.enqueueUniquePeriodicWork(AUTOSUBMISSION_WORKER_TAG, ExistingPeriodicWorkPolicy.KEEP, request) + } + + private fun enableAutoSubmission(lastActivity: Instant) { + submissionSettings.apply { + // Setting last activity to EPOCH will skip the user activity period. + lastSubmissionUserActivityUTC.update { lastActivity } + + // Will trigger worker scheduling + autoSubmissionEnabled.update { true } + } + + scheduleWorker() + } + + private fun disableAutoSubmission() { + Timber.tag(TAG).v("disableAutoSubmission()") + workManager.cancelAllWorkByTag(AUTOSUBMISSION_WORKER_TAG) + + submissionSettings.apply { + autoSubmissionEnabled.update { false } + lastSubmissionUserActivityUTC.update { Instant.EPOCH } + autoSubmissionAttemptsCount.update { 0 } + autoSubmissionAttemptsLast.update { Instant.EPOCH } + } + } + + /** + * User is still active in the submission flow, causes the submission task to skip if within timeout. + */ + fun updateLastSubmissionUserActivity() { + Timber.tag(TAG).d("updateLastSubmissionUserActivity()") + submissionSettings.lastSubmissionUserActivityUTC.update { timeStamper.nowUTC } + } + + enum class Mode { + // Default, no data, no submission. + DISABLED, + + // Data is available, but user may not be finished, submit on timeout + MONITOR, + + // Data is available, assume user finished, try async submission ASAP, reset user activity timeout. + SUBMIT_ASAP + } + + companion object { + private const val AUTOSUBMISSION_WORKER_TAG = "AutoSubmissionWorker" + private const val TAG = "AutoSubmission" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/auto/SubmissionWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/auto/SubmissionWorker.kt new file mode 100644 index 0000000000000000000000000000000000000000..40746db268446f6b307c2b0bace487e30b580a74 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/auto/SubmissionWorker.kt @@ -0,0 +1,51 @@ +package de.rki.coronawarnapp.submission.auto + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.exception.ExceptionCategory +import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.submission.task.SubmissionTask +import de.rki.coronawarnapp.task.TaskController +import de.rki.coronawarnapp.task.TaskFactory +import de.rki.coronawarnapp.task.common.DefaultTaskRequest +import de.rki.coronawarnapp.task.submitBlocking +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory +import timber.log.Timber + +class SubmissionWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters, + private val taskController: TaskController +) : CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result = try { + Timber.tag(TAG).d("Attempting background submission of TEKs.") + + val result = taskController.submitBlocking( + DefaultTaskRequest( + type = SubmissionTask::class, + arguments = SubmissionTask.Arguments(checkUserActivity = true), + errorHandling = TaskFactory.Config.ErrorHandling.SILENT, + originTag = TAG + ) + ) + result.error?.let { throw it } + + Timber.tag(TAG).d("Submission task completed with: %s", result.result) + Result.success() + } catch (e: Exception) { + Timber.tag(TAG).e(e, "TEK submission failed.") + e.report(ExceptionCategory.HTTP, prefix = "TEK Submission failed.") + Result.retry() + } + + @AssistedInject.Factory + interface Factory : InjectedWorkerFactory<SubmissionWorker> + + companion object { + private const val TAG = "SubmissionWorker" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVector.kt similarity index 54% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVector.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVector.kt index 825fa48362bd491d325a78e16a2ff61723c62726..f5cdfb882cc710976e344830317e668fc7cc346d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVector.kt @@ -1,3 +1,3 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task typealias DaysSinceOnsetOfSymptomsVector = IntArray diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVectorDeterminator.kt similarity index 95% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminator.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVectorDeterminator.kt index f37199188d60f902be1cd58ae7dab5a976f570f0..2c4c71c20a31d320b3614f8e42fc80081e3364c2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminator.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVectorDeterminator.kt @@ -1,6 +1,7 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import dagger.Reusable +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.util.TimeAndDateExtensions.ageInDays import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDate import de.rki.coronawarnapp.util.TimeStamper diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DefaultKeyConverter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DefaultKeyConverter.kt similarity index 95% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DefaultKeyConverter.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DefaultKeyConverter.kt index 61155b62eb49dfb6e855960cb25c666236b97d5b..5c58a8cbfdab61a15010b920a8a92d4feb895c0a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DefaultKeyConverter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/DefaultKeyConverter.kt @@ -1,9 +1,9 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.protobuf.ByteString -import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass import dagger.Reusable +import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass import javax.inject.Inject @Reusable diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculations.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/ExposureKeyHistoryCalculations.kt similarity index 97% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculations.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/ExposureKeyHistoryCalculations.kt index c368c0714a9da96e3ed7c479458d88c75c77215f..c016532b1827dc8f659ec31823994fd805ed34d5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculations.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/ExposureKeyHistoryCalculations.kt @@ -1,8 +1,9 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import androidx.annotation.VisibleForTesting import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.util.TimeAndDateExtensions.ageInDays import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDate import de.rki.coronawarnapp.util.TimeStamper diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/KeyConverter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/KeyConverter.kt similarity index 90% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/KeyConverter.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/KeyConverter.kt index 131ebd905a7b97e698f9600245c518252d154a71..99be698c6742678fe420b9382eb76e8a9630e548 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/KeyConverter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/KeyConverter.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..7b0deee24ba9b006478b63ad034bcd1c6627dcaf --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt @@ -0,0 +1,216 @@ +package de.rki.coronawarnapp.submission.task + +import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException +import de.rki.coronawarnapp.notification.TestResultNotificationService +import de.rki.coronawarnapp.playbook.Playbook +import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission +import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage +import de.rki.coronawarnapp.task.Task +import de.rki.coronawarnapp.task.TaskCancellationException +import de.rki.coronawarnapp.task.TaskFactory +import de.rki.coronawarnapp.task.common.DefaultProgress +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.worker.BackgroundWorkScheduler +import kotlinx.coroutines.channels.ConflatedBroadcastChannel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.first +import org.joda.time.Duration +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Provider + +class SubmissionTask @Inject constructor( + private val playbook: Playbook, + private val appConfigProvider: AppConfigProvider, + private val tekHistoryCalculations: ExposureKeyHistoryCalculations, + private val tekHistoryStorage: TEKHistoryStorage, + private val submissionSettings: SubmissionSettings, + private val autoSubmission: AutoSubmission, + private val timeStamper: TimeStamper, + private val testResultNotificationService: TestResultNotificationService +) : Task<DefaultProgress, SubmissionTask.Result> { + + private val internalProgress = ConflatedBroadcastChannel<DefaultProgress>() + override val progress: Flow<DefaultProgress> = internalProgress.asFlow() + + private var isCanceled = false + + override suspend fun run(arguments: Task.Arguments): Result { + try { + Timber.tag(TAG).d("Running with arguments=%s", arguments) + arguments as Arguments + + if (arguments.checkUserActivity && hasRecentUserActivity()) { + Timber.tag(TAG).w("User has recently been active in submission, skipping submission.") + return Result(state = Result.State.SKIPPED) + } + + if (!submissionSettings.hasGivenConsent.value) { + Timber.tag(TAG).w("Consent unavailable. Skipping execution, disabling auto submission.") + autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) + return Result(state = Result.State.SKIPPED) + } + + if (hasExceededRetryAttempts()) { + throw IllegalStateException("Submission task retry limit exceeded") + } + + Timber.tag(TAG).i("Proceeding with submission.") + return performSubmission() + } catch (error: Exception) { + Timber.tag(TAG).e(error, "TEK submission failed.") + throw error + } finally { + Timber.i("Finished (isCanceled=$isCanceled).") + internalProgress.close() + } + } + + private fun hasRecentUserActivity(): Boolean { + val nowUTC = timeStamper.nowUTC + val lastUserActivity = submissionSettings.lastSubmissionUserActivityUTC.value + val userInactivity = Duration(lastUserActivity, nowUTC) + Timber.tag(TAG).d( + "now=%s, lastUserActivity=%s, userInactivity=%dmin", + nowUTC, + lastUserActivity, + userInactivity.standardMinutes + ) + + return userInactivity.millis >= 0 && userInactivity < USER_INACTIVITY_TIMEOUT + } + + private fun hasExceededRetryAttempts(): Boolean { + val currentAttempt = submissionSettings.autoSubmissionAttemptsCount.value + val lastAttemptAt = submissionSettings.autoSubmissionAttemptsLast.value + Timber.tag(TAG).i( + "checkRetryAttempts(): submissionAttemptsCount=%d, lastAttemptAt=%s", currentAttempt, lastAttemptAt + ) + + return if (currentAttempt >= RETRY_ATTEMPTS) { + Timber.tag(TAG).e("We have execeed our submission attempts, restoring positive test state.") + autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) + true + } else { + Timber.tag(TAG).d("Within the attempts limit, continuing.") + submissionSettings.autoSubmissionAttemptsCount.update { it + 1 } + submissionSettings.autoSubmissionAttemptsLast.update { timeStamper.nowUTC } + false + } + } + + private suspend fun performSubmission(): Result { + val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() + Timber.tag(TAG).d("Using registrationToken=$registrationToken") + + val keys: List<TemporaryExposureKey> = try { + tekHistoryStorage.tekData.first().flatMap { it.keys } + } catch (e: NoSuchElementException) { + Timber.tag(TAG).e(e, "No TEKs available, aborting.") + autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) + throw e + } + + val symptoms: Symptoms = submissionSettings.symptoms.value ?: Symptoms.NO_INFO_GIVEN + + val transformedKeys = tekHistoryCalculations.transformToKeyHistoryInExternalFormat( + keys, + symptoms + ) + Timber.tag(TAG).d("Transformed keys with symptoms %s from %s to %s", symptoms, keys, transformedKeys) + + val submissionData = Playbook.SubmissionData( + registrationToken, + transformedKeys, + true, + getSupportedCountries() + ) + + checkCancel() + + Timber.tag(TAG).d("Submitting %s", submissionData) + playbook.submit(submissionData) + + Timber.tag(TAG).d("Submission successful, deleting submission data.") + tekHistoryStorage.clear() + submissionSettings.symptoms.update { null } + + autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) + + setSubmissionFinished() + + return Result(state = Result.State.SUCCESSFUL) + } + + private fun setSubmissionFinished() { + Timber.tag(TAG).d("setSubmissionFinished()") + BackgroundWorkScheduler.stopWorkScheduler() + LocalData.numberOfSuccessfulSubmissions(1) + + testResultNotificationService.cancelPositiveTestResultNotification() + } + + data class Arguments( + val checkUserActivity: Boolean = false + ) : Task.Arguments + + data class Result( + val state: State + ) : Task.Result { + enum class State { + SUCCESSFUL, + SKIPPED + } + } + + private suspend fun getSupportedCountries(): List<String> { + val countries = appConfigProvider.getAppConfig().supportedCountries + return when { + countries.isEmpty() -> { + Timber.w("Country list was empty, corrected") + listOf(FALLBACK_COUNTRY) + } + else -> countries + }.also { Timber.i("Supported countries = $it") } + } + + private fun checkCancel() { + if (isCanceled) throw TaskCancellationException() + } + + override suspend fun cancel() { + Timber.w("cancel() called.") + isCanceled = true + } + + data class Config( + override val executionTimeout: Duration = Duration.standardMinutes(8), // TODO unit-test that not > 9 min + + override val collisionBehavior: TaskFactory.Config.CollisionBehavior = + TaskFactory.Config.CollisionBehavior.ENQUEUE + + ) : TaskFactory.Config + + class Factory @Inject constructor( + private val taskByDagger: Provider<SubmissionTask> + ) : TaskFactory<DefaultProgress, Task.Result> { + + override suspend fun createConfig(): TaskFactory.Config = Config() + override val taskProvider: () -> Task<DefaultProgress, Task.Result> = { + taskByDagger.get() + } + } + + companion object { + private const val FALLBACK_COUNTRY = "DE" + private const val RETRY_ATTEMPTS = Int.MAX_VALUE + private val USER_INACTIVITY_TIMEOUT = Duration.standardMinutes(30) + private const val TAG: String = "SubmissionTask" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTaskModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTaskModule.kt similarity index 91% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTaskModule.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTaskModule.kt index ed52d5cb0dde238271716249f33d1efb50d1b338..59f90df414729f5c7e101bc305d0c063839fbcbc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTaskModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTaskModule.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import dagger.Binds import dagger.Module diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVector.kt similarity index 87% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVector.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVector.kt index f3a09fff76bdb0ce63e4207ba388050b7cfe795a..7626ed16c93d32b230512fd33947e015f3f52d1d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVector.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task class TransmissionRiskVector(private val values: IntArray) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVectorDeterminator.kt similarity index 97% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVectorDeterminator.kt index f3b45ad55d98aabc9b24a01744c74146c12af42b..483d6b1f9b5e0d967fd146aeb60e7a04cf4d4d77 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVectorDeterminator.kt @@ -1,7 +1,8 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import dagger.Reusable import de.rki.coronawarnapp.bugreporting.reportProblem +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.submission.Symptoms.Indication import de.rki.coronawarnapp.submission.Symptoms.StartOf import de.rki.coronawarnapp.util.TimeAndDateExtensions.ageInDays diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt index 306c051f8bf4ab983c3a15e8a0fbe3b3c84180ca..cbb701de15b06a0af099a671f6b967659fc2f5fd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.submission.ui.homecards import dagger.Reusable import de.rki.coronawarnapp.exception.http.CwaServerError import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskController.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskController.kt index fe2c288a07b0de8a74ae7476549c647e230ce225..1d6330132a26f4e5d40bb8cfff4552916e9ff133 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskController.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskController.kt @@ -166,7 +166,8 @@ class TaskController @Inject constructor( state.job.getCompleted() } else { Timber.tag(TAG).e(error, "Task failed: %s", state) - if (state.config.errorHandling == TaskFactory.Config.ErrorHandling.ALERT) { + val errorHandling = state.request.errorHandling ?: state.config.errorHandling + if (errorHandling == TaskFactory.Config.ErrorHandling.ALERT) { error.report(ExceptionCategory.INTERNAL) } error.reportProblem(tag = state.request.type.simpleName) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskRequest.kt index a797579410c4ba88b7b75073cacd1153cad19728..b32fee7b91b879aa20a0092761138ccf58439078 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskRequest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/TaskRequest.kt @@ -7,4 +7,5 @@ interface TaskRequest { val id: UUID val type: KClass<out Task<Task.Progress, Task.Result>> val arguments: Task.Arguments + val errorHandling: TaskFactory.Config.ErrorHandling? } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt index e34681d7755b32565a6865a90c69a688e2184391..be859ab2a43299b15817133a8a43cfe00e886b6c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.task.common import de.rki.coronawarnapp.task.Task +import de.rki.coronawarnapp.task.TaskFactory import de.rki.coronawarnapp.task.TaskRequest import java.util.UUID import kotlin.reflect.KClass @@ -9,7 +10,8 @@ data class DefaultTaskRequest( override val type: KClass<out Task<Task.Progress, Task.Result>>, override val arguments: Task.Arguments = object : Task.Arguments {}, override val id: UUID = UUID.randomUUID(), - val originTag: String? = null + val originTag: String? = null, + override val errorHandling: TaskFactory.Config.ErrorHandling? = null ) : TaskRequest { fun toNewTask(): DefaultTaskRequest = copy(id = UUID.randomUUID()) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index eaf40e18e733062bc5c28aad903e887831d0c24d..a279229c686d324a967d52e425b08b57024f3cfd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -9,8 +9,8 @@ import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.TestResultNotificationService import de.rki.coronawarnapp.risk.TimeVariables import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.TracingRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.FetchingResult import de.rki.coronawarnapp.submission.ui.homecards.NoTest import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt index e6412428b040247edea21f0f041fe1aab6928395..d7b3622c569b77d0d7f3f833e09d9d2def173481 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt @@ -6,7 +6,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.notification.TestResultNotificationService -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.SingleLiveEvent import de.rki.coronawarnapp.util.DataReset import de.rki.coronawarnapp.util.coroutine.DispatcherProvider diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt index eeda799a51864941379e9cc3874db29745d4d999..d88c33332bcad1439ff1b5f4e3b48228fe88ffe9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.ui.submission.qrcode.consent import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt index 560f95873309d1c6935a0c1d5d6f17c56b505656..8ce0bd3342bc2ea9359326b5ea45a97aa07ea962 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt @@ -7,7 +7,7 @@ import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.service.submission.QRScanResult -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.ui.submission.ScanStatus import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt index 880901adc0a0915347802d800dfd2d368d2ad113..0fa61081e177ce16a2f72c62af52d432083e3c04 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt @@ -8,7 +8,8 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater import de.rki.coronawarnapp.ui.SingleLiveEvent import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -20,7 +21,8 @@ import timber.log.Timber class SubmissionTestResultAvailableViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory, - submissionRepository: SubmissionRepository + submissionRepository: SubmissionRepository, + private val autoSubmission: AutoSubmission ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val routeToScreen = SingleLiveEvent<NavDirections>() @@ -33,6 +35,9 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor( private val tekHistoryUpdater = tekHistoryUpdaterFactory.create(object : TEKHistoryUpdater.Callback { override fun onTEKAvailable(teks: List<TemporaryExposureKey>) { + Timber.d("onTEKAvailable(teks.size=%d)", teks.size) + autoSubmission.updateMode(AutoSubmission.Mode.MONITOR) + routeToScreen.postValue( SubmissionTestResultAvailableFragmentDirections .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultConsentGivenFragment() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyFragment.kt index 690593a7b7875b1fea5fec93e052d720ec355185..92f10cc9a535593ceac5c95e78c3467cad058724 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyFragment.kt @@ -61,6 +61,7 @@ class SubmissionResultReadyFragment : Fragment(R.layout.fragment_submission_resu override fun onResume() { super.onResume() + viewModel.onNewUserActivity() binding.submissionDoneNoConsentContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) } @@ -78,7 +79,7 @@ class SubmissionResultReadyFragment : Fragment(R.layout.fragment_submission_resu private fun onConfirmSkipSymptomsInput() { SubmissionCancelDialog(requireContext()).show { - viewModel.onSkipSymptomInput() + viewModel.onSkipSymptomsConfirmed() } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyViewModel.kt index 0e335b5f5f68881f6dfff04c8611d9a2a931dfbb..6bdeb9959c75460666cd76bdf5938036d9c8fd5b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultready/SubmissionResultReadyViewModel.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.ui.submission.resultready import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -11,11 +11,11 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import timber.log.Timber class SubmissionResultReadyViewModel @AssistedInject constructor( - private val submissionRepository: SubmissionRepository, + private val autoSubmission: AutoSubmission, dispatcherProvider: DispatcherProvider ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val showUploadDialog = submissionRepository.isSubmissionRunning + val showUploadDialog = autoSubmission.isSubmissionRunning .asLiveData(context = dispatcherProvider.Default) val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent() @@ -23,19 +23,24 @@ class SubmissionResultReadyViewModel @AssistedInject constructor( routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSymptomIntroduction) } - fun onSkipSymptomInput() { - Timber.d("Symptom submission was cancelled.") + fun onSkipSymptomsConfirmed() { + Timber.d("Symptom submission was skipped.") launch { try { - submissionRepository.startSubmission() + autoSubmission.runSubmissionNow() } catch (e: Exception) { - Timber.e(e, "onCancelConfirmed() failed.") + Timber.e(e, "greenlightSubmission() failed.") } finally { routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity) } } } + fun onNewUserActivity() { + Timber.d("onNewUserActivity()") + autoSubmission.updateLastSubmissionUserActivity() + } + @AssistedInject.Factory interface Factory : SimpleCWAViewModelFactory<SubmissionResultReadyViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt index 3272ce3beff8e71b999b08879a825abf6baf1b96..3e8aa519347dcde3e817bfd6a15b3ff9e840126b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt @@ -117,4 +117,9 @@ class SubmissionSymptomCalendarFragment : Fragment(R.layout.fragment_submission_ } } } + + override fun onResume() { + super.onResume() + viewModel.onNewUserActivity() + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt index 4c99371b8e383d299b4cf03db78bbd16628ffef5..e8fc814d481acadd48da09bd35ed1b9186be30de 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt @@ -5,8 +5,9 @@ import androidx.navigation.NavDirections import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.bugreporting.reportProblem -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -18,7 +19,8 @@ import timber.log.Timber class SubmissionSymptomCalendarViewModel @AssistedInject constructor( @Assisted val symptomIndication: Symptoms.Indication, dispatcherProvider: DispatcherProvider, - private val submissionRepository: SubmissionRepository + private val submissionRepository: SubmissionRepository, + private val autoSubmission: AutoSubmission ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { private val symptomStartInternal = MutableStateFlow<Symptoms.StartOf?>(null) @@ -26,7 +28,7 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( val routeToScreen = SingleLiveEvent<NavDirections>() val showCancelDialog = SingleLiveEvent<Unit>() - val showUploadDialog = submissionRepository.isSubmissionRunning + val showUploadDialog = autoSubmission.isSubmissionRunning .asLiveData(context = dispatcherProvider.Default) fun onLastSevenDaysStart() { @@ -81,7 +83,7 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( private fun performSubmission() { launch { try { - submissionRepository.startSubmission() + autoSubmission.runSubmissionNow() } catch (e: Exception) { Timber.tag(TAG).e(e, "performSubmission() failed.") } finally { @@ -92,6 +94,11 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( } } + fun onNewUserActivity() { + Timber.d("onNewUserActivity()") + autoSubmission.updateLastSubmissionUserActivity() + } + @AssistedInject.Factory interface Factory : CWAViewModelFactory<SubmissionSymptomCalendarViewModel> { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt index 89b4d135682be4ae49464fbd0442b376f61004aa..849f519a166cc336c518914ef03ce0b88bcfb763 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt @@ -94,4 +94,9 @@ class SubmissionSymptomIntroductionFragment : Fragment(R.layout.fragment_submiss setOnClickListener { viewModel.onNextClicked() } } } + + override fun onResume() { + super.onResume() + viewModel.onNewUserActivity() + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt index a94ebdcd122fecb2cd1d4c4c6772b20e3a0ad405..363e91b4b883c1d8c717e4994e9df2d7fa7b8a64 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt @@ -3,8 +3,9 @@ package de.rki.coronawarnapp.ui.submission.symptoms.introduction import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -14,7 +15,8 @@ import timber.log.Timber class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, - private val submissionRepository: SubmissionRepository + private val submissionRepository: SubmissionRepository, + private val autoSubmission: AutoSubmission ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { private val symptomIndicationInternal = MutableStateFlow<Symptoms.Indication?>(null) @@ -23,7 +25,7 @@ class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( val navigation = SingleLiveEvent<NavDirections>() val showCancelDialog = SingleLiveEvent<Unit>() - val showUploadDialog = submissionRepository.isSubmissionRunning + val showUploadDialog = autoSubmission.isSubmissionRunning .asLiveData(context = dispatcherProvider.Default) fun onNextClicked() { @@ -80,7 +82,7 @@ class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( private fun doSubmit() { launch { try { - submissionRepository.startSubmission() + autoSubmission.runSubmissionNow() } catch (e: Exception) { Timber.e(e, "doSubmit() failed.") } finally { @@ -92,6 +94,11 @@ class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( } } + fun onNewUserActivity() { + Timber.d("onNewUserActivity()") + autoSubmission.updateLastSubmissionUserActivity() + } + @AssistedInject.Factory interface Factory : SimpleCWAViewModelFactory<SubmissionSymptomIntroductionViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt index 365fbb98c9029420a59935c976bfc39c6bd14583..86d569dda52f00215fdd75863813065c96bd367b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt @@ -7,7 +7,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt index aa0ac313c7e6fcdc84e7c15015808a61d2b99639..cea45a1c5b8c81fb79d87fa29d16848a04ee3ae6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.flow.combine diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt index e3473683655ddae03f649a38f67908803ed04e25..59ce8eb6ebe99856343346b881c84f45bed35437 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.flow.combine diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt index 701a113e7f553b71deccdc0e9a347fe923c5f9af..6149c922a10258b789653dfe864f22178685d770 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.notification.TestResultNotificationService -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt index c29f404fee16592253b2b21811b764e8f9464e23..edc8e35a592dd593e134177160442c4b7b81676a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt @@ -77,6 +77,7 @@ class SubmissionTestResultConsentGivenFragment : Fragment(R.layout.fragment_subm override fun onResume() { super.onResume() binding.submissionTestResultContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) + viewModel.onNewUserActivity() } private fun setButtonOnClickListener() { @@ -98,7 +99,7 @@ class SubmissionTestResultConsentGivenFragment : Fragment(R.layout.fragment_subm setTitle(R.string.submission_error_dialog_confirm_cancellation_title) setMessage(R.string.submission_error_dialog_confirm_cancellation_body) setPositiveButton(R.string.submission_error_dialog_confirm_cancellation_button_positive) { _, _ -> - viewModel.cancelTestSubmission() + viewModel.onCancelConfirmed() } setNegativeButton(R.string.submission_error_dialog_confirm_cancellation_button_negative) { _, _ -> // NOOP diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt index 03911bf9138241f22194958cb5ea9e17d4973129..723f7a0cda2580de46f0aaecf29b0bef987bb1ca 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt @@ -3,7 +3,8 @@ package de.rki.coronawarnapp.ui.submission.testresult.positive import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -16,10 +17,11 @@ import timber.log.Timber class SubmissionTestResultConsentGivenViewModel @AssistedInject constructor( private val submissionRepository: SubmissionRepository, + private val autoSubmission: AutoSubmission, dispatcherProvider: DispatcherProvider ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val showUploadDialog = submissionRepository.isSubmissionRunning + val showUploadDialog = autoSubmission.isSubmissionRunning .asLiveData(context = dispatcherProvider.Default) val uiState: LiveData<TestResultUIState> = combine( @@ -49,18 +51,23 @@ class SubmissionTestResultConsentGivenViewModel @AssistedInject constructor( showCancelDialog.postValue(Unit) } - fun cancelTestSubmission() { + fun onCancelConfirmed() { launch { try { - submissionRepository.startSubmission() + autoSubmission.runSubmissionNow() } catch (e: Exception) { - Timber.e(e, "cancelTestSubmission() failed.") + Timber.e(e, "onCancelConfirmed() failed.") } finally { routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity) } } } + fun onNewUserActivity() { + Timber.d("onNewUserActivity()") + autoSubmission.updateLastSubmissionUserActivity() + } + @AssistedInject.Factory interface Factory : SimpleCWAViewModelFactory<SubmissionTestResultConsentGivenViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt index 4e639d566327976e1bf6ac3ec8756e5215093951..33b1aaa7f02df217740a9a7d64addfbb407bafbe 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.ui.submission.testresult.positive import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.util.flow.combine import de.rki.coronawarnapp.util.viewmodel.CWAViewModel diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt index bd5ff6c8c211a58e00550e0c4788445fcec6e05f..c0244d398feb2713db69050a3afa9a62ab95f3da 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt @@ -10,6 +10,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -21,6 +22,7 @@ import timber.log.Timber class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val enfClient: ENFClient, + private val autoSubmission: AutoSubmission, tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory, interoperabilityRepository: InteroperabilityRepository ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { @@ -38,6 +40,9 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con private val tekHistoryUpdater = tekHistoryUpdaterFactory.create(object : TEKHistoryUpdater.Callback { override fun onTEKAvailable(teks: List<TemporaryExposureKey>) { + Timber.d("onTEKAvailable(tek.size=%d)", teks.size) + autoSubmission.updateMode(AutoSubmission.Mode.MONITOR) + routeToScreen.postValue( SubmissionResultPositiveOtherWarningNoConsentFragmentDirections .actionSubmissionResultPositiveOtherWarningNoConsentFragmentToSubmissionResultReadyFragment() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt index a0fce136fe88b66f72b7cda8c6b4d4693f77ea2a..a55d1c049dc6e5ab0f5e8b0e720f0c1adfdb7d84 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.ui.submission.yourconsent import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.SingleLiveEvent import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.viewmodel.CWAViewModel diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt index 446f6d6da73fd89d1d1c0402d600ae84ee57253e..90689da5e9a7c9b60c8655997972b69f6eaa9f81 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt @@ -31,7 +31,7 @@ import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTra import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.storage.AppDatabase import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.security.SecurityHelper import kotlinx.coroutines.sync.Mutex diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt index 77ed3ae0a374c276371fa1f29ce995a8b4e9c5d7..ba3270685ab4ef1d76c153c40f842d7b547cc0ed 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt @@ -23,7 +23,7 @@ import de.rki.coronawarnapp.receiver.ReceiverBinder import de.rki.coronawarnapp.risk.RiskModule import de.rki.coronawarnapp.service.ServiceBinder import de.rki.coronawarnapp.submission.SubmissionModule -import de.rki.coronawarnapp.submission.SubmissionTaskModule +import de.rki.coronawarnapp.submission.task.SubmissionTaskModule import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.internal.TaskModule import de.rki.coronawarnapp.test.DeviceForTestersModule diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LazyString.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LazyString.kt index 4fd37b49fb823679d637553e239d24b52361c3f0..548bd0b9abfb1c7b61c22cc9b702465dfb3e3fde 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LazyString.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LazyString.kt @@ -17,3 +17,7 @@ data class CachedString(val provider: (Context) -> String) : LazyString { fun String.toLazyString() = object : LazyString { override fun get(context: Context) = this@toLazyString } + +fun Int.toResolvingString(vararg formatArgs: Any): LazyString = object : LazyString { + override fun get(context: Context): String = context.getString(this@toResolvingString, *formatArgs) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt index 0a6bdf779804a17cd668e16cf24740570142c19a..8b46456b27c1def869e714c1eaedc68b8111346b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryRetentionWorker import de.rki.coronawarnapp.deadman.DeadmanNotificationOneTimeWorker import de.rki.coronawarnapp.deadman.DeadmanNotificationPeriodicWorker import de.rki.coronawarnapp.nearby.ExposureStateUpdateWorker +import de.rki.coronawarnapp.submission.auto.SubmissionWorker import de.rki.coronawarnapp.worker.BackgroundNoiseOneTimeWorker import de.rki.coronawarnapp.worker.BackgroundNoisePeriodicWorker import de.rki.coronawarnapp.worker.DiagnosisKeyRetrievalOneTimeWorker @@ -73,6 +74,13 @@ abstract class WorkerBinder { factory: DeadmanNotificationPeriodicWorker.Factory ): InjectedWorkerFactory<out ListenableWorker> + @Binds + @IntoMap + @WorkerKey(SubmissionWorker::class) + abstract fun submissionBackgroundWorker( + factory: SubmissionWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> + @Binds @IntoMap @WorkerKey(ContactDiaryRetentionWorker::class) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt index cc7b277a69444e7cb9dd6298fa1c612cf75a477a..f751980fdb460b3df59bb3e6e0f4bb2781e0919a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt @@ -73,7 +73,7 @@ object BackgroundWorkScheduler { fun startWorkScheduler() { val notificationBody = StringBuilder() notificationBody.append("Jobs starting: ") - if (LocalData.numberOfSuccessfulSubmissions() > 0) return + if (LocalData.submissionWasSuccessful()) return val isPeriodicWorkActive = isWorkActive(WorkTag.DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER.tag) logWorkActiveStatus( WorkTag.DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER.tag, diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_day_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_day_fragment.xml index 86f18b40f376b7b7f9549cbce8de5d4ab66e677f..ef861b939119c20f3506db228ecd86cf1a7fc3d6 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_day_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_day_fragment.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools"> <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/content_container" android:layout_width="match_parent" android:layout_height="match_parent" android:focusable="true"> @@ -12,6 +13,7 @@ android:id="@+id/contact_diary_day_header" android:layout_width="@dimen/match_constraint" android:layout_height="wrap_content" + android:background="@drawable/contact_diary_background" android:elevation="@dimen/elevation_weak" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml index 676b33aaded966c25aa479e9a6965550a44cf224..bf7ffdc7db5c8aa237b2b24a9533f286b7b6f837 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml @@ -15,7 +15,8 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" style="@style/CWAToolbar.Close" - app:title="@string/contact_diary_edit_locations_title" /> + app:title="@string/contact_diary_edit_locations_title" + android:contentDescription="@string/contact_diary_edit_locations_title"/> <androidx.constraintlayout.widget.Group android:id="@+id/contact_diary_location_list_no_items_group" diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml index b555a066468164c4527eef398461de1f13bae006..afbd27e68f0d259627979d7d1d50763af8850c4f 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml @@ -11,11 +11,12 @@ android:id="@+id/toolbar" android:layout_width="@dimen/match_constraint" android:layout_height="wrap_content" + style="@style/CWAToolbar.Close" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - style="@style/CWAToolbar.Close" - app:title="@string/contact_diary_edit_persons_title" /> + app:title="@string/contact_diary_edit_persons_title" + android:contentDescription="@string/contact_diary_edit_persons_title"/> <androidx.constraintlayout.widget.Group android:id="@+id/contact_diary_person_list_no_items_group" diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml index a23af27c787479f4a1af9f64bf61839fd59b6c72..7d4c67491adc0dffce5949f570376d0691192137 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml @@ -4,13 +4,14 @@ xmlns:tools="http://schemas.android.com/tools"> <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/contact_diary_location_list_item" style="@style/contactDiaryCardRipple" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="@dimen/spacing_huge"> <ImageView - android:id="@+id/contact_diary_location_list_line_icon" + android:id="@+id/contact_diary_location_list_item_icon" android:layout_width="@dimen/spacing_medium" android:layout_height="@dimen/spacing_medium" android:layout_marginStart="@dimen/spacing_small" @@ -20,7 +21,7 @@ tools:srcCompat="@drawable/ic_selected" /> <TextView - android:id="@+id/contact_diary_location_list_line_name" + android:id="@+id/contact_diary_location_list_item_name" style="@style/subtitle" android:layout_width="@dimen/match_constraint" android:layout_height="wrap_content" @@ -28,7 +29,7 @@ android:layout_marginVertical="@dimen/spacing_small" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/contact_diary_location_list_line_icon" + app:layout_constraintStart_toEndOf="@+id/contact_diary_location_list_item_icon" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml index 2a4ff782c3c9699fb9ec4d4c872dce3790e70af0..f84d209ab2070ecfa0ff417e97dab6f675901e39 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml @@ -13,13 +13,13 @@ android:id="@+id/toolbar" android:layout_width="@dimen/match_constraint" android:layout_height="wrap_content" + style="@style/CWAToolbar.BackArrow" android:background="@color/colorBackground" - app:popupTheme="@style/CWAToolbar.Menu" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:title="@string/contact_diary_overview_header" - style="@style/CWAToolbar.BackArrow"/> + app:popupTheme="@style/CWAToolbar.Menu" + app:title="@string/contact_diary_overview_header" /> <TextView android:id="@+id/onboarding_headline" diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_item.xml index 2abe8ea4c51bb6a4d418a53a2432a72df13e1971..856626b5998f21c605e2b14068ce642de42825c1 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_item.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_item.xml @@ -5,12 +5,13 @@ <androidx.constraintlayout.widget.ConstraintLayout style="@style/contactDiaryCardRipple" + android:id="@+id/contact_diary_person_list_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="@dimen/spacing_huge"> <ImageView - android:id="@+id/contact_diary_person_list_line_icon" + android:id="@+id/contact_diary_person_list_item_icon" android:layout_width="@dimen/spacing_medium" android:layout_height="@dimen/spacing_medium" android:layout_marginStart="@dimen/spacing_small" @@ -20,7 +21,7 @@ tools:srcCompat="@drawable/ic_selected" /> <TextView - android:id="@+id/contact_diary_person_list_line_name" + android:id="@+id/contact_diary_person_list_item_name" style="@style/subtitle" android:layout_width="@dimen/match_constraint" android:layout_height="wrap_content" @@ -28,7 +29,7 @@ android:layout_marginVertical="@dimen/spacing_small" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/contact_diary_person_list_line_icon" + app:layout_constraintStart_toEndOf="@+id/contact_diary_person_list_item_icon" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml index 80caa276221d1c6d04a8a76cb4950bf8595f4b25..4e87bc92be1a3735fb31dd58101e3396696a1777 100644 --- a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml @@ -98,4 +98,24 @@ <string name="contact_diary_delete_location_title">"Wollen Sie wirklich diesen Ort entfernen?"</string> <!-- XHED: Title for the contact diary dialog to delete delete a single person --> <string name="contact_diary_delete_person_title">"Wollen Sie wirklich diese Person entfernen?"</string> + + <!-- XTXT: location (description for screen readers) --> + <string name="accessibility_location">Ort %s</string> + <!-- XTXT: person (description for screen readers) --> + <string name="accessibility_person">Person %s</string> + <!-- XTXT: location is not selected (description for screen readers) --> + <string name="accessibility_location_unselected">Ort %s ist nicht ausgewählt</string> + <!-- XTXT: person is not selected (description for screen readers) --> + <string name="accessibility_person_unselected">Person %s ist nicht ausgewählt</string> + <!-- XTXT: location is selected (description for screen readers) --> + <string name="accessibility_location_selected">Ort %s ist ausgewählt</string> + <!-- XTXT: person is selected (description for screen readers) --> + <string name="accessibility_person_selected">Person %s ist ausgewählt</string> + + <!-- XTXT: Select (description for screen readers) --> + <string name="accessibility_action_select">auswählen</string> + <!-- XTXT: Deselect (description for screen readers) --> + <string name="accessibility_action_deselect">Auswahl aufheben</string> + <!-- XTXT: Edit (description for screen readers) --> + <string name="accessibility_edit">Bearbeiten</string> </resources> diff --git a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml index e87b2c52adbe648d0eb24deae7345ab22cc320c8..e474cb2a90ca0a031d81991e8f90320becf807e5 100644 --- a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml @@ -105,4 +105,25 @@ <string name="contact_diary_export_intro_one" translatable="false">"Kontakte der letzten 14 Tage (%1$s - %2$s)"</string> <!-- XTXT: Intro text for the contact journal email export part two--> <string name="contact_diary_export_intro_two" translatable="false">"Die nachfolgende Liste dient dem zuständigen Gesundheitsamt zur Kontaktnachverfolgung gem. § 25 IfSG."</string> + + <!-- XTXT: location (description for screen readers) --> + <string name="accessibility_location">Ort %s</string> + <!-- XTXT: person (description for screen readers) --> + <string name="accessibility_person">Person %s</string> + <!-- XTXT: location is not selected (description for screen readers) --> + <string name="accessibility_location_unselected">Ort %s ist nicht ausgewählt</string> + <!-- XTXT: person is not selected (description for screen readers) --> + <string name="accessibility_person_unselected">Person %s ist nicht ausgewählt</string> + <!-- XTXT: location is selected (description for screen readers) --> + <string name="accessibility_location_selected">Ort %s ist ausgewählt</string> + <!-- XTXT: person is selected (description for screen readers) --> + <string name="accessibility_person_selected">Person %s ist ausgewählt</string> + + <!-- XTXT: Select (description for screen readers) --> + <string name="accessibility_action_select">auswählen</string> + <!-- XTXT: Deselect (description for screen readers) --> + <string name="accessibility_action_deselect">Auswahl aufheben</string> + <!-- XTXT: Edit (description for screen readers) --> + <string name="accessibility_edit">Bearbeiten</string> + </resources> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryDateFormatterExtensionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryDateFormatterExtensionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..18a4cd7d5ca8dcde944edeb3c4287f7613078cf7 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryDateFormatterExtensionTest.kt @@ -0,0 +1,52 @@ +package de.rki.coronawarnapp.contactdiary.util + +import io.kotest.matchers.shouldBe +import org.joda.time.LocalDate +import org.junit.jupiter.api.Test +import java.util.Locale + +class ContactDiaryDateFormatterExtensionTest { + + @Test + fun `format bulgarian date`() { + LocalDate("2021-01-01").toFormattedDay( + Locale("bg", "BG") + ) shouldBe "петък, 1.01.21 г." + } + + @Test + fun `format german date`() { + LocalDate("2021-01-02").toFormattedDay(Locale.GERMANY) shouldBe "Samstag, 02.01.21" + } + + @Test + fun `format english (us) date`() { + LocalDate("2021-01-03").toFormattedDay(Locale.US) shouldBe "Sunday, 1/3/21" + } + + @Test + fun `format english (uk) date`() { + LocalDate("2021-01-03").toFormattedDay(Locale.UK) shouldBe "Sunday, 03/01/2021" + } + + @Test + fun `format polish date`() { + LocalDate("2021-01-04").toFormattedDay( + Locale("pl", "PL") + ) shouldBe "poniedziałek, 04.01.2021" + } + + @Test + fun `format romanian date`() { + LocalDate("2021-01-05").toFormattedDay( + Locale("ro", "RO") + ) shouldBe "marți, 05.01.2021" + } + + @Test + fun `format turkish date`() { + LocalDate("2021-01-06").toFormattedDay( + Locale("tr", "TR") + ) shouldBe "Çarşamba, 6.01.2021" + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensionsTest.kt index ee98a610d59b9994cbd3d652d0ca837c7bd877a9..c380d34f94e962168f623b5baab946790b7aab78 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensionsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/util/ContactDiaryExtensionsTest.kt @@ -21,13 +21,13 @@ class ContactDiaryExtensionsTest { val testList = listOf( DefaultContactDiaryPerson(1, "Max Mustermann"), DefaultContactDiaryPerson(2, "Erika Musterfrau"), - DefaultContactDiaryPerson(3, "erika musterfrau2"), + DefaultContactDiaryPerson(3, "erika musterfrau2") ) val expectedResult = listOf( DefaultContactDiaryPerson(2, "Erika Musterfrau"), DefaultContactDiaryPerson(3, "erika musterfrau2"), - DefaultContactDiaryPerson(1, "Max Mustermann"), + DefaultContactDiaryPerson(1, "Max Mustermann") ) // Test that lowercase "erika musterfrau2" is sorted to the 2nd position instead of the end @@ -39,13 +39,13 @@ class ContactDiaryExtensionsTest { val testList = listOf( DefaultContactDiaryPerson(1, "Max Mustermann"), DefaultContactDiaryPerson(3, "Erika Musterfrau"), - DefaultContactDiaryPerson(2, "Erika Musterfrau"), + DefaultContactDiaryPerson(2, "Erika Musterfrau") ) val expectedResult = listOf( DefaultContactDiaryPerson(2, "Erika Musterfrau"), DefaultContactDiaryPerson(3, "Erika Musterfrau"), - DefaultContactDiaryPerson(1, "Max Mustermann"), + DefaultContactDiaryPerson(1, "Max Mustermann") ) // Test that "Erika Musterfrau" with lower personId comes before the other one, even though it was @@ -58,13 +58,13 @@ class ContactDiaryExtensionsTest { val testList = listOf( DefaultContactDiaryLocation(1, "Berlin"), DefaultContactDiaryLocation(2, "At home"), - DefaultContactDiaryLocation(3, "at home"), + DefaultContactDiaryLocation(3, "at home") ) val expectedResult = listOf( DefaultContactDiaryLocation(2, "At home"), DefaultContactDiaryLocation(3, "at home"), - DefaultContactDiaryLocation(1, "Berlin"), + DefaultContactDiaryLocation(1, "Berlin") ) // Test that lowercase "at home" is sorted to the 2nd position instead of the end @@ -76,13 +76,13 @@ class ContactDiaryExtensionsTest { val testList = listOf( DefaultContactDiaryLocation(1, "Berlin"), DefaultContactDiaryLocation(3, "At home"), - DefaultContactDiaryLocation(2, "At home"), + DefaultContactDiaryLocation(2, "At home") ) val expectedResult = listOf( DefaultContactDiaryLocation(2, "At home"), DefaultContactDiaryLocation(3, "At home"), - DefaultContactDiaryLocation(1, "Berlin"), + DefaultContactDiaryLocation(1, "Berlin") ) // Test that "At home" with lower locationId comes before the other one, even though it was diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt index 8e8d7a49231b2588b597bc405f220e8570bc1dd8..6f8740a5e53a26c730e04e0a3a89b147fa7613b3 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt @@ -4,8 +4,8 @@ import android.content.Context import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.TestResultNotificationService -import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.TracingRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider import de.rki.coronawarnapp.tracing.GeneralTracingStatus @@ -22,7 +22,6 @@ import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.clearAllMocks import io.mockk.coEvery -import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt index c814341bd10a65af6efdfe97604eb0d29ec8de6b..e8094f0becd1b7150a6d8807e3d85f864206924f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.storage import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.service.submission.SubmissionService +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage @@ -88,7 +89,6 @@ class SubmissionRepositoryTest { submissionSettings = submissionSettings, submissionService = submissionService, timeStamper = timeStamper, - taskController = taskController, tekHistoryStorage = tekHistoryStorage ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/auto/AutoSubmissionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/auto/AutoSubmissionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ee3988f0ede1a1fd7cdb883d4944cea07c4fbb92 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/auto/AutoSubmissionTest.kt @@ -0,0 +1,196 @@ +package de.rki.coronawarnapp.submission.auto + +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.WorkManager +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.submission.task.SubmissionTask +import de.rki.coronawarnapp.task.TaskController +import de.rki.coronawarnapp.task.TaskRequest +import de.rki.coronawarnapp.task.TaskState +import de.rki.coronawarnapp.task.common.DefaultTaskRequest +import de.rki.coronawarnapp.task.submitBlocking +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.preferences.FlowPreference +import io.mockk.Called +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerifySequence +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.verify +import io.mockk.verifySequence +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runBlockingTest +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference + +class AutoSubmissionTest : BaseTest() { + + @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var submissionSettings: SubmissionSettings + @MockK lateinit var workManager: WorkManager + @MockK lateinit var taskController: TaskController + + private val autoSubmissionEnabled: FlowPreference<Boolean> = mockFlowPreference(false) + private val lastSubmissionUserActivityUTC: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH) + private val autoSubmissionAttemptsCount: FlowPreference<Int> = mockFlowPreference(0) + private val autoSubmissionAttemptsLast: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH) + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { workManager.cancelAllWorkByTag(any()) } returns mockk() + every { workManager.enqueueUniquePeriodicWork(any(), any(), any()) } returns mockk() + + every { submissionSettings.autoSubmissionEnabled } returns autoSubmissionEnabled + every { submissionSettings.lastSubmissionUserActivityUTC } returns lastSubmissionUserActivityUTC + every { submissionSettings.autoSubmissionAttemptsCount } returns autoSubmissionAttemptsCount + every { submissionSettings.autoSubmissionAttemptsLast } returns autoSubmissionAttemptsLast + + every { taskController.tasks } returns emptyFlow() + + every { timeStamper.nowUTC } returns Instant.ofEpochMilli(123456789) + + mockkStatic("de.rki.coronawarnapp.task.TaskControllerExtensionsKt") + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createInstance() = AutoSubmission( + timeStamper = timeStamper, + submissionSettings = submissionSettings, + workManager = workManager, + taskController = taskController + ) + + @Test + fun `init is sideeffect free`() { + createInstance() + + verify { workManager wasNot Called } + } + + @Test + fun `update mode DISABLED`() { + val instance = createInstance() + + instance.updateMode(AutoSubmission.Mode.DISABLED) + + verifySequence { + workManager.cancelAllWorkByTag("AutoSubmissionWorker") + autoSubmissionEnabled.update(match { !it.invoke(true) }) + lastSubmissionUserActivityUTC.update(match { it.invoke(Instant.now()) == Instant.EPOCH }) + autoSubmissionAttemptsCount.update(match { it.invoke(123) == 0 }) + autoSubmissionAttemptsLast.update(match { it.invoke(Instant.now()) == Instant.EPOCH }) + } + } + + @Test + fun `update mode MONITOR`() { + val instance = createInstance() + + instance.updateMode(AutoSubmission.Mode.MONITOR) + + verifySequence { + lastSubmissionUserActivityUTC.update(match { it.invoke(Instant.now()) == Instant.ofEpochMilli(123456789) }) + autoSubmissionEnabled.update(match { it.invoke(false) }) + + workManager.enqueueUniquePeriodicWork( + "AutoSubmissionWorker", + ExistingPeriodicWorkPolicy.KEEP, + any() + ) + } + } + + @Test + fun `update mode SUBMIT_ASAP`() { + val instance = createInstance() + + instance.updateMode(AutoSubmission.Mode.SUBMIT_ASAP) + + verifySequence { + lastSubmissionUserActivityUTC.update(match { it.invoke(Instant.now()) == Instant.EPOCH }) + autoSubmissionEnabled.update(match { it.invoke(false) }) + + workManager.enqueueUniquePeriodicWork( + "AutoSubmissionWorker", + ExistingPeriodicWorkPolicy.KEEP, + match { + it.workSpec.constraints.requiredNetworkType == NetworkType.CONNECTED && + it.workSpec.intervalDuration == 15 * 60 * 1000L + } + ) + } + } + + @Test + fun `blocking submission successful`() { + val instance = createInstance() + + val slot = slot<TaskRequest>() + + val taskResult = mockk<TaskState>().apply { + every { isSuccessful } returns true + } + + coEvery { taskController.submitBlocking(capture(slot)) } returns taskResult + + runBlockingTest { + instance.runSubmissionNow() + } + + coVerifySequence { + taskController.submitBlocking( + DefaultTaskRequest( + id = slot.captured.id, + type = SubmissionTask::class, + arguments = SubmissionTask.Arguments(checkUserActivity = false), + originTag = "AutoSubmission" + ) + ) + } + } + + @Test + fun `blocking submission failure sets up SUBMIT_ASAP`() { + val instance = createInstance() + + val slot = slot<TaskRequest>() + + val taskResult = mockk<TaskState>().apply { + every { isSuccessful } returns false + every { error } returns Exception() + } + + coEvery { taskController.submitBlocking(capture(slot)) } returns taskResult + + runBlockingTest { + instance.runSubmissionNow() + } + + verifySequence { + lastSubmissionUserActivityUTC.update(match { it.invoke(Instant.now()) == Instant.EPOCH }) + autoSubmissionEnabled.update(match { it.invoke(false) }) + + workManager.enqueueUniquePeriodicWork( + "AutoSubmissionWorker", + ExistingPeriodicWorkPolicy.KEEP, + any() + ) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/auto/SubmissionWorkerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/auto/SubmissionWorkerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..19c83a55b8c7aa4992cde93585ecfc364fb7e03e --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/auto/SubmissionWorkerTest.kt @@ -0,0 +1,87 @@ +package de.rki.coronawarnapp.submission.auto + +import android.content.Context +import androidx.work.ListenableWorker +import androidx.work.WorkerParameters +import de.rki.coronawarnapp.submission.task.SubmissionTask +import de.rki.coronawarnapp.task.TaskController +import de.rki.coronawarnapp.task.TaskFactory +import de.rki.coronawarnapp.task.TaskRequest +import de.rki.coronawarnapp.task.TaskState +import de.rki.coronawarnapp.task.common.DefaultTaskRequest +import de.rki.coronawarnapp.task.submitBlocking +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.mockkStatic +import io.mockk.slot +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class SubmissionWorkerTest : BaseTest() { + + @RelaxedMockK lateinit var workerParams: WorkerParameters + @MockK lateinit var context: Context + @MockK lateinit var taskController: TaskController + @MockK(relaxed = true) lateinit var taskResult: TaskState + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + mockkStatic("de.rki.coronawarnapp.task.TaskControllerExtensionsKt") + + coEvery { taskController.submitBlocking(any()) } returns taskResult + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createWorker() = SubmissionWorker( + context = context, + workerParams = workerParams, + taskController = taskController + ) + + @Test + fun `worker runs task with user activity check enabled`() = runBlockingTest { + every { taskResult.error } returns null + val slot = slot<TaskRequest>() + coEvery { taskController.submitBlocking(capture(slot)) } returns taskResult + + val worker = createWorker() + + worker.doWork() shouldBe ListenableWorker.Result.success() + + slot.captured shouldBe DefaultTaskRequest( + id = slot.captured.id, + type = SubmissionTask::class, + arguments = SubmissionTask.Arguments(checkUserActivity = true), + errorHandling = TaskFactory.Config.ErrorHandling.SILENT, + originTag = "SubmissionWorker" + ) + } + + @Test + fun `task errors are rethrown `() = runBlockingTest { + every { taskResult.error } returns Exception() + + val worker = createWorker() + + worker.doWork() shouldBe ListenableWorker.Result.retry() + + coVerify { + taskController.submitBlocking(any()) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVectorDeterminatorTest.kt similarity index 97% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminatorTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVectorDeterminatorTest.kt index 782116fd0e313bef35b80a0fad1ae1a0e6568723..3138e182de262456f883ab42893a26f295d67011 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminatorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/DaysSinceOnsetOfSymptomsVectorDeterminatorTest.kt @@ -1,5 +1,6 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.submission.Symptoms.Indication.NEGATIVE import de.rki.coronawarnapp.submission.Symptoms.Indication.NO_INFORMATION import de.rki.coronawarnapp.submission.Symptoms.Indication.POSITIVE diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculationsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/ExposureKeyHistoryCalculationsTest.kt similarity index 99% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculationsTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/ExposureKeyHistoryCalculationsTest.kt index 5b999174f82d0ef22fce93e9ac2c3c89045032e7..d2e87ca0fbd61f8c3987c15b4f4d006faf710422 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculationsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/ExposureKeyHistoryCalculationsTest.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/SubmissionTaskConfigTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskConfigTest.kt similarity index 88% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/SubmissionTaskConfigTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskConfigTest.kt index d1fbd6e793ceeedf06030c83379e4a6e3218fdc5..bf4fc95db4c04763b9e33f0b95f45e2300d8e1a2 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/SubmissionTaskConfigTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskConfigTest.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import io.kotest.matchers.comparables.shouldBeLessThan import org.joda.time.Duration diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/SubmissionTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt similarity index 51% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/SubmissionTaskTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt index 598527408794591fb47d3ebcb3f1ae2e95c3bf49..4e9905c86f3738ee388fdec1a118dbb7fbafe4da 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/SubmissionTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.appconfig.AppConfigProvider @@ -8,11 +8,15 @@ import de.rki.coronawarnapp.notification.TestResultNotificationService import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage -import de.rki.coronawarnapp.task.Task +import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.preferences.FlowPreference import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import io.kotest.assertions.throwables.shouldThrow +import io.kotest.assertions.throwables.shouldThrowMessage import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -22,9 +26,14 @@ import io.mockk.coVerifySequence import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just +import io.mockk.mockk import io.mockk.mockkObject +import io.mockk.verify +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest +import org.joda.time.Duration +import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -39,6 +48,7 @@ class SubmissionTaskTest : BaseTest() { @MockK lateinit var tekHistoryStorage: TEKHistoryStorage @MockK lateinit var submissionSettings: SubmissionSettings @MockK lateinit var testResultNotificationService: TestResultNotificationService + @MockK lateinit var autoSubmission: AutoSubmission @MockK lateinit var tekBatch: TEKHistoryStorage.TEKBatch @MockK lateinit var tek: TemporaryExposureKey @@ -47,7 +57,15 @@ class SubmissionTaskTest : BaseTest() { @MockK lateinit var appConfigData: ConfigData - private lateinit var mockSymptomsPreference: FlowPreference<Symptoms?> + @MockK lateinit var timeStamper: TimeStamper + + private lateinit var settingSymptomsPreference: FlowPreference<Symptoms?> + + private val settingHasGivenConsent: FlowPreference<Boolean> = mockFlowPreference(true) + private val settingAutoSubmissionAttemptsCount: FlowPreference<Int> = mockFlowPreference(0) + private val settingAutoSubmissionAttemptsLast: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH) + + private val settingLastUserActivityUTC: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH.plus(1)) @BeforeEach fun setup() { @@ -65,11 +83,15 @@ class SubmissionTaskTest : BaseTest() { coEvery { tekHistoryStorage.clear() } just Runs every { - tekHistoryCalculations.transformToKeyHistoryInExternalFormat(listOf(tek), userSymptoms) + tekHistoryCalculations.transformToKeyHistoryInExternalFormat(listOf(tek), any()) } returns listOf(transformedKey) - mockSymptomsPreference = mockFlowPreference(userSymptoms) - every { submissionSettings.symptoms } returns mockSymptomsPreference + settingSymptomsPreference = mockFlowPreference(userSymptoms) + every { submissionSettings.symptoms } returns settingSymptomsPreference + every { submissionSettings.hasGivenConsent } returns settingHasGivenConsent + every { submissionSettings.lastSubmissionUserActivityUTC } returns settingLastUserActivityUTC + every { submissionSettings.autoSubmissionAttemptsCount } returns settingAutoSubmissionAttemptsCount + every { submissionSettings.autoSubmissionAttemptsLast } returns settingAutoSubmissionAttemptsLast coEvery { appConfigProvider.getAppConfig() } returns appConfigData every { appConfigData.supportedCountries } returns listOf("NL") @@ -77,6 +99,10 @@ class SubmissionTaskTest : BaseTest() { coEvery { playbook.submit(any()) } just Runs every { testResultNotificationService.cancelPositiveTestResultNotification() } just Runs + + every { autoSubmission.updateMode(any()) } just Runs + + every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardHours(1)) } private fun createTask() = SubmissionTask( @@ -85,18 +111,25 @@ class SubmissionTaskTest : BaseTest() { tekHistoryCalculations = tekHistoryCalculations, tekHistoryStorage = tekHistoryStorage, submissionSettings = submissionSettings, - testResultNotificationService + testResultNotificationService = testResultNotificationService, + timeStamper = timeStamper, + autoSubmission = autoSubmission ) @Test fun `submission flow`() = runBlockingTest { val task = createTask() - task.run(object : Task.Arguments {}) + task.run(SubmissionTask.Arguments(checkUserActivity = true)) shouldBe SubmissionTask.Result( + state = SubmissionTask.Result.State.SUCCESSFUL + ) coVerifySequence { + settingLastUserActivityUTC.value + settingHasGivenConsent.value + LocalData.registrationToken() tekHistoryStorage.tekData - mockSymptomsPreference.value + settingSymptomsPreference.value tekHistoryCalculations.transformToKeyHistoryInExternalFormat(listOf(tek), userSymptoms) @@ -111,15 +144,30 @@ class SubmissionTaskTest : BaseTest() { ) tekHistoryStorage.clear() - mockSymptomsPreference.update(any()) + settingSymptomsPreference.update(match { it.invoke(mockk()) == null }) + + autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) BackgroundWorkScheduler.stopWorkScheduler() LocalData.numberOfSuccessfulSubmissions(1) testResultNotificationService.cancelPositiveTestResultNotification() } + } + + @Test + fun `NO_INFORMATION symptoms are used when the stored symptoms are null`() = runBlockingTest { + val emptySymptoms: FlowPreference<Symptoms?> = mockFlowPreference(null) + every { submissionSettings.symptoms } returns emptySymptoms + + val task = createTask() + task.run(SubmissionTask.Arguments()) shouldBe SubmissionTask.Result( + state = SubmissionTask.Result.State.SUCCESSFUL + ) - submissionSettings.symptoms.value shouldBe null + verify { + tekHistoryCalculations.transformToKeyHistoryInExternalFormat(listOf(tek), Symptoms.NO_INFO_GIVEN) + } } @Test @@ -127,13 +175,15 @@ class SubmissionTaskTest : BaseTest() { coEvery { playbook.submit(any()) } throws IOException() shouldThrow<IOException> { - createTask().run(object : Task.Arguments {}) + createTask().run(SubmissionTask.Arguments()) } coVerifySequence { + settingHasGivenConsent.value + LocalData.registrationToken() tekHistoryStorage.tekData - mockSymptomsPreference.value + settingSymptomsPreference.value tekHistoryCalculations.transformToKeyHistoryInExternalFormat(listOf(tek), userSymptoms) @@ -149,8 +199,9 @@ class SubmissionTaskTest : BaseTest() { } coVerify(exactly = 0) { tekHistoryStorage.clear() - mockSymptomsPreference.update(any()) + settingSymptomsPreference.update(any()) testResultNotificationService.cancelPositiveTestResultNotification() + autoSubmission.updateMode(any()) } submissionSettings.symptoms.value shouldBe userSymptoms } @@ -161,7 +212,7 @@ class SubmissionTaskTest : BaseTest() { val task = createTask() shouldThrow<NoRegistrationTokenSetException> { - task.run(object : Task.Arguments {}) + task.run(SubmissionTask.Arguments()) } } @@ -169,7 +220,9 @@ class SubmissionTaskTest : BaseTest() { fun `DE is used as fallback country`() = runBlockingTest { every { appConfigData.supportedCountries } returns listOf("DE") - createTask().run(object : Task.Arguments {}) + createTask().run(SubmissionTask.Arguments()) shouldBe SubmissionTask.Result( + state = SubmissionTask.Result.State.SUCCESSFUL + ) coVerifySequence { playbook.submit( @@ -184,7 +237,58 @@ class SubmissionTaskTest : BaseTest() { } @Test - fun `NO_INFORMATION symptoms are used when the stored symptoms are null`() { - // TODO + fun `submission is skipped if user was recently active in submission`() = runBlockingTest { + settingLastUserActivityUTC.update { Instant.EPOCH.plus(Duration.standardHours(1)) } + val task = createTask() + task.run(SubmissionTask.Arguments(checkUserActivity = true)) shouldBe SubmissionTask.Result( + state = SubmissionTask.Result.State.SKIPPED + ) + + coVerify(exactly = 0) { tekHistoryCalculations.transformToKeyHistoryInExternalFormat(any(), any()) } + } + + @Test + fun `user activity is only checked if enabled via arguments`() = runBlockingTest { + val task = createTask() + + task.run(SubmissionTask.Arguments(checkUserActivity = false)) + verify(exactly = 0) { settingLastUserActivityUTC.value } + + task.run(SubmissionTask.Arguments(checkUserActivity = true)) + verify { settingLastUserActivityUTC.value } + } + + @Test + fun `user activity is not checked by default`() { + SubmissionTask.Arguments().checkUserActivity shouldBe false + } + + @Test + fun `negative user activity durations lead to immediate submission`() = runBlockingTest { + settingLastUserActivityUTC.update { Instant.ofEpochMilli(Long.MAX_VALUE) } + val task = createTask() + task.run(SubmissionTask.Arguments(checkUserActivity = true)) shouldBe SubmissionTask.Result( + state = SubmissionTask.Result.State.SUCCESSFUL + ) + } + + @Test + fun `task executed with empty TEKs disables autosubmission too`() = runBlockingTest { + every { tekHistoryStorage.tekData } returns emptyFlow() + val task = createTask() + shouldThrow<NoSuchElementException> { + task.run(SubmissionTask.Arguments()) + } + verify { autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) } + } + + @Test + fun `exceeding retry attempts throws error and disables autosubmission`() = runBlockingTest { + settingAutoSubmissionAttemptsCount.update { Int.MAX_VALUE } + val task = createTask() + shouldThrowMessage("Submission task retry limit exceeded") { + task.run(SubmissionTask.Arguments()) + } + verify { autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVectorDeterminatorTest.kt similarity index 97% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminatorTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVectorDeterminatorTest.kt index 39424b5dec9f7bc867af91fceceade206da516d6..07dee2671ed06e47b2cc3233d3797769702f8310 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminatorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/TransmissionRiskVectorDeterminatorTest.kt @@ -1,5 +1,6 @@ -package de.rki.coronawarnapp.submission +package de.rki.coronawarnapp.submission.task +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.submission.Symptoms.Indication.NEGATIVE import de.rki.coronawarnapp.submission.Symptoms.Indication.NO_INFORMATION import de.rki.coronawarnapp.submission.Symptoms.Indication.POSITIVE diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt index 978a25068ebbaafff67dd6cb2f8e73f1626725cd..255fa58ebde74b9e402e2631496ce50fe14204a9 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.tracing.ui.homecards import android.content.Context import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.NoTest import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider import de.rki.coronawarnapp.util.DeviceUIState diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt index a5f39e2e9deda72b0f4c56fe8e4c7bf94d826f3b..151651436838878fcc4cd85133c3c06ae578dabf 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.ui.submission.qrcode.consent -import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.Country import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import io.kotest.matchers.shouldBe diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt index c221aaa07bc6dedfc46014d966cdc111741fb008..b4136caab09231e84b44b7dcac4c68f042fd0aef 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.ui.submission.qrcode.scan import de.rki.coronawarnapp.playbook.BackgroundNoise -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ScanStatus import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt index 83478fd77d89e1d06be94240e5a6f96cf5a2b259..591b391e48ba7638d225c1bbfc462086d6a51476 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt @@ -1,7 +1,8 @@ package de.rki.coronawarnapp.ui.submission.symptoms.calendar -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.util.preferences.FlowPreference import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations @@ -29,6 +30,7 @@ import testhelpers.preferences.mockFlowPreference class SubmissionSymptomCalendarViewModelTest : BaseTest() { @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var autoSubmission: AutoSubmission private lateinit var currentSymptoms: FlowPreference<Symptoms?> @BeforeEach @@ -37,8 +39,9 @@ class SubmissionSymptomCalendarViewModelTest : BaseTest() { currentSymptoms = mockFlowPreference(null) - every { submissionRepository.isSubmissionRunning } returns flowOf(false) - coEvery { submissionRepository.startSubmission() } just Runs + every { autoSubmission.isSubmissionRunning } returns flowOf(false) + every { autoSubmission.updateMode(any()) } just Runs + coEvery { autoSubmission.runSubmissionNow() } just Runs every { submissionRepository.currentSymptoms } returns currentSymptoms } @@ -51,7 +54,8 @@ class SubmissionSymptomCalendarViewModelTest : BaseTest() { SubmissionSymptomCalendarViewModel( symptomIndication = indication, dispatcherProvider = TestDispatcherProvider, - submissionRepository = submissionRepository + submissionRepository = submissionRepository, + autoSubmission = autoSubmission ) @Test @@ -75,9 +79,9 @@ class SubmissionSymptomCalendarViewModelTest : BaseTest() { } coVerifySequence { - submissionRepository.isSubmissionRunning + autoSubmission.isSubmissionRunning submissionRepository.currentSymptoms - submissionRepository.startSubmission() + autoSubmission.runSubmissionNow() } currentSymptoms.value shouldBe Symptoms( @@ -91,15 +95,15 @@ class SubmissionSymptomCalendarViewModelTest : BaseTest() { createViewModel().onCancelConfirmed() coVerifySequence { - submissionRepository.isSubmissionRunning - submissionRepository.startSubmission() + autoSubmission.isSubmissionRunning + autoSubmission.runSubmissionNow() } } @Test fun `submission shows upload dialog`() { val uploadStatus = MutableStateFlow(false) - every { submissionRepository.isSubmissionRunning } returns uploadStatus + every { autoSubmission.isSubmissionRunning } returns uploadStatus createViewModel().apply { showUploadDialog.observeForever { } showUploadDialog.value shouldBe false diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt index 81aa516b710ac3295327d89f0612bcf30fd74e36..f3e1ee087cf6863a7b071919360b948ce16b9b50 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt @@ -1,7 +1,8 @@ package de.rki.coronawarnapp.ui.submission.symptoms.introduction -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -28,14 +29,15 @@ import testhelpers.preferences.mockFlowPreference class SubmissionSymptomIntroductionViewModelTest : BaseTest() { @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var autoSubmission: AutoSubmission private val currentSymptoms = mockFlowPreference<Symptoms?>(null) @BeforeEach fun setUp() { MockKAnnotations.init(this, relaxed = true) - every { submissionRepository.isSubmissionRunning } returns flowOf(false) - coEvery { submissionRepository.startSubmission() } just Runs + every { autoSubmission.isSubmissionRunning } returns flowOf(false) + coEvery { autoSubmission.runSubmissionNow() } just Runs every { submissionRepository.currentSymptoms } returns currentSymptoms } @@ -46,7 +48,8 @@ class SubmissionSymptomIntroductionViewModelTest : BaseTest() { private fun createViewModel() = SubmissionSymptomIntroductionViewModel( dispatcherProvider = TestDispatcherProvider, - submissionRepository = submissionRepository + submissionRepository = submissionRepository, + autoSubmission = autoSubmission ) @Test @@ -88,7 +91,7 @@ class SubmissionSymptomIntroductionViewModelTest : BaseTest() { ) } - coVerify { submissionRepository.startSubmission() } + coVerify { autoSubmission.runSubmissionNow() } } @Test @@ -110,15 +113,15 @@ class SubmissionSymptomIntroductionViewModelTest : BaseTest() { currentSymptoms.value shouldBe null coVerifySequence { - submissionRepository.isSubmissionRunning - submissionRepository.startSubmission() + autoSubmission.isSubmissionRunning + autoSubmission.runSubmissionNow() } } @Test fun `submission shows upload dialog`() { val uploadStatus = MutableStateFlow(false) - every { submissionRepository.isSubmissionRunning } returns uploadStatus + every { autoSubmission.isSubmissionRunning } returns uploadStatus createViewModel().apply { showUploadDialog.observeForever { } showUploadDialog.value shouldBe false diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt index 7be6344dedc5bdc0f15d85eea31dc700f57b08ad..8d0f77ea5c10ac01d76fcd2b611ac8608c05e274 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt @@ -1,6 +1,6 @@ package de.rki.coronawarnapp.ui.submission.tan -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt index 52eaba01d9e4e4cc591364bda8f01f505c8b6bb9..1341d2771a2d3468a9d7853394fb84827de7a550 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.ui.submission.testavailable -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater import de.rki.coronawarnapp.ui.submission.resultavailable.SubmissionTestResultAvailableFragmentDirections import de.rki.coronawarnapp.ui.submission.resultavailable.SubmissionTestResultAvailableViewModel @@ -27,6 +28,7 @@ import testhelpers.extensions.InstantExecutorExtension class SubmissionTestResultAvailableViewModelTest : BaseTest() { @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var autoSubmission: AutoSubmission @MockK lateinit var tekHistoryUpdater: TEKHistoryUpdater @MockK lateinit var tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory @@ -45,7 +47,8 @@ class SubmissionTestResultAvailableViewModelTest : BaseTest() { private fun createViewModel(): SubmissionTestResultAvailableViewModel = SubmissionTestResultAvailableViewModel( submissionRepository = submissionRepository, dispatcherProvider = TestDispatcherProvider, - tekHistoryUpdaterFactory = tekHistoryUpdaterFactory + tekHistoryUpdaterFactory = tekHistoryUpdaterFactory, + autoSubmission = autoSubmission ) @AfterEach diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt index 3d4b671343019a3cdd820687a5600ea267b10ee3..6e90e1fd624ef6ebf2ad30589269e5c49c4f7058 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.ui.submission.testresult -import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenViewModel import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import io.kotest.matchers.shouldBe @@ -15,8 +16,8 @@ import testhelpers.extensions.InstantExecutorExtension @ExtendWith(InstantExecutorExtension::class) class SubmissionTestResultConsentGivenViewModelTest : BaseTest() { - @MockK - lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var autoSubmission: AutoSubmission lateinit var viewModel: SubmissionTestResultConsentGivenViewModel @BeforeEach @@ -26,7 +27,8 @@ class SubmissionTestResultConsentGivenViewModelTest : BaseTest() { private fun createViewModel() = SubmissionTestResultConsentGivenViewModel( submissionRepository = submissionRepository, - dispatcherProvider = TestDispatcherProvider + dispatcherProvider = TestDispatcherProvider, + autoSubmission = autoSubmission ) @Test @@ -39,7 +41,7 @@ class SubmissionTestResultConsentGivenViewModelTest : BaseTest() { @Test fun testOnCancelled() { viewModel = createViewModel() - viewModel.cancelTestSubmission() + viewModel.onCancelConfirmed() viewModel.routeToScreen.value shouldBe SubmissionNavigationEvents.NavigateToMainActivity } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt index ae54f2e94067f5e810a00766d60c63ab20c202ad..184fae9da67cfb236f7f7f650b6db6df663736e0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.ui.submission.yourconsent -import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.Country import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000000000000000000000000000000000..7a118b49be750543e59f7b9c55123e11322b00c6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000000000000000000000000000000000..5be70a69121e37ed0c5e9dfe8e30fc6d0a5bc200 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,182 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.3) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + atomos (0.1.3) + aws-eventstream (1.1.0) + aws-partitions (1.414.0) + aws-sdk-core (3.110.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.40.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.87.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.0.3) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.20) + declarative-option (0.1.0) + digest-crc (0.6.3) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + emoji_regex (3.2.1) + excon (0.78.1) + faraday (1.3.0) + faraday-net_http (~> 1.0) + multipart-post (>= 1.2, < 3) + ruby2_keywords + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-net_http (1.0.0) + faraday_middleware (1.0.0) + faraday (~> 1.0) + fastimage (2.2.1) + fastlane (2.171.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.37.0, < 0.39.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + gh_inspector (1.1.3) + google-api-client (0.38.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.4.0) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.0.1) + google-cloud-storage (1.29.2) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.14.0) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.14) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.4.0) + json (2.5.1) + jwt (2.2.2) + memoist (0.16.2) + mini_magick (4.11.0) + mini_mime (1.0.2) + multi_json (1.15.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + naturally (2.2.0) + os (1.1.1) + plist (3.6.0) + public_suffix (4.0.6) + rake (13.0.3) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + ruby2_keywords (0.0.2) + rubyzip (2.3.0) + security (0.1.3) + signet (0.14.0) + addressable (~> 2.3) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + slack-notifier (2.3.2) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) + word_wrap (1.0.0) + xcodeproj (1.19.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.2.4