diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index f96ff2b5c72c7cb822b610468eb1cc3872ade337..a45a78f7566c2a5626e7356d4e5d39d8cee90818 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -28,7 +28,7 @@ apply plugin: 'jacoco'
 def environmentExtractor = { File path ->
     def rawJson = path.text
     def escapedJson = rawJson.replace("\"", "\\\"").replace("\n", "").replace("\r", "")
-    "\"${escapedJson}\""
+    return "\"${escapedJson}\""
 }
 
 android {
@@ -47,14 +47,11 @@ android {
 
         resConfigs "de", "en", "tr", "bg", "pl", "ro"
 
-        buildConfigField "String", "ENVIRONMENT_TYPE_DEFAULT", "\"PROD\""
-        buildConfigField "String", "ENVIRONMENT_TYPE_ALTERNATIVE", "\"PROD\""
-
         def prodEnvJson = environmentExtractor(file("../prod_environments.json"))
         buildConfigField "String", "ENVIRONMENT_JSONDATA", prodEnvJson
 
         def devEnvironmentFile = file("../test_environments.json")
-        if(devEnvironmentFile.exists()) {
+        if (devEnvironmentFile.exists()) {
             def devEnvJson = environmentExtractor(devEnvironmentFile)
             buildConfigField "String", "ENVIRONMENT_JSONDATA", devEnvJson
         }
@@ -76,24 +73,35 @@ android {
         }
     }
 
-    // One version contains our Test Fragments
+
     flavorDimensions "version"
     productFlavors {
         device {
             dimension "version"
             buildConfigField "String", "BUILD_VARIANT", "\"device\""
             resValue "string", "app_name", "Corona-Warn"
+
+            ext {
+                envTypeDefault = [debug: "INT", release: "PROD"]
+            }
         }
         deviceForTesters {
+            // Contains test fragments
             dimension "version"
             buildConfigField "String", "BUILD_VARIANT", "\"deviceForTesters\""
             resValue "string", "app_name", "CWA TEST"
             applicationIdSuffix '.dev'
 
-            buildConfigField "String", "ENVIRONMENT_TYPE_DEFAULT", "\"DEV\""
-            buildConfigField "String", "ENVIRONMENT_TYPE_ALTERNATIVE", "\"WRU-XA\""
+            ext {
+                envTypeDefault = [debug: "INT", release: "WRU-XD"]
+            }
         }
     }
+    applicationVariants.all { variant ->
+        def flavor = variant.productFlavors[0]
+        def typeName = variant.buildType.name // debug/release
+        variant.buildConfigField "String", "ENVIRONMENT_TYPE_DEFAULT", "\"${flavor.envTypeDefault[typeName]}\""
+    }
 
     buildFeatures {
         dataBinding true
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/DebugOptionsState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/DebugOptionsState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a86be3932b084a0c27af1488d8681d0aeab6e427
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/DebugOptionsState.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.test.api.ui
+
+data class DebugOptionsState(
+    val areNotificationsEnabled: Boolean,
+    val is3HourModeEnabled: Boolean
+)
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/EnvironmentState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/EnvironmentState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..239f7dcbb47ce8d504da94721fea6e76c02357b3
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/EnvironmentState.kt
@@ -0,0 +1,21 @@
+package de.rki.coronawarnapp.test.api.ui
+
+import de.rki.coronawarnapp.environment.EnvironmentSetup
+
+data class EnvironmentState(
+    val current: EnvironmentSetup.Type,
+    val available: List<EnvironmentSetup.Type>,
+    val urlSubmission: String,
+    val urlDownload: String,
+    val urlVerification: String
+) {
+    companion object {
+        internal fun EnvironmentSetup.toEnvironmentState() = EnvironmentState(
+            current = currentEnvironment,
+            available = EnvironmentSetup.Type.values().toList(),
+            urlSubmission = submissionCdnUrl,
+            urlDownload = downloadCdnUrl,
+            urlVerification = verificationCdnUrl
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/GoogleServicesState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/GoogleServicesState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c6052212966e2cefdfc21a1e17751c79263c755e
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/GoogleServicesState.kt
@@ -0,0 +1,5 @@
+package de.rki.coronawarnapp.test.api.ui
+
+data class GoogleServicesState(
+    val version: Long
+)
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/LoggerState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/LoggerState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3244c5f34b18712f59e379f811d8d198268bca99
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/LoggerState.kt
@@ -0,0 +1,13 @@
+package de.rki.coronawarnapp.test.api.ui
+
+import de.rki.coronawarnapp.util.CWADebug
+
+data class LoggerState(
+    val isLogging: Boolean
+) {
+    companion object {
+        internal fun CWADebug.toLoggerState() = LoggerState(
+            isLogging = fileLogger?.isLogging ?: false
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt
index 6f8e10d248095717d40e0de6284c41a731b541bc..7acc24c296162b1ff94af0ebd3bf0c5350d5d2d9 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt
@@ -1,5 +1,6 @@
 package de.rki.coronawarnapp.test.api.ui
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Intent
 import android.graphics.Bitmap
@@ -12,16 +13,17 @@ import android.view.ViewGroup
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.InputMethodManager
 import android.widget.ImageView
+import android.widget.RadioButton
+import android.widget.RadioGroup
 import android.widget.Toast
-import androidx.core.content.pm.PackageInfoCompat
+import androidx.core.view.ViewCompat.generateViewId
+import androidx.core.view.children
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.viewModelScope
 import androidx.recyclerview.widget.RecyclerView
 import androidx.viewpager2.widget.ViewPager2
-import com.google.android.gms.common.GoogleApiAvailability
-import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
 import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient
 import com.google.android.gms.nearby.exposurenotification.ExposureSummary
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
@@ -52,18 +54,16 @@ import de.rki.coronawarnapp.storage.AppDatabase
 import de.rki.coronawarnapp.storage.ExposureSummaryRepository
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository
-import de.rki.coronawarnapp.transaction.RiskLevelTransaction
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
-import de.rki.coronawarnapp.util.CWADebug
 import de.rki.coronawarnapp.util.KeyFileHelper
 import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.setGone
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import org.joda.time.DateTime
@@ -108,7 +108,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
     private var token: String? = null
 
     // ViewModel for MainActivity
-    private val tracingViewModel: TracingViewModel by activityViewModels()
+    private val tracingVM: TracingViewModel by activityViewModels()
 
     private lateinit var qrPager: ViewPager2
     private lateinit var qrPagerAdapter: RecyclerView.Adapter<QRPagerAdapter.QRViewHolder>
@@ -118,19 +118,11 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
 
     private var lastSetCountries: List<String>? = null
 
+    @SuppressLint("SetTextI18n")
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        binding.tracingViewModel = tracingViewModel
-
-        val v: Long = PackageInfoCompat.getLongVersionCode(
-            activity?.packageManager!!.getPackageInfo(
-                GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE,
-                0
-            )
-        )
-        binding.labelGooglePlayServicesVersion.text =
-            "Google Play Services version: " + v.toString()
+        binding.tracingViewModel = tracingVM
 
         token = UUID.randomUUID().toString()
 
@@ -143,150 +135,173 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
         qrPagerAdapter = QRPagerAdapter()
         qrPager.adapter = qrPagerAdapter
 
-        // Load countries from App config and update Country UI element states
-        lifecycleScope.launch {
-            lastSetCountries =
-                ApplicationConfigurationService.asyncRetrieveApplicationConfiguration()
-                    .supportedCountriesList
-
-            binding.inputCountryCodesEditText.setText(
-                lastSetCountries?.joinToString(
-                    ","
-                )
-            )
-
-            updateCountryStatusLabel()
+        // Debug card
+        binding.threeHourModeToggle.apply {
+            setOnClickListener { vm.setLast3HoursMode(isChecked) }
         }
-
-        binding.buttonApiTestStart.setOnClickListener {
-            start()
+        vm.last3HourToggleEvent.observe2(this) {
+            showToast("Last 3 Hours Mode is activated: $it")
         }
-
-        binding.buttonApiGetExposureKeys.setOnClickListener {
-            getExposureKeys()
+        binding.backgroundNotificationsToggle.apply {
+            setOnClickListener { vm.setBackgroundNotifications(isChecked) }
         }
-
-        val last3HoursSwitch = binding.testApiSwitchLastThreeHoursFromServer
-        last3HoursSwitch.isChecked = LocalData.last3HoursMode()
-        last3HoursSwitch.setOnClickListener {
-            vm.setLast3HoursMode(last3HoursSwitch.isChecked)
+        vm.backgroundNotificationsToggleEvent.observe2(this@TestForAPIFragment) {
+            showToast("Background Notifications are activated: $it")
         }
-
-        vm.last3HourToggleEvent.observe2(this) {
-            showToast("Last 3 Hours Mode is activated: $it")
+        vm.debugOptionsState.observe2(this) { state ->
+            binding.apply {
+                backgroundNotificationsToggle.isChecked = state.areNotificationsEnabled
+                threeHourModeToggle.isChecked = state.is3HourModeEnabled
+            }
         }
-
-        val backgroundNotificationSwitch = binding.testApiSwitchBackgroundNotifications
-        backgroundNotificationSwitch.isChecked = LocalData.backgroundNotification()
-        backgroundNotificationSwitch.setOnClickListener {
-            val isBackgroundNotificationsActive = backgroundNotificationSwitch.isChecked
-            showToast("Background Notifications are activated: $isBackgroundNotificationsActive")
-            LocalData.backgroundNotification(isBackgroundNotificationsActive)
+        binding.testLogfileToggle.apply {
+            setOnClickListener { vm.setLoggerEnabled(isChecked) }
         }
+        vm.loggerState.observe2(this) { state ->
+            binding.apply {
+                testLogfileToggle.isChecked = state.isLogging
+                testLogfileShare.setGone(!state.isLogging)
+            }
+        }
+        binding.testLogfileShare.setOnClickListener { vm.shareLogFile() }
+        vm.logShareEvent.observe2(this) { showToast("Logfile copied to $it") }
 
-        val testCountriesSwitch = binding.testApiSwitchTestCountries
-        testCountriesSwitch.isChecked = vm.isCurrentEnvironmentAlternate()
-        testCountriesSwitch.setOnClickListener {
-            vm.toggleEnvironment(testCountriesSwitch.isChecked)
+        // Server environment card
+        binding.environmentToggleGroup.apply {
+            setOnCheckedChangeListener { group, checkedId ->
+                val chip = group.findViewById<RadioButton>(checkedId)
+                if (!chip.isPressed) return@setOnCheckedChangeListener
+                vm.selectEnvironmentTytpe(chip.text.toString())
+            }
         }
 
+        vm.environmentState.observe2(this) { state ->
+            binding.apply {
+                if (environmentToggleGroup.childCount != state.available.size) {
+                    environmentToggleGroup.removeAllViews()
+                    state.available.forEach { type ->
+                        RadioButton(requireContext()).apply {
+                            id = generateViewId()
+                            text = type.rawKey
+                            layoutParams = RadioGroup.LayoutParams(
+                                RadioGroup.LayoutParams.MATCH_PARENT,
+                                RadioGroup.LayoutParams.WRAP_CONTENT
+                            )
+                            environmentToggleGroup.addView(this)
+                        }
+                    }
+                }
+
+                environmentToggleGroup.children.forEach {
+                    it as RadioButton
+                    it.isChecked = it.text == state.current.rawKey
+                }
+
+                environmentCdnurlDownload.text = "Download CDN:\n${state.urlDownload}"
+                environmentCdnurlSubmission.text = "Submission CDN:\n${state.urlSubmission}"
+                environmentCdnurlVerification.text = "Verification CDN:\n${state.urlVerification}"
+            }
+        }
         vm.environmentChangeEvent.observe2(this) {
-            showSnackBar(
-                "Environment changed to: $it" +
-                    "\nForce stop & restart the app!"
-            )
+            showSnackBar("Environment changed to: $it\nForce stop & restart the app!")
         }
 
-        binding.buttonApiGetCheckExposure.setOnClickListener {
-            checkExposure()
+        // GMS Info card
+        vm.gmsState.observe2(this) { state ->
+            binding.googlePlayServicesVersionInfo.text =
+                "Google Play Services version: ${state.version}"
         }
 
-        binding.buttonApiScanQrCode.setOnClickListener {
-            IntentIntegrator.forSupportFragment(this)
-                .setOrientationLocked(false)
-                .setBeepEnabled(false)
-                .initiateScan()
-        }
+        // Test action card
+        binding.apply {
+            buttonApiTestStart.setOnClickListener { start() }
+            buttonApiGetExposureKeys.setOnClickListener { getExposureKeys() }
+            buttonApiGetCheckExposure.setOnClickListener { checkExposure() }
 
-        binding.buttonApiShareMyKeys.setOnClickListener {
-            shareMyKeys()
-        }
+            buttonApiScanQrCode.setOnClickListener {
+                IntentIntegrator.forSupportFragment(this@TestForAPIFragment)
+                    .setOrientationLocked(false)
+                    .setBeepEnabled(false)
+                    .initiateScan()
+            }
 
-        binding.buttonApiEnterOtherKeys.setOnClickListener {
-            enterOtherKeys()
-        }
+            buttonApiShareMyKeys.setOnClickListener { shareMyKeys() }
+            buttonApiEnterOtherKeys.setOnClickListener { enterOtherKeys() }
 
-        binding.buttonApiSubmitKeys.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                try {
-                    internalExposureNotificationPermissionHelper.requestPermissionToShareKeys()
+            buttonApiSubmitKeys.setOnClickListener {
+                tracingVM.viewModelScope.launch {
+                    try {
+                        internalExposureNotificationPermissionHelper.requestPermissionToShareKeys()
 
-                    // SubmitDiagnosisKeysTransaction.start("123")
-                    withContext(Dispatchers.Main) {
-                        showToast("Key submission successful")
+                        // SubmitDiagnosisKeysTransaction.start("123")
+                        withContext(Dispatchers.Main) {
+                            showToast("Key submission successful")
+                        }
+                    } catch (e: TransactionException) {
+                        e.report(INTERNAL)
                     }
-                } catch (e: TransactionException) {
-                    e.report(INTERNAL)
                 }
             }
-        }
 
-        binding.buttonCalculateRiskLevel.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                try {
-                    RiskLevelTransaction.start()
-                } catch (e: TransactionException) {
-                    e.report(INTERNAL)
-                }
-            }
-        }
+            buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevelClicked() }
 
-        binding.buttonInsertExposureSummary.setOnClickListener {
-            // Now broadcasts them to the worker.
-            val intent = Intent(
-                context,
-                ExposureStateUpdateReceiver::class.java
-            )
-            intent.action = ExposureNotificationClient.ACTION_EXPOSURE_STATE_UPDATED
-            context?.sendBroadcast(intent)
-        }
-
-        binding.buttonRetrieveExposureSummary.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                showToast(
-                    ExposureSummaryRepository.getExposureSummaryRepository()
-                        .getExposureSummaryEntities().toString()
+            buttonInsertExposureSummary.setOnClickListener {
+                // Now broadcasts them to the worker.
+                val intent = Intent(
+                    context,
+                    ExposureStateUpdateReceiver::class.java
                 )
+                intent.action = ExposureNotificationClient.ACTION_EXPOSURE_STATE_UPDATED
+                context?.sendBroadcast(intent)
             }
-        }
 
-        binding.buttonClearDb.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                withContext(Dispatchers.IO) {
-                    AppDatabase.getInstance(requireContext()).clearAllTables()
+            buttonRetrieveExposureSummary.setOnClickListener {
+                tracingVM.viewModelScope.launch {
+                    showToast(
+                        ExposureSummaryRepository.getExposureSummaryRepository()
+                            .getExposureSummaryEntities().toString()
+                    )
                 }
             }
-        }
 
-        binding.buttonTracingIntervals.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                showToast(
-                    TracingIntervalRepository.getDateRepository(requireContext()).getIntervals()
-                        .toString()
-                )
+            buttonClearDb.setOnClickListener {
+                tracingVM.viewModelScope.launch {
+                    withContext(Dispatchers.IO) {
+                        AppDatabase.getInstance(requireContext()).clearAllTables()
+                    }
+                }
+            }
+
+            buttonTracingIntervals.setOnClickListener {
+                tracingVM.viewModelScope.launch {
+                    showToast(
+                        TracingIntervalRepository.getDateRepository(requireContext()).getIntervals()
+                            .toString()
+                    )
+                }
             }
-        }
 
-        binding.buttonTracingDurationInRetentionPeriod.setOnClickListener {
-            tracingViewModel.viewModelScope.launch {
-                showToast(TimeVariables.getActiveTracingDaysInRetentionPeriod().toString())
+            buttonTracingDurationInRetentionPeriod.setOnClickListener {
+                tracingVM.viewModelScope.launch {
+                    showToast(TimeVariables.getActiveTracingDaysInRetentionPeriod().toString())
+                }
             }
         }
 
-        binding.buttonFilterCountryCodes.setOnClickListener {
-            filterCountryCodes()
-        }
+        // Country benchmark card
+        // Load countries from App config and update Country UI element states
+        lifecycleScope.launch {
+            lastSetCountries =
+                ApplicationConfigurationService.asyncRetrieveApplicationConfiguration()
+                    .supportedCountriesList
+
+            binding.inputCountryCodesEditText.setText(
+                lastSetCountries?.joinToString(",")
+            )
 
+            updateCountryStatusLabel()
+        }
+        binding.buttonFilterCountryCodes.setOnClickListener { filterCountryCodes() }
         binding.buttonRetrieveDiagnosisKeysAndCalcRiskLevel.setOnClickListener {
             startKeyRetrievalAndRiskCalcBenchmark()
         }
@@ -297,51 +312,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
             }
             false
         }
-
-        binding.testLogfileToggle.isChecked = CWADebug.fileLogger?.isLogging ?: false
-        binding.testLogfileToggle.setOnClickListener { buttonView ->
-            CWADebug.fileLogger?.let {
-                if (binding.testLogfileToggle.isChecked) {
-                    it.start()
-                } else {
-                    it.stop()
-                }
-            }
-        }
-
-        binding.testLogfileShare.setOnClickListener {
-            CWADebug.fileLogger?.let {
-                lifecycleScope.launch {
-                    val targetPath = withContext(Dispatchers.IO) {
-                        async {
-                            if (!it.logFile.exists()) return@async null
-
-                            val externalPath = File(
-                                requireContext().getExternalFilesDir(null),
-                                "LogFile-${System.currentTimeMillis()}.log"
-                            )
-
-                            it.logFile.copyTo(externalPath)
-
-                            return@async externalPath
-                        }
-                    }.await()
-                    if (targetPath != null) {
-                        Toast.makeText(
-                            requireActivity(),
-                            "Logfile copied to $targetPath",
-                            Toast.LENGTH_SHORT
-                        ).show()
-                    } else {
-                        Toast.makeText(
-                            requireActivity(),
-                            "No log file available",
-                            Toast.LENGTH_SHORT
-                        ).show()
-                    }
-                }
-            }
-        }
     }
 
     override fun onResume() {
@@ -372,7 +342,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
         val rawCountryCodes = binding.inputCountryCodesEditText.text.toString()
 
         // Country codes can be separated by space or ,
-        var countryCodes = rawCountryCodes.split(',', ' ').filter { it.isNotEmpty() }
+        val countryCodes = rawCountryCodes.split(',', ' ').filter { it.isNotEmpty() }
 
         lastSetCountries = countryCodes
 
@@ -397,7 +367,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
      */
     private fun updateCountryStatusLabel() {
         binding.labelCountryCodeFilterStatus.text = "Country filter applied for: \n " +
-                "${lastSetCountries?.joinToString(",")}"
+            "${lastSetCountries?.joinToString(",")}"
     }
 
     private val prettyKey = { key: AppleLegacyKeyExchange.Key ->
@@ -428,7 +398,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
             IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
         if (result != null) {
             if (result.contents == null) {
-                Toast.makeText(requireContext(), "Cancelled", Toast.LENGTH_LONG).show()
+                showToast("Cancelled")
             } else {
                 ExposureSharingService.getOthersKeys(result.contents, onScannedKey)
             }
@@ -484,11 +454,10 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
                     .build()
             )
 
-            val dir =
-                File(
-                    File(requireContext().getExternalFilesDir(null), "key-export"),
-                    token ?: ""
-                )
+            val dir = File(
+                File(requireContext().getExternalFilesDir(null), "key-export"),
+                token ?: ""
+            )
             dir.mkdirs()
 
             var googleFileList: List<File>
@@ -582,12 +551,11 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
     }
 
     private fun showToast(message: String) {
-        val toast = Toast.makeText(context, message, Toast.LENGTH_LONG)
-        toast.show()
+        Toast.makeText(context, message, Toast.LENGTH_LONG).show()
     }
 
     private fun showSnackBar(message: String) {
-            view?.let { Snackbar.make(it, message, Snackbar.LENGTH_LONG) }?.show()
+        Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG).show()
     }
 
     override fun onFailure(exception: Exception?) {
@@ -606,50 +574,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
         updateKeysDisplay()
     }
 
-    private fun getCustomConfig(): ExposureConfiguration = ExposureConfiguration
-        .ExposureConfigurationBuilder()
-        .setAttenuationScores(
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE
-        )
-        .setDaysSinceLastExposureScores(
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE
-        )
-        .setDurationScores(
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE
-        )
-        .setTransmissionRiskScores(
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE,
-            CONFIG_SCORE
-        )
-        .build()
-
     private inner class QRPagerAdapter :
         RecyclerView.Adapter<QRPagerAdapter.QRViewHolder>() {
 
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
index 935eb809cd18518e88a5088debe19760f27727c4..5593009bb47d1ae831c2a115032e0c8577a22119 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
@@ -1,31 +1,122 @@
 package de.rki.coronawarnapp.test.api.ui
 
+import android.content.Context
+import androidx.core.content.pm.PackageInfoCompat
+import androidx.lifecycle.viewModelScope
+import com.google.android.gms.common.GoogleApiAvailability
 import com.squareup.inject.assisted.AssistedInject
 import de.rki.coronawarnapp.environment.EnvironmentSetup
+import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnvironmentType
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.TransactionException
+import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.test.api.ui.EnvironmentState.Companion.toEnvironmentState
+import de.rki.coronawarnapp.test.api.ui.LoggerState.Companion.toLoggerState
+import de.rki.coronawarnapp.transaction.RiskLevelTransaction
+import de.rki.coronawarnapp.util.CWADebug
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.ui.smartLiveData
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.File
 
 class TestForApiFragmentViewModel @AssistedInject constructor(
+    private val context: Context,
     private val envSetup: EnvironmentSetup
 ) : CWAViewModel() {
 
+    val debugOptionsState by smartLiveData {
+        DebugOptionsState(
+            areNotificationsEnabled = LocalData.backgroundNotification(),
+            is3HourModeEnabled = LocalData.last3HoursMode()
+        )
+    }
+
     val last3HourToggleEvent = SingleLiveEvent<Boolean>()
+
+    fun setLast3HoursMode(enabled: Boolean) {
+        debugOptionsState.update {
+            LocalData.last3HoursMode(enabled)
+            it.copy(is3HourModeEnabled = enabled)
+        }
+        last3HourToggleEvent.postValue(enabled)
+    }
+
+    val environmentState by smartLiveData {
+        envSetup.toEnvironmentState()
+    }
     val environmentChangeEvent = SingleLiveEvent<EnvironmentSetup.Type>()
 
-    fun setLast3HoursMode(isLast3HoursModeEnabled: Boolean) {
-        LocalData.last3HoursMode(isLast3HoursModeEnabled)
-        last3HourToggleEvent.postValue(isLast3HoursModeEnabled)
+    fun selectEnvironmentTytpe(type: String) {
+        environmentState.update {
+            envSetup.currentEnvironment = type.toEnvironmentType()
+            environmentChangeEvent.postValue(envSetup.currentEnvironment)
+            envSetup.toEnvironmentState()
+        }
     }
 
-    fun toggleEnvironment(isTestCountyEnabled: Boolean) {
-        envSetup.currentEnvironment = if (isTestCountyEnabled) envSetup.alternativeEnvironment else envSetup.defaultEnvironment
-        environmentChangeEvent.postValue(envSetup.currentEnvironment)
+    val backgroundNotificationsToggleEvent = SingleLiveEvent<Boolean>()
+
+    fun setBackgroundNotifications(enabled: Boolean) {
+        debugOptionsState.update {
+            LocalData.backgroundNotification(enabled)
+            it.copy(areNotificationsEnabled = enabled)
+        }
+        backgroundNotificationsToggleEvent.postValue(enabled)
+    }
+
+    val loggerState by smartLiveData {
+        CWADebug.toLoggerState()
+    }
+
+    fun setLoggerEnabled(enable: Boolean) {
+        CWADebug.fileLogger?.let {
+            if (enable) it.start() else it.stop()
+        }
+        loggerState.update { CWADebug.toLoggerState() }
+    }
+
+    fun calculateRiskLevelClicked() {
+        viewModelScope.launch {
+            try {
+                RiskLevelTransaction.start()
+            } catch (e: TransactionException) {
+                e.report(ExceptionCategory.INTERNAL)
+            }
+        }
+    }
+
+    val logShareEvent = SingleLiveEvent<File?>()
+
+    fun shareLogFile() {
+        CWADebug.fileLogger?.let {
+            viewModelScope.launch(context = Dispatchers.Default) {
+                if (!it.logFile.exists()) return@launch
+
+                val externalPath = File(
+                    context.getExternalFilesDir(null),
+                    "LogFile-${System.currentTimeMillis()}.log"
+                )
+
+                it.logFile.copyTo(externalPath)
+
+                logShareEvent.postValue(externalPath)
+            }
+        }
     }
 
-    fun isCurrentEnvironmentAlternate(): Boolean {
-        return envSetup.currentEnvironment == envSetup.alternativeEnvironment
+    val gmsState by smartLiveData {
+        GoogleServicesState(
+            version = PackageInfoCompat.getLongVersionCode(
+                context.packageManager.getPackageInfo(
+                    GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE,
+                    0
+                )
+            )
+        )
     }
 
     @AssistedInject.Factory
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml
index 313cde74b8255a21b362b66b703f87a9d101c9f3..9f51b5f868023c208e1c39c163a1619ea9b76dcb 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:tools="http://schemas.android.com/tools"
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     tools:ignore="HardcodedText">
 
     <data>
@@ -18,326 +19,456 @@
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_margin="@dimen/spacing_normal"
+            android:layout_margin="@dimen/spacing_tiny"
             android:orientation="vertical">
 
-            <TextView
-                android:id="@+id/label_googlePlayServices_version"
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/debug_container"
+                style="@style/card"
+                android:layout_margin="@dimen/spacing_tiny"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-
-
-            <Switch
-                android:id="@+id/test_api_switch_last_three_hours_from_server"
-                style="@style/body1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="@string/test_api_switch_last_three_hours_from_server"
-                android:theme="@style/switchBase" />
-
-            <Switch
-                android:id="@+id/test_api_switch_background_notifications"
-                style="@style/body1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:text="@string/test_api_switch_background_notifications"
-                android:theme="@style/switchBase" />
+                android:layout_height="wrap_content">
 
-            <Switch
-                android:id="@+id/test_api_switch_test_countries"
-                style="@style/body1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:text="Switch server environment"
-                android:theme="@style/switchBase" />
+                <TextView
+                    android:id="@+id/debug_container_title"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="Debug options"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
 
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:orientation="horizontal"
-                android:paddingTop="8dp"
-                android:layout_height="wrap_content">
+                <Switch
+                    android:id="@+id/three_hour_mode_toggle"
+                    style="@style/body1"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    android:layout_marginTop="@dimen/spacing_small"
+                    android:text="@string/test_api_switch_last_three_hours_from_server"
+                    android:theme="@style/switchBase"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/debug_container_title" />
 
-                <Button
-                    android:layout_width="wrap_content"
+                <Switch
+                    android:id="@+id/background_notifications_toggle"
+                    style="@style/body1"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:id="@+id/test_logfile_share"
-                    android:text="Share log" />
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="@string/test_api_switch_background_notifications"
+                    android:theme="@style/switchBase"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/three_hour_mode_toggle" />
 
                 <Switch
                     android:id="@+id/test_logfile_toggle"
                     style="@style/body1"
-                    android:layout_weight="1"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:layout_weight="1"
                     android:text="Logfile enabled"
-                    android:theme="@style/switchBase" />
-
+                    android:theme="@style/switchBase"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/background_notifications_toggle" />
 
-            </LinearLayout>
-
-            <TextView
-                android:id="@+id/label_exposure_summary"
-                style="@style/headline6"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="16dp"
-                android:accessibilityHeading="true"
-                android:text="@string/test_api_exposure_summary_headline" />
+                <Button
+                    android:id="@+id/test_logfile_share"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="Share log"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/test_logfile_toggle" />
+            </androidx.constraintlayout.widget.ConstraintLayout>
 
-            <TextView
-                android:id="@+id/label_exposure_summary_matchedKeyCount"
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/environment_container"
+                style="@style/card"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/test_api_body_matchedKeyCount" />
+                android:layout_margin="@dimen/spacing_tiny">
 
-            <TextView
-                android:id="@+id/label_exposure_summary_daysSinceLastExposure"
+                <TextView
+                    android:id="@+id/environment_title"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="Server environment"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <TextView
+                    android:id="@+id/environment_cdnurl_download"
+                    style="@style/body2"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/environment_title"
+                    tools:text="Download: ?" />
+
+                <TextView
+                    android:id="@+id/environment_cdnurl_submission"
+                    style="@style/body2"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/environment_cdnurl_download"
+                    tools:text="Submission: ?" />
+
+                <TextView
+                    android:id="@+id/environment_cdnurl_verification"
+                    style="@style/body2"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/environment_cdnurl_submission"
+                    tools:text="Verification: ?" />
+
+                <RadioGroup
+                    android:id="@+id/environment_toggle_group"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:orientation="vertical"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/environment_cdnurl_verification" />
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/gms_container"
+                style="@style/card"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/test_api_body_daysSinceLastExposure" />
+                android:layout_margin="@dimen/spacing_tiny">
 
-            <TextView
-                android:id="@+id/label_exposure_summary_maximumRiskScore"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="@string/test_api_body_maximumRiskScore" />
+                <TextView
+                    android:id="@+id/gms_container_title"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="GMS Infos"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <TextView
+                    android:id="@+id/google_play_services_version_info"
+                    style="@style/body2"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="Google Play Services Version: ?"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/gms_container_title" />
 
-            <TextView
-                android:id="@+id/label_exposure_summary_summationRiskScore"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="@string/test_api_body_summation_risk" />
+            </androidx.constraintlayout.widget.ConstraintLayout>
 
-            <TextView
-                android:id="@+id/label_exposure_summary_attenuation"
+            <LinearLayout
+                android:id="@+id/exposure_summary_container"
+                style="@style/card"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/test_api_body_attenuation" />
+                android:layout_margin="@dimen/spacing_tiny"
+                android:orientation="vertical">
 
-            <Button
-                android:id="@+id/button_api_scan_qr_code"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="@string/test_api_button_scan_qr_code" />
+                <TextView
+                    android:id="@+id/label_exposure_summary"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="@dimen/spacing_tiny"
+                    android:text="@string/test_api_exposure_summary_headline" />
 
-            <Button
-                android:id="@+id/button_api_enter_other_keys"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="@string/test_api_button_enter_other_keys" />
+                <TextView
+                    android:id="@+id/label_exposure_summary_matchedKeyCount"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/test_api_body_matchedKeyCount" />
 
-            <Button
-                android:id="@+id/button_api_get_check_exposure"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="@string/test_api_button_check_exposure" />
+                <TextView
+                    android:id="@+id/label_exposure_summary_daysSinceLastExposure"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/test_api_body_daysSinceLastExposure" />
 
-            <TextView
-                android:id="@+id/label_my_keys"
-                style="@style/headline6"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:accessibilityHeading="true"
-                android:text="@string/test_api_body_my_keys" />
+                <TextView
+                    android:id="@+id/label_exposure_summary_maximumRiskScore"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/test_api_body_maximumRiskScore" />
 
-            <TextView
-                android:id="@+id/label_latest_key_date"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="Latest key is from: -" />
+                <TextView
+                    android:id="@+id/label_exposure_summary_summationRiskScore"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/test_api_body_summation_risk" />
 
-            <TextView
-                android:id="@+id/text_my_keys"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:hint="Your keys will be displayed here"
-                android:lines="5"
-                android:visibility="gone" />
+                <TextView
+                    android:id="@+id/label_exposure_summary_attenuation"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/test_api_body_attenuation" />
 
-            <androidx.viewpager2.widget.ViewPager2
-                android:id="@+id/qr_code_viewpager"
-                android:layout_width="match_parent"
-                android:layout_height="200dp" />
+                <Button
+                    android:id="@+id/button_api_scan_qr_code"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_small"
+                    android:text="@string/test_api_button_scan_qr_code" />
 
-            <TextView
-                android:id="@+id/label_other_keys"
-                style="@style/headline6"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:accessibilityHeading="true"
-                android:text="@string/test_api_body_other_keys" />
+                <Button
+                    android:id="@+id/button_api_enter_other_keys"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="@string/test_api_button_enter_other_keys" />
 
-            <TextView
-                android:id="@+id/text_scanned_key"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
+                <Button
+                    android:id="@+id/button_api_get_check_exposure"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="@string/test_api_button_check_exposure" />
+            </LinearLayout>
 
-            <Button
-                android:id="@+id/button_api_test_start"
-                style="@style/buttonPrimary"
+            <LinearLayout
+                android:id="@+id/mykeys_container"
+                style="@style/card"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/test_api_button_start" />
+                android:layout_margin="@dimen/spacing_tiny"
+                android:orientation="vertical">
 
-            <Button
-                android:id="@+id/button_api_get_exposure_keys"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="@string/test_api_button_get_exposure_keys" />
+                <TextView
+                    android:id="@+id/label_my_keys"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="@dimen/spacing_tiny"
+                    android:text="@string/test_api_body_my_keys" />
 
-            <Button
-                android:id="@+id/button_api_submit_keys"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="@string/test_api_button_submit_keys" />
+                <TextView
+                    android:id="@+id/label_latest_key_date"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="Latest key is from: -" />
 
-            <Button
-                android:id="@+id/button_api_share_my_keys"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="@string/test_api_button_share_my_keys" />
+                <TextView
+                    android:id="@+id/text_my_keys"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="Your keys will be displayed here"
+                    android:lines="5"
+                    android:visibility="gone" />
+
+                <androidx.viewpager2.widget.ViewPager2
+                    android:id="@+id/qr_code_viewpager"
+                    android:layout_width="match_parent"
+                    android:layout_height="200dp" />
+
+                <TextView
+                    android:id="@+id/label_other_keys"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/test_api_body_other_keys" />
 
-            <Button
-                android:id="@+id/button_calculate_risk_level"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="@string/test_api_calculate_risk_level" />
+                <TextView
+                    android:id="@+id/text_scanned_key"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
 
-            <Button
-                android:id="@+id/button_insert_exposure_summary"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="Insert ExposureSummary" />
+            </LinearLayout>
 
-            <Button
-                android:id="@+id/button_retrieve_exposure_summary"
-                style="@style/buttonPrimary"
+            <LinearLayout
+                android:id="@+id/testactions_container"
+                style="@style/card"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="Retrieve ExposureSummary" />
+                android:layout_margin="@dimen/spacing_tiny"
+                android:orientation="vertical">
 
-            <Button
-                android:id="@+id/button_clear_db"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="Clear Internal DB" />
+                <Button
+                    android:id="@+id/button_api_test_start"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/test_api_button_start" />
 
-            <Button
-                android:id="@+id/button_tracing_intervals"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:text="Get Inactive Tracing Intervals" />
+                <Button
+                    android:id="@+id/button_api_get_exposure_keys"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="@string/test_api_button_get_exposure_keys" />
 
-            <Button
-                android:id="@+id/button_tracing_duration_in_retention_period"
-                style="@style/buttonPrimary"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:layout_marginBottom="@dimen/spacing_normal"
-                android:text="Get Active Tracing Duration in Retention Period" />
+                <Button
+                    android:id="@+id/button_api_submit_keys"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="@string/test_api_button_submit_keys" />
 
-            <TextView
-                android:id="@+id/label_country_filter"
-                style="@style/headline6"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:accessibilityHeading="true"
-                android:text="Country Settings" />
+                <Button
+                    android:id="@+id/button_api_share_my_keys"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="@string/test_api_button_share_my_keys" />
 
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
+                <Button
+                    android:id="@+id/button_calculate_risk_level"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="@string/test_api_calculate_risk_level" />
 
-                <EditText
-                    android:id="@+id/input_country_codes_editText"
-                    android:layout_width="0dp"
+                <Button
+                    android:id="@+id/button_insert_exposure_summary"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_weight="1" />
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="Insert ExposureSummary" />
 
                 <Button
-                    android:id="@+id/button_filter_country_codes"
+                    android:id="@+id/button_retrieve_exposure_summary"
                     style="@style/buttonPrimary"
-                    android:layout_width="wrap_content"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:text="Apply" />
-            </LinearLayout>
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="Retrieve ExposureSummary" />
 
-            <TextView
-                android:id="@+id/label_country_code_filter_status"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="Country filter applied for:">
+                <Button
+                    android:id="@+id/button_clear_db"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="Clear Internal DB" />
 
-            </TextView>
+                <Button
+                    android:id="@+id/button_tracing_intervals"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="Get Inactive Tracing Intervals" />
 
-            <TextView
-                android:id="@+id/label_test_api_measure"
-                style="@style/headline6"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_normal"
-                android:accessibilityHeading="true"
-                android:text="Statistics" />
+                <Button
+                    android:id="@+id/button_tracing_duration_in_retention_period"
+                    style="@style/buttonPrimary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    android:text="Get Active Tracing Duration in Retention Period" />
+            </LinearLayout>
 
             <LinearLayout
+                android:id="@+id/country_container"
+                style="@style/card"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:gravity="center_vertical"
-                android:orientation="horizontal">
+                android:layout_margin="@dimen/spacing_tiny"
+                android:orientation="vertical">
 
-                <EditText
-                    android:id="@+id/input_measure_risk_key_repeat_count"
-                    android:layout_width="98dp"
+                <TextView
+                    android:id="@+id/label_country_filter"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:inputType="number"
-                    android:text="1" />
+                    android:text="Country Settings" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                    <EditText
+                        android:id="@+id/input_country_codes_editText"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1" />
+
+                    <Button
+                        android:id="@+id/button_filter_country_codes"
+                        style="@style/buttonPrimary"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="Apply" />
+                </LinearLayout>
+
+                <TextView
+                    android:id="@+id/label_country_code_filter_status"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="Country filter applied for:">
 
-                <Button
-                    android:id="@+id/button_retrieve_diagnosis_keys_and_calc_risk_level"
-                    style="@style/buttonPrimary"
-                    android:layout_width="0dp"
+                </TextView>
+
+                <TextView
+                    android:id="@+id/label_test_api_measure"
+                    style="@style/headline6"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="@dimen/spacing_normal"
-                    android:layout_marginBottom="@dimen/spacing_normal"
-                    android:layout_weight="1"
-                    android:imeOptions="actionDone"
-                    android:text="Measure: Calculate Risk Level / Key Retrieval" />
+                    android:text="Statistics" />
 
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical"
+                    android:orientation="horizontal">
+
+                    <EditText
+                        android:id="@+id/input_measure_risk_key_repeat_count"
+                        android:layout_width="90dp"
+                        android:layout_height="wrap_content"
+                        android:inputType="number"
+                        android:text="1" />
+
+                    <Button
+                        android:id="@+id/button_retrieve_diagnosis_keys_and_calc_risk_level"
+                        style="@style/buttonPrimary"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/spacing_normal"
+                        android:layout_marginBottom="@dimen/spacing_normal"
+                        android:layout_weight="1"
+                        android:imeOptions="actionDone"
+                        android:text="Measure: Calculate Risk Level / Key Retrieval" />
+
+                </LinearLayout>
+
+                <TextView
+                    android:id="@+id/label_test_api_measure_calc_key_status"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="Result: " />
 
             </LinearLayout>
 
-            <TextView
-                android:id="@+id/label_test_api_measure_calc_key_status"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="Result: " />
-
             <de.rki.coronawarnapp.ui.calendar.CalendarView
                 android:id="@+id/calendar_container"
                 android:layout_width="match_parent"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt
index bf7accefe90a76c613fb93f9bd4cd124462ed103..0d18d7a2711ad7b9cefe009156adc87a96704e63 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt
@@ -10,7 +10,6 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.legacy.LegacyKeyCacheMigration
 import de.rki.coronawarnapp.risk.TimeVariables
 import de.rki.coronawarnapp.storage.AppSettings
 import de.rki.coronawarnapp.storage.DeviceStorage
-import de.rki.coronawarnapp.util.CWADebug
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
@@ -78,7 +77,7 @@ class KeyFileDownloader @Inject constructor(
             )
 
             val availableKeys =
-                if (CWADebug.isDebugBuildOrMode && settings.isLast3HourModeEnabled) {
+                if (settings.isLast3HourModeEnabled) {
                     syncMissing3Hours(filteredCountries, DEBUG_HOUR_LIMIT)
                     keyCache.getEntriesForType(CachedKeyInfo.Type.COUNTRY_HOUR)
                 } else {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt
index b00775b2c59dc2ee56b8d32c3348ac7513bc3756..8fc88e77f9cb9b5ce3f49404a59b1d16a5386d04 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt
@@ -8,5 +8,4 @@ object BuildConfigWrap {
 
     val ENVIRONMENT_JSONDATA = BuildConfig.ENVIRONMENT_JSONDATA
     val ENVIRONMENT_TYPE_DEFAULT = BuildConfig.ENVIRONMENT_TYPE_DEFAULT
-    val ENVIRONMENT_TYPE_ALTERNATIVE = BuildConfig.ENVIRONMENT_TYPE_ALTERNATIVE
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
index 1150661ab79ef27596bdb1554247f64ce0462c54..6b4c9ae3e40838f0d8546c68057d97a1a62cf915 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
@@ -8,6 +8,7 @@ import de.rki.coronawarnapp.environment.EnvironmentSetup.ENVKEY.DOWNLOAD
 import de.rki.coronawarnapp.environment.EnvironmentSetup.ENVKEY.SUBMISSION
 import de.rki.coronawarnapp.environment.EnvironmentSetup.ENVKEY.VERIFICATION
 import de.rki.coronawarnapp.environment.EnvironmentSetup.ENVKEY.VERIFICATION_KEYS
+import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnvironmentType
 import de.rki.coronawarnapp.util.CWADebug
 import timber.log.Timber
 import javax.inject.Inject
@@ -31,7 +32,13 @@ class EnvironmentSetup @Inject constructor(
         DEV("DEV"),
         WRU("WRU"),
         WRU_XA("WRU-XA"), // (aka ACME)
-        WRU_XD("WRU-XD") // (aka Germany)
+        WRU_XD("WRU-XD"); // (aka Germany)
+
+        companion object {
+            internal fun String.toEnvironmentType(): Type = values().single {
+                it.rawKey == this
+            }
+        }
     }
 
     private val prefs by lazy {
@@ -48,9 +55,6 @@ class EnvironmentSetup @Inject constructor(
     val defaultEnvironment: Type
         get() = BuildConfigWrap.ENVIRONMENT_TYPE_DEFAULT.toEnvironmentType()
 
-    val alternativeEnvironment: Type
-        get() = BuildConfigWrap.ENVIRONMENT_TYPE_ALTERNATIVE.toEnvironmentType()
-
     var currentEnvironment: Type
         get() {
             return prefs
@@ -95,10 +99,6 @@ class EnvironmentSetup @Inject constructor(
     val appConfigVerificationKey: String
         get() = getEnvironmentValue(VERIFICATION_KEYS)
 
-    private fun String.toEnvironmentType(): Type = Type.values().single {
-        it.rawKey == this
-    }
-
     companion object {
         private const val PKEY_CURRENT_ENVINROMENT = "environment.current"
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppSettings.kt
index f687e24adf587300bfb131d4bc42d1d9b6bffde6..c89378bffdc07c8cb218c5b1bbdbe884f8f3daff 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppSettings.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppSettings.kt
@@ -1,5 +1,6 @@
 package de.rki.coronawarnapp.storage
 
+import de.rki.coronawarnapp.util.CWADebug
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -7,5 +8,5 @@ import javax.inject.Singleton
 class AppSettings @Inject constructor() {
 
     val isLast3HourModeEnabled: Boolean
-        get() = LocalData.last3HoursMode()
+        get() = LocalData.last3HoursMode() && CWADebug.isDebugBuildOrMode
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt
index 6ba82485f07bf8de8b8f829e99e450e487dcfb7d..99471c8af0e066c09ec73a50c8a73d13efe918e1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.util.ui
 
 import androidx.appcompat.app.AppCompatActivity
 import androidx.fragment.app.Fragment
+import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Observer
 
@@ -12,3 +13,17 @@ fun <T> LiveData<T>.observe2(fragment: Fragment, callback: (T) -> Unit) {
 fun <T> LiveData<T>.observe2(activity: AppCompatActivity, callback: (T) -> Unit) {
     observe(activity, Observer { callback.invoke(it) })
 }
+
+fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner? = null, observer: Observer<T>) {
+    val internalObserver = object : Observer<T> {
+        override fun onChanged(t: T?) {
+            observer.onChanged(t)
+            removeObserver(this)
+        }
+    }
+    if (lifecycleOwner == null) {
+        observeForever(internalObserver)
+    } else {
+        observe(lifecycleOwner, internalObserver)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cdd903d6510f3e194b270797433d9661285dca25
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt
@@ -0,0 +1,53 @@
+package de.rki.coronawarnapp.util.ui
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+fun <T : Any> ViewModel.smartLiveData(
+    dispatcher: CoroutineDispatcher = Dispatchers.Default,
+    initAction: suspend () -> T
+) = SmartLiveDataProperty(dispatcher, initAction)
+
+class SmartLiveDataProperty<T : Any>(
+    private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
+    private val initialValueProvider: suspend () -> T
+) : ReadOnlyProperty<ViewModel, SmartLiveData<T>> {
+
+    private var liveData: SmartLiveData<T>? = null
+
+    override fun getValue(
+        thisRef: ViewModel,
+        property: KProperty<*>
+    ): SmartLiveData<T> {
+        liveData?.let {
+            return@getValue it
+        }
+
+        return SmartLiveData<T>(thisRef, dispatcher).also {
+            liveData = it
+            thisRef.viewModelScope.launch(context = dispatcher) {
+                it.postValue(initialValueProvider())
+            }
+        }
+    }
+}
+
+class SmartLiveData<T : Any>(
+    private val viewModel: ViewModel,
+    private val dispatcher: CoroutineDispatcher
+) : MutableLiveData<T>() {
+
+    fun update(updateAction: (T) -> T) {
+        observeOnce {
+            viewModel.viewModelScope.launch(context = dispatcher) {
+                postValue(updateAction(it))
+            }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b5be612335123304ec35db0e87d0f3de55a4badc
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewExtensions.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.util.ui
+
+import android.view.View
+
+fun View.setGone(gone: Boolean) {
+    visibility = if (gone) View.GONE else View.VISIBLE
+}
+
+fun View.setInvisible(invisible: Boolean) {
+    visibility = if (invisible) View.INVISIBLE else View.VISIBLE
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt
index 25746dcf25305d46b8e8e11271198ae5352b94b3..a39102dbedd78b885a5de32443d1093493ada3f3 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt
@@ -10,7 +10,6 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.legacy.LegacyKeyCacheMigration
 import de.rki.coronawarnapp.storage.AppSettings
 import de.rki.coronawarnapp.storage.DeviceStorage
 import de.rki.coronawarnapp.storage.InsufficientStorageException
-import de.rki.coronawarnapp.util.CWADebug
 import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
@@ -20,7 +19,6 @@ import io.mockk.coVerify
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.mockk
-import io.mockk.mockkObject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
 import org.joda.time.Instant
@@ -33,13 +31,16 @@ import org.junit.jupiter.api.extension.ExtendWith
 import testhelpers.BaseIOTest
 import testhelpers.extensions.CoroutinesTestExtension
 import testhelpers.extensions.InstantExecutorExtension
+import testhelpers.flakyTest
 import timber.log.Timber
 import java.io.File
 import java.io.IOException
+import kotlin.time.ExperimentalTime
 
 /**
  * CachedKeyFileHolder test.
  */
+@ExperimentalTime
 @ExperimentalCoroutinesApi
 @ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class)
 class KeyFileDownloaderTest : BaseIOTest() {
@@ -68,8 +69,6 @@ class KeyFileDownloaderTest : BaseIOTest() {
         testDir.mkdirs()
         testDir.exists() shouldBe true
 
-        mockkObject(CWADebug)
-        every { CWADebug.isDebugBuildOrMode } returns false
         every { settings.isLast3HourModeEnabled } returns false
 
         coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf(
@@ -129,11 +128,10 @@ class KeyFileDownloaderTest : BaseIOTest() {
             val type = arg<CachedKeyInfo.Type>(0)
             keyRepoData.values.filter { it.type == type }.map { it to File(testDir, it.id) }
         }
-        coEvery { keyCache.getAllCachedKeys() } returns keyRepoData.values.map {
-            it to File(
-                testDir,
-                it.id
-            )
+        coEvery { keyCache.getAllCachedKeys() } answers {
+            keyRepoData.values.map {
+                it to File(testDir, it.id)
+            }
         }
         coEvery { keyCache.delete(any()) } answers {
             val keyInfos = arg<List<CachedKeyInfo>>(0)
@@ -227,7 +225,8 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `wanted country list is empty, day mode`() {
+    fun `wanted country list is empty, day mode`() = flakyTest {
+
         val downloader = createDownloader()
         runBlocking {
             downloader.asyncFetchKeyFiles(emptyList()) shouldBe emptyList()
@@ -235,8 +234,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `wanted country list is empty, hour mode`() {
-        every { CWADebug.isDebugBuildOrMode } returns true
+    fun `wanted country list is empty, hour mode`() = flakyTest {
         every { settings.isLast3HourModeEnabled } returns true
 
         val downloader = createDownloader()
@@ -246,7 +244,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `fetching is aborted in day if not enough free storage`() {
+    fun `fetching is aborted in day if not enough free storage`() = flakyTest {
         coEvery { deviceStorage.requireSpacePrivateStorage(1048576L) } throws InsufficientStorageException(
             mockk(relaxed = true)
         )
@@ -261,8 +259,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `fetching is aborted in hour if not enough free storage`() {
-        every { CWADebug.isDebugBuildOrMode } returns true
+    fun `fetching is aborted in hour if not enough free storage`() = flakyTest {
         every { settings.isLast3HourModeEnabled } returns true
 
         coEvery { deviceStorage.requireSpacePrivateStorage(67584L) } throws InsufficientStorageException(
@@ -279,7 +276,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `error during country index fetch`() {
+    fun `error during country index fetch`() = flakyTest {
         coEvery { diagnosisKeyServer.getCountryIndex() } throws IOException()
 
         val downloader = createDownloader()
@@ -292,7 +289,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `day fetch without prior data`() {
+    fun `day fetch without prior data`() = flakyTest {
         val downloader = createDownloader()
 
         runBlocking {
@@ -333,7 +330,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `day fetch with existing data`() {
+    fun `day fetch with existing data`() = flakyTest {
         mockAddData(
             type = CachedKeyInfo.Type.COUNTRY_DAY,
             location = LocationCode("DE"),
@@ -380,7 +377,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `day fetch deletes stale data`() {
+    fun `day fetch deletes stale data`() = flakyTest {
         coEvery { diagnosisKeyServer.getDayIndex(LocationCode("DE")) } returns listOf(
             LocalDate.parse("2020-09-02")
         )
@@ -428,7 +425,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `day fetch skips single download failures`() {
+    fun `day fetch skips single download failures`() = flakyTest {
         var dlCounter = 0
         coEvery { diagnosisKeyServer.downloadKeyFile(any(), any(), any(), any(), any()) } answers {
             dlCounter++
@@ -455,8 +452,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `last3Hours fetch without prior data`() {
-        every { CWADebug.isDebugBuildOrMode } returns true
+    fun `last3Hours fetch without prior data`() = flakyTest {
         every { settings.isLast3HourModeEnabled } returns true
 
         val downloader = createDownloader()
@@ -515,8 +511,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `last3Hours fetch with prior data`() {
-        every { CWADebug.isDebugBuildOrMode } returns true
+    fun `last3Hours fetch with prior data`() = flakyTest {
         every { settings.isLast3HourModeEnabled } returns true
 
         mockAddData(
@@ -582,8 +577,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `last3Hours fetch deletes stale data`() {
-        every { CWADebug.isDebugBuildOrMode } returns true
+    fun `last3Hours fetch deletes stale data`() = flakyTest {
         every { settings.isLast3HourModeEnabled } returns true
 
         val (staleKey1, _) = mockAddData(
@@ -665,8 +659,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `last3Hours fetch skips single download failures`() {
-        every { CWADebug.isDebugBuildOrMode } returns true
+    fun `last3Hours fetch skips single download failures`() = flakyTest {
         every { settings.isLast3HourModeEnabled } returns true
 
         var dlCounter = 0
@@ -695,7 +688,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `not completed cache entries are overwritten`() {
+    fun `not completed cache entries are overwritten`() = flakyTest {
         mockAddData(
             type = CachedKeyInfo.Type.COUNTRY_DAY,
             location = LocationCode("DE"),
@@ -723,7 +716,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `database errors do not abort the whole process`() {
+    fun `database errors do not abort the whole process`() = flakyTest {
         var completionCounter = 0
         coEvery { keyCache.markKeyComplete(any(), any()) } answers {
             completionCounter++
@@ -751,7 +744,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `store server md5`() {
+    fun `store server md5`() = flakyTest {
         coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf(LocationCode("DE"))
         coEvery { diagnosisKeyServer.getDayIndex(LocationCode("DE")) } returns listOf(
             LocalDate.parse("2020-09-01")
@@ -781,7 +774,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     }
 
     @Test
-    fun `use local MD5 as fallback if there is none available from the server`() {
+    fun `use local MD5 as fallback if there is none available from the server`() = flakyTest {
         coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf(LocationCode("DE"))
         coEvery { diagnosisKeyServer.getDayIndex(LocationCode("DE")) } returns listOf(
             LocalDate.parse("2020-09-01")
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/BuildConfigWrapTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/BuildConfigWrapTest.kt
index c346d5ce69a7dde396963421fa4b539d42539e97..cded06c92cf5f0f3783bcc1bb2c3f4684d72e03e 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/BuildConfigWrapTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/BuildConfigWrapTest.kt
@@ -1,7 +1,6 @@
 package de.rki.coronawarnapp.environment
 
-import de.rki.coronawarnapp.BuildConfig
-import io.kotest.matchers.shouldBe
+import io.kotest.matchers.collections.shouldBeIn
 import org.junit.jupiter.api.Test
 import testhelpers.BaseTest
 
@@ -9,11 +8,6 @@ class BuildConfigWrapTest : BaseTest() {
 
     @Test
     fun `default environment type should be DEV`() {
-        BuildConfigWrap.ENVIRONMENT_TYPE_DEFAULT shouldBe BuildConfig.ENVIRONMENT_TYPE_DEFAULT
-    }
-
-    @Test
-    fun `alternative environment type should be WRU-XD`() {
-        BuildConfigWrap.ENVIRONMENT_TYPE_ALTERNATIVE shouldBe BuildConfig.ENVIRONMENT_TYPE_ALTERNATIVE
+        BuildConfigWrap.ENVIRONMENT_TYPE_DEFAULT shouldBeIn listOf("DEV", "INT", "WRU-XD", "PROD")
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt
index 3dc9e4407217e55ca4d2b814e71d09f191ad805a..ae27016b8c1cc61c8c5d3df12855859baafc22e5 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt
@@ -1,6 +1,7 @@
 package de.rki.coronawarnapp.environment
 
 import android.content.Context
+import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnvironmentType
 import de.rki.coronawarnapp.util.CWADebug
 import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.matchers.shouldBe
@@ -70,34 +71,34 @@ class EnvironmentSetupTest : BaseTest() {
 
     @Test
     fun `default environment type is set correctly`() {
-        if (CWADebug.buildFlavor == CWADebug.BuildFlavor.DEVICE_FOR_TESTERS) {
-            createEnvSetup().defaultEnvironment shouldBe EnvironmentSetup.Type.DEV
-            createEnvSetup().alternativeEnvironment shouldBe EnvironmentSetup.Type.WRU_XA
-        } else {
-            createEnvSetup().defaultEnvironment shouldBe EnvironmentSetup.Type.PRODUCTION
-            createEnvSetup().alternativeEnvironment shouldBe EnvironmentSetup.Type.PRODUCTION
-        }
+        createEnvSetup().defaultEnvironment shouldBe BuildConfigWrap.ENVIRONMENT_TYPE_DEFAULT.toEnvironmentType()
     }
 
     @Test
     fun `switching the default type is persisted in storage (preferences)`() {
+        every { BuildConfigWrap.ENVIRONMENT_TYPE_DEFAULT } returns EnvironmentSetup.Type.DEV.rawKey
         if (CWADebug.buildFlavor == CWADebug.BuildFlavor.DEVICE_FOR_TESTERS) {
             createEnvSetup().apply {
                 defaultEnvironment shouldBe EnvironmentSetup.Type.DEV
-                currentEnvironment shouldBe EnvironmentSetup.Type.DEV
+                currentEnvironment shouldBe defaultEnvironment
                 currentEnvironment = EnvironmentSetup.Type.WRU
                 currentEnvironment shouldBe EnvironmentSetup.Type.WRU
             }
+            mockPreferences.dataMapPeek.values.single() shouldBe EnvironmentSetup.Type.WRU.rawKey
             createEnvSetup().apply {
                 defaultEnvironment shouldBe EnvironmentSetup.Type.DEV
                 currentEnvironment shouldBe EnvironmentSetup.Type.WRU
             }
         } else {
             createEnvSetup().apply {
-                defaultEnvironment shouldBe EnvironmentSetup.Type.PRODUCTION
-                currentEnvironment shouldBe EnvironmentSetup.Type.PRODUCTION
-                currentEnvironment = EnvironmentSetup.Type.DEV
-                currentEnvironment shouldBe EnvironmentSetup.Type.PRODUCTION
+                defaultEnvironment shouldBe EnvironmentSetup.Type.DEV
+                currentEnvironment shouldBe defaultEnvironment
+                currentEnvironment = EnvironmentSetup.Type.WRU
+                currentEnvironment shouldBe defaultEnvironment
+            }
+            mockPreferences.dataMapPeek.values shouldBe emptyList()
+            createEnvSetup().apply {
+                currentEnvironment shouldBe defaultEnvironment
             }
         }
     }
diff --git a/Corona-Warn-App/src/test/java/testhelpers/KotestExtensions.kt b/Corona-Warn-App/src/test/java/testhelpers/KotestExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4841a1926aff437c96ae59385930252709e2ff63
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/testhelpers/KotestExtensions.kt
@@ -0,0 +1,27 @@
+package testhelpers
+
+import io.kotest.assertions.retry
+import kotlinx.coroutines.runBlocking
+import timber.log.Timber
+import kotlin.time.ExperimentalTime
+import kotlin.time.seconds
+
+/**
+ * TODO Remove flaky tests where possible
+ * A flaky test is a test that sometimes fails and sometimes doesn't
+ * Feel free to find usages of this method and to refactor them such that they are working reliably.
+ */
+@ExperimentalTime
+fun <T> flakyTest(flakyAction: () -> T): Unit = runBlocking {
+    retry(
+        maxRetry = 10,
+        timeout = 60.seconds,
+        delay = 1.seconds,
+        multiplier = 1,
+        exceptionClass = Exception::class,
+        f = {
+            Timber.v("Flaky test try...")
+            flakyAction()
+        }
+    )
+}
diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModelTest.kt
index 26d1132fe8e5d51d234a703ab9cc9f44c3d4d26e..9003b9ce6ddb1c575ae6258e00ce84eb221c4b4f 100644
--- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModelTest.kt
+++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModelTest.kt
@@ -1,32 +1,47 @@
 package de.rki.coronawarnapp.test.api.ui
 
+import android.content.Context
+import androidx.lifecycle.Observer
 import de.rki.coronawarnapp.environment.EnvironmentSetup
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
+import io.mockk.Runs
 import io.mockk.clearAllMocks
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.mockk
 import io.mockk.verify
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
 import testhelpers.BaseTest
+import testhelpers.extensions.CoroutinesTestExtension
 import testhelpers.extensions.InstantExecutorExtension
+import testhelpers.flakyTest
+import kotlin.time.ExperimentalTime
 
-@ExtendWith(InstantExecutorExtension::class)
+@ExperimentalTime
+@ExperimentalCoroutinesApi
+@ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class)
 class TestForApiFragmentViewModelTest : BaseTest() {
 
     @MockK private lateinit var environmentSetup: EnvironmentSetup
+    @MockK private lateinit var context: Context
 
-    var currentEnvironment = EnvironmentSetup.Type.DEV
+    private var currentEnvironment = EnvironmentSetup.Type.DEV
 
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
+        currentEnvironment = EnvironmentSetup.Type.DEV
 
         every { environmentSetup.defaultEnvironment } returns EnvironmentSetup.Type.DEV
-        every { environmentSetup.alternativeEnvironment } returns EnvironmentSetup.Type.WRU_XA
+        every { environmentSetup.submissionCdnUrl } returns "submissionUrl"
+        every { environmentSetup.downloadCdnUrl } returns "downloadUrl"
+        every { environmentSetup.verificationCdnUrl } returns "verificationUrl"
 
         every { environmentSetup.currentEnvironment = any() } answers {
             currentEnvironment = arg(0)
@@ -42,24 +57,45 @@ class TestForApiFragmentViewModelTest : BaseTest() {
         clearAllMocks()
     }
 
-    private fun createViewModel(): TestForApiFragmentViewModel {
-        return TestForApiFragmentViewModel(environmentSetup)
-    }
+    private fun createViewModel(): TestForApiFragmentViewModel = TestForApiFragmentViewModel(
+        envSetup = environmentSetup,
+        context = context
+    )
 
     @Test
-    fun `toggeling the env works`() {
+    fun `toggeling the env works`() = flakyTest {
+        currentEnvironment = EnvironmentSetup.Type.DEV
         val vm = createViewModel()
 
-        currentEnvironment = EnvironmentSetup.Type.DEV
-        vm.isCurrentEnvironmentAlternate() shouldBe false
-        currentEnvironment = EnvironmentSetup.Type.WRU_XA
-        vm.isCurrentEnvironmentAlternate() shouldBe true
-
-        vm.environmentChangeEvent.value shouldBe null
-        vm.toggleEnvironment(true)
-        vm.environmentChangeEvent.value shouldBe EnvironmentSetup.Type.WRU_XA
-        verify { environmentSetup.currentEnvironment = EnvironmentSetup.Type.WRU_XA }
-        vm.toggleEnvironment(false)
-        verify { environmentSetup.currentEnvironment = EnvironmentSetup.Type.DEV }
+        val states = mutableListOf<EnvironmentState>()
+        val observerState = mockk<Observer<EnvironmentState>>()
+        every { observerState.onChanged(capture(states)) } just Runs
+        vm.environmentState.observeForever(observerState)
+
+        val events = mutableListOf<EnvironmentSetup.Type>()
+        val observerEvent = mockk<Observer<EnvironmentSetup.Type>>()
+        every { observerEvent.onChanged(capture(events)) } just Runs
+        vm.environmentChangeEvent.observeForever(observerEvent)
+
+        vm.selectEnvironmentTytpe(EnvironmentSetup.Type.DEV.rawKey)
+        vm.selectEnvironmentTytpe(EnvironmentSetup.Type.WRU_XA.rawKey)
+
+        verify(exactly = 3, timeout = 3000) { observerState.onChanged(any()) }
+        verify(exactly = 2, timeout = 3000) { observerEvent.onChanged(any()) }
+
+        states[0].apply {
+            current shouldBe EnvironmentSetup.Type.DEV
+        }
+
+        states[1].apply {
+            current shouldBe EnvironmentSetup.Type.DEV
+        }
+        events[0] shouldBe EnvironmentSetup.Type.DEV
+
+
+        states[2].apply {
+            current shouldBe EnvironmentSetup.Type.WRU_XA
+        }
+        events[1] shouldBe EnvironmentSetup.Type.WRU_XA
     }
 }