diff --git a/.github/ISSUE_TEMPLATE/01_bugs.md b/.github/ISSUE_TEMPLATE/01_bugs.md
index 87643902da22e4b1deb8dc73179d2cd687f5c662..e1c0c762e321b188de8bf3113deb6d746fe05667 100644
--- a/.github/ISSUE_TEMPLATE/01_bugs.md
+++ b/.github/ISSUE_TEMPLATE/01_bugs.md
@@ -17,13 +17,15 @@ Also, be sure to check our documentation first: https://github.com/corona-warn-a
 * [ ] Bug is specific for Android only, for general issues / questions that apply to iOS and Android please raise them in the [documentation repository](https://github.com/corona-warn-app/cwa-documentation)
 * [ ] Bug is not already reported in another issue
 
-## Describe the bug
+## Technical details
 
-<!-- Describe your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
+- Device name:
+- Android version:
+- App version:
 
-## Expected behaviour
+## Describe the bug
 
-<!-- A clear and concise description of what you expected to happen. -->
+<!-- Describe your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
 
 ## Steps to reproduce the issue
 
@@ -36,10 +38,9 @@ Also, be sure to check our documentation first: https://github.com/corona-warn-a
 4. See error
 -->
 
-## Technical details
+## Expected behaviour
 
-- Mobile device:
-- Android version:
+<!-- A clear and concise description of what you expected to happen. -->
 
 ## Possible Fix
 
diff --git a/Corona-Warn-App/src/main/AndroidManifest.xml b/Corona-Warn-App/src/main/AndroidManifest.xml
index 23b0a591b2ae07771449a46d1eb4e5cc4e68ce37..a1d99bd4515ca2e7e674a1aef7aa513547badde6 100644
--- a/Corona-Warn-App/src/main/AndroidManifest.xml
+++ b/Corona-Warn-App/src/main/AndroidManifest.xml
@@ -53,7 +53,7 @@
             android:enabled="true"/>
 
         <activity
-            android:name=".ui.LauncherActivity"
+            android:name=".ui.launcher.LauncherActivity"
             android:screenOrientation="portrait"
             android:theme="@style/AppTheme.Launcher">
             <intent-filter>
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt
index f7bde81b1a52cfd96f878fa0dc43d310df3f7afd..9f67e506ed173a831038cb98c546008e0ef90789 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt
@@ -15,8 +15,10 @@ data class ConfigDataContainer(
 ) : ConfigData, ConfigMapping by mappedConfig {
     override val updatedAt: Instant = serverTime.plus(localOffset)
 
-    override fun isValid(nowUTC: Instant): Boolean {
+    override fun isValid(nowUTC: Instant): Boolean = if (cacheValidity == Duration.ZERO) {
+        false
+    } else {
         val expiresAt = updatedAt.plus(cacheValidity)
-        return nowUTC.isBefore(expiresAt)
+        nowUTC.isBefore(expiresAt)
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt
index 4f0db33f244672f31344b0217142d6ef1305ac21..498b6ed9a828a6b80de9eb4dec50dea601741ae7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt
@@ -49,10 +49,10 @@ class AppConfigStorage @Inject constructor(
             return@withLock try {
                 InternalConfigData(
                     rawData = legacyConfigFile.readBytes(),
-                    serverTime = timeStamper.nowUTC,
+                    serverTime = Instant.ofEpochMilli(legacyConfigFile.lastModified()),
                     localOffset = Duration.ZERO,
                     etag = "legacy.migration",
-                    cacheValidity = Duration.standardMinutes(5)
+                    cacheValidity = Duration.standardSeconds(0)
                 )
             } catch (e: Exception) {
                 Timber.e(e, "Legacy config exits but couldn't be read.")
@@ -81,6 +81,12 @@ class AppConfigStorage @Inject constructor(
             Timber.v("Overwriting %d from %s", configFile.length(), configFile.lastModified())
         }
 
+        if (legacyConfigFile.exists()) {
+            if (legacyConfigFile.delete()) {
+                Timber.i("Legacy config file deleted, superseeded.")
+            }
+        }
+
         if (value == null) {
             if (configFile.delete()) Timber.d("Config file was deleted (value=null).")
             return
@@ -88,12 +94,6 @@ class AppConfigStorage @Inject constructor(
 
         try {
             gson.toJson(value, configFile)
-
-            if (legacyConfigFile.exists()) {
-                if (legacyConfigFile.delete()) {
-                    Timber.i("Legacy config file deleted, superseeded.")
-                }
-            }
         } catch (e: Exception) {
             // We'll not rethrow as we could still keep working just with the remote config,
             // but we will notify the user.
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt
index b0c972544a74ef5442d0fa6da7173d55f9e3ff82..a0f7936a9711fb0a640ff6927b29ebad35845cc7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt
@@ -1,16 +1,10 @@
 package de.rki.coronawarnapp.storage.interoperability
 
-import android.text.TextUtils
-import androidx.lifecycle.asLiveData
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.ui.Country
-import de.rki.coronawarnapp.util.coroutine.AppScope
-import de.rki.coronawarnapp.util.coroutine.DefaultDispatcherProvider
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import timber.log.Timber
 import java.util.Locale
 import javax.inject.Inject
@@ -18,26 +12,13 @@ import javax.inject.Singleton
 
 @Singleton
 class InteroperabilityRepository @Inject constructor(
-    private val appConfigProvider: AppConfigProvider,
-    @AppScope private val appScope: CoroutineScope,
-    private val dispatcherProvider: DefaultDispatcherProvider
+    private val appConfigProvider: AppConfigProvider
 ) {
 
-    private val countryListFlowInternal = MutableStateFlow(listOf<Country>())
-    val countryListFlow: Flow<List<Country>> = countryListFlowInternal
-
-    @Deprecated("Use  countryListFlow")
-    val countryList = countryListFlow.asLiveData()
-
-    init {
-        getAllCountries()
-    }
-
-    fun getAllCountries() {
-        // TODO Make this reactive, the AppConfigProvider should refresh itself on network changes.
-        appScope.launch(context = dispatcherProvider.IO) {
+    val countryList = appConfigProvider.currentConfig
+        .map {
             try {
-                val countries = appConfigProvider.getAppConfig()
+                appConfigProvider.getAppConfig()
                     .supportedCountries
                     .mapNotNull { rawCode ->
                         val countryCode = rawCode.toLowerCase(Locale.ROOT)
@@ -46,17 +27,15 @@ class InteroperabilityRepository @Inject constructor(
                         if (mappedCountry == null) Timber.e("Unknown countrycode: %s", rawCode)
                         mappedCountry
                     }
-                countryListFlowInternal.value = countries
-                Timber.d("Country list: ${TextUtils.join(System.lineSeparator(), countries)}")
             } catch (e: Exception) {
-                Timber.e(e)
-                countryListFlowInternal.value = emptyList()
+                Timber.e(e, "Failed to map country list.")
+                emptyList()
             }
         }
-    }
+        .onEach { Timber.d("Country list: %s", it.joinToString(",")) }
 
-    fun clear() {
-        countryListFlowInternal.value = emptyList()
+    suspend fun refreshCountries() {
+        appConfigProvider.getAppConfig()
     }
 
     fun saveInteroperabilityUsed() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt
index 011e464a4139ce61ae3d27277f58feee268fd9ef..be99bd0647f1cfd07e087f595989465870bc5099 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt
@@ -2,6 +2,8 @@ package de.rki.coronawarnapp.ui
 
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.ui.launcher.LauncherActivity
+import de.rki.coronawarnapp.ui.launcher.LauncherActivityModule
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.main.MainActivityModule
 import de.rki.coronawarnapp.ui.main.MainActivityTestModule
@@ -13,7 +15,7 @@ abstract class ActivityBinder {
     @ContributesAndroidInjector(modules = [MainActivityModule::class, MainActivityTestModule::class])
     abstract fun mainActivity(): MainActivity
 
-    @ContributesAndroidInjector
+    @ContributesAndroidInjector(modules = [LauncherActivityModule::class])
     abstract fun launcherActivity(): LauncherActivity
 
     @ContributesAndroidInjector(modules = [OnboardingActivityModule::class])
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
deleted file mode 100644
index 1f55312ecea5c8dd259a6445f235aca801693e86..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package de.rki.coronawarnapp.ui
-
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
-import de.rki.coronawarnapp.storage.LocalData
-import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity
-import de.rki.coronawarnapp.update.UpdateChecker
-import de.rki.coronawarnapp.util.di.AppInjector
-import kotlinx.coroutines.launch
-
-class LauncherActivity : AppCompatActivity() {
-    companion object {
-        private val TAG: String? = LauncherActivity::class.simpleName
-    }
-
-    private lateinit var updateChecker: UpdateChecker
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        AppInjector.setup(this)
-        super.onCreate(savedInstanceState)
-    }
-
-    override fun onResume() {
-        super.onResume()
-
-        updateChecker = UpdateChecker(this)
-        lifecycleScope.launch {
-            updateChecker.checkForUpdate()
-        }
-    }
-
-    fun navigateToActivities() {
-        if (LocalData.isOnboarded()) {
-            startMainActivity()
-        } else {
-            startOnboardingActivity()
-        }
-    }
-
-    private fun startOnboardingActivity() {
-        OnboardingActivity.start(this)
-        this.overridePendingTransition(0, 0)
-        finish()
-    }
-
-    private fun startMainActivity() {
-        MainActivity.start(this)
-        this.overridePendingTransition(0, 0)
-        finish()
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt
index be8e13c919927fcf9ea8da8c30100b8b8fadfd82..52beb8ba1fc8bb080a451945f501e52c355dcdae 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt
@@ -29,7 +29,7 @@ class InteroperabilityConfigurationFragment :
     private var isNetworkCallbackRegistered = false
     private val networkCallback = object : ConnectivityHelper.NetworkCallback() {
         override fun onNetworkAvailable() {
-            vm.getAllCountries()
+            vm.refreshCountries()
         }
 
         override fun onNetworkUnavailable() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModel.kt
index 4d73e11f99f4b63f41033b114b6a7081812167ae..95d96003cd7d339b74719e4a99c84317c4dde355 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModel.kt
@@ -1,16 +1,20 @@
 package de.rki.coronawarnapp.ui.interoperability
 
+import androidx.lifecycle.asLiveData
 import com.squareup.inject.assisted.AssistedInject
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 
 class InteroperabilityConfigurationFragmentViewModel @AssistedInject constructor(
-    private val interoperabilityRepository: InteroperabilityRepository
-) : CWAViewModel() {
+    private val interoperabilityRepository: InteroperabilityRepository,
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
     val countryList = interoperabilityRepository.countryList
+        .asLiveData(context = dispatcherProvider.Default)
     val navigateBack = SingleLiveEvent<Boolean>()
 
     fun onBackPressed() {
@@ -21,8 +25,10 @@ class InteroperabilityConfigurationFragmentViewModel @AssistedInject constructor
         interoperabilityRepository.saveInteroperabilityUsed()
     }
 
-    fun getAllCountries() {
-        interoperabilityRepository.getAllCountries()
+    fun refreshCountries() {
+        launch {
+            interoperabilityRepository.refreshCountries()
+        }
     }
 
     @AssistedInject.Factory
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..af70ecc409fafd944ebeae4ef6e3d0e1c82bbdd4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt
@@ -0,0 +1,58 @@
+package de.rki.coronawarnapp.ui.launcher
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.main.MainActivity
+import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity
+import de.rki.coronawarnapp.util.di.AppInjector
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import javax.inject.Inject
+
+class LauncherActivity : AppCompatActivity() {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: LauncherActivityViewModel by cwaViewModels(
+        ownerProducer = { viewModelStore },
+        factoryProducer = { viewModelFactory }
+    )
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        AppInjector.setup(this)
+        super.onCreate(savedInstanceState)
+
+        vm.events.observe2(this) {
+            when (it) {
+                LauncherEvent.GoToOnboarding -> {
+                    OnboardingActivity.start(this)
+                    this.overridePendingTransition(0, 0)
+                    finish()
+                }
+                LauncherEvent.GoToMainActivity -> {
+                    MainActivity.start(this)
+                    this.overridePendingTransition(0, 0)
+                    finish()
+                }
+                is LauncherEvent.ShowUpdateDialog -> {
+                    showUpdateNeededDialog(it.updateIntent)
+                }
+            }
+        }
+    }
+
+    private fun showUpdateNeededDialog(intent: Intent) {
+        AlertDialog.Builder(this)
+            .setTitle(R.string.update_dialog_title)
+            .setMessage(R.string.update_dialog_message)
+            .setCancelable(false)
+            .setPositiveButton(R.string.update_dialog_button) { _, _ ->
+                ContextCompat.startActivity(this, intent, null)
+            }
+            .show()
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ca5fbbc15195e480e5e4b075512a15ace806bb16
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityModule.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.ui.launcher
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class LauncherActivityModule {
+
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(LauncherActivityViewModel::class)
+    abstract fun launcherActivity(
+        factory: LauncherActivityViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d344f94c4a8960dc5da662697f398b0646178e26
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt
@@ -0,0 +1,31 @@
+package de.rki.coronawarnapp.ui.launcher
+
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.update.UpdateChecker
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class LauncherActivityViewModel @AssistedInject constructor(
+    private val updateChecker: UpdateChecker,
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    val events = SingleLiveEvent<LauncherEvent>()
+
+    init {
+        launch {
+            val updateResult = updateChecker.checkForUpdate()
+            when {
+                updateResult.isUpdateNeeded -> LauncherEvent.ShowUpdateDialog(updateResult.updateIntent?.invoke()!!)
+                LocalData.isOnboarded() -> LauncherEvent.GoToMainActivity
+                else -> LauncherEvent.GoToOnboarding
+            }.let { events.postValue(it) }
+        }
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<LauncherActivityViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherEvent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..71470a17155c7f03ff8b85f672aa6cff9437de4d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherEvent.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.ui.launcher
+
+import android.content.Intent
+
+sealed class LauncherEvent {
+    object GoToOnboarding : LauncherEvent()
+    object GoToMainActivity : LauncherEvent()
+    data class ShowUpdateDialog(
+        val updateIntent: Intent
+    ) : LauncherEvent()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentViewModel.kt
index d817af9e90e91a55f57f2c9fb5cdadfea1847041..10ff693cd92a8bfcd921353a5c0aaf6a09a0196f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentViewModel.kt
@@ -1,16 +1,19 @@
 package de.rki.coronawarnapp.ui.onboarding
 
+import androidx.lifecycle.asLiveData
 import com.squareup.inject.assisted.AssistedInject
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 
 class OnboardingDeltaInteroperabilityFragmentViewModel @AssistedInject constructor(
-    private val interoperabilityRepository: InteroperabilityRepository
-) : CWAViewModel() {
+    private val interopRepo: InteroperabilityRepository,
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
-    val countryList = interoperabilityRepository.countryList
+    val countryList = interopRepo.countryList.asLiveData(context = dispatcherProvider.Default)
     val navigateBack = SingleLiveEvent<Boolean>()
 
     fun onBackPressed() {
@@ -18,7 +21,7 @@ class OnboardingDeltaInteroperabilityFragmentViewModel @AssistedInject construct
     }
 
     fun saveInteroperabilityUsed() {
-        interoperabilityRepository.saveInteroperabilityUsed()
+        interopRepo.saveInteroperabilityUsed()
     }
 
     @AssistedInject.Factory
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt
index ad4d8e2808078116ea7f8be6538055d29e176fb4..078e462f9def169a578006b396056627fe9be5a0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt
@@ -11,16 +11,19 @@ import de.rki.coronawarnapp.nearby.TracingPermissionHelper
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 import timber.log.Timber
 
 class OnboardingTracingFragmentViewModel @AssistedInject constructor(
     private val interoperabilityRepository: InteroperabilityRepository,
-    private val tracingPermissionHelper: TracingPermissionHelper
-) : CWAViewModel() {
+    private val tracingPermissionHelper: TracingPermissionHelper,
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
-    val countryList = interoperabilityRepository.countryListFlow.asLiveData()
+    val countryList = interoperabilityRepository.countryList
+        .asLiveData(context = dispatcherProvider.Default)
     val routeToScreen: SingleLiveEvent<OnboardingNavigationEvents> = SingleLiveEvent()
     val permissionRequestEvent = SingleLiveEvent<(Activity) -> Unit>()
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a6153679a5251c26f3a08649433fe610c5ef5463
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt
@@ -0,0 +1,111 @@
+package de.rki.coronawarnapp.ui.submission.warnothers
+
+import androidx.lifecycle.asLiveData
+import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
+import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.notification.TestResultNotificationService
+import de.rki.coronawarnapp.service.submission.SubmissionService
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
+import de.rki.coronawarnapp.submission.SubmissionTask
+import de.rki.coronawarnapp.submission.Symptoms
+import de.rki.coronawarnapp.task.TaskController
+import de.rki.coronawarnapp.task.common.DefaultTaskRequest
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.flow.combine
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import timber.log.Timber
+import java.util.UUID
+
+class SubmissionResultPositiveOtherWarningViewModel @AssistedInject constructor(
+    @Assisted private val symptoms: Symptoms,
+    dispatcherProvider: DispatcherProvider,
+    private val enfClient: ENFClient,
+    private val taskController: TaskController,
+    interoperabilityRepository: InteroperabilityRepository,
+    private val testResultNotificationService: TestResultNotificationService
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    private var currentSubmissionRequestId: UUID? = null
+
+    private val currentSubmission = taskController.tasks
+        .map { it.find { taskInfo -> taskInfo.taskState.request.id == currentSubmissionRequestId }?.taskState }
+        .onEach {
+            it?.let {
+                when {
+                    it.isFailed -> submissionError.postValue(it.error)
+                    it.isSuccessful -> routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSubmissionDone)
+                }
+            }
+        }
+
+    val uiState = combine(
+        currentSubmission,
+        interoperabilityRepository.countryList
+    ) { state, countries ->
+        WarnOthersState(
+            submitTaskState = state,
+            countryList = countries
+        )
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    val submissionError = SingleLiveEvent<Throwable>()
+    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
+
+    val requestKeySharing = SingleLiveEvent<Unit>()
+    val showEnableTracingEvent = SingleLiveEvent<Unit>()
+
+    fun onBackPressed() {
+        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToTestResult)
+    }
+
+    fun onWarnOthersPressed() {
+        launch {
+            if (enfClient.isTracingEnabled.first()) {
+                requestKeySharing.postValue(Unit)
+            } else {
+                showEnableTracingEvent.postValue(Unit)
+            }
+        }
+    }
+
+    fun onKeysShared(keys: List<TemporaryExposureKey>) {
+        if (keys.isNotEmpty()) {
+            submitDiagnosisKeys(keys)
+        } else {
+            submitWithNoDiagnosisKeys()
+            routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSubmissionDone)
+        }
+        testResultNotificationService.cancelPositiveTestResultNotification()
+    }
+
+    private fun submitDiagnosisKeys(keys: List<TemporaryExposureKey>) {
+        Timber.d("submitDiagnosisKeys(keys=%s, symptoms=%s)", keys, symptoms)
+        val registrationToken =
+            LocalData.registrationToken() ?: throw NoRegistrationTokenSetException()
+        val taskRequest = DefaultTaskRequest(
+            SubmissionTask::class,
+            SubmissionTask.Arguments(registrationToken, keys, symptoms)
+        )
+        currentSubmissionRequestId = taskRequest.id
+        taskController.submit(taskRequest)
+    }
+
+    private fun submitWithNoDiagnosisKeys() {
+        Timber.d("submitWithNoDiagnosisKeys()")
+        SubmissionService.submissionSuccessful()
+    }
+
+    @AssistedInject.Factory
+    interface Factory : CWAViewModelFactory<SubmissionResultPositiveOtherWarningViewModel> {
+        fun create(symptoms: Symptoms): SubmissionResultPositiveOtherWarningViewModel
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt
index 695e297a1d7d12d223abc79a9303a4a69890d62a..dd664afaed6e8b74e869a4114b6b25bd6b27f761 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt
@@ -2,85 +2,73 @@ package de.rki.coronawarnapp.update
 
 import android.content.Intent
 import android.net.Uri
-import androidx.appcompat.app.AlertDialog
-import androidx.core.content.ContextCompat.startActivity
+import dagger.Reusable
 import de.rki.coronawarnapp.BuildConfig
-import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.appconfig.CWAConfig
 import de.rki.coronawarnapp.appconfig.internal.ApplicationConfigurationCorruptException
-import de.rki.coronawarnapp.ui.LauncherActivity
-import de.rki.coronawarnapp.util.di.AppInjector
+import de.rki.coronawarnapp.environment.BuildConfigWrap
 import timber.log.Timber
+import javax.inject.Inject
 
-class UpdateChecker(private val activity: LauncherActivity) {
+@Reusable
+class UpdateChecker @Inject constructor(
+    private val appConfigProvider: AppConfigProvider
+) {
 
-    companion object {
-        val TAG: String? = UpdateChecker::class.simpleName
-
-        const val STORE_PREFIX = "https://play.google.com/store/apps/details?id="
-        const val COM_ANDROID_VENDING = "com.android.vending"
-    }
-
-    suspend fun checkForUpdate() {
-        // check if an update is needed based on server config
-        val updateNeededFromServer: Boolean = try {
-            checkIfUpdatesNeededFromServer()
-        } catch (exception: ApplicationConfigurationCorruptException) {
-            Timber.e(
-                "ApplicationConfigurationCorruptException caught:%s",
-                exception.localizedMessage
-            )
-            true
-        } catch (exception: Exception) {
-            Timber.e("Exception caught:%s", exception.localizedMessage)
-            false
-        }
-
-        if (updateNeededFromServer) {
-            showUpdateNeededDialog()
+    suspend fun checkForUpdate(): Result = try {
+        if (isUpdateNeeded()) {
+            Result(isUpdateNeeded = true, updateIntent = createUpdateAction())
         } else {
-            activity.navigateToActivities()
+            Result(isUpdateNeeded = false)
         }
-    }
-
-    /**
-     * Show dialog there an update is needed and links to the play store
-     */
-    private fun showUpdateNeededDialog() {
-        AlertDialog.Builder(activity)
-            .setTitle(activity.getString(R.string.update_dialog_title))
-            .setMessage(activity.getString(R.string.update_dialog_message))
-            .setCancelable(false)
-            .setPositiveButton(activity.getString(R.string.update_dialog_button)) { _, _ ->
+    } catch (exception: ApplicationConfigurationCorruptException) {
+        Timber.e(
+            "ApplicationConfigurationCorruptException caught:%s",
+            exception.localizedMessage
+        )
 
-                val uriStringInPlayStore = STORE_PREFIX + BuildConfig.APPLICATION_ID
-                val intent = Intent(Intent.ACTION_VIEW).apply {
-                    data = Uri.parse(
-                        uriStringInPlayStore
-                    )
-                    setPackage(COM_ANDROID_VENDING)
-                }
-                startActivity(activity, intent, null)
-            }
-            .create().show()
+        Result(isUpdateNeeded = true, updateIntent = createUpdateAction())
+    } catch (exception: Exception) {
+        Timber.tag(TAG).e("Exception caught:%s", exception.localizedMessage)
+        Result(isUpdateNeeded = false)
     }
 
-    private suspend fun checkIfUpdatesNeededFromServer(): Boolean {
-        val cwaAppConfig: CWAConfig = AppInjector.component.appConfigProvider.getAppConfig()
+    private suspend fun isUpdateNeeded(): Boolean {
+        val cwaAppConfig: CWAConfig = appConfigProvider.getAppConfig()
 
         val minVersionFromServer = cwaAppConfig.minVersionCode
 
-        Timber.d(
-            "minVersionFromServer:%s",
-            minVersionFromServer
-        )
-        Timber.d("Current app version:%s", BuildConfig.VERSION_CODE)
+        val currentVersion = BuildConfigWrap.VERSION_CODE
+
+        Timber.tag(TAG).d("minVersionFromServer:%s", minVersionFromServer)
+        Timber.tag(TAG).d("Current app version:%s", currentVersion)
 
         val needsImmediateUpdate = VersionComparator.isVersionOlder(
-            BuildConfig.VERSION_CODE.toLong(),
+            currentVersion,
             minVersionFromServer
         )
-        Timber.e("needs update:$needsImmediateUpdate")
+        Timber.tag(TAG).e("needs update:$needsImmediateUpdate")
         return needsImmediateUpdate
     }
+
+    private fun createUpdateAction(): () -> Intent = {
+        val uriStringInPlayStore = STORE_PREFIX + BuildConfig.APPLICATION_ID
+        Intent(Intent.ACTION_VIEW).apply {
+            data = Uri.parse(uriStringInPlayStore)
+            setPackage(COM_ANDROID_VENDING)
+        }
+    }
+
+    data class Result(
+        val isUpdateNeeded: Boolean,
+        val updateIntent: (() -> Intent)? = null
+    )
+
+    companion object {
+        private const val TAG: String = "UpdateChecker"
+
+        private const val STORE_PREFIX = "https://play.google.com/store/apps/details?id="
+        private const val COM_ANDROID_VENDING = "com.android.vending"
+    }
 }
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 e5438192ad55b436bd559dc31e998c7e162facd2..03896cec4a8964a186cf8ac45da2a0349052c307 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
@@ -73,7 +73,6 @@ class DataReset @Inject constructor(
         submissionRepository.reset()
         keyCacheRepository.clear()
         appConfigProvider.clear()
-        interoperabilityRepository.clear()
         exposureDetectionTracker.clear()
         downloadDiagnosisKeysSettings.clear()
         riskLevelStorage.clear()
diff --git a/Corona-Warn-App/src/main/res/layout/include_16_years.xml b/Corona-Warn-App/src/main/res/layout/include_16_years.xml
index 2d6fbcdb4fe40deffad6cabf3142e14ff70066ff..cb9865c2471dcbfa2a23240a385c0daa33a62c2c 100644
--- a/Corona-Warn-App/src/main/res/layout/include_16_years.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_16_years.xml
@@ -2,48 +2,28 @@
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <data>
-
-        <variable
-            name="headline"
-            type="String" />
-
-        <variable
-            name="body"
-            type="String" />
-
-    </data>
-
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/sixteen_years"
-        style="@style/SixteenInclude"
+        style="@style/card"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:focusable="true">
+        android:layout_margin="@dimen/spacing_small"
+        android:backgroundTint="@color/colorCardBackgroundHighlightGray"
+        android:focusable="true"
+        android:padding="@dimen/card_padding"
+        android:textColor="@color/colorStableLight">
 
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/sixteen_years_header"
-            android:layout_width="@dimen/match_constraint"
+        <TextView
+            android:id="@+id/sixteen_years_headline"
+            style="@style/headline6Sixteen"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
+            android:accessibilityHeading="true"
+            android:contentDescription="@string/sixteen_title_text"
+            android:text="@string/sixteen_title_text"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent">
-
-            <TextView
-                android:id="@+id/sixteen_years_headline"
-                style="@style/headline6Sixteen"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="@dimen/spacing_small"
-                android:accessibilityHeading="true"
-                android:contentDescription="@{headline}"
-                android:text="@{headline}"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
+            app:layout_constraintTop_toTopOf="parent" />
 
         <TextView
             android:id="@+id/sixteen_years_body"
@@ -51,10 +31,11 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/spacing_small"
-            android:text="@{body}"
+            android:text="@string/sixteen_description_text"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/sixteen_years_header" />
+            app:layout_constraintTop_toBottomOf="@+id/sixteen_years_headline" />
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/include_interoperability.xml b/Corona-Warn-App/src/main/res/layout/include_interoperability.xml
index afd91f1ecea348d005c50995bb0b649c47d6973b..158adfad9e6dd935dbb9372aa94dfa34feb452ee 100644
--- a/Corona-Warn-App/src/main/res/layout/include_interoperability.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_interoperability.xml
@@ -226,6 +226,7 @@
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
                 android:padding="@dimen/card_padding"
+                tools:visibility="visible"
                 android:visibility="@{FormatterHelper.formatVisibility(showFooter)}"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/include_onboarding.xml b/Corona-Warn-App/src/main/res/layout/include_onboarding.xml
index c3d9ad1d20136c335457220bd162d01e2b2d1bcf..2236318df353218c3203ccaa60970758e4474d39 100644
--- a/Corona-Warn-App/src/main/res/layout/include_onboarding.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_onboarding.xml
@@ -200,11 +200,10 @@
                 layout="@layout/include_16_years"
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_medium"
+                android:layout_marginTop="@dimen/spacing_small"
                 android:focusable="true"
+                tools:visibility="visible"
                 android:visibility="@{FormatterHelper.formatVisibilityText(locationHeadlineCard)}"
-                app:body="@{@string/sixteen_description_text}"
-                app:headline="@{@string/sixteen_title_text}"
                 app:layout_constraintEnd_toStartOf="@+id/guideline_card_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_card_start"
                 app:layout_constraintTop_toBottomOf="@+id/onboarding_location_card" />
@@ -214,7 +213,7 @@
                 layout="@layout/include_tracing_status_card"
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_medium"
+                android:layout_marginTop="@dimen/spacing_small"
                 android:focusable="true"
                 android:visibility="@{FormatterHelper.formatVisibilityText(headlineCard)}"
                 app:body="@{bodyCard}"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml b/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml
index 0dc277c240812c260c786037fe7ae97ac6bc2eb9..b5a79867553ab763abb0170601c8dba691fb58c8 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml
@@ -82,10 +82,8 @@
                 layout="@layout/include_16_years"
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
+                android:layout_marginTop="@dimen/spacing_small"
                 android:focusable="true"
-                app:body="@{@string/sixteen_description_text}"
-                app:headline="@{@string/sixteen_title_text}"
                 app:layout_constraintEnd_toStartOf="@+id/guideline_card_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_card_start"
                 app:layout_constraintTop_toBottomOf="@+id/countryList" />
@@ -95,7 +93,7 @@
                 layout="@layout/include_privacy_card"
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_large"
+                android:layout_marginTop="@dimen/spacing_small"
                 app:layout_constraintEnd_toStartOf="@+id/guideline_card_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_card_start"
                 app:layout_constraintTop_toBottomOf="@+id/submission_positive_location_card_16_years" />
diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
index bcaf5c5974c2030e1648febe5a25b692b6bdcf29..d9fd3b077a133f96abd4a76d9bbc146502ce0ee2 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -288,7 +288,7 @@
 
     <activity
         android:id="@+id/launcherActivity"
-        android:name="de.rki.coronawarnapp.ui.LauncherActivity"
+        android:name="de.rki.coronawarnapp.ui.launcher.LauncherActivity"
         android:label="LauncherActivity">
         <deepLink
             android:id="@+id/deepLink"
diff --git a/Corona-Warn-App/src/main/res/values/colors.xml b/Corona-Warn-App/src/main/res/values/colors.xml
index 6c335bd728d4d9a5951b71a168d8a99544bc8c1d..972080081cf72dbe8e585585084623c74dbf26f8 100644
--- a/Corona-Warn-App/src/main/res/values/colors.xml
+++ b/Corona-Warn-App/src/main/res/values/colors.xml
@@ -14,6 +14,11 @@
     <color name="colorSurface2Pressed">#D7D7D7</color>
     <color name="colorHairline">#3317191A</color>
 
+    <color name="cwaGrayHighlight">#5D6F80</color>
+
+    <!--Cards-->
+    <color name="colorCardBackgroundHighlightGray">@color/cwaGrayHighlight</color>
+
     <!-- Text -->
     <color name="colorTextPrimary1">#17191A</color>
     <color name="colorTextPrimary1Stable">#17191A</color>
@@ -24,7 +29,7 @@
     <color name="colorTextSixteen">#17191A</color>
     <color name="colorTextSemanticRed">#C00F2D</color>
     <color name="colorTextSemanticGreen">#2E854B</color>
-    <color name="colorTextSemanticNeutral">#5D6E80</color>
+    <color name="colorTextSemanticNeutral">@color/cwaGrayHighlight</color>
     <color name="colorTextTint">#007FAD</color>
 
     <!-- Semantic -->
@@ -32,7 +37,7 @@
     <color name="colorSemanticHighRiskPressed">#AE102B</color>
     <color name="colorSemanticLowRisk">#2E854B</color>
     <color name="colorSemanticLowRiskPressed">#2B7A46</color>
-    <color name="colorSemanticNeutralRisk">#5D6E80</color>
+    <color name="colorSemanticNeutralRisk">@color/cwaGrayHighlight</color>
     <color name="colorSemanticNeutralRiskPressed">#556675</color>
     <color name="colorSemanticUnknownRisk">#FFFFFF</color>
     <color name="colorSemanticUnknownRiskPressed">#E7E8E8</color>
@@ -56,10 +61,10 @@
     <color name="colorStableHairlineDark">#3317191A</color>
 
     <!-- Calendar -->
-    <color name="colorCalendarSelectedDayBackground">#5D6F80</color>
+    <color name="colorCalendarSelectedDayBackground">@color/cwaGrayHighlight</color>
     <color name="colorCalendarTodayBorder">#007FAD</color>
     <color name="colorCalendarTodayText">#007FAD</color>
     <color name="colorCalendarMonthText">#DE000000</color>
-    <color name="colorCalendarLayoutFocusOn">#FF5D6F80</color>
+    <color name="colorCalendarLayoutFocusOn">@color/cwaGrayHighlight</color>
     <color name="colorCalendarLayoutFocusOff">#F5F5F5</color>
 </resources>
diff --git a/Corona-Warn-App/src/main/res/values/styles.xml b/Corona-Warn-App/src/main/res/values/styles.xml
index 08685ba09018032bc25a3b2252a2e1bfa62b5554..fb0b8a3a4444824fba211ec5ae23999a938de26b 100644
--- a/Corona-Warn-App/src/main/res/values/styles.xml
+++ b/Corona-Warn-App/src/main/res/values/styles.xml
@@ -133,12 +133,6 @@
         <item name="android:backgroundTint">@color/colorSurface2</item>
     </style>
 
-    <style name="SixteenInclude">
-        <item name="android:padding">@dimen/card_padding</item>
-        <item name="android:background">@drawable/card</item>
-        <item name="android:textColor">@color/colorStableLight</item>
-    </style>
-
     <style name="selectionButton" parent="@style/Widget.AppCompat.Button.Borderless">
         <item name="android:padding">@dimen/card_padding</item>
         <item name="android:gravity">left</item>
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dc6752cb715a3c5288c4144832f0f4847ed134af
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainerTest.kt
@@ -0,0 +1,69 @@
+package de.rki.coronawarnapp.appconfig.internal
+
+import de.rki.coronawarnapp.appconfig.ConfigData
+import io.kotest.matchers.shouldBe
+import io.mockk.mockk
+import org.joda.time.Duration
+import org.joda.time.Instant
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class ConfigDataContainerTest : BaseTest() {
+
+    @Test
+    fun `cache validity is evaluated`() {
+        val now = Instant.EPOCH
+        val config = ConfigDataContainer(
+            serverTime = now,
+            localOffset = Duration.standardHours(1),
+            mappedConfig = mockk(),
+            configType = ConfigData.Type.LAST_RETRIEVED,
+            identifier = "localetag",
+            cacheValidity = Duration.standardSeconds(300)
+        )
+        config.isValid(now) shouldBe true
+        config.isValid(now.plus(Duration.standardSeconds(300))) shouldBe true
+        config.isValid(now.minus(Duration.standardSeconds(300))) shouldBe true
+
+        val nowWithOffset = now.plus(config.localOffset)
+        config.isValid(nowWithOffset.plus(Duration.standardSeconds(299))) shouldBe true
+        config.isValid(nowWithOffset.minus(Duration.standardSeconds(299))) shouldBe true
+
+        config.isValid(nowWithOffset) shouldBe true
+        config.isValid(nowWithOffset.minus(Duration.standardSeconds(300))) shouldBe true
+        config.isValid(nowWithOffset.plus(Duration.standardSeconds(300))) shouldBe false
+    }
+
+    @Test
+    fun `cache validity can be set to 0`() {
+        val config = ConfigDataContainer(
+            serverTime = Instant.EPOCH,
+            localOffset = Duration.standardHours(1),
+            mappedConfig = mockk(),
+            configType = ConfigData.Type.LAST_RETRIEVED,
+            identifier = "localetag",
+            cacheValidity = Duration.standardSeconds(0)
+        )
+        config.isValid(Instant.EPOCH) shouldBe false
+        config.isValid(Instant.EPOCH.plus(Duration.standardHours(1))) shouldBe false
+        config.isValid(Instant.EPOCH.plus(Duration.standardHours(24))) shouldBe false
+        config.isValid(Instant.EPOCH.plus(Duration.standardDays(14))) shouldBe false
+
+        config.isValid(Instant.EPOCH.minus(Duration.standardHours(1))) shouldBe false
+        config.isValid(Instant.EPOCH.minus(Duration.standardHours(24))) shouldBe false
+        config.isValid(Instant.EPOCH.minus(Duration.standardDays(14))) shouldBe false
+    }
+
+    @Test
+    fun `updated at is based on servertime and offset`() {
+        val config = ConfigDataContainer(
+            serverTime = Instant.EPOCH,
+            localOffset = Duration.standardHours(1),
+            mappedConfig = mockk(),
+            configType = ConfigData.Type.LAST_RETRIEVED,
+            identifier = "localetag",
+            cacheValidity = Duration.standardSeconds(0)
+        )
+        config.updatedAt shouldBe Instant.EPOCH.plus(Duration.standardHours(1))
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorageTest.kt
index 13b19949d1f28e9aee3ea878d73df6cb34e387dd..8696718e5bf785905a7726ad6367a8becebe99f7 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorageTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorageTest.kt
@@ -130,6 +130,26 @@ class AppConfigStorageTest : BaseIOTest() {
         configPath.exists() shouldBe false
     }
 
+    @Test
+    fun `nulling deletes legacy config`() = runBlockingTest {
+        val storage = createStorage()
+        configPath.exists() shouldBe false
+
+        storage.getStoredConfig() shouldBe null
+        storage.setStoredConfig(null)
+        configPath.exists() shouldBe false
+
+        legacyConfigPath.exists() shouldBe false
+        legacyConfigPath.parentFile!!.mkdirs()
+        legacyConfigPath.writeBytes(APPCONFIG_RAW)
+        legacyConfigPath.exists() shouldBe true
+
+        storage.setStoredConfig(null)
+        storage.getStoredConfig() shouldBe null
+        configPath.exists() shouldBe false
+        legacyConfigPath.exists() shouldBe false
+    }
+
     @Test
     fun `if no fallback exists, but we have a legacy config, use that`() = runBlockingTest {
         configPath.exists() shouldBe false
@@ -142,10 +162,10 @@ class AppConfigStorageTest : BaseIOTest() {
 
         storage.getStoredConfig() shouldBe InternalConfigData(
             rawData = APPCONFIG_RAW,
-            serverTime = Instant.ofEpochMilli(1234),
+            serverTime = Instant.ofEpochMilli(legacyConfigPath.lastModified()),
             localOffset = Duration.ZERO,
             etag = "I am an ETag :)!",
-            cacheValidity = Duration.standardMinutes(5)
+            cacheValidity = Duration.standardSeconds(0)
         )
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt
index d5e13842a13a8de0cdacbd91bffca9cae2b65fce..87c6c0f008a10f8fc3c5ff0a2b4cb8b4f5c876ec 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt
@@ -1,18 +1,18 @@
 package de.rki.coronawarnapp.ui.interoperability
 
-import androidx.lifecycle.MutableLiveData
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.ui.Country
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
-import io.mockk.Runs
+import io.mockk.coVerify
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
-import io.mockk.just
 import io.mockk.verify
+import kotlinx.coroutines.flow.flowOf
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.TestDispatcherProvider
 import testhelpers.extensions.InstantExecutorExtension
 import testhelpers.extensions.getOrAwaitValue
 
@@ -25,28 +25,27 @@ class InteroperabilityConfigurationFragmentViewModelTest {
     fun setupFreshViewModel() {
         MockKAnnotations.init(this)
 
-        every { interoperabilityRepository.countryList } returns MutableLiveData(
-            Country.values().toList()
-        )
-        every { interoperabilityRepository.getAllCountries() } just Runs
+        every { interoperabilityRepository.countryList } returns flowOf(Country.values().toList())
     }
 
     private fun createViewModel() =
-        InteroperabilityConfigurationFragmentViewModel(interoperabilityRepository)
+        InteroperabilityConfigurationFragmentViewModel(interoperabilityRepository, TestDispatcherProvider)
 
     @Test
     fun `viewmodel returns interop repo countryList`() {
         val vm = createViewModel()
 
         vm.countryList.getOrAwaitValue() shouldBe Country.values().toList()
+
+        verify { interoperabilityRepository.countryList }
     }
 
     @Test
-    fun testFetchCountryList() {
+    fun `forced countrylist refresh via app config`() {
         val vm = createViewModel()
-        verify(exactly = 0) { interoperabilityRepository.getAllCountries() }
-        vm.getAllCountries()
-        verify(exactly = 1) { interoperabilityRepository.getAllCountries() }
+        coVerify(exactly = 0) { interoperabilityRepository.refreshCountries() }
+        vm.refreshCountries()
+        coVerify(exactly = 1) { interoperabilityRepository.refreshCountries() }
     }
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..343f7b9609c1ee4baf50c2b74dbf63bf1ac2b47d
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt
@@ -0,0 +1,67 @@
+package de.rki.coronawarnapp.ui.launcher
+
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.update.UpdateChecker
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.types.instanceOf
+import io.mockk.MockKAnnotations
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
+import io.mockk.mockkObject
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.TestDispatcherProvider
+import testhelpers.extensions.InstantExecutorExtension
+
+@ExtendWith(InstantExecutorExtension::class)
+class LauncherActivityViewModelTest {
+
+    @MockK lateinit var updateChecker: UpdateChecker
+
+    @BeforeEach
+    fun setupFreshViewModel() {
+        MockKAnnotations.init(this)
+
+        mockkObject(LocalData)
+        every { LocalData.isOnboarded() } returns false
+
+        coEvery { updateChecker.checkForUpdate() } returns UpdateChecker.Result(isUpdateNeeded = false)
+    }
+
+    private fun createViewModel() = LauncherActivityViewModel(
+        updateChecker = updateChecker,
+        dispatcherProvider = TestDispatcherProvider
+    )
+
+    @Test
+    fun `update is available`() = runBlockingTest {
+        coEvery { updateChecker.checkForUpdate() } returns UpdateChecker.Result(
+            isUpdateNeeded = true,
+            updateIntent = { mockk() }
+        )
+
+        val vm = createViewModel()
+
+        vm.events.value shouldBe instanceOf(LauncherEvent.ShowUpdateDialog::class)
+    }
+
+    @Test
+    fun `fresh install no update needed`() {
+        val vm = createViewModel()
+
+        vm.events.value shouldBe LauncherEvent.GoToOnboarding
+    }
+
+    @Test
+    fun `onboarding finished`() {
+        every { LocalData.isOnboarded() } returns true
+
+        val vm = createViewModel()
+
+        vm.events.value shouldBe LauncherEvent.GoToMainActivity
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/UpdateCheckerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/UpdateCheckerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5a783eb0516ee09a9aa95233b751035c8b8f149c
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/UpdateCheckerTest.kt
@@ -0,0 +1,100 @@
+package de.rki.coronawarnapp.update
+
+import de.rki.coronawarnapp.appconfig.AppConfigProvider
+import de.rki.coronawarnapp.appconfig.ConfigData
+import de.rki.coronawarnapp.appconfig.internal.ApplicationConfigurationCorruptException
+import de.rki.coronawarnapp.environment.BuildConfigWrap
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
+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.mockkObject
+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 UpdateCheckerTest : BaseTest() {
+
+    @MockK private lateinit var configData: ConfigData
+    @MockK private lateinit var appConfigProvider: AppConfigProvider
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+        mockkObject(BuildConfigWrap)
+
+
+        coEvery { appConfigProvider.getAppConfig() } returns configData
+    }
+
+    @AfterEach
+    fun teardown() {
+        clearAllMocks()
+    }
+
+    fun createInstance() = UpdateChecker(
+        appConfigProvider = appConfigProvider
+    )
+
+    @Test
+    fun `update is required`() = runBlockingTest {
+        every { configData.minVersionCode } returns 10
+        every { BuildConfigWrap.VERSION_CODE } returns 9
+
+        createInstance().checkForUpdate().apply {
+            isUpdateNeeded shouldBe true
+            updateIntent shouldNotBe null
+        }
+
+        coVerifySequence {
+            appConfigProvider.getAppConfig()
+            BuildConfigWrap.VERSION_CODE
+        }
+    }
+
+    @Test
+    fun `update is NOT required`() = runBlockingTest {
+        every { configData.minVersionCode } returns 10
+        every { BuildConfigWrap.VERSION_CODE } returns 11
+
+        createInstance().checkForUpdate().apply {
+            isUpdateNeeded shouldBe false
+            updateIntent shouldBe null
+        }
+
+        coVerifySequence {
+            appConfigProvider.getAppConfig()
+            BuildConfigWrap.VERSION_CODE
+        }
+    }
+
+    @Test
+    fun `general error defaults to no update required`() = runBlockingTest {
+        every { configData.minVersionCode } throws Exception()
+
+        createInstance().checkForUpdate().apply {
+            isUpdateNeeded shouldBe false
+            updateIntent shouldBe null
+        }
+    }
+
+    @Test
+    fun `config parsing error means update is required`() = runBlockingTest {
+        every { configData.minVersionCode } throws ApplicationConfigurationCorruptException()
+
+        createInstance().checkForUpdate().apply {
+            isUpdateNeeded shouldBe true
+            updateIntent shouldNotBe null
+        }
+
+        coVerifySequence {
+            appConfigProvider.getAppConfig()
+        }
+    }
+}