From 75c4f38e1059dcb0de69f2b8c216446d666b1d4e Mon Sep 17 00:00:00 2001
From: Matthias Urhahn <matthias.urhahn@sap.com>
Date: Thu, 15 Oct 2020 11:51:43 +0200
Subject: [PATCH] Change "last3HoursMode" to "24 hour mode" and cleanup related
 code (DEV) (#1395)

* Switch 3 to 24 hour mode

* Fix incorrect toast message.

* Create `TestSettings` class.

* Adjust test to handle 24 hour mode.

* Adjust test to check for day rollover in hourly keypkg mode.

Co-authored-by: Ralf Gehrer <ralfgehrer@users.noreply.github.com>
---
 .../test/api/ui/DebugOptionsState.kt          |  2 +-
 .../test/api/ui/TestForAPIFragment.kt         | 10 ++-
 .../api/ui/TestForApiFragmentViewModel.kt     | 15 ++--
 .../res/layout/fragment_test_for_a_p_i.xml    |  6 +-
 .../download/KeyFileDownloader.kt             |  8 +--
 .../rki/coronawarnapp/storage/AppSettings.kt  | 12 ----
 .../de/rki/coronawarnapp/storage/LocalData.kt | 13 ----
 .../rki/coronawarnapp/storage/TestSettings.kt | 30 ++++++++
 .../src/main/res/values-bg/strings.xml        |  2 -
 .../src/main/res/values-de/strings.xml        |  2 -
 .../src/main/res/values-en/strings.xml        |  2 -
 .../src/main/res/values-pl/strings.xml        |  2 -
 .../src/main/res/values-ro/strings.xml        |  2 -
 .../src/main/res/values-tr/strings.xml        |  2 -
 .../src/main/res/values/strings.xml           |  2 -
 .../download/KeyFileDownloaderTest.kt         | 64 +++++++++--------
 .../coronawarnapp/storage/TestSettingsTest.kt | 69 +++++++++++++++++++
 .../test/java/testhelpers/KotestExtensions.kt |  4 +-
 .../api/ui/TestForApiFragmentViewModelTest.kt |  5 +-
 19 files changed, 155 insertions(+), 97 deletions(-)
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppSettings.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/TestSettingsTest.kt

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
index a86be3932..1b6dfba3e 100644
--- 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
@@ -2,5 +2,5 @@ package de.rki.coronawarnapp.test.api.ui
 
 data class DebugOptionsState(
     val areNotificationsEnabled: Boolean,
-    val is3HourModeEnabled: Boolean
+    val isHourlyTestingMode: Boolean
 )
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 56b698bd9..f9a8c07a3 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
@@ -140,12 +140,10 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
         qrPager.adapter = qrPagerAdapter
 
         // Debug card
-        binding.threeHourModeToggle.apply {
-            setOnClickListener { vm.setLast3HoursMode(isChecked) }
-        }
-        vm.last3HourToggleEvent.observe2(this) {
-            showToast("Last 3 Hours Mode is activated: $it")
+        binding.hourlyKeyPkgMode.apply {
+            setOnClickListener { vm.setHourlyKeyPkgMode(isChecked) }
         }
+
         binding.backgroundNotificationsToggle.apply {
             setOnClickListener { vm.setBackgroundNotifications(isChecked) }
         }
@@ -155,7 +153,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
         vm.debugOptionsState.observe2(this) { state ->
             binding.apply {
                 backgroundNotificationsToggle.isChecked = state.areNotificationsEnabled
-                threeHourModeToggle.isChecked = state.is3HourModeEnabled
+                hourlyKeyPkgMode.isChecked = state.isHourlyTestingMode
             }
         }
         binding.testLogfileToggle.apply {
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 47f89e72e..d18f0f814 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
@@ -11,6 +11,7 @@ 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.storage.TestSettings
 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
@@ -26,24 +27,22 @@ import java.io.File
 
 class TestForApiFragmentViewModel @AssistedInject constructor(
     @AppContext private val context: Context,
-    private val envSetup: EnvironmentSetup
+    private val envSetup: EnvironmentSetup,
+    private val testSettings: TestSettings
 ) : CWAViewModel() {
 
     val debugOptionsState by smartLiveData {
         DebugOptionsState(
             areNotificationsEnabled = LocalData.backgroundNotification(),
-            is3HourModeEnabled = LocalData.last3HoursMode()
+            isHourlyTestingMode = testSettings.isHourKeyPkgMode
         )
     }
 
-    val last3HourToggleEvent = SingleLiveEvent<Boolean>()
-
-    fun setLast3HoursMode(enabled: Boolean) {
+    fun setHourlyKeyPkgMode(enabled: Boolean) {
         debugOptionsState.update {
-            LocalData.last3HoursMode(enabled)
-            it.copy(is3HourModeEnabled = enabled)
+            testSettings.isHourKeyPkgMode = enabled
+            it.copy(isHourlyTestingMode = enabled)
         }
-        last3HourToggleEvent.postValue(enabled)
     }
 
     val environmentState by smartLiveData {
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 9f51b5f86..41cce3893 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
@@ -40,12 +40,12 @@
                     app:layout_constraintTop_toTopOf="parent" />
 
                 <Switch
-                    android:id="@+id/three_hour_mode_toggle"
+                    android:id="@+id/hourly_key_pkg_mode"
                     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:text="Hourly keyfile mode (last 24)"
                     android:theme="@style/switchBase"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
@@ -61,7 +61,7 @@
                     android:theme="@style/switchBase"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@+id/three_hour_mode_toggle" />
+                    app:layout_constraintTop_toBottomOf="@+id/hourly_key_pkg_mode" />
 
                 <Switch
                     android:id="@+id/test_logfile_toggle"
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 7c70a16f9..60f6aca34 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
@@ -8,8 +8,8 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKeyInfo
 import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
 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.storage.TestSettings
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
@@ -28,7 +28,7 @@ class KeyFileDownloader @Inject constructor(
     private val keyServer: DiagnosisKeyServer,
     private val keyCache: KeyCacheRepository,
     private val legacyKeyCache: LegacyKeyCacheMigration,
-    private val settings: AppSettings,
+    private val testSettings: TestSettings,
     private val dispatcherProvider: DispatcherProvider
 ) {
 
@@ -78,7 +78,7 @@ class KeyFileDownloader @Inject constructor(
             )
 
             val availableKeys =
-                if (settings.isLast3HourModeEnabled) {
+                if (testSettings.isHourKeyPkgMode) {
                     syncMissing3Hours(filteredCountries, DEBUG_HOUR_LIMIT)
                     keyCache.getEntriesForType(CachedKeyInfo.Type.COUNTRY_HOUR)
                 } else {
@@ -338,7 +338,7 @@ class KeyFileDownloader @Inject constructor(
 
     companion object {
         private val TAG: String? = KeyFileDownloader::class.simpleName
-        private const val DEBUG_HOUR_LIMIT = 3
+        private const val DEBUG_HOUR_LIMIT = 24
 
         // Daymode: ~512KB per day, ~14 days
         // Hourmode: ~20KB per hour, 24 hours, also ~512KB
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
deleted file mode 100644
index c89378bff..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppSettings.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package de.rki.coronawarnapp.storage
-
-import de.rki.coronawarnapp.util.CWADebug
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class AppSettings @Inject constructor() {
-
-    val isLast3HourModeEnabled: Boolean
-        get() = LocalData.last3HoursMode() && CWADebug.isDebugBuildOrMode
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt
index 30e0c58f6..a7fbff324 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt
@@ -682,19 +682,6 @@ object LocalData {
         CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan), null
     )
 
-    fun last3HoursMode(value: Boolean) = getSharedPreferenceInstance().edit(true) {
-        putBoolean(
-            CoronaWarnApplication.getAppContext()
-                .getString(R.string.preference_last_three_hours_from_server),
-            value
-        )
-    }
-
-    fun last3HoursMode(): Boolean = getSharedPreferenceInstance().getBoolean(
-        CoronaWarnApplication.getAppContext()
-            .getString(R.string.preference_last_three_hours_from_server), false
-    )
-
     fun backgroundNotification(value: Boolean) = getSharedPreferenceInstance().edit(true) {
         putBoolean(
             CoronaWarnApplication.getAppContext()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt
new file mode 100644
index 000000000..11152b8ff
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.storage
+
+import android.content.Context
+import androidx.core.content.edit
+import de.rki.coronawarnapp.util.CWADebug
+import de.rki.coronawarnapp.util.di.AppContext
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class TestSettings @Inject constructor(
+    @AppContext private val context: Context
+) {
+    private val prefs by lazy {
+        context.getSharedPreferences("test_settings", Context.MODE_PRIVATE)
+    }
+
+    var isHourKeyPkgMode: Boolean
+        get() {
+            val value = prefs.getBoolean(PKEY_HOURLY_TESTING_MODE, false)
+            return value && CWADebug.isDeviceForTestersBuild
+        }
+        set(value) = prefs.edit {
+            putBoolean(PKEY_HOURLY_TESTING_MODE, value)
+        }
+
+    companion object {
+        private const val PKEY_HOURLY_TESTING_MODE = "diagnosiskeys.hourlytestmode"
+    }
+}
diff --git a/Corona-Warn-App/src/main/res/values-bg/strings.xml b/Corona-Warn-App/src/main/res/values-bg/strings.xml
index d38cc68a8..f7dc5b021 100644
--- a/Corona-Warn-App/src/main/res/values-bg/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml
@@ -66,8 +66,6 @@
     <!-- NOTR -->
     <string name="preference_teletan"><xliff:g id="preference">"preference_teletan"</xliff:g></string>
     <!-- NOTR -->
-    <string name="preference_last_three_hours_from_server"><xliff:g id="preference">"preference_last_three_hours_from_server"</xliff:g></string>
-    <!-- NOTR -->
     <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml
index c5f35e085..622ccee35 100644
--- a/Corona-Warn-App/src/main/res/values-de/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/strings.xml
@@ -67,8 +67,6 @@
     <!-- NOTR -->
     <string name="preference_teletan"><xliff:g id="preference">"preference_teletan"</xliff:g></string>
     <!-- NOTR -->
-    <string name="preference_last_three_hours_from_server"><xliff:g id="preference">"preference_last_three_hours_from_server"</xliff:g></string>
-    <!-- NOTR -->
     <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
diff --git a/Corona-Warn-App/src/main/res/values-en/strings.xml b/Corona-Warn-App/src/main/res/values-en/strings.xml
index 4bd148520..4cecbea3b 100644
--- a/Corona-Warn-App/src/main/res/values-en/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/strings.xml
@@ -66,8 +66,6 @@
     <!-- NOTR -->
     <string name="preference_teletan"><xliff:g id="preference">"preference_teletan"</xliff:g></string>
     <!-- NOTR -->
-    <string name="preference_last_three_hours_from_server"><xliff:g id="preference">"preference_last_three_hours_from_server"</xliff:g></string>
-    <!-- NOTR -->
     <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
diff --git a/Corona-Warn-App/src/main/res/values-pl/strings.xml b/Corona-Warn-App/src/main/res/values-pl/strings.xml
index 0e9f29f0f..63847ae8c 100644
--- a/Corona-Warn-App/src/main/res/values-pl/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml
@@ -66,8 +66,6 @@
     <!-- NOTR -->
     <string name="preference_teletan"><xliff:g id="preference">"preference_teletan"</xliff:g></string>
     <!-- NOTR -->
-    <string name="preference_last_three_hours_from_server"><xliff:g id="preference">"preference_last_three_hours_from_server"</xliff:g></string>
-    <!-- NOTR -->
     <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
diff --git a/Corona-Warn-App/src/main/res/values-ro/strings.xml b/Corona-Warn-App/src/main/res/values-ro/strings.xml
index f65289c55..d5fb207a3 100644
--- a/Corona-Warn-App/src/main/res/values-ro/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml
@@ -66,8 +66,6 @@
     <!-- NOTR -->
     <string name="preference_teletan"><xliff:g id="preference">"preference_teletan"</xliff:g></string>
     <!-- NOTR -->
-    <string name="preference_last_three_hours_from_server"><xliff:g id="preference">"preference_last_three_hours_from_server"</xliff:g></string>
-    <!-- NOTR -->
     <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
diff --git a/Corona-Warn-App/src/main/res/values-tr/strings.xml b/Corona-Warn-App/src/main/res/values-tr/strings.xml
index 2c2df2c62..7c38e8f7f 100644
--- a/Corona-Warn-App/src/main/res/values-tr/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml
@@ -66,8 +66,6 @@
     <!-- NOTR -->
     <string name="preference_teletan"><xliff:g id="preference">"preference_teletan"</xliff:g></string>
     <!-- NOTR -->
-    <string name="preference_last_three_hours_from_server"><xliff:g id="preference">"preference_last_three_hours_from_server"</xliff:g></string>
-    <!-- NOTR -->
     <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index 01bc71d86..1a60ecb5d 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -67,8 +67,6 @@
     <!-- NOTR -->
     <string name="preference_teletan"><xliff:g id="preference">"preference_teletan"</xliff:g></string>
     <!-- NOTR -->
-    <string name="preference_last_three_hours_from_server"><xliff:g id="preference">"preference_last_three_hours_from_server"</xliff:g></string>
-    <!-- NOTR -->
     <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
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 960dae206..b105a1b36 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
@@ -8,9 +8,9 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKeyInfo
 import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKeyInfo.Type
 import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
 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.storage.TestSettings
 import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
@@ -55,7 +55,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
     private lateinit var deviceStorage: DeviceStorage
 
     @MockK
-    private lateinit var settings: AppSettings
+    private lateinit var testSettings: TestSettings
 
     private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!)
     private val keyRepoData = mutableMapOf<String, CachedKeyInfo>()
@@ -70,7 +70,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
         testDir.mkdirs()
         testDir.exists() shouldBe true
 
-        every { settings.isLast3HourModeEnabled } returns false
+        every { testSettings.isHourKeyPkgMode } returns false
 
         coEvery { server.getCountryIndex() } returns listOf("DE".loc, "NL".loc)
         coEvery { deviceStorage.requireSpacePrivateStorage(any()) } returns mockk<DeviceStorage.CheckResult>().apply {
@@ -212,7 +212,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
             keyServer = server,
             keyCache = keyCache,
             legacyKeyCache = legacyMigration,
-            settings = settings,
+            testSettings = testSettings,
             dispatcherProvider = TestDispatcherProvider
         )
         Timber.i("createDownloader(): %s", downloader)
@@ -229,7 +229,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
 
     @Test
     fun `wanted country list is empty, hour mode`() {
-        every { settings.isLast3HourModeEnabled } returns true
+        every { testSettings.isHourKeyPkgMode } returns true
 
         val downloader = createDownloader()
         runBlocking {
@@ -254,9 +254,9 @@ class KeyFileDownloaderTest : BaseIOTest() {
 
     @Test
     fun `fetching is aborted in hour if not enough free storage`() {
-        every { settings.isLast3HourModeEnabled } returns true
+        every { testSettings.isHourKeyPkgMode } returns true
 
-        coEvery { deviceStorage.requireSpacePrivateStorage(67584L) } throws InsufficientStorageException(
+        coEvery { deviceStorage.requireSpacePrivateStorage(540672L) } throws InsufficientStorageException(
             mockk(relaxed = true)
         )
 
@@ -435,12 +435,12 @@ class KeyFileDownloaderTest : BaseIOTest() {
 
     @Test
     fun `last3Hours fetch without prior data`() {
-        every { settings.isLast3HourModeEnabled } returns true
+        every { testSettings.isHourKeyPkgMode } returns true
 
         val downloader = createDownloader()
 
         runBlocking {
-            downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 6
+            downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 48
         }
 
         coVerify {
@@ -482,17 +482,17 @@ class KeyFileDownloaderTest : BaseIOTest() {
                 hourIdentifier = "10".hour
             )
         }
-        coVerify(exactly = 6) { keyCache.markKeyComplete(any(), any()) }
+        coVerify(exactly = 48) { keyCache.markKeyComplete(any(), any()) }
 
-        keyRepoData.size shouldBe 6
+        keyRepoData.size shouldBe 48
         keyRepoData.values.forEach { it.isDownloadComplete shouldBe true }
 
-        coVerify { deviceStorage.requireSpacePrivateStorage(135168L) }
+        coVerify { deviceStorage.requireSpacePrivateStorage(1081344L) }
     }
 
     @Test
     fun `last3Hours fetch with prior data`() {
-        every { settings.isLast3HourModeEnabled } returns true
+        every { testSettings.isHourKeyPkgMode } returns true
 
         mockAddData(
             type = Type.COUNTRY_HOUR,
@@ -512,9 +512,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
         val downloader = createDownloader()
 
         runBlocking {
-            downloader.asyncFetchKeyFiles(
-                listOf("DE".loc, "NL".loc)
-            ).size shouldBe 6
+            downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 48
         }
 
         coVerify {
@@ -527,8 +525,8 @@ class KeyFileDownloaderTest : BaseIOTest() {
             keyCache.createCacheEntry(
                 type = Type.COUNTRY_HOUR,
                 location = "DE".loc,
-                dayIdentifier = "2020-09-03".day,
-                hourIdentifier = "10".hour
+                dayIdentifier = "2020-09-02".day,
+                hourIdentifier = "13".hour
             )
 
             keyCache.createCacheEntry(
@@ -540,19 +538,19 @@ class KeyFileDownloaderTest : BaseIOTest() {
             keyCache.createCacheEntry(
                 type = Type.COUNTRY_HOUR,
                 location = "NL".loc,
-                dayIdentifier = "2020-09-03".day,
-                hourIdentifier = "10".hour
+                dayIdentifier = "2020-09-02".day,
+                hourIdentifier = "13".hour
             )
         }
-        coVerify(exactly = 4) {
+        coVerify(exactly = 46) {
             server.downloadKeyFile(any(), any(), any(), any(), any())
         }
-        coVerify { deviceStorage.requireSpacePrivateStorage(90112L) }
+        coVerify { deviceStorage.requireSpacePrivateStorage(1036288L) }
     }
 
     @Test
     fun `last3Hours fetch deletes stale data`() {
-        every { settings.isLast3HourModeEnabled } returns true
+        every { testSettings.isHourKeyPkgMode } returns true
 
         val (staleKey1, _) = mockAddData(
             type = Type.COUNTRY_HOUR,
@@ -588,7 +586,7 @@ class KeyFileDownloaderTest : BaseIOTest() {
         val downloader = createDownloader()
 
         runBlocking {
-            downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 6
+            downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 48
         }
 
         coVerify {
@@ -601,8 +599,8 @@ class KeyFileDownloaderTest : BaseIOTest() {
             keyCache.createCacheEntry(
                 type = Type.COUNTRY_HOUR,
                 location = "DE".loc,
-                dayIdentifier = "2020-09-03".day,
-                hourIdentifier = "11".hour
+                dayIdentifier = "2020-09-02".day,
+                hourIdentifier = "13".hour
             )
 
             keyCache.createCacheEntry(
@@ -614,11 +612,11 @@ class KeyFileDownloaderTest : BaseIOTest() {
             keyCache.createCacheEntry(
                 type = Type.COUNTRY_HOUR,
                 location = "NL".loc,
-                dayIdentifier = "2020-09-03".day,
-                hourIdentifier = "11".hour
+                dayIdentifier = "2020-09-02".day,
+                hourIdentifier = "13".hour
             )
         }
-        coVerify(exactly = 4) {
+        coVerify(exactly = 46) {
             server.downloadKeyFile(any(), any(), any(), any(), any())
         }
         coVerify(exactly = 1) { keyCache.delete(listOf(staleKey1, staleKey2)) }
@@ -626,12 +624,12 @@ class KeyFileDownloaderTest : BaseIOTest() {
 
     @Test
     fun `last3Hours fetch skips single download failures`() {
-        every { settings.isLast3HourModeEnabled } returns true
+        every { testSettings.isHourKeyPkgMode } returns true
 
         var dlCounter = 0
         coEvery { server.downloadKeyFile(any(), any(), any(), any(), any()) } answers {
             dlCounter++
-            if (dlCounter == 2) throw IOException("Timeout")
+            if (dlCounter % 3 == 0) throw IOException("Timeout")
             mockDownloadServerDownload(
                 locationCode = arg(0),
                 day = arg(1),
@@ -643,11 +641,11 @@ class KeyFileDownloaderTest : BaseIOTest() {
         val downloader = createDownloader()
 
         runBlocking {
-            downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 5
+            downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 32
         }
 
         // We delete the entry for the failed download
-        coVerify(exactly = 1) { keyCache.delete(any()) }
+        coVerify(exactly = 16) { keyCache.delete(any()) }
     }
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/TestSettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/TestSettingsTest.kt
new file mode 100644
index 000000000..6468dda95
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/TestSettingsTest.kt
@@ -0,0 +1,69 @@
+package de.rki.coronawarnapp.storage
+
+import android.content.Context
+import de.rki.coronawarnapp.util.CWADebug
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.clearAllMocks
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.mockkObject
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import testhelpers.preferences.MockSharedPreferences
+
+class TestSettingsTest : BaseTest() {
+
+    @MockK lateinit var context: Context
+    private lateinit var mockPreferences: MockSharedPreferences
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        mockkObject(CWADebug)
+
+        mockPreferences = MockSharedPreferences()
+        every {
+            context.getSharedPreferences("test_settings", Context.MODE_PRIVATE)
+        } returns mockPreferences
+    }
+
+    @AfterEach
+    fun teardown() {
+        clearAllMocks()
+    }
+
+    private fun buildInstance(): TestSettings = TestSettings(
+        context = context
+    )
+
+    @Test
+    fun `hourly keypkg testing mode`() {
+        buildInstance().apply {
+            every { CWADebug.isDeviceForTestersBuild } returns true
+
+            isHourKeyPkgMode shouldBe false
+            isHourKeyPkgMode = true
+            isHourKeyPkgMode shouldBe true
+            mockPreferences.dataMapPeek.values.single() shouldBe true
+
+            isHourKeyPkgMode = false
+            isHourKeyPkgMode shouldBe false
+            mockPreferences.dataMapPeek.values.single() shouldBe false
+
+            isHourKeyPkgMode = true
+        }
+
+        buildInstance().apply {
+            isHourKeyPkgMode shouldBe true
+
+            // In normal builds this should default to false
+            every { CWADebug.isDeviceForTestersBuild } returns false
+
+            isHourKeyPkgMode shouldBe false
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/testhelpers/KotestExtensions.kt b/Corona-Warn-App/src/test/java/testhelpers/KotestExtensions.kt
index 4841a1926..c48b1a7be 100644
--- a/Corona-Warn-App/src/test/java/testhelpers/KotestExtensions.kt
+++ b/Corona-Warn-App/src/test/java/testhelpers/KotestExtensions.kt
@@ -14,8 +14,8 @@ import kotlin.time.seconds
 @ExperimentalTime
 fun <T> flakyTest(flakyAction: () -> T): Unit = runBlocking {
     retry(
-        maxRetry = 10,
-        timeout = 60.seconds,
+        maxRetry = 5,
+        timeout = 30.seconds,
         delay = 1.seconds,
         multiplier = 1,
         exceptionClass = Exception::class,
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 9003b9ce6..b54765d66 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
@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.test.api.ui
 import android.content.Context
 import androidx.lifecycle.Observer
 import de.rki.coronawarnapp.environment.EnvironmentSetup
+import de.rki.coronawarnapp.storage.TestSettings
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
@@ -30,6 +31,7 @@ class TestForApiFragmentViewModelTest : BaseTest() {
 
     @MockK private lateinit var environmentSetup: EnvironmentSetup
     @MockK private lateinit var context: Context
+    @MockK private lateinit var testSettings: TestSettings
 
     private var currentEnvironment = EnvironmentSetup.Type.DEV
 
@@ -59,7 +61,8 @@ class TestForApiFragmentViewModelTest : BaseTest() {
 
     private fun createViewModel(): TestForApiFragmentViewModel = TestForApiFragmentViewModel(
         envSetup = environmentSetup,
-        context = context
+        context = context,
+        testSettings = testSettings
     )
 
     @Test
-- 
GitLab