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