diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 5b75aebd94871c4b9fd4b57f986af43e1fafc09e..5c4e109e9de73e96914a91b6df63463b321f4edf 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -25,8 +25,8 @@ __Thank you for this this PR! Please consider the following:__
    * Short step by step instructions help the reviewer test your changes, e.g. how to navigate to a new UI element you added.
    * The PR _won't be reviewed_ if CircleCi is failing or if there are merge conflicts. If Circle CI is still failing mark the PR as a draft and write a little comment on your status.
    * Provide at least a few unit and/or instrumentation tests.
-   * Use a meaning full branch name. Use either `fix` or `feature` as prefix for your branch, e.g. `fix/prevent-npe-on-device-rotation-issue_123`
-   * Test your changes thoroughly. Only open PRs which you think is ready to be merged. If you explicitly need feedback mark the PR as `DRAFT` on Github.
+   * Use a meaningful branch name. Use either `fix` or `feature` as prefix for your branch, e.g. `fix/prevent-npe-on-device-rotation-issue_123`
+   * Test your changes thoroughly. Only open PRs which you think is ready to be merged. If you explicitly need feedback mark the PR as `DRAFT` on GitHub.
    * Don't introduce unrelated code reformatting (e.g., on-save hooks in your IDE)
    * Remove this checklist before creating your pull request.
 
diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index 25be009f467df291bb66c886d5c653ec3b92bff2..338f6bcecb65f8da9a4a404026964624ee57b44d 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -68,6 +68,8 @@ android {
         buildConfigField "int", "VERSION_MAJOR", VERSION_MAJOR
         buildConfigField "int", "VERSION_MINOR", VERSION_MINOR
         buildConfigField "int", "VERSION_PATCH", VERSION_PATCH
+
+        vectorDrawables.useSupportLibrary = true
     }
 
     def signingPropFile = file("../keystore.properties")
diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.presencetracing.storage.TraceLocationDatabase/2.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.presencetracing.storage.TraceLocationDatabase/2.json
new file mode 100644
index 0000000000000000000000000000000000000000..006ae7f97f5b769e222fb4347857b4b64e070653
--- /dev/null
+++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.presencetracing.storage.TraceLocationDatabase/2.json
@@ -0,0 +1,204 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "3950e8c7f3123a41f0960bc30b4f07f4",
+    "entities": [
+      {
+        "tableName": "checkin",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `traceLocationIdBase64` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `traceLocationStart` TEXT, `traceLocationEnd` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `cryptographicSeedBase64` TEXT NOT NULL, `cnPublicKey` TEXT NOT NULL, `checkInStart` TEXT NOT NULL, `checkInEnd` TEXT NOT NULL, `completed` INTEGER NOT NULL, `createJournalEntry` INTEGER NOT NULL, `submitted` INTEGER NOT NULL, `submissionConsent` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "traceLocationIdBase64",
+            "columnName": "traceLocationIdBase64",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "version",
+            "columnName": "version",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "traceLocationStart",
+            "columnName": "traceLocationStart",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "traceLocationEnd",
+            "columnName": "traceLocationEnd",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "defaultCheckInLengthInMinutes",
+            "columnName": "defaultCheckInLengthInMinutes",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "cryptographicSeedBase64",
+            "columnName": "cryptographicSeedBase64",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "cnPublicKey",
+            "columnName": "cnPublicKey",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "checkInStart",
+            "columnName": "checkInStart",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "checkInEnd",
+            "columnName": "checkInEnd",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "completed",
+            "columnName": "completed",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "createJournalEntry",
+            "columnName": "createJournalEntry",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isSubmitted",
+            "columnName": "submitted",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasSubmissionConsent",
+            "columnName": "submissionConsent",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "traceLocations",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `cryptographicSeedBase64` TEXT NOT NULL, `cnPublicKey` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "version",
+            "columnName": "version",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "startDate",
+            "columnName": "startDate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "endDate",
+            "columnName": "endDate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "defaultCheckInLengthInMinutes",
+            "columnName": "defaultCheckInLengthInMinutes",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "cryptographicSeedBase64",
+            "columnName": "cryptographicSeedBase64",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "cnPublicKey",
+            "columnName": "cnPublicKey",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3950e8c7f3123a41f0960bc30b4f07f4')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/presencetracing/migration/PresenceTracingDatabaseMigrationTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/presencetracing/migration/PresenceTracingDatabaseMigrationTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d38ce5b4023491d3073d20a6e27d4fca2c85f483
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/presencetracing/migration/PresenceTracingDatabaseMigrationTest.kt
@@ -0,0 +1,133 @@
+package de.rki.coronawarnapp.presencetracing.migration
+
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import de.rki.coronawarnapp.presencetracing.storage.TraceLocationDatabase
+import de.rki.coronawarnapp.presencetracing.storage.entity.TraceLocationCheckInEntity
+import de.rki.coronawarnapp.presencetracing.storage.migration.PresenceTracingDatabaseMigration1To2
+import io.kotest.matchers.shouldBe
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import okio.ByteString.Companion.encode
+import org.joda.time.Instant
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseTestInstrumentation
+import java.io.IOException
+
+@RunWith(AndroidJUnit4::class)
+class PresenceTracingDatabaseMigrationTest : BaseTestInstrumentation() {
+    private val DB_NAME = "TraceLocations_test_db"
+
+    @get:Rule
+    val helper: MigrationTestHelper = MigrationTestHelper(
+        InstrumentationRegistry.getInstrumentation(),
+        TraceLocationDatabase::class.java.canonicalName,
+        FrameworkSQLiteOpenHelperFactory()
+    )
+
+    @Test
+    fun migrate1To2() {
+        val locationIdBase64 = "41da2115-eba2-49bd-bf17-adb3d635ddaf".encode().base64()
+        val cryptoGraphicSeed = "cryptographicSeed".encode().base64()
+        val locationStart = Instant.parse("2021-01-01T12:30:00.000Z")
+        val locationEnd = Instant.parse("2021-01-01T18:30:00.000Z")
+        val checkInStart = Instant.parse("2021-01-01T14:30:00.000Z")
+        val checkInEnd = Instant.parse("2021-01-01T16:30:00.000Z")
+        helper.createDatabase(DB_NAME, 1).apply {
+            execSQL(
+                """
+                    INSERT INTO "checkin" (
+                        "id",
+                        "traceLocationIdBase64",
+                        "version",
+                        "type",
+                        "description",
+                        "address",
+                        "traceLocationStart",
+                        "traceLocationEnd",
+                        "defaultCheckInLengthInMinutes",
+                        "cryptographicSeedBase64",
+                        "cnPublicKey",
+                        "checkInStart",
+                        "checkInEnd",
+                        "completed",
+                        "createJournalEntry",
+                        "submitted"
+                    ) VALUES (
+                        '1',
+                        '$locationIdBase64',
+                        '1',
+                        '2',
+                        'brothers birthday',
+                        'Malibu',
+                        '$locationStart',
+                        '$locationEnd',
+                        '42',
+                        '$cryptoGraphicSeed',
+                        'cnPublicKey',
+                        '$checkInStart',
+                        '$checkInEnd',
+                        '0',
+                        '1',
+                        '0'
+                    );
+                """.trimIndent()
+            )
+            close()
+        }
+
+        // Run migration
+        helper.runMigrationsAndValidate(
+            DB_NAME,
+            2,
+            true,
+            PresenceTracingDatabaseMigration1To2
+        )
+
+        val daoDb = TraceLocationDatabase.Factory(
+            context = ApplicationProvider.getApplicationContext()
+        ).create(databaseName = DB_NAME)
+
+        val checkin = TraceLocationCheckInEntity(
+            id = 1L,
+            traceLocationIdBase64 = locationIdBase64,
+            version = 1,
+            type = 2,
+            description = "brothers birthday",
+            address = "Malibu",
+            traceLocationStart = locationStart,
+            traceLocationEnd = locationEnd,
+            defaultCheckInLengthInMinutes = 42,
+            cryptographicSeedBase64 = cryptoGraphicSeed,
+            cnPublicKey = "cnPublicKey",
+            checkInStart = checkInStart,
+            checkInEnd = checkInEnd,
+            completed = false,
+            createJournalEntry = true,
+            isSubmitted = false,
+            hasSubmissionConsent = false,
+        )
+        runBlocking { daoDb.checkInDao().allEntries().first() }.single() shouldBe checkin
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun migrateAll() {
+        helper.createDatabase(DB_NAME, 1).apply {
+            close()
+        }
+
+        // Open latest version of the database. Room will validate the schema once all migrations execute.
+        TraceLocationDatabase.Factory(
+            context = ApplicationProvider.getApplicationContext()
+        ).create(databaseName = DB_NAME).apply {
+            openHelper.writableDatabase
+            close()
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/presencetracing/storage/CheckInDatabaseData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/presencetracing/storage/CheckInDatabaseData.kt
index 1be7e8f1a81516ffdbd331e55ae209a6a4bb71c4..11601d4bfe7ca3dedb917489015d652d076acf3c 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/presencetracing/storage/CheckInDatabaseData.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/presencetracing/storage/CheckInDatabaseData.kt
@@ -23,6 +23,7 @@ object CheckInDatabaseData {
         completed = false,
         createJournalEntry = true,
         isSubmitted = false,
+        hasSubmissionConsent = false,
     )
 
     val testCheckInWithoutCheckOutTime = TraceLocationCheckInEntity(
@@ -41,5 +42,6 @@ object CheckInDatabaseData {
         completed = false,
         createJournalEntry = true,
         isSubmitted = false,
+        hasSubmissionConsent = false,
     )
 }
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt
index 2a8bc98f65164640d0f1ce0c63292ebb7390d435..d610fb4905df02d3f210878cee1c6ad16b09b225 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt
@@ -1,21 +1,21 @@
 package de.rki.coronawarnapp.ui.main.home
 
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.FetchingResult
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.NoTest
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.SubmissionDone
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestError
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestInvalid
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestNegative
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestPending
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestPositive
 import de.rki.coronawarnapp.risk.RiskState
-import de.rki.coronawarnapp.submission.ui.homecards.FetchingResult
-import de.rki.coronawarnapp.submission.ui.homecards.NoTest
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone
-import de.rki.coronawarnapp.submission.ui.homecards.TestError
-import de.rki.coronawarnapp.submission.ui.homecards.TestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestInvalidCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestNegativeCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPendingCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestSubmissionDoneCard
 import de.rki.coronawarnapp.submission.ui.homecards.TestFetchingCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestInvalid
-import de.rki.coronawarnapp.submission.ui.homecards.TestInvalidCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestNegative
-import de.rki.coronawarnapp.submission.ui.homecards.TestNegativeCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestPending
-import de.rki.coronawarnapp.submission.ui.homecards.TestPendingCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestPositive
-import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard
 import de.rki.coronawarnapp.submission.ui.homecards.TestUnregisteredCard
 import de.rki.coronawarnapp.tracing.TracingProgress
 import de.rki.coronawarnapp.tracing.states.IncreasedRisk
@@ -30,7 +30,6 @@ import de.rki.coronawarnapp.tracing.ui.homecards.TracingFailedCard
 import de.rki.coronawarnapp.tracing.ui.homecards.TracingProgressCard
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc
 import org.joda.time.Instant
-import java.util.Date
 
 object HomeData {
 
@@ -134,34 +133,33 @@ object HomeData {
             state = FetchingResult
         )
 
-        val TEST_POSITIVE_ITEM = TestPositiveCard.Item(
+        val TEST_POSITIVE_ITEM = PcrTestPositiveCard.Item(
             state = TestPositive,
             onClickAction = {}
         )
 
-        val TEST_NEGATIVE_ITEM = TestNegativeCard.Item(
-            state = TestNegative,
-            onClickAction = {}
+        val TEST_NEGATIVE_ITEM = PcrTestNegativeCard.Item(
+            state = TestNegative
         )
 
-        val TEST_INVALID_ITEM = TestInvalidCard.Item(
+        val TEST_INVALID_ITEM = PcrTestInvalidCard.Item(
             state = TestInvalid,
             onDeleteTest = {}
         )
 
-        val TEST_ERROR_ITEM = TestErrorCard.Item(
+        val TEST_ERROR_ITEM = PcrTestErrorCard.Item(
             state = TestError,
             onDeleteTest = {}
         )
 
-        val TEST_PENDING_ITEM = TestPendingCard.Item(
+        val TEST_PENDING_ITEM = PcrTestPendingCard.Item(
             state = TestPending,
             onClickAction = {}
         )
 
-        val TEST_SUBMISSION_DONE_ITEM = TestSubmissionDoneCard.Item(
+        val TEST_SUBMISSION_DONE_ITEM = PcrTestSubmissionDoneCard.Item(
             state = SubmissionDone(
-                testRegisteredOn = Date()
+                testRegisteredAt = Instant.now()
             )
         )
     }
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
index b2c8c041d1d768c817a92586dc4abf3440a38fc8..db6e15d1b787f3a1ff5f73150390ecd0b5c85096 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
@@ -11,6 +11,7 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.main.CWASettings
 import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
@@ -19,17 +20,16 @@ import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard
 import de.rki.coronawarnapp.storage.TracingRepository
 import de.rki.coronawarnapp.storage.TracingSettings
 import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider
-import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestSubmissionDoneCard
 import de.rki.coronawarnapp.submission.ui.homecards.TestResultItem
-import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard
 import de.rki.coronawarnapp.tracing.GeneralTracingStatus
 import de.rki.coronawarnapp.tracing.states.TracingStateProvider
 import de.rki.coronawarnapp.tracing.ui.homecards.TracingStateItem
 import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState
-import de.rki.coronawarnapp.ui.presencetracing.organizer.TraceLocationOrganizerSettings
 import de.rki.coronawarnapp.ui.main.home.items.FAQCard
 import de.rki.coronawarnapp.ui.main.home.items.HomeItem
+import de.rki.coronawarnapp.ui.presencetracing.organizer.TraceLocationOrganizerSettings
 import de.rki.coronawarnapp.ui.statistics.Statistics
 import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool
 import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper
@@ -62,7 +62,7 @@ class HomeFragmentTest : BaseUITest() {
     @MockK lateinit var errorResetTool: EncryptionErrorResetTool
     @MockK lateinit var tracingStatus: GeneralTracingStatus
     @MockK lateinit var tracingStateProviderFactory: TracingStateProvider.Factory
-    @MockK lateinit var submissionStateProvider: SubmissionStateProvider
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
     @MockK lateinit var tracingRepository: TracingRepository
     @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService
     @MockK lateinit var submissionRepository: SubmissionRepository
@@ -270,7 +270,7 @@ class HomeFragmentTest : BaseUITest() {
             appConfigProvider = appConfigProvider,
             tracingStatus = tracingStatus,
             submissionRepository = submissionRepository,
-            submissionStateProvider = submissionStateProvider,
+            coronaTestRepository = coronaTestRepository,
             cwaSettings = cwaSettings,
             statisticsProvider = statisticsProvider,
             deadmanNotificationScheduler = deadmanNotificationScheduler,
@@ -288,8 +288,8 @@ class HomeFragmentTest : BaseUITest() {
         MutableLiveData(
             mutableListOf<HomeItem>().apply {
                 when (submissionTestResultItem) {
-                    is TestSubmissionDoneCard.Item,
-                    is TestPositiveCard.Item -> {
+                    is PcrTestSubmissionDoneCard.Item,
+                    is PcrTestPositiveCard.Item -> {
                         Timber.d("Tracing item is not added, submission:$submissionTestResultItem")
                     }
                     else -> add(tracingStateItem)
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt
index 2fc4006c280e2badf61984db0df2613ff33dd788..ac04a0a70d5c7cfea2dc3b466b939b4aeefa3053 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt
@@ -59,13 +59,6 @@ class SubmissionContactFragmentTest : BaseUITest() {
         launchFragment2<SubmissionContactFragment>()
     }
 
-    @Test
-    fun testContactCallClicked() {
-        launchFragmentInContainer2<SubmissionContactFragment>()
-        onView(withId(R.id.submission_contact_button_call))
-            .perform(click())
-    }
-
     @Test
     fun testContactEnterTanClicked() {
         val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt
index 33dc537e4cc00d5866850b2411f9bd2c90c86ad9..20941db024c95eaf479e2c8afa8b113bb6a49377 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt
@@ -5,6 +5,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.auto.AutoSubmission
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater_Factory_Impl
@@ -39,6 +40,7 @@ class SubmissionTestResultAvailableFragmentTest : BaseUITest() {
     @MockK lateinit var autoSubmission: AutoSubmission
     @MockK lateinit var appShortcutsHelper: AppShortcutsHelper
     @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
+    @MockK lateinit var checkInRepository: CheckInRepository
 
     @Rule
     @JvmField
@@ -51,17 +53,17 @@ class SubmissionTestResultAvailableFragmentTest : BaseUITest() {
     fun setup() {
         MockKAnnotations.init(this, relaxed = true)
 
-        every { submissionRepository.deviceUIStateFlow } returns flowOf()
-        every { submissionRepository.testResultReceivedDateFlow } returns flowOf()
+        every { submissionRepository.testForType(any()) } returns flowOf()
         every { appShortcutsHelper.removeAppShortcut() } just Runs
 
         viewModel = spyk(
             SubmissionTestResultAvailableViewModel(
-                TestDispatcherProvider(),
-                tekHistoryUpdaterFactory,
-                submissionRepository,
-                autoSubmission,
-                analyticsKeySubmissionCollector
+                dispatcherProvider = TestDispatcherProvider(),
+                tekHistoryUpdaterFactory = tekHistoryUpdaterFactory,
+                submissionRepository = submissionRepository,
+                autoSubmission = autoSubmission,
+                analyticsKeySubmissionCollector = analyticsKeySubmissionCollector,
+                checkInRepository = checkInRepository
             )
         )
 
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt
index 352a9a57e08923857aa906ed603bbea20aa37b17..5e48790a6dbfd987b0a88beb50c7607a5cf8d63b 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt
@@ -12,6 +12,8 @@ import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiT
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
@@ -19,12 +21,12 @@ import de.rki.coronawarnapp.submission.auto.AutoSubmission
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenFragment
 import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenViewModel
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
 import io.mockk.spyk
+import org.joda.time.Instant
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -38,7 +40,6 @@ import testhelpers.captureScreenshot
 import testhelpers.launchFragment2
 import testhelpers.launchFragmentInContainer2
 import tools.fastlane.screengrab.locale.LocaleTestRule
-import java.util.Date
 
 @RunWith(AndroidJUnit4::class)
 class SubmissionTestResultConsentGivenFragmentTest : BaseUITest() {
@@ -107,10 +108,10 @@ class SubmissionTestResultConsentGivenFragmentTest : BaseUITest() {
     fun capture_fragment() {
         every { viewModel.uiState } returns MutableLiveData(
             TestResultUIState(
-                NetworkRequestWrapper.RequestSuccessful(
-                    DeviceUIState.PAIRED_POSITIVE
-                ),
-                Date()
+                coronaTest = mockk<CoronaTest>().apply {
+                    every { testResult } returns CoronaTestResult.PCR_POSITIVE
+                    every { registeredAt } returns Instant.now()
+                }
             )
         )
 
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt
index dbc5de25ebf30886fe62f36034c11d4f55ca9d12..4b970b3a999263b3f99687dc97b6d4355939cfa7 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt
@@ -8,20 +8,22 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragment
 import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingViewModel
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.just
+import io.mockk.mockk
 import io.mockk.spyk
 import kotlinx.coroutines.flow.flowOf
+import org.joda.time.Instant
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -35,7 +37,6 @@ import testhelpers.captureScreenshot
 import testhelpers.launchFragment2
 import testhelpers.launchFragmentInContainer2
 import tools.fastlane.screengrab.locale.LocaleTestRule
-import java.util.Date
 
 @RunWith(AndroidJUnit4::class)
 class SubmissionTestResultFragmentTest : BaseUITest() {
@@ -55,8 +56,7 @@ class SubmissionTestResultFragmentTest : BaseUITest() {
     fun setup() {
         MockKAnnotations.init(this, relaxed = true)
 
-        every { submissionRepository.deviceUIStateFlow } returns flowOf()
-        every { submissionRepository.testResultReceivedDateFlow } returns flowOf()
+        every { submissionRepository.testForType(any()) } returns flowOf()
 
         viewModel = spyk(
             SubmissionTestResultPendingViewModel(
@@ -71,8 +71,11 @@ class SubmissionTestResultFragmentTest : BaseUITest() {
             every { consentGiven } returns MutableLiveData(true)
             every { testState } returns MutableLiveData(
                 TestResultUIState(
-                    deviceUiState = NetworkRequestWrapper.RequestSuccessful(data = DeviceUIState.PAIRED_POSITIVE),
-                    testResultReceivedDate = Date()
+                    coronaTest = mockk<CoronaTest>().apply {
+                        every { testResult } returns CoronaTestResult.PCR_POSITIVE
+                        every { registeredAt } returns Instant.now()
+                        every { isProcessing } returns false
+                    }
                 )
             )
         }
@@ -113,10 +116,11 @@ class SubmissionTestResultFragmentTest : BaseUITest() {
     fun capture_fragment() {
         every { viewModel.testState } returns MutableLiveData(
             TestResultUIState(
-                NetworkRequestWrapper.RequestSuccessful(
-                    DeviceUIState.PAIRED_NO_RESULT
-                ),
-                Date()
+                coronaTest = mockk<CoronaTest>().apply {
+                    every { testResult } returns CoronaTestResult.PCR_OR_RAT_PENDING
+                    every { registeredAt } returns Instant.now()
+                    every { isProcessing } returns false
+                }
             )
         )
         captureScreenshot<SubmissionTestResultPendingFragment>()
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt
index d18ae41de25400d3c51c441904c6c07098995b03..5eba253d54bb986fbd8c95f225c8c3e7eeb4876e 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt
@@ -4,18 +4,20 @@ import androidx.lifecycle.MutableLiveData
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.ui.submission.testresult.negative.SubmissionTestResultNegativeFragment
 import de.rki.coronawarnapp.ui.submission.testresult.negative.SubmissionTestResultNegativeViewModel
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
 import io.mockk.spyk
 import kotlinx.coroutines.flow.flowOf
+import org.joda.time.Instant
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -27,7 +29,6 @@ import testhelpers.SystemUIDemoModeRule
 import testhelpers.TestDispatcherProvider
 import testhelpers.captureScreenshot
 import tools.fastlane.screengrab.locale.LocaleTestRule
-import java.util.Date
 
 @RunWith(AndroidJUnit4::class)
 class SubmissionTestResultNegativeFragmentTest : BaseUITest() {
@@ -47,8 +48,7 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() {
     fun setup() {
         MockKAnnotations.init(this, relaxed = true)
 
-        every { submissionRepository.deviceUIStateFlow } returns flowOf()
-        every { submissionRepository.testResultReceivedDateFlow } returns flowOf()
+        every { submissionRepository.testForType(any()) } returns flowOf()
 
         viewModel = spyk(
             SubmissionTestResultNegativeViewModel(
@@ -75,10 +75,10 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() {
     fun capture_fragment() {
         every { viewModel.testResult } returns MutableLiveData(
             TestResultUIState(
-                NetworkRequestWrapper.RequestSuccessful(
-                    DeviceUIState.PAIRED_NEGATIVE
-                ),
-                Date()
+                coronaTest = mockk<CoronaTest>().apply {
+                    every { testResult } returns CoronaTestResult.PCR_NEGATIVE
+                    every { registeredAt } returns Instant.now()
+                }
             )
         )
         captureScreenshot<SubmissionTestResultNegativeFragment>()
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt
index 8c640d2568570c263e634009b2f896b8d8de3fde..55d8144158bfc191c60d729b26af79204b5ea397 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt
@@ -4,18 +4,20 @@ import androidx.lifecycle.MutableLiveData
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultNoConsentFragment
 import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultNoConsentViewModel
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
 import io.mockk.spyk
+import org.joda.time.Instant
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -26,7 +28,6 @@ import testhelpers.Screenshot
 import testhelpers.SystemUIDemoModeRule
 import testhelpers.captureScreenshot
 import tools.fastlane.screengrab.locale.LocaleTestRule
-import java.util.Date
 
 @RunWith(AndroidJUnit4::class)
 class SubmissionTestResultNoConsentGivenFragmentTest : BaseUITest() {
@@ -72,10 +73,10 @@ class SubmissionTestResultNoConsentGivenFragmentTest : BaseUITest() {
     fun capture_fragment() {
         every { viewModel.uiState } returns MutableLiveData(
             TestResultUIState(
-                NetworkRequestWrapper.RequestSuccessful(
-                    DeviceUIState.PAIRED_POSITIVE
-                ),
-                Date()
+                coronaTest = mockk<CoronaTest>().apply {
+                    every { testResult } returns CoronaTestResult.PCR_POSITIVE
+                    every { registeredAt } returns Instant.now()
+                }
             )
         )
 
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionYourConsentFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionYourConsentFragmentTest.kt
index 3084dc54851102f60d6703230fd87df7a4a768f9..0fd406215f89d46298b82483eec683e8142cdae7 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionYourConsentFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionYourConsentFragmentTest.kt
@@ -42,7 +42,7 @@ class SubmissionYourConsentFragmentTest : BaseUITest() {
     @Before
     fun setup() {
         MockKAnnotations.init(this, relaxed = true)
-        every { submissionRepository.hasGivenConsentToSubmission } returns flowOf()
+        every { submissionRepository.testForType(any()) } returns flowOf()
         viewModel =
             SubmissionYourConsentViewModel(TestDispatcherProvider(), interoperabilityRepository, submissionRepository)
         setupMockViewModel(
diff --git a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt
index 92111156ce7451c297cd779ec6333f70f9d2ee0d..0ab2b436cb66ae70971a996d43e7695c6eb9704a 100644
--- a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt
+++ b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt
@@ -27,7 +27,7 @@ class DefaultRiskLevelStorage @Inject constructor(
     // Taken from TimeVariables.MAX_STALE_EXPOSURE_RISK_RANGE
     override val storedResultLimit: Int = 2 * 6
 
-    override suspend fun storeExposureWindows(storedResultId: String, result: EwRiskLevelResult) {
+    override suspend fun storeExposureWindows(storedResultId: String, resultEw: EwRiskLevelResult) {
         Timber.d("storeExposureWindows(): NOOP")
         // NOOP
     }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragment.kt
index 051c3aa5ac77ddc14bb2637e38ef480f868b058c..1fa7e54d4fa91340b2b93f0cd7e5cc449a1811a8 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragment.kt
@@ -5,10 +5,10 @@ import android.os.Bundle
 import android.view.View
 import androidx.fragment.app.Fragment
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.ui.durationpicker.DurationPicker
-import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat
 import de.rki.coronawarnapp.databinding.FragmentTestContactDiaryBinding
 import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
+import de.rki.coronawarnapp.ui.durationpicker.DurationPicker
+import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.observe2
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
@@ -96,7 +96,7 @@ class ContactDiaryTestFragment :
         val TAG: String = ContactDiaryTestFragment::class.simpleName!!
         val MENU_ITEM = TestMenuItem(
             title = "Contact Diary Test Options",
-            description = "Contact Diary related test options..",
+            description = "Contact Diary related test options.",
             targetId = R.id.test_contact_diary_fragment
         )
     }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db311e72acf102f8facc9498d1fb09c92ae2a9bb
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragment.kt
@@ -0,0 +1,97 @@
+package de.rki.coronawarnapp.test.coronatest.ui
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import com.google.zxing.BarcodeFormat
+import com.journeyapps.barcodescanner.DefaultDecoderFactory
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentTestCoronatestBinding
+import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.permission.CameraPermissionHelper
+import de.rki.coronawarnapp.util.tryHumanReadableError
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import javax.inject.Inject
+
+@SuppressLint("SetTextI18n")
+class CoronaTestTestFragment : Fragment(R.layout.fragment_test_coronatest), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: CoronaTestTestFragmentViewModel by cwaViewModels { viewModelFactory }
+
+    private val binding: FragmentTestCoronatestBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        binding.apply {
+            qrcodeScanAction.setOnClickListener {
+                if (!CameraPermissionHelper.hasCameraPermission(requireActivity())) {
+                    requestPermissions(arrayOf(Manifest.permission.CAMERA), 99)
+                    return@setOnClickListener
+                }
+
+                val stop = {
+                    qrcodeScanPreview.stopDecoding()
+                    qrcodeScanPreview.pause()
+                    qrcodeScanContainer.isGone = true
+                }
+
+                val start = {
+                    qrcodeScanContainer.isVisible = true
+                    qrcodeScanPreview.resume()
+                    qrcodeScanPreview.decodeSingle { result ->
+                        vm.onQRCodeScanner(result)
+                        stop()
+                    }
+                }
+
+                if (qrcodeScanContainer.isVisible) {
+                    stop()
+                } else {
+                    start()
+                }
+            }
+            qrcodeScanPreview.decoderFactory = DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE))
+            qrcodeScanViewfinder.setCameraPreview(binding.qrcodeScanPreview)
+        }
+
+        vm.pcrtState.observe2(this) {
+            binding.pcrtData.text = it.getNiceTextForHumans(requireContext())
+        }
+        binding.apply {
+            pcrtDeleteAction.setOnClickListener { vm.deletePCRT() }
+            pcrtRefreshAction.setOnClickListener { vm.refreshPCRT() }
+        }
+
+        vm.ratState.observe2(this) {
+            binding.ratData.text = it.getNiceTextForHumans(requireContext())
+        }
+        binding.apply {
+            ratDeleteAction.setOnClickListener { vm.deleteRAT() }
+            ratRefreshAction.setOnClickListener { vm.refreshRAT() }
+        }
+
+        vm.errorEvents.observe2(this) {
+            val error = it.tryHumanReadableError(requireContext())
+            Toast.makeText(requireContext(), error.description, Toast.LENGTH_LONG).show()
+        }
+    }
+
+    companion object {
+        val MENU_ITEM = TestMenuItem(
+            title = "Corona Tests",
+            description = "PCR / RapidAntigen Test Options",
+            targetId = R.id.coronaTestTestFragment
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3aa8c30aaf1b5428ac2d5b1a10516c78fcbccb77
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.test.coronatest.ui
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class CoronaTestTestFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(CoronaTestTestFragmentViewModel::class)
+    abstract fun coronaTest(
+        factory: CoronaTestTestFragmentViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1f5a05014104c4351e03b1e35f2bd50ebfe035a3
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentViewModel.kt
@@ -0,0 +1,128 @@
+package de.rki.coronawarnapp.test.coronatest.ui
+
+import android.content.Context
+import androidx.lifecycle.asLiveData
+import com.journeyapps.barcodescanner.BarcodeResult
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.latestPCRT
+import de.rki.coronawarnapp.coronatest.latestRAT
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import timber.log.Timber
+
+class CoronaTestTestFragmentViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider,
+    private val coronaTestRepository: CoronaTestRepository,
+    private val coronaTestQrCodeValidator: CoronaTestQrCodeValidator,
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    val errorEvents = SingleLiveEvent<Throwable>()
+    val pcrtState = coronaTestRepository.latestPCRT.map {
+        PCRTState(
+            coronaTest = it
+        )
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    val ratState = coronaTestRepository.latestRAT.map {
+        RATState(
+            coronaTest = it
+        )
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    fun refreshPCRT() = launch {
+        try {
+            Timber.d("Refreshing PCR")
+            coronaTestRepository.refresh(type = CoronaTest.Type.PCR)
+        } catch (e: Exception) {
+            Timber.e(e, "Failed to refresh PCR test.")
+            errorEvents.postValue(e)
+        }
+    }
+
+    fun deletePCRT() = launch {
+        try {
+            val pcrTest = coronaTestRepository.latestPCRT.first()
+            if (pcrTest == null) {
+                Timber.d("No PCR test to delete")
+                return@launch
+            }
+            coronaTestRepository.removeTest(pcrTest.identifier)
+        } catch (e: Exception) {
+            Timber.e(e, "Failed to delete PCR test.")
+            errorEvents.postValue(e)
+        }
+    }
+
+    fun refreshRAT() = launch {
+        try {
+            Timber.d("Refreshing RAT")
+            coronaTestRepository.refresh(type = CoronaTest.Type.RAPID_ANTIGEN)
+        } catch (e: Exception) {
+            Timber.e(e, "Failed to refresh RAT test.")
+            errorEvents.postValue(e)
+        }
+    }
+
+    fun deleteRAT() = launch {
+        try {
+            val raTest = coronaTestRepository.latestRAT.first()
+            if (raTest == null) {
+                Timber.d("No RA test to delete")
+                return@launch
+            }
+            coronaTestRepository.removeTest(raTest.identifier)
+        } catch (e: Exception) {
+            Timber.e(e, "Failed to delete RA test.")
+            errorEvents.postValue(e)
+        }
+    }
+
+    fun onQRCodeScanner(result: BarcodeResult) = launch {
+        try {
+            val qrCode = coronaTestQrCodeValidator.validate(result.text)
+            coronaTestRepository.registerTest(qrCode)
+        } catch (e: Exception) {
+            Timber.e(e, "Failed to decode qrcode.")
+            errorEvents.postValue(e)
+        }
+    }
+
+    data class PCRTState(
+        val coronaTest: PCRCoronaTest?
+    ) {
+        fun getNiceTextForHumans(context: Context): String {
+            return coronaTest
+                ?.toString()
+                ?.replace("PCRCoronaTest(", "")
+                ?.replace(",", ",\n")
+                ?.trimEnd { it == ')' }
+                ?: "No PCR test registered."
+        }
+    }
+
+    data class RATState(
+        val coronaTest: RACoronaTest?
+    ) {
+        fun getNiceTextForHumans(context: Context): String {
+            return coronaTest
+                ?.toString()
+                ?.replace("RACoronaTest(", "")
+                ?.replace(",", ",\n")
+                ?.trimEnd { it == ')' }
+                ?: "No rapid antigen test registered."
+        }
+    }
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<CoronaTestTestFragmentViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7983ba7decb18272f7b0f5266c72786a13ec113c
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragment.kt
@@ -0,0 +1,58 @@
+package de.rki.coronawarnapp.test.hometestcards.ui
+
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.LinearLayoutManager
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentTestHomeTestCardsLayoutBinding
+import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator
+import de.rki.coronawarnapp.util.lists.diffutil.update
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import javax.inject.Inject
+
+class HomeTestCardsFragment : Fragment(R.layout.fragment_test_home_test_cards_layout), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel: HomeTestCardsFragmentViewModel by cwaViewModels { viewModelFactory }
+
+    val binding: FragmentTestHomeTestCardsLayoutBinding by viewBindingLazy()
+
+    private val homeAdapter = HomeAdapter()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        binding.recyclerView.apply {
+            layoutManager = LinearLayoutManager(requireContext())
+            itemAnimator = DefaultItemAnimator()
+            addItemDecoration(TopBottomPaddingDecorator(topPadding = R.dimen.spacing_tiny))
+            adapter = homeAdapter
+        }
+
+        viewModel.homeItems.observe2(this) {
+            homeAdapter.update(it)
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        binding.container.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+    }
+
+    companion object {
+        val MENU_ITEM = TestMenuItem(
+            title = "Home Cards",
+            description = "View all possible test result cards (PCR and Antigen).",
+            targetId = R.id.homeTestCardsFragment
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..93450056161753d0dc239d17c55653e4083e30dd
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.test.hometestcards.ui
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class HomeTestCardsFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(HomeTestCardsFragmentViewModel::class)
+    abstract fun testHomeTestCardsFragment(
+        factory: HomeTestCardsFragmentViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8d43b3595cdcbfd7dda641e9bc9d043f7fb9dc70
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentViewModel.kt
@@ -0,0 +1,65 @@
+package de.rki.coronawarnapp.test.hometestcards.ui
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.asLiveData
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestInvalidCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestNegativeCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPendingCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestReadyCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestSubmissionDoneCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestInvalidCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestNegativeCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestOutdatedCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPendingCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestReadyCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestSubmissionDoneCard
+import de.rki.coronawarnapp.submission.ui.homecards.TestFetchingCard
+import de.rki.coronawarnapp.submission.ui.homecards.TestResultItem
+import de.rki.coronawarnapp.submission.ui.homecards.TestUnregisteredCard
+import de.rki.coronawarnapp.ui.main.home.items.HomeItem
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import org.joda.time.Instant
+
+class HomeTestCardsFragmentViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    private val cards: Flow<List<TestResultItem>> = flowOf(
+        listOf(
+            TestUnregisteredCard.Item(SubmissionStatePCR.NoTest) {},
+            TestFetchingCard.Item(SubmissionStatePCR.FetchingResult),
+            PcrTestPendingCard.Item(SubmissionStatePCR.TestPending) {},
+            PcrTestReadyCard.Item(SubmissionStatePCR.TestResultReady) {},
+            PcrTestInvalidCard.Item(SubmissionStatePCR.TestInvalid) {},
+            PcrTestErrorCard.Item(SubmissionStatePCR.TestError) {},
+            PcrTestNegativeCard.Item(SubmissionStatePCR.TestNegative),
+            PcrTestPositiveCard.Item(SubmissionStatePCR.TestPositive) {},
+            PcrTestSubmissionDoneCard.Item(SubmissionStatePCR.SubmissionDone(Instant.now())),
+            RapidTestPendingCard.Item(SubmissionStateRAT.TestPending) {},
+            RapidTestReadyCard.Item(SubmissionStateRAT.TestResultReady) {},
+            RapidTestInvalidCard.Item(SubmissionStateRAT.TestInvalid) {},
+            RapidTestOutdatedCard.Item(SubmissionStateRAT.TestInvalid) {},
+            RapidTestErrorCard.Item(SubmissionStateRAT.TestError) {},
+            RapidTestNegativeCard.Item(SubmissionStateRAT.TestNegative),
+            RapidTestPositiveCard.Item(SubmissionStateRAT.TestPositive) {},
+            RapidTestSubmissionDoneCard.Item(SubmissionStateRAT.SubmissionDone(Instant.now()))
+        )
+    )
+
+    val homeItems: LiveData<List<HomeItem>> = cards.asLiveData(dispatcherProvider.Default)
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<HomeTestCardsFragmentViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/keydownload/ui/KeyDownloadTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/keydownload/ui/KeyDownloadTestFragment.kt
index 60233a4eaee37dfb957b7aef30ecf52be4186d2d..4ef7851fb2c31975621c3e299f37caf508bab83a 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/keydownload/ui/KeyDownloadTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/keydownload/ui/KeyDownloadTestFragment.kt
@@ -72,7 +72,7 @@ class KeyDownloadTestFragment : Fragment(R.layout.fragment_test_keydownload), Au
     companion object {
         val MENU_ITEM = TestMenuItem(
             title = "Key Packages",
-            description = "View & Control the downloaded key pkgs..",
+            description = "View & Control the downloaded key pkgs.",
             targetId = R.id.test_keydownload_fragment
         )
     }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt
index b4200bc33c96ba2d8132bc803a3657f3831a69f5..5481b8257082656430d2bc3b06d3bab62401cc68 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt
@@ -6,13 +6,15 @@ import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.miscinfo.MiscInfoFragment
 import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragment
 import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragment
+import de.rki.coronawarnapp.test.coronatest.ui.CoronaTestTestFragment
 import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment
 import de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragment
 import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment
 import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment
-import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragment
+import de.rki.coronawarnapp.test.hometestcards.ui.HomeTestCardsFragment
 import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment
 import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment
+import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragment
 import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment
 import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment
 import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment
@@ -25,18 +27,20 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() {
     val testMenuData by lazy {
         listOf(
             DebugOptionsFragment.MENU_ITEM,
+            SettingsCrashReportFragment.MENU_ITEM,
             AppConfigTestFragment.MENU_ITEM,
             TestRiskLevelCalculationFragment.MENU_ITEM,
+            MiscInfoFragment.MENU_ITEM,
             KeyDownloadTestFragment.MENU_ITEM,
             TestTaskControllerFragment.MENU_ITEM,
             SubmissionTestFragment.MENU_ITEM,
-            SettingsCrashReportFragment.MENU_ITEM,
-            MiscInfoFragment.MENU_ITEM,
             ContactDiaryTestFragment.MENU_ITEM,
             PlaygroundFragment.MENU_ITEM,
             DataDonationTestFragment.MENU_ITEM,
             DeltaonboardingFragment.MENU_ITEM,
             PresenceTracingTestFragment.MENU_ITEM,
+            HomeTestCardsFragment.MENU_ITEM,
+            CoronaTestTestFragment.MENU_ITEM,
         ).let { MutableLiveData(it) }
     }
     val showTestScreenEvent = SingleLiveEvent<TestMenuItem>()
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragment.kt
index 0fc0a95a98eceef10b89513943d36696945192cd..df4137e7ea3f37172547d6fce752f39beb97ff1a 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragment.kt
@@ -10,6 +10,7 @@ import androidx.core.text.color
 import androidx.core.text.scale
 import androidx.core.view.isVisible
 import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentTestPresenceTracingBinding
 import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocation
@@ -62,6 +63,10 @@ class PresenceTracingTestFragment : Fragment(R.layout.fragment_test_presence_tra
             viewModel.runPresenceTracingWarningTask()
         }
 
+        binding.openConsent.setOnClickListener {
+            findNavController().navigate(R.id.checkInsConsentFragment)
+        }
+
         viewModel.lastOrganiserLocation.observe(viewLifecycleOwner) {
             binding.lastOrganiserLocationCard.isVisible = it != null
             it?.let { traceLocation ->
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt
index ce982c5e0300a707a6a3a449916433cef0dafdb7..5281b569b4fe3d20f13c0f176603b50d21f8ba83 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt
@@ -28,15 +28,6 @@ class SubmissionTestFragment : Fragment(R.layout.fragment_test_submission), Auto
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        vm.currentTestId.observe2(this) {
-            binding.registrationTokenCurrent.text = "Current: '$it'"
-        }
-
-        binding.apply {
-            deleteTokenAction.setOnClickListener { vm.deleteRegistrationToken() }
-            scrambleTokenAction.setOnClickListener { vm.scrambleRegistrationToken() }
-        }
-
         val tekHistoryAdapter = TEKHistoryAdapter()
         binding.tekHistoryList.apply {
             adapter = tekHistoryAdapter
@@ -85,7 +76,7 @@ class SubmissionTestFragment : Fragment(R.layout.fragment_test_submission), Auto
         val TAG: String = SubmissionTestFragment::class.simpleName!!
         val MENU_ITEM = TestMenuItem(
             title = "Submission Test Options",
-            description = "Submission related test options..",
+            description = "Submission related test options.",
             targetId = R.id.test_submission_fragment
         )
     }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt
index 0218b306014d78f70d9cebd057cc2978cfbd7d66..bd02ec31ee3b2315e569d919de86ca3bfba02907 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt
@@ -8,7 +8,6 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import com.google.gson.Gson
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
@@ -19,12 +18,10 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
 import timber.log.Timber
-import java.util.UUID
 
 class SubmissionTestFragmentViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     private val tekHistoryStorage: TEKHistoryStorage,
-    private val submissionSettings: SubmissionSettings,
     tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory,
     @BaseGson baseGson: Gson
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
@@ -58,7 +55,6 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor(
     )
 
     val errorEvents = SingleLiveEvent<Throwable>()
-    val currentTestId = submissionSettings.registrationToken.flow.asLiveData()
 
     val shareTEKsEvent = SingleLiveEvent<TEKExport>()
 
@@ -81,18 +77,6 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor(
         .map { historyItems -> historyItems.sortedBy { it.obtainedAt } }
         .asLiveData(context = dispatcherProvider.Default)
 
-    fun scrambleRegistrationToken() {
-        submissionSettings.registrationToken.update {
-            UUID.randomUUID().toString()
-        }
-    }
-
-    fun deleteRegistrationToken() {
-        submissionSettings.registrationToken.update {
-            null
-        }
-    }
-
     fun updateStorage() {
         tekHistoryUpdater.updateTEKHistoryOrRequestPermission()
     }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
index 1ed880647cd7385d13cc4b49a23f18850d05d1bd..65aa2e6524a9f0a56a2ae9c9dff8d3e590c2e4fe 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
@@ -8,20 +8,24 @@ import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragment
 import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragmentModule
 import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragment
 import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragmentModule
+import de.rki.coronawarnapp.test.coronatest.ui.CoronaTestTestFragment
+import de.rki.coronawarnapp.test.coronatest.ui.CoronaTestTestFragmentModule
 import de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragment
 import de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragmentModule
 import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment
 import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragmentModule
 import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaOnboardingFragmentModule
 import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment
-import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragment
-import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragmentModule
+import de.rki.coronawarnapp.test.hometestcards.ui.HomeTestCardsFragment
+import de.rki.coronawarnapp.test.hometestcards.ui.HomeTestCardsFragmentModule
 import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment
 import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragmentModule
 import de.rki.coronawarnapp.test.menu.ui.TestMenuFragment
 import de.rki.coronawarnapp.test.menu.ui.TestMenuFragmentModule
 import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment
 import de.rki.coronawarnapp.test.playground.ui.PlaygroundModule
+import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragment
+import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragmentModule
 import de.rki.coronawarnapp.test.presencetracing.ui.poster.QrCodePosterTestFragment
 import de.rki.coronawarnapp.test.presencetracing.ui.poster.QrCodePosterTestFragmentModule
 import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment
@@ -75,4 +79,10 @@ abstract class MainActivityTestModule {
 
     @ContributesAndroidInjector(modules = [QrCodePosterTestFragmentModule::class])
     abstract fun qrCodePosterTestFragment(): QrCodePosterTestFragment
+
+    @ContributesAndroidInjector(modules = [HomeTestCardsFragmentModule::class])
+    abstract fun homeTestCards(): HomeTestCardsFragment
+
+    @ContributesAndroidInjector(modules = [CoronaTestTestFragmentModule::class])
+    abstract fun coronaTest(): CoronaTestTestFragment
 }
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_coronatest.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_coronatest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3d0c992179a6ae0aea58323db3146fab379c57b5
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_coronatest.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.core.widget.NestedScrollView 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"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true"
+    tools:ignore="HardcodedText">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/spacing_tiny"
+        android:orientation="vertical">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/qrcode_container"
+            style="@style/Card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/spacing_tiny">
+
+            <TextView
+                android:id="@+id/qrcode_title"
+                style="@style/headline6Sixteen"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:text="QRCodes (ɔ◔‿◔)ɔ ♥"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <Button
+                android:id="@+id/qrcode_scan_action"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="Scan using CWA"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/qrcode_title" />
+
+            <FrameLayout
+                android:id="@+id/qrcode_scan_container"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/scan_qr_code_viewfinder_size"
+                android:visibility="gone"
+                app:layout_constraintTop_toBottomOf="@id/qrcode_scan_action"
+                tools:visibility="visible">
+
+                <com.journeyapps.barcodescanner.BarcodeView
+                    android:id="@+id/qrcode_scan_preview"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"
+                    app:zxing_framing_rect_height="@dimen/scan_qr_code_viewfinder_size"
+                    app:zxing_framing_rect_width="@dimen/scan_qr_code_viewfinder_size" />
+
+                <com.journeyapps.barcodescanner.ViewfinderView
+                    android:id="@+id/qrcode_scan_viewfinder"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"
+                    app:zxing_viewfinder_laser_visibility="false"
+                    app:zxing_viewfinder_mask="@color/colorQrCodeScanMask" />
+
+            </FrameLayout>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/pcrt_container"
+            style="@style/Card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/spacing_tiny">
+
+            <TextView
+                android:id="@+id/pcrt_title"
+                style="@style/headline6Sixteen"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:text="Polymerase chain reaction test"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <Button
+                android:id="@+id/pcrt_delete_action"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="Delete"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/pcrt_title" />
+
+            <Button
+                android:id="@+id/pcrt_refresh_action"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="Refresh"
+                app:layout_constraintStart_toEndOf="@id/pcrt_delete_action"
+                app:layout_constraintTop_toBottomOf="@id/pcrt_title" />
+
+            <TextView
+                android:id="@+id/pcrt_data"
+                style="@style/TextAppearance.MaterialComponents.Caption"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="No data."
+                app:layout_constraintTop_toBottomOf="@id/pcrt_delete_action" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/rat_container"
+            style="@style/Card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/spacing_tiny">
+
+            <TextView
+                android:id="@+id/rat_title"
+                style="@style/headline6Sixteen"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:text="Rapid antigen test"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <Button
+                android:id="@+id/rat_delete_action"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="Delete"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/rat_title" />
+
+            <Button
+                android:id="@+id/rat_refresh_action"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="Refresh"
+                app:layout_constraintStart_toEndOf="@id/rat_delete_action"
+                app:layout_constraintTop_toBottomOf="@id/rat_title" />
+
+            <TextView
+                android:id="@+id/rat_data"
+                style="@style/TextAppearance.MaterialComponents.Caption"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="No data."
+                app:layout_constraintTop_toBottomOf="@id/rat_delete_action" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </LinearLayout>
+</androidx.core.widget.NestedScrollView>
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_presence_tracing.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_presence_tracing.xml
index dba2acc1b028e69527c54e05b6eb93271f194782..19c37f7ec85bd30bdafebae56ee1b811c71ad37c 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_presence_tracing.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_presence_tracing.xml
@@ -16,8 +16,8 @@
             style="@style/Card"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
             android:layout_marginHorizontal="@dimen/spacing_tiny"
+            android:layout_marginTop="10dp"
             android:orientation="vertical">
 
             <TextView
@@ -168,5 +168,14 @@
                 tools:text="ID" />
         </LinearLayout>
 
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/open_consent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="16dp"
+            android:text="Consent" />
+
     </LinearLayout>
 </androidx.core.widget.NestedScrollView>
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_submission.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_submission.xml
index 2bcf0b827e69d49351dce960a513fc6e08de3661..df2c61f6063b2746edb906e2ad0b0405535402f9 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_submission.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_submission.xml
@@ -14,57 +14,6 @@
         android:orientation="vertical"
         android:paddingBottom="32dp">
 
-        <androidx.constraintlayout.widget.ConstraintLayout
-            style="@style/Card"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/spacing_tiny"
-            android:layout_marginStart="8dp"
-            android:layout_marginEnd="8dp"
-            android:orientation="vertical">
-            <TextView
-                android:id="@+id/registration_token_title"
-                style="@style/body1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="Submission registration token "
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-            <TextView
-                android:id="@+id/registration_token_current"
-                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/registration_token_title"
-                tools:text="Current test ID: 1234567890" />
-            <Button
-                android:id="@+id/delete_token_action"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_tiny"
-                android:layout_weight="1"
-                android:text="Delete token"
-                app:layout_constraintEnd_toStartOf="@+id/scramble_token_action"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/registration_token_current" />
-            <Button
-                android:id="@+id/scramble_token_action"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/spacing_tiny"
-                android:layout_weight="1"
-                android:text="Random token"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@+id/delete_token_action"
-                app:layout_constraintTop_toBottomOf="@+id/registration_token_current" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
-
-
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/tek_history"
             style="@style/Card"
diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
index a8cdc52cfa9795a8acb3cced1acab554f81ed1d9..fcb6ad2366b5adc63ef6954a1d6f37083b4163c6 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
@@ -49,6 +49,12 @@
         <action
             android:id="@+id/action_test_menu_fragment_to_presenceTracingTestFragment"
             app:destination="@id/presenceTracingTestFragment" />
+        <action
+            android:id="@+id/action_test_menu_fragment_to_homeTestCardsFragment"
+            app:destination="@id/homeTestCardsFragment" />
+        <action
+            android:id="@+id/action_test_menu_fragment_to_coronaTestTestFragment"
+            app:destination="@id/coronaTestTestFragment" />
     </fragment>
 
     <fragment
@@ -146,4 +152,14 @@
             android:name="traceLocationId"
             app:argType="long" />
     </fragment>
+    <fragment
+        android:id="@+id/homeTestCardsFragment"
+        android:name="de.rki.coronawarnapp.test.hometestcards.ui.HomeTestCardsFragment"
+        android:label="HomeTestCards"
+        tools:layout="@layout/fragment_test_home_test_cards_layout" />
+    <fragment
+        android:id="@+id/coronaTestTestFragment"
+        android:name="de.rki.coronawarnapp.test.coronatest.ui.CoronaTestTestFragment"
+        tools:layout="@layout/fragment_test_coronatest"
+        android:label="CoronaTestTestFragment" />
 </navigation>
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
index 0e1a49d5cee914765e1b923d7504324298e03b09..d10c520260fb53b3cc45d2457c95d168d37a8329 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
@@ -15,6 +15,7 @@ import de.rki.coronawarnapp.appconfig.ConfigChangeDetector
 import de.rki.coronawarnapp.appconfig.devicetime.DeviceTimeHandler
 import de.rki.coronawarnapp.bugreporting.loghistory.LogHistoryTree
 import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryWorkScheduler
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler
 import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpScheduler
@@ -24,7 +25,6 @@ import de.rki.coronawarnapp.notification.GeneralNotifications
 import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut
 import de.rki.coronawarnapp.risk.RiskLevelChangeDetector
 import de.rki.coronawarnapp.storage.OnboardingSettings
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.submission.auto.AutoSubmission
 import de.rki.coronawarnapp.task.TaskController
 import de.rki.coronawarnapp.util.CWADebug
@@ -34,8 +34,10 @@ import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.util.di.ApplicationComponent
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import org.conscrypt.Conscrypt
 import timber.log.Timber
 import java.security.Security
@@ -61,7 +63,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector {
     @Inject lateinit var notificationHelper: GeneralNotifications
     @Inject lateinit var deviceTimeHandler: DeviceTimeHandler
     @Inject lateinit var autoSubmission: AutoSubmission
-    @Inject lateinit var submissionSettings: SubmissionSettings
+    @Inject lateinit var coronaTestRepository: CoronaTestRepository
     @Inject lateinit var onboardingSettings: OnboardingSettings
     @Inject lateinit var autoCheckOut: AutoCheckOut
     @Inject lateinit var traceLocationDbCleanupScheduler: TraceLocationDbCleanUpScheduler
@@ -100,9 +102,14 @@ class CoronaWarnApplication : Application(), HasAndroidInjector {
             .launchIn(GlobalScope)
 
         if (onboardingSettings.isOnboarded) {
-            if (!submissionSettings.isAllowedToSubmitKeys) {
-                deadmanNotificationScheduler.schedulePeriodic()
+            // TODO this is on the main thread, not very nice...
+            runBlocking {
+                val isAllowedToSubmitKeys = coronaTestRepository.coronaTests.first().any { it.isSubmissionAllowed }
+                if (!isAllowedToSubmitKeys) {
+                    deadmanNotificationScheduler.schedulePeriodic()
+                }
             }
+
             contactDiaryWorkScheduler.schedulePeriodic()
         }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt
index 9ba3aef2945746ad3eb62b44777b806aff810454..4e51b2cbb71c755166aa94419ec1e3579947c582 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt
@@ -1,6 +1,7 @@
 package de.rki.coronawarnapp.appconfig
 
 import androidx.annotation.VisibleForTesting
+import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningTask
 import de.rki.coronawarnapp.risk.RiskLevelSettings
 import de.rki.coronawarnapp.risk.RiskLevelTask
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
@@ -46,8 +47,16 @@ class ConfigChangeDetector @Inject constructor(
         val oldConfigId = riskLevelSettings.lastUsedConfigIdentifier
         if (newIdentifier != oldConfigId) {
             Timber.tag(TAG).i("New config id ($newIdentifier) differs from last one ($oldConfigId), resetting.")
-            riskLevelStorage.clear()
-            taskController.submit(DefaultTaskRequest(RiskLevelTask::class, originTag = "ConfigChangeDetector"))
+            riskLevelStorage.clearResults()
+            taskController.submit(
+                DefaultTaskRequest(RiskLevelTask::class, originTag = "ConfigChangeDetector")
+            )
+            taskController.submit(
+                DefaultTaskRequest(
+                    PresenceTracingWarningTask::class,
+                    originTag = "ConfigChangeDetector"
+                )
+            )
         } else {
             Timber.tag(TAG).v("Config identifier ($oldConfigId) didn't change, NOOP.")
         }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt
index 81f2bece36227c378fb5c212b3a27171c2598a64..b3c47171c9024dcd36ff61c6ff86d1d5417bd0ec 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt
@@ -3,22 +3,33 @@ package de.rki.coronawarnapp.bugreporting.censors
 import dagger.Reusable
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
 import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
-import de.rki.coronawarnapp.submission.SubmissionSettings
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.util.CWADebug
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
 import javax.inject.Inject
 
 @Reusable
 class RegistrationTokenCensor @Inject constructor(
-    private val submissionSettings: SubmissionSettings
+    private val coronaTestRepository: CoronaTestRepository,
 ) : BugCensor {
     override suspend fun checkLog(entry: LogLine): LogLine? {
-        val token = submissionSettings.registrationToken.value ?: return null
-        if (!entry.message.contains(token)) return null
+        val tokens = coronaTestRepository.coronaTests.map { tests ->
+            tests.map { it.registrationToken }
+        }.first()
 
-        val newMessage = if (CWADebug.isDeviceForTestersBuild) {
-            entry.message.replace(token, PLACEHOLDER_TESTER + token.takeLast(27))
-        } else {
-            entry.message.replace(token, PLACEHOLDER + token.takeLast(4))
+        if (tokens.isEmpty()) return null
+
+        var newMessage = entry.message
+
+        for (token in tokens) {
+            if (!entry.message.contains(token)) continue
+
+            newMessage = if (CWADebug.isDeviceForTestersBuild) {
+                newMessage.replace(token, PLACEHOLDER_TESTER + token.takeLast(27))
+            } else {
+                newMessage.replace(token, PLACEHOLDER + token.takeLast(4))
+            }
         }
 
         return entry.toNewLogLineIfDifferent(newMessage)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt
index 33987d166b62141a4f7cd36c646b2fedafc8375e..554211b35ee9bf856825979deef234f7b3bf62dc 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt
@@ -17,6 +17,7 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
@@ -43,45 +44,50 @@ class ContactDiaryLocationListViewModel @AssistedInject constructor(
     private val dayElement = contactDiaryRepository.locationVisitsForDate(localDate)
     private val selectableLocations = contactDiaryRepository.locations
 
-    val uiList = selectableLocations.combine(dayElement) { locations, encounters ->
-        locations.map { location ->
-            val visit = encounters.singleOrNull {
-                it.contactDiaryLocation.locationId == location.locationId
-            }
-            DiaryLocationListItem(
-                item = location,
-                visit = visit,
-                onItemClick = { onLocationSelectionChanged(it as DiaryLocationListItem) },
-                onDurationChanged = { item, duration ->
-                    onDurationChanged(item, duration)
-                },
-                onCircumstancesChanged = { item, circumstances ->
-                    onCircumstancesChanged(item, circumstances)
-                },
-                onCircumStanceInfoClicked = {
-                    openCommentInfo.postValue(Unit)
-                },
-                onDurationDialog = { item, durationString ->
-                    onDurationDialog(item, durationString)
+    private val diaryLocationListItems: Flow<List<DiaryLocationListItem>> =
+        selectableLocations.combine(dayElement) { locations, encounters ->
+            locations.map { location ->
+                val visit = encounters.singleOrNull {
+                    it.contactDiaryLocation.locationId == location.locationId
                 }
-            )
-        }
-    }.asLiveData()
-
-    private fun onLocationSelectionChanged(item: DiaryLocationListItem) = launchOnAppScope {
-        if (!item.selected) {
-            contactDiaryRepository.addLocationVisit(
-                DefaultContactDiaryLocationVisit(
-                    date = localDate,
-                    contactDiaryLocation = item.item
+                DiaryLocationListItem(
+                    item = location,
+                    visit = visit,
+                    onItemClick = { onLocationSelectionChanged(it.stableId) },
+                    onDurationChanged = { item, duration ->
+                        onDurationChanged(item, duration)
+                    },
+                    onCircumstancesChanged = { item, circumstances ->
+                        onCircumstancesChanged(item, circumstances)
+                    },
+                    onCircumStanceInfoClicked = {
+                        openCommentInfo.postValue(Unit)
+                    },
+                    onDurationDialog = { item, durationString ->
+                        onDurationDialog(item, durationString)
+                    }
                 )
-            )
-        } else {
-            val visit = dayElement
-                .first()
-                .find { it.contactDiaryLocation.locationId == item.item.locationId }
-            visit?.let { contactDiaryRepository.deleteLocationVisit(it) }
+            }
         }
+
+    val uiList = diaryLocationListItems.asLiveData(dispatcherProvider.Default)
+
+    private fun onLocationSelectionChanged(itemId: Long) = launchOnAppScope {
+        diaryLocationListItems.first().find { it.stableId == itemId }?.let { item ->
+            if (!item.selected) {
+                contactDiaryRepository.addLocationVisit(
+                    DefaultContactDiaryLocationVisit(
+                        date = localDate,
+                        contactDiaryLocation = item.item
+                    )
+                )
+            } else {
+                val visit = dayElement
+                    .first()
+                    .find { it.contactDiaryLocation.locationId == item.item.locationId }
+                visit?.let { contactDiaryRepository.deleteLocationVisit(it) }
+            }
+        } ?: run { Timber.d("No item found for id $itemId") }
     }
 
     private fun onDurationDialog(
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt
index e9c93ba31002468d8431ec261184f6b5845d9d64..01cc23cd02002644647a976484e5c4e289dd6f1c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt
@@ -9,7 +9,6 @@ import de.rki.coronawarnapp.databinding.ContactDiaryLocationListItemBinding
 import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat
 import de.rki.coronawarnapp.ui.lists.BaseAdapter
 import de.rki.coronawarnapp.util.lists.BindableVH
-import de.rki.coronawarnapp.util.ui.setOnClickListenerThrottled
 
 class DiaryLocationViewHolder(
     parent: ViewGroup
@@ -25,7 +24,7 @@ class DiaryLocationViewHolder(
         val item = changes.firstOrNull() as? DiaryLocationListItem ?: initial
 
         mainBox.apply {
-            header.setOnClickListenerThrottled {
+            header.setOnClickListener {
                 it.contentDescription = item.onClickDescription.get(context)
                 it.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION)
                 item.onItemClick(item)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt
index e2c86625dd5934658828fc1238e1f0068bd85222..e9124ed2aa41daa571ffa2eb8da94dc2e5e47eb4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt
@@ -20,6 +20,7 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import org.joda.time.LocalDate
@@ -42,7 +43,7 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor(
     private val dayEncounters = contactDiaryRepository.personEncountersForDate(localDate)
     private val selectablePersons = contactDiaryRepository.people
 
-    val uiList: LiveData<List<DiaryPersonListItem>> = combine(
+    private val diaryPersonListItems: Flow<List<DiaryPersonListItem>> = combine(
         selectablePersons,
         dayEncounters
     ) { persons, encounters ->
@@ -53,7 +54,7 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor(
             DiaryPersonListItem(
                 item = person,
                 personEncounter = encounter,
-                onItemClick = { onPersonSelectionChanged(it as DiaryPersonListItem) },
+                onItemClick = { onPersonSelectionChanged(it.stableId) },
                 onDurationChanged = { item, duration ->
                     onDurationChanged(item, duration)
                 },
@@ -71,23 +72,28 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor(
                 }
             )
         }
-    }.asLiveData(context = dispatcherProvider.Default)
+    }
+
+    val uiList: LiveData<List<DiaryPersonListItem>> = diaryPersonListItems
+        .asLiveData(context = dispatcherProvider.Default)
 
     private fun onPersonSelectionChanged(
-        item: DiaryPersonListItem
+        itemId: Long
     ) = launchOnAppScope {
-        if (!item.selected) {
-            contactDiaryRepository.addPersonEncounter(
-                DefaultContactDiaryPersonEncounter(
-                    date = localDate,
-                    contactDiaryPerson = item.item
+        diaryPersonListItems.first().find { it.stableId == itemId }?.let { item ->
+            if (!item.selected) {
+                contactDiaryRepository.addPersonEncounter(
+                    DefaultContactDiaryPersonEncounter(
+                        date = localDate,
+                        contactDiaryPerson = item.item
+                    )
                 )
-            )
-        } else {
-            val visit = dayEncounters.first()
-                .find { it.contactDiaryPerson.personId == item.item.personId }
-            visit?.let { contactDiaryRepository.deletePersonEncounter(it) }
-        }
+            } else {
+                val visit = dayEncounters.first()
+                    .find { it.contactDiaryPerson.personId == item.item.personId }
+                visit?.let { contactDiaryRepository.deletePersonEncounter(it) }
+            }
+        } ?: run { Timber.d("No item found for id $itemId") }
     }
 
     private fun onDurationChanged(
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/DiaryPersonViewHolder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/DiaryPersonViewHolder.kt
index 1e6fe5c83e73ccd8113c9246185f749f1e0b0ebb..d7d4c9fba3835f5c3fed9b5af01ce529cf091001 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/DiaryPersonViewHolder.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/DiaryPersonViewHolder.kt
@@ -10,7 +10,6 @@ import de.rki.coronawarnapp.contactdiary.util.setClickLabel
 import de.rki.coronawarnapp.databinding.ContactDiaryPersonListItemBinding
 import de.rki.coronawarnapp.ui.lists.BaseAdapter
 import de.rki.coronawarnapp.util.lists.BindableVH
-import de.rki.coronawarnapp.util.ui.setOnClickListenerThrottled
 
 class DiaryPersonViewHolder(
     parent: ViewGroup
@@ -26,7 +25,7 @@ class DiaryPersonViewHolder(
         val item = changes.firstOrNull() as? DiaryPersonListItem ?: initial
 
         mainBox.apply {
-            header.setOnClickListenerThrottled {
+            header.setOnClickListener {
                 hideKeyboard()
                 it.contentDescription = item.onClickDescription.get(context)
                 it.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt
index b5d208955534aa06fac622a0eea94ddc05a8ead4..74dff70083d075cca4827a6ef246a94810b66db0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt
@@ -28,24 +28,26 @@ import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
 import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass
 import de.rki.coronawarnapp.task.TaskController
 import de.rki.coronawarnapp.task.common.DefaultTaskRequest
-import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.flow.combine
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flowOf
+import org.joda.time.Days
 import org.joda.time.LocalDate
 import timber.log.Timber
+import kotlin.concurrent.fixedRateTimer
 
 class ContactDiaryOverviewViewModel @AssistedInject constructor(
     taskController: TaskController,
     dispatcherProvider: DispatcherProvider,
     contactDiaryRepository: ContactDiaryRepository,
     riskLevelStorage: RiskLevelStorage,
-    timeStamper: TimeStamper,
+    private val timeStamper: TimeStamper,
     checkInRepository: CheckInRepository,
     private val exporter: ContactDiaryExporter
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
@@ -53,7 +55,18 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor(
     val routeToScreen: SingleLiveEvent<ContactDiaryOverviewNavigationEvents> = SingleLiveEvent()
     val exportLocationsAndPersons: SingleLiveEvent<String> = SingleLiveEvent()
 
-    private val dates = (0 until DAY_COUNT).map { timeStamper.nowUTC.toLocalDateUtc().minusDays(it) }
+    private fun TimeStamper.localDate(): LocalDate = nowUTC.toUserTimeZone().toLocalDate()
+
+    private fun dates() = (0 until DAY_COUNT).map { timeStamper.localDate().minusDays(it) }
+    private val datesFlow = MutableStateFlow(dates())
+
+    private val reloadDatesMidnightTimer = fixedRateTimer(
+        name = "Reload-contact-journal-dates-timer-thread",
+        daemon = true,
+        startAt = timeStamper.localDate().plusDays(1).toDate(),
+        period = Days.ONE.toStandardDuration().millis,
+        action = { datesFlow.value = dates() }
+    )
 
     private val locationVisitsFlow = contactDiaryRepository.locationVisits
     private val personEncountersFlow = contactDiaryRepository.personEncounters
@@ -63,7 +76,7 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor(
     private val checkInsWithinRetentionFlow = checkInRepository.checkInsWithinRetention
 
     val listItems = combine(
-        flowOf(dates),
+        datesFlow,
         locationVisitsFlow,
         personEncountersFlow,
         riskLevelPerDateFlow,
@@ -303,6 +316,11 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor(
         }
     }
 
+    override fun onCleared() {
+        super.onCleared()
+        reloadDatesMidnightTimer.cancel()
+    }
+
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<ContactDiaryOverviewViewModel>
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt
index 1a6c81a94c34a9caf1ce566111e10c30f5c6098b..3fc92f6ac584cc36b0ca7079a53d52afeeba7254 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt
@@ -1,6 +1,27 @@
 package de.rki.coronawarnapp.coronatest
 
+import dagger.Binds
 import dagger.Module
+import dagger.multibindings.IntoSet
+import de.rki.coronawarnapp.coronatest.server.VerificationModule
+import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRProcessor
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RapidAntigenProcessor
 
-@Module
-class CoronaTestModule
+@Module(
+    includes = [VerificationModule::class]
+)
+abstract class CoronaTestModule {
+
+    @Binds
+    @IntoSet
+    abstract fun pcrProcessor(
+        processor: PCRProcessor
+    ): CoronaTestProcessor
+
+    @Binds
+    @IntoSet
+    abstract fun ratProcessor(
+        processor: RapidAntigenProcessor
+    ): CoronaTestProcessor
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt
index 832198cf3a59fa31bbda8f0bb55a31fc896f3a6c..2921477f4d0bbddf6ae3ea9a7041d0df92055cd8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt
@@ -1,50 +1,253 @@
 package de.rki.coronawarnapp.coronatest
 
+import androidx.annotation.VisibleForTesting
+import de.rki.coronawarnapp.bugreporting.reportProblem
+import de.rki.coronawarnapp.coronatest.migration.PCRTestMigration
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestGUID
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
-import de.rki.coronawarnapp.coronatest.server.CoronaTestServer
 import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage
+import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
+import de.rki.coronawarnapp.coronatest.type.TestIdentifier
+import de.rki.coronawarnapp.util.coroutine.AppScope
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.flow.HotDataFlow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.plus
+import kotlinx.coroutines.withContext
 import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
 class CoronaTestRepository @Inject constructor(
+    @AppScope private val appScope: CoroutineScope,
+    private val dispatcherProvider: DispatcherProvider,
     private val storage: CoronaTestStorage,
-    private val server: CoronaTestServer,
+    private val processors: Set<@JvmSuppressWildcards CoronaTestProcessor>,
+    private val legacyMigration: PCRTestMigration,
 ) {
 
-    val coronaTests: Flow<Set<CoronaTest>> = emptyFlow()
+    private val internalData: HotDataFlow<Map<CoronaTestGUID, CoronaTest>> = HotDataFlow(
+        loggingTag = TAG,
+        scope = appScope + dispatcherProvider.IO,
+        sharingBehavior = SharingStarted.Eagerly,
+    ) {
+        val legacyTests = legacyMigration.startMigration()
+        val persistedTests = storage.coronaTests
+        (legacyTests + persistedTests).map { it.identifier to it }.toMap().also {
+            Timber.tag(TAG).v("Restored CoronaTest data: %s", it)
+        }
+    }
 
-    /**
-     * When this returns and there was no exception, the test was registered and a valid registrationToken obtained.
-     * Your new test should be available via **coronaTests**.
-     */
-    suspend fun registerTest(request: CoronaTestQRCode) {
-        Timber.tag(TAG).i("registerTest(request=%s)", request)
+    val coronaTests: Flow<Set<CoronaTest>> = internalData.data.map { it.values.toSet() }
+
+    init {
+        internalData.data
+            .onStart { Timber.tag(TAG).d("Observing test data.") }
+            .onEach {
+                Timber.tag(TAG).v("CoronaTest data changed: %s", it)
+                storage.coronaTests = it.values.toSet()
+                legacyMigration.finishMigration()
+            }
+            .catch {
+                it.reportProblem(TAG, "Failed to snapshot CoronaTest data to storage.")
+                throw it
+            }
+            .launchIn(appScope + dispatcherProvider.IO)
     }
 
-    suspend fun removeTest(guid: CoronaTestGUID): CoronaTest {
-        Timber.tag(TAG).i("removeTest(guid=%s)", guid)
+    private fun getProcessor(type: CoronaTest.Type) = processors.single { it.type == type }
 
-        throw NotImplementedError()
+    suspend fun registerTest(registrationRequest: TestRegistrationRequest): CoronaTest = when (registrationRequest) {
+        is CoronaTestQRCode -> registerTestByQRCode(registrationRequest)
+        is CoronaTestTAN -> registerTestByTAN(registrationRequest)
+        else -> throw IllegalArgumentException("Unknown test request: $registrationRequest")
     }
 
-    suspend fun markAsSubmitted(guid: CoronaTestGUID) {
-        Timber.tag(TAG).i("markAsSubmitted(guid=%s)", guid)
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    internal suspend fun registerTestByTAN(request: CoronaTestTAN): CoronaTest {
+        Timber.tag(TAG).i("registerTestByQRCode(request=%s)", request)
+        // We check early, if there is no processor, crash early, "should" never happen though...
+        val processor = getProcessor(request.type)
+
+        val currentTests = internalData.updateBlocking {
+            if (values.any { it.type == request.type }) {
+                throw IllegalStateException("There is already a test of this type: ${request.type}.")
+            }
+
+            val test = processor.create(request)
+            Timber.tag(TAG).i("Adding new test: %s", test)
+
+            toMutableMap().apply { this[test.identifier] = test }
+        }
+
+        return currentTests[request.identifier]!!
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    internal suspend fun registerTestByQRCode(request: CoronaTestQRCode): CoronaTest {
+        Timber.tag(TAG).i("registerTestByQRCode(request=%s)", request)
+        // We check early, if there is no processor, crash early, "should" never happen though...
+        val processor = getProcessor(request.type)
+
+        val currentTests = internalData.updateBlocking {
+            if (values.any { it.type == request.type }) {
+                throw IllegalStateException("There is already a test of this type: ${request.type}.")
+            }
+
+            val test = processor.create(request)
+            Timber.tag(TAG).i("Adding new test: %s", test)
+
+            toMutableMap().apply { this[test.identifier] = test }
+        }
+
+        return currentTests[request.identifier]!!
+    }
+
+    suspend fun removeTest(identifier: TestIdentifier): CoronaTest {
+        Timber.tag(TAG).i("removeTest(identifier=%s)", identifier)
+
+        var removedTest: CoronaTest? = null
+
+        internalData.updateBlocking {
+            val toBeRemoved = values.singleOrNull { it.identifier == identifier }
+                ?: throw IllegalArgumentException("No found for $identifier")
+
+            getProcessor(toBeRemoved.type).onRemove(toBeRemoved)
+
+            toMutableMap().apply {
+                removedTest = remove(toBeRemoved.identifier)
+                Timber.tag(TAG).d("Removed: %s", removedTest)
+            }
+        }
+
+        return removedTest!!
     }
 
     /**
      * Passing **null** will refresh all test types.
      */
-    fun refresh(type: CoronaTest.Type? = null) {
+    suspend fun refresh(type: CoronaTest.Type? = null): Set<CoronaTest> {
         Timber.tag(TAG).d("refresh(type=%s)", type)
+
+        val toRefresh = internalData.data
+            .first().values
+            .filter { if (type == null) true else it.type == type }
+            .map { it.identifier }
+
+        Timber.tag(TAG).d("Will refresh %s", toRefresh)
+
+        toRefresh.forEach {
+            modifyTest(it) { processor, test ->
+                processor.markProcessing(test, true)
+            }
+        }
+
+        val refreshedData = internalData.updateBlocking {
+            val polling = values
+                .filter { if (type == null) true else it.type == type }
+                .filter { toRefresh.contains(it.identifier) }
+                .map { coronaTest ->
+
+                    withContext(context = dispatcherProvider.IO) {
+                        async {
+                            Timber.tag(TAG).v("Polling for %s", coronaTest)
+                            // This will not throw an exception
+                            // Any error encountered during polling will be in CoronaTest.lastError
+                            getProcessor(coronaTest.type).pollServer(coronaTest)
+                        }
+                    }
+                }
+
+            Timber.tag(TAG).d("Waiting for test status polling: %s", polling)
+            val pollingResults = polling.awaitAll()
+
+            this.toMutableMap().apply {
+                for (updatedResult in pollingResults) {
+                    this[updatedResult.identifier] = updatedResult
+                }
+            }
+        }
+
+        toRefresh.forEach {
+            modifyTest(it) { processor, test ->
+                processor.markProcessing(test, false)
+            }
+        }
+
+        return refreshedData.values.toSet()
+    }
+
+    suspend fun clear() {
+        Timber.tag(TAG).i("clear()")
+        internalData.updateBlocking {
+            Timber.tag(TAG).d("Clearing %s", this)
+            emptyMap()
+        }
+    }
+
+    suspend fun markAsSubmitted(identifier: TestIdentifier) {
+        Timber.tag(TAG).i("markAsSubmitted(identifier=%s)", identifier)
+
+        modifyTest(identifier) { processor, before ->
+            processor.markSubmitted(before)
+        }
+    }
+
+    suspend fun markAsViewed(identifier: TestIdentifier) {
+        Timber.tag(TAG).i("markAsViewed(identifier=%s)", identifier)
+
+        modifyTest(identifier) { processor, before ->
+            processor.markViewed(before)
+        }
+    }
+
+    suspend fun updateConsent(identifier: TestIdentifier, consented: Boolean) {
+        Timber.tag(TAG).i("updateConsent(identifier=%s, consented=%b)", identifier, consented)
+
+        modifyTest(identifier) { processor, before ->
+            processor.updateConsent(before, consented)
+        }
+    }
+
+    suspend fun updateResultNotification(identifier: TestIdentifier, sent: Boolean) {
+        Timber.tag(TAG).i("updateResultNotification(identifier=%s, sent=%b)", identifier, sent)
+
+        modifyTest(identifier) { processor, before ->
+            processor.updateResultNotification(before, sent)
+        }
+    }
+
+    private suspend fun modifyTest(
+        identifier: TestIdentifier,
+        update: suspend (CoronaTestProcessor, CoronaTest) -> CoronaTest
+    ) {
+        internalData.updateBlocking {
+            val original = values.singleOrNull { it.identifier == identifier }
+                ?: throw IllegalArgumentException("No found for $identifier")
+
+            val processor = getProcessor(original.type)
+
+            val updated = update(processor, original)
+            Timber.tag(TAG).d("Updated %s to %s", original, updated)
+
+            toMutableMap().apply { this[original.identifier] = updated }
+        }
     }
 
     companion object {
-        const val TAG = "CoronaTestRepo"
+        const val TAG = "CoronaTestRepository"
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8237875c2262584de2afbacaaf9c6e735221b9ef
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryExtensions.kt
@@ -0,0 +1,26 @@
+package de.rki.coronawarnapp.coronatest
+
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+val CoronaTestRepository.latestPCRT: Flow<PCRCoronaTest?>
+    get() = this.coronaTests
+        .map { allTests ->
+            allTests.singleOrNull {
+                it.type == CoronaTest.Type.PCR
+            } as? PCRCoronaTest
+        }
+        .distinctUntilChanged()
+
+val CoronaTestRepository.latestRAT: Flow<RACoronaTest?>
+    get() = this.coronaTests
+        .map { allTests ->
+            allTests.singleOrNull {
+                it.type == CoronaTest.Type.RAPID_ANTIGEN
+            } as? RACoronaTest
+        }
+        .distinctUntilChanged()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..27542f0fd03527fd95ec339e5b4b68d18538efcd
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt
@@ -0,0 +1,8 @@
+package de.rki.coronawarnapp.coronatest
+
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+
+interface TestRegistrationRequest {
+    val type: CoronaTest.Type
+    val identifier: String
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/execution/TestResultScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/execution/TestResultScheduler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a1c7ba36bb4f42702e665b6e0231b356ef35f3a4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/execution/TestResultScheduler.kt
@@ -0,0 +1,85 @@
+package de.rki.coronawarnapp.coronatest.execution
+
+import androidx.work.BackoffPolicy
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import dagger.Reusable
+import de.rki.coronawarnapp.storage.TracingSettings
+import de.rki.coronawarnapp.util.TimeStamper
+import de.rki.coronawarnapp.util.coroutine.await
+import de.rki.coronawarnapp.worker.BackgroundConstants
+import de.rki.coronawarnapp.worker.BackgroundWorkHelper
+import de.rki.coronawarnapp.worker.DiagnosisTestResultRetrievalPeriodicWorker
+import kotlinx.coroutines.runBlocking
+import timber.log.Timber
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+@Reusable
+class TestResultScheduler @Inject constructor(
+    private val tracingSettings: TracingSettings,
+    private val workManager: WorkManager,
+    private val timeStamper: TimeStamper,
+) {
+
+    private suspend fun isScheduled(): Boolean {
+        val workerInfos = workManager.getWorkInfosForUniqueWork(PCR_TESTRESULT_WORKER_UNIQUEUNAME).await()
+
+        return workerInfos.any { it.isScheduled }
+    }
+
+    fun setPeriodicTestPolling(enabled: Boolean) {
+        if (enabled) {
+            // TODO Refactor runBlocking away
+            val isScheduled = runBlocking { isScheduled() }
+            if (isScheduled) {
+                Timber.tag(TAG).w("Already scheduled, skipping")
+                return
+            }
+            Timber.tag(TAG).i("Queueing test result worker (DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER)")
+            workManager.enqueueUniquePeriodicWork(
+                PCR_TESTRESULT_WORKER_UNIQUEUNAME,
+                ExistingPeriodicWorkPolicy.REPLACE,
+                buildDiagnosisTestResultRetrievalPeriodicWork()
+            )
+        } else {
+            Timber.tag(TAG).d("cancelWorker()")
+            workManager.cancelUniqueWork(PCR_TESTRESULT_WORKER_UNIQUEUNAME)
+        }
+    }
+
+    private fun buildDiagnosisTestResultRetrievalPeriodicWork() =
+        PeriodicWorkRequestBuilder<DiagnosisTestResultRetrievalPeriodicWorker>(
+            BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(),
+            TimeUnit.MINUTES
+        )
+            .addTag(PCR_TESTRESULT_WORKER_TAG)
+            .setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork())
+            .setInitialDelay(
+                DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY,
+                TimeUnit.SECONDS
+            ).setBackoffCriteria(
+                BackoffPolicy.LINEAR,
+                BackgroundConstants.KIND_DELAY,
+                TimeUnit.MINUTES
+            )
+            .build()
+
+    private val WorkInfo.isScheduled: Boolean
+        get() = state == WorkInfo.State.RUNNING || state == WorkInfo.State.ENQUEUED
+
+    companion object {
+        /**
+         * Kind initial delay in minutes for periodic work for accessibility reason
+         *
+         * @see TimeUnit.SECONDS
+         */
+        private const val DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY = 10L
+        private const val PCR_TESTRESULT_WORKER_TAG = "DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER"
+        private const val PCR_TESTRESULT_WORKER_UNIQUEUNAME = "DiagnosisTestResultBackgroundPeriodicWork"
+
+        private const val TAG = "TestResultScheduler"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/migration/PCRTestMigration.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/migration/PCRTestMigration.kt
new file mode 100644
index 0000000000000000000000000000000000000000..851c81cd75815948ad67746b31df4bc4e98d6d71
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/migration/PCRTestMigration.kt
@@ -0,0 +1,100 @@
+package de.rki.coronawarnapp.coronatest.migration
+
+import dagger.Reusable
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.RegistrationToken
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
+import de.rki.coronawarnapp.storage.TracingSettings
+import de.rki.coronawarnapp.submission.SubmissionSettings
+import de.rki.coronawarnapp.util.CWADebug
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import timber.log.Timber
+import javax.inject.Inject
+
+@Reusable
+class PCRTestMigration @Inject constructor(
+    private val submissionSettings: SubmissionSettings,
+    private val tracingSettings: TracingSettings,
+) {
+
+    private val mutex = Mutex()
+    private var isMigrating: Boolean = false
+
+    @Suppress("DEPRECATION")
+    suspend fun startMigration(): Set<CoronaTest> = mutex.withLock {
+        if (isMigrating) throw IllegalStateException("Migration already in progress")
+        isMigrating = true
+        Timber.tag(TAG).i("startMigration()")
+
+        val token: RegistrationToken? = submissionSettings.registrationTokenMigration
+        if (token == null) {
+            Timber.tag(TAG).d("Nothing to migrate, token was null.")
+            return emptySet()
+        } else {
+            Timber.tag(TAG).i("Migrating token %s", token)
+        }
+
+        val devicePairingSuccessfulAt = submissionSettings.devicePairingSuccessfulAtMigration
+        Timber.tag(TAG).v("devicePairingSuccessfulAt=%s", devicePairingSuccessfulAt)
+        if (devicePairingSuccessfulAt == null) {
+            if (CWADebug.isDeviceForTestersBuild) {
+                throw IllegalStateException("Can't have registration token without device pairing timestamp !?")
+            }
+            return emptySet()
+        }
+
+        val isAllowedToSubmitKeys = submissionSettings.isAllowedToSubmitKeysMigration
+        Timber.tag(TAG).v("isAllowedToSubmitKeys=%s", isAllowedToSubmitKeys)
+
+        val isSubmissionSuccessful = submissionSettings.isSubmissionSuccessfulMigration
+        Timber.tag(TAG).v("isSubmissionSuccessful=%s", isSubmissionSuccessful)
+
+        val hasViewedTestResult = submissionSettings.hasViewedTestResultMigration
+        Timber.tag(TAG).v("hasViewedTestResult=%s", hasViewedTestResult)
+
+        val hasGivenConsent = submissionSettings.hasGivenConsentMigration
+        Timber.tag(TAG).v("hasGivenConsent=%s", hasGivenConsent)
+
+        // TODO per test ?
+        val testResultNotificationSent = tracingSettings.isTestResultAvailableNotificationSentMigration
+        Timber.tag(TAG).v("testResultNotificationSent=%s", testResultNotificationSent)
+
+        val legacyPCRTest = PCRCoronaTest(
+            identifier = LEGACY_IDENTIFIER,
+            registrationToken = token,
+            registeredAt = devicePairingSuccessfulAt,
+            testResult = when (isAllowedToSubmitKeys) {
+                true -> CoronaTestResult.PCR_POSITIVE
+                else -> CoronaTestResult.PCR_OR_RAT_PENDING
+            },
+            testResultReceivedAt = devicePairingSuccessfulAt,
+            isSubmitted = isSubmissionSuccessful,
+            isViewed = hasViewedTestResult,
+            isAdvancedConsentGiven = hasGivenConsent,
+            isResultAvailableNotificationSent = testResultNotificationSent,
+        )
+        return setOf(legacyPCRTest).also {
+            Timber.tag(TAG).d("Offering converted legacy CoronaTest: %s", it)
+        }
+    }
+
+    suspend fun finishMigration() = mutex.withLock {
+        if (!isMigrating) return@withLock
+        isMigrating = false
+        Timber.tag(TAG).i("finishMigration()")
+
+        submissionSettings.deleteLegacyTestData()
+        tracingSettings.deleteLegacyTestData()
+    }
+
+    companion object {
+        private const val TAG = "CoronaTestMigration"
+
+        /**
+         * We only use this for identification, needs to be guaranteed different from any non-legacy identifiers.
+         */
+        private const val LEGACY_IDENTIFIER = "qrcode-pcr-legacy"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt
index 36eda1768e6b139014ba46e35fc13b3fb68d4f0c..0db2d0ed6f9343596216423d14c64f380f662583 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt
@@ -1,30 +1,57 @@
 package de.rki.coronawarnapp.coronatest.qrcode
 
 import android.os.Parcelable
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.util.HashExtensions.toSHA256
+import kotlinx.parcelize.IgnoredOnParcel
 import kotlinx.parcelize.Parcelize
 import org.joda.time.Instant
+import org.joda.time.LocalDate
 
-sealed class CoronaTestQRCode : Parcelable {
+sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest {
 
-    abstract val type: CoronaTest.Type
-    abstract val guid: CoronaTestGUID
+    abstract override val type: CoronaTest.Type
+    abstract val registrationIdentifier: String
 
     @Parcelize
     data class PCR(
-        override val type: CoronaTest.Type,
-        override val guid: CoronaTestGUID,
-    ) : CoronaTestQRCode()
+        val qrCodeGUID: CoronaTestGUID,
+    ) : CoronaTestQRCode() {
+
+        @IgnoredOnParcel
+        override val type: CoronaTest.Type = CoronaTest.Type.PCR
+
+        @IgnoredOnParcel
+        override val identifier: String
+            get() = "qrcode-${type.raw}-$qrCodeGUID"
+
+        @IgnoredOnParcel
+        override val registrationIdentifier: String
+            get() = qrCodeGUID
+    }
 
     @Parcelize
     data class RapidAntigen(
-        override val type: CoronaTest.Type,
-        override val guid: CoronaTestGUID,
+        val hash: RapidAntigenHash,
         val createdAt: Instant,
         val firstName: String?,
         val lastName: String?,
-        val dateOfBirth: String?,
-    ) : CoronaTestQRCode()
+        val dateOfBirth: LocalDate?,
+    ) : CoronaTestQRCode() {
+
+        @IgnoredOnParcel
+        override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
+
+        @IgnoredOnParcel
+        override val identifier: String
+            get() = "qrcode-${type.raw}-$hash"
+
+        @IgnoredOnParcel
+        override val registrationIdentifier: String
+            get() = hash.toSHA256()
+    }
 }
 
 typealias CoronaTestGUID = String
+typealias RapidAntigenHash = String
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeValidation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeValidation.kt
deleted file mode 100644
index 717aaf78317c7822d4fc7225da3d76ea5a746839..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeValidation.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package de.rki.coronawarnapp.coronatest.qrcode
-
-import dagger.Reusable
-import javax.inject.Inject
-
-@Reusable
-class CoronaTestQRCodeValidation @Inject constructor() {
-
-    suspend fun validate(qrCode: String): CoronaTestQRCode {
-        throw NotImplementedError()
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeValidator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeValidator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a3a54f940680e7457a3b57ade7ce668adc51f251
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeValidator.kt
@@ -0,0 +1,27 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+import dagger.Reusable
+import timber.log.Timber
+import javax.inject.Inject
+
+@Reusable
+class CoronaTestQrCodeValidator @Inject constructor(
+    ratExtractor: RapidAntigenQrCodeExtractor,
+    pcrExtractor: PcrQrCodeExtractor
+) {
+
+    private val extractors = setOf(ratExtractor, pcrExtractor)
+
+    fun validate(rawString: String): CoronaTestQRCode {
+        return extractors
+            .find { it.canHandle(rawString) }
+            ?.extract(rawString)
+            ?.also { Timber.i("Extracted data from QR code is $it") }
+            ?: throw InvalidQRCodeException()
+    }
+}
+
+interface QrCodeExtractor {
+    fun canHandle(rawString: String): Boolean
+    fun extract(rawString: String): CoronaTestQRCode
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/InvalidQRCodeException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/InvalidQRCodeException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..24dd420a35009d94e55b06ece7c80802cd6db8eb
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/InvalidQRCodeException.kt
@@ -0,0 +1,5 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+open class InvalidQRCodeException(
+    message: String = "An error occurred while parsing the qr code"
+) : Exception(message)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/PcrQrCodeExtractor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/PcrQrCodeExtractor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..924bd4e89f6b0bef3ca0d7f09cd15539c300797c
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/PcrQrCodeExtractor.kt
@@ -0,0 +1,34 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+import java.util.regex.Pattern
+import javax.inject.Inject
+
+class PcrQrCodeExtractor @Inject constructor() : QrCodeExtractor {
+
+    override fun canHandle(rawString: String): Boolean = rawString.startsWith(prefix, ignoreCase = true)
+
+    override fun extract(rawString: String): CoronaTestQRCode.PCR {
+        return CoronaTestQRCode.PCR(
+            extractGUID(rawString)
+        )
+    }
+
+    private fun extractGUID(rawString: String): CoronaTestGUID {
+        if (!pattern.toRegex().matches(rawString)) throw InvalidQRCodeException()
+
+        val matcher = pattern.matcher(rawString)
+        return if (matcher.matches()) {
+            matcher.group(1) as CoronaTestGUID
+        } else throw InvalidQRCodeException()
+    }
+
+    private val prefix: String = "https://localhost"
+
+    private val pattern: Pattern = (
+        "^" + // Match start of string
+            "(?:https:\\/{2}localhost)" + // Match `https://localhost`
+            "(?:\\/{1}\\?)" + // Match the query param `/?`
+            "([a-f\\d]{6}[-][a-f\\d]{8}[-](?:[a-f\\d]{4}[-]){3}[a-f\\d]{12})" + // Match the UUID
+            "\$"
+        ).toPattern(Pattern.CASE_INSENSITIVE)
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..deffb83ef479b45aa257c30868fd1c0e468c6395
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
@@ -0,0 +1,92 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+import com.google.common.io.BaseEncoding
+import com.google.gson.Gson
+import com.google.gson.annotations.SerializedName
+import de.rki.coronawarnapp.util.serialization.fromJson
+import okio.internal.commonToUtf8String
+import org.joda.time.Instant
+import org.joda.time.LocalDate
+import timber.log.Timber
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+import javax.inject.Inject
+
+class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor {
+
+    private val prefix: String = "https://s.coronawarn.app?v=1#"
+    private val prefix2: String = "https://s.coronawarn.app/?v=1#"
+    private val hexPattern: Pattern = Pattern.compile("\\p{XDigit}+")
+
+    override fun canHandle(rawString: String): Boolean {
+        return rawString.startsWith(prefix, ignoreCase = true) || rawString.startsWith(prefix2, ignoreCase = true)
+    }
+
+    override fun extract(rawString: String): CoronaTestQRCode.RapidAntigen {
+        val data = extractData(rawString).validate()
+        return CoronaTestQRCode.RapidAntigen(
+            hash = data.hash!!,
+            createdAt = data.createdAt!!,
+            firstName = data.firstName,
+            lastName = data.lastName,
+            dateOfBirth = data.dateOfBirth
+        )
+    }
+
+    private fun Payload.validate(): Payload {
+        if (hash == null || !hash.isSha256Hash()) throw InvalidQRCodeException("Hash is invalid")
+        if (timestamp == null || timestamp <= 0) throw InvalidQRCodeException("Timestamp is invalid")
+        createdAt = Instant.ofEpochSecond(timestamp)
+        dateOfBirth = dob?.let {
+            try {
+                LocalDate.parse(it)
+            } catch (e: Exception) {
+                Timber.e("Invalid date format")
+                throw InvalidQRCodeException("Date of birth has wrong format: $it. It should be YYYY-MM-DD")
+            }
+        }
+        return this
+    }
+
+    private fun String.isSha256Hash(): Boolean {
+        return length == 64 && isHexadecimal()
+    }
+
+    private fun String.isHexadecimal(): Boolean {
+        val matcher: Matcher = hexPattern.matcher(this)
+        return matcher.matches()
+    }
+
+    private fun extractData(rawString: String): Payload {
+        return rawString
+            .removePrefix(prefix)
+            .removePrefix(prefix2)
+            .decode()
+    }
+
+    private fun String.decode(): Payload {
+        val decoded = if (
+            this.contains("+") ||
+            this.contains("/") ||
+            this.contains("=")
+        ) {
+            BaseEncoding.base64().decode(this)
+        } else {
+            BaseEncoding.base64Url().decode(this)
+        }
+        return Gson().fromJson(decoded.commonToUtf8String())
+    }
+
+    private data class Payload(
+        val hash: String?,
+        val timestamp: Long?,
+        @SerializedName("fn")
+        val firstName: String?,
+        @SerializedName("ln")
+        val lastName: String?,
+        val dob: String?
+    ) {
+        var dateOfBirth: LocalDate? = null
+        var createdAt: Instant? = null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6807bf6701e021461901e28736c8d2e2e7cd4e59
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt
@@ -0,0 +1,76 @@
+package de.rki.coronawarnapp.coronatest.server
+
+import com.google.gson.TypeAdapter
+import com.google.gson.stream.JsonReader
+import com.google.gson.stream.JsonWriter
+import org.json.JSONObject
+
+enum class CoronaTestResult(val value: Int) {
+    /**
+     * Pending (PCR test) or Pending (rapid antigen test)
+     */
+    PCR_OR_RAT_PENDING(0),
+
+    /**
+     * Negative (PCR test)
+     */
+    PCR_NEGATIVE(1),
+
+    /**
+     * Positive (PCR test)
+     */
+    PCR_POSITIVE(2),
+
+    /**
+     * Invalid (PCR test)
+     */
+    PCR_INVALID(3),
+
+    /**
+     * Redeemed (PCR test; locally referred to as Expired)
+     */
+    PCR_REDEEMED(4),
+
+    /**
+     * 	Pending (rapid antigen test)
+     */
+    RAT_PENDING(5),
+
+    /**
+     *  Negative (rapid antigen test)
+     */
+    RAT_NEGATIVE(6),
+
+    /**
+     * Positive (rapid antigen test)
+     */
+    RAT_POSITIVE(7),
+
+    /**
+     * 	Invalid (rapid antigen test)
+     */
+    RAT_INVALID(8),
+
+    /**
+     * Redeemed (rapid antigen test; locally referred to as Expired))
+     */
+    RAT_REDEEMED(9);
+
+    override fun toString(): String = "$name($value)"
+
+    companion object {
+        fun fromInt(value: Int) = values().single { it.value == value }
+    }
+
+    class GsonAdapter : TypeAdapter<CoronaTestResult>() {
+        override fun write(out: JsonWriter, value: CoronaTestResult?) {
+            if (value == null) out.nullValue()
+            else out.value(value.value)
+        }
+
+        override fun read(reader: JsonReader): CoronaTestResult? = when (reader.peek()) {
+            JSONObject.NULL -> reader.nextNull().let { null }
+            else -> fromInt(reader.nextInt())
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestServer.kt
deleted file mode 100644
index 7319755a4eb97a49def0752e2daf003d3afd0b39..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestServer.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.rki.coronawarnapp.coronatest.server
-
-import dagger.Reusable
-import javax.inject.Inject
-
-@Reusable
-class CoronaTestServer @Inject constructor()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt
similarity index 97%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationApiV1.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt
index a45f6617de515e732f22c1e769bc37a788d7e301..3a11d242906c2835821f7e7e241d39e54d5b332c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationApiV1.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.verification.server
+package de.rki.coronawarnapp.coronatest.server
 
 import com.google.gson.annotations.SerializedName
 import retrofit2.http.Body
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationHttpClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationHttpClient.kt
similarity index 74%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationHttpClient.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationHttpClient.kt
index 1c244ae61732a1e8ab53ca6acc6ec5303c5b203b..160a6fd7a52dd45a31215ff43e31fb0975a69615 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationHttpClient.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationHttpClient.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.verification.server
+package de.rki.coronawarnapp.coronatest.server
 
 import javax.inject.Qualifier
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationKeyType.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationKeyType.kt
similarity index 52%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationKeyType.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationKeyType.kt
index f8112264c1a6c5491e6c5f0c7483c02044582f38..c64cfe628a9c28c3ec05437ba20f5cf81fc82a8b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationKeyType.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationKeyType.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.verification.server
+package de.rki.coronawarnapp.coronatest.server
 
 enum class VerificationKeyType {
     GUID, TELETAN;
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/VerificationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationModule.kt
similarity index 90%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/VerificationModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationModule.kt
index 85d2bea5611ab57d9b81613fa80d065c1d6e461e..24dba8c5d9506837dc158fb1f2f0676b78787f9e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/VerificationModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.verification
+package de.rki.coronawarnapp.coronatest.server
 
 import android.content.Context
 import dagger.Module
@@ -8,8 +8,6 @@ import de.rki.coronawarnapp.environment.verification.VerificationCDNServerUrl
 import de.rki.coronawarnapp.http.HttpClientDefault
 import de.rki.coronawarnapp.http.RestrictedConnectionSpecs
 import de.rki.coronawarnapp.util.di.AppContext
-import de.rki.coronawarnapp.verification.server.VerificationApiV1
-import de.rki.coronawarnapp.verification.server.VerificationHttpClient
 import okhttp3.Cache
 import okhttp3.ConnectionSpec
 import okhttp3.OkHttpClient
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationServer.kt
similarity index 82%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationServer.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationServer.kt
index 1411ca60db6ce02dcdbfb997bbe617345ee3b568..5f6463236d1f9aec470e66832b9ca42ab554d007 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/server/VerificationServer.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationServer.kt
@@ -1,10 +1,12 @@
-package de.rki.coronawarnapp.verification.server
+package de.rki.coronawarnapp.coronatest.server
 
 import dagger.Lazy
+import de.rki.coronawarnapp.coronatest.type.RegistrationToken
 import de.rki.coronawarnapp.util.PaddingTool.requestPadding
 import de.rki.coronawarnapp.util.security.HashHelper
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
+import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -19,7 +21,7 @@ class VerificationServer @Inject constructor(
     suspend fun retrieveRegistrationToken(
         key: String,
         keyType: VerificationKeyType
-    ): String = withContext(Dispatchers.IO) {
+    ): RegistrationToken = withContext(Dispatchers.IO) {
         val keyStr = if (keyType == VerificationKeyType.GUID) {
             HashHelper.hash256(key)
         } else {
@@ -42,21 +44,25 @@ class VerificationServer @Inject constructor(
         ).registrationToken
     }
 
-    suspend fun retrieveTestResults(
-        registrationToken: String
-    ): Int = withContext(Dispatchers.IO) {
-        api.getTestResult(
+    suspend fun pollTestResult(
+        token: RegistrationToken
+    ): CoronaTestResult = withContext(Dispatchers.IO) {
+        Timber.tag(TAG).d("retrieveTestResults(token=%s)", token)
+        val response = api.getTestResult(
             fake = "0",
             headerPadding = requestPadding(PADDING_LENGTH_HEADER_TEST_RESULT),
             request = VerificationApiV1.RegistrationRequest(
-                registrationToken,
+                token,
                 requestPadding(PADDING_LENGTH_BODY_TEST_RESULT)
             )
-        ).testResult
+        )
+
+        Timber.tag(TAG).d("retrieveTestResults(token=%s) -> %s", token, response)
+        CoronaTestResult.fromInt(response.testResult)
     }
 
     suspend fun retrieveTan(
-        registrationToken: String
+        registrationToken: RegistrationToken
     ): String = withContext(Dispatchers.IO) {
         api.getTAN(
             fake = "0",
@@ -96,5 +102,7 @@ class VerificationServer @Inject constructor(
         const val PADDING_LENGTH_BODY_TAN = 31 + VERIFICATION_BODY_FILL
         const val PADDING_LENGTH_BODY_TAN_FAKE = 31 + VERIFICATION_BODY_FILL
         const val DUMMY_REGISTRATION_TOKEN = "11111111-2222-4444-8888-161616161616"
+
+        private const val TAG = "VerificationServer"
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorage.kt
index 771397027a879b3817c8c516904865cfcf79b088..0fc19f03bbef50d2fd228b96103ece548998c863 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorage.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorage.kt
@@ -1,23 +1,96 @@
 package de.rki.coronawarnapp.coronatest.storage
 
+import android.content.Context
+import androidx.core.content.edit
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
+import de.rki.coronawarnapp.util.di.AppContext
+import de.rki.coronawarnapp.util.serialization.BaseGson
 import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
-class CoronaTestStorage @Inject constructor() {
+class CoronaTestStorage @Inject constructor(
+    @AppContext val context: Context,
+    @BaseGson val baseGson: Gson
+) {
 
-    suspend fun load(): Set<CoronaTest> {
-        Timber.tag(TAG).d("load()")
-        throw NotImplementedError()
+    private val prefs by lazy {
+        context.getSharedPreferences("coronatest_localdata", Context.MODE_PRIVATE)
     }
 
-    suspend fun save(tests: Set<CoronaTest>) {
-        Timber.tag(TAG).d("save(tests=%s)", tests)
+    private val gson by lazy {
+        baseGson.newBuilder().apply {
+            registerTypeAdapter(CoronaTestResult::class.java, CoronaTestResult.GsonAdapter())
+        }.create()
     }
 
+    private val typeTokenPCR by lazy {
+        object : TypeToken<Set<PCRCoronaTest>>() {}.type
+    }
+
+    private val typeTokenRA by lazy {
+        object : TypeToken<Set<RACoronaTest>>() {}.type
+    }
+
+    var coronaTests: Collection<CoronaTest>
+        get() {
+            Timber.tag(TAG).d("load()")
+
+            val pcrTests: Set<PCRCoronaTest> = run {
+                val raw = prefs.getString(PKEY_DATA_PCR, null) ?: return@run emptySet()
+                gson.fromJson<Set<PCRCoronaTest>>(raw, typeTokenPCR).onEach {
+                    Timber.tag(TAG).v("PCR loaded: %s", it)
+                    requireNotNull(it.identifier)
+                }
+            }
+
+            val raTests: Set<RACoronaTest> = run {
+                val raw = prefs.getString(PKEY_DATA_RA, null) ?: return@run emptySet()
+                gson.fromJson<Set<RACoronaTest>>(raw, typeTokenRA).onEach {
+                    Timber.tag(TAG).v("PCR loaded: %s", it)
+                    requireNotNull(it.identifier)
+                }
+            }
+
+            val tests = pcrTests + raTests
+            Timber.tag(TAG).v("Loaded %d tests.", tests.size)
+            return tests
+        }
+        set(value) {
+            Timber.tag(TAG).d("save(tests=%s)", value)
+            prefs.edit {
+                value.filter { it.type == CoronaTest.Type.PCR }.run {
+                    if (isNotEmpty()) {
+                        val raw = gson.toJson(this, typeTokenPCR)
+                        Timber.tag(TAG).v("PCR storing: %s", raw)
+                        putString(PKEY_DATA_PCR, raw)
+                    } else {
+                        Timber.tag(TAG).v("No PCR tests available, clearing.")
+                        remove(PKEY_DATA_PCR)
+                    }
+                }
+                value.filter { it.type == CoronaTest.Type.RAPID_ANTIGEN }.run {
+                    if (isNotEmpty()) {
+                        val raw = gson.toJson(this, typeTokenRA)
+                        Timber.tag(TAG).v("RA storing: %s", raw)
+                        putString(PKEY_DATA_RA, raw)
+                    } else {
+                        Timber.tag(TAG).v("No RA tests available, clearing.")
+                        remove(PKEY_DATA_RA)
+                    }
+                }
+            }
+        }
+
     companion object {
         private const val TAG = "CoronaTestStorage"
+        private const val PKEY_DATA_RA = "coronatest.data.ra"
+        private const val PKEY_DATA_PCR = "coronatest.data.pcr"
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt
new file mode 100644
index 0000000000000000000000000000000000000000..62200ac43ba6e549b36fe4246c201c209ef88451
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt
@@ -0,0 +1,35 @@
+package de.rki.coronawarnapp.coronatest.tan
+
+import android.os.Parcelable
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import kotlinx.parcelize.IgnoredOnParcel
+import kotlinx.parcelize.Parcelize
+
+sealed class CoronaTestTAN : Parcelable, TestRegistrationRequest {
+
+    abstract override val type: CoronaTest.Type
+    abstract val tan: TestTAN
+
+    @IgnoredOnParcel
+    override val identifier: String
+        get() = "tan-${type.raw}-$tan"
+
+    @Parcelize
+    data class PCR(
+        override val tan: TestTAN,
+    ) : CoronaTestTAN() {
+
+        @IgnoredOnParcel override val type: CoronaTest.Type = CoronaTest.Type.PCR
+    }
+
+    @Parcelize
+    data class RapidAntigen(
+        override val tan: TestTAN,
+    ) : CoronaTestTAN() {
+
+        @IgnoredOnParcel override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
+    }
+}
+
+typealias TestTAN = String
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CommonSubmissionStates.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CommonSubmissionStates.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1867629ee401e40b7d8ed2f2ec6c62d74b40465b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CommonSubmissionStates.kt
@@ -0,0 +1,13 @@
+package de.rki.coronawarnapp.coronatest.type
+
+import org.joda.time.Instant
+
+interface CommonSubmissionStates {
+    interface TestUnregistered : CommonSubmissionStates
+
+    interface TestFetching : CommonSubmissionStates
+
+    interface SubmissionDone : CommonSubmissionStates {
+        val testRegisteredAt: Instant
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt
index 08041c76e154830db21c59e6595e416067c038ca..6a3e9b1142e5c68ea4bc995906d3a9b66565b1d1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt
@@ -1,23 +1,42 @@
 package de.rki.coronawarnapp.coronatest.type
 
 import com.google.gson.annotations.SerializedName
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestGUID
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import org.joda.time.Instant
 
 interface CoronaTest {
-    val testGUID: CoronaTestGUID
-    val registrationToken: RegistrationToken
+    val identifier: TestIdentifier
     val type: Type
-    val isRefreshing: Boolean
+
+    val registeredAt: Instant
+    val registrationToken: RegistrationToken
+
+    val isProcessing: Boolean
+    val lastError: Throwable?
+
     val isSubmissionAllowed: Boolean
     val isSubmitted: Boolean
+    val isViewed: Boolean
+
+    val testResultReceivedAt: Instant?
+    val testResult: CoronaTestResult
+
+    // TODO why do we need this PER test
+    val isAdvancedConsentGiven: Boolean
+
+    // TODO Why do we need to store this?
+    val isJournalEntryCreated: Boolean
+
+    val isResultAvailableNotificationSent: Boolean
 
-    enum class Type {
+    enum class Type(val raw: String) {
         @SerializedName("PCR")
-        PCR,
+        PCR("PCR"),
 
         @SerializedName("RAPID_ANTIGEN")
-        RAPID_ANTIGEN,
+        RAPID_ANTIGEN("RAPID_ANTIGEN"),
     }
 }
 
 typealias RegistrationToken = String
+typealias TestIdentifier = String
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestProcessor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0605ddfc98541a337d7d2ebfb4c47a55f04a3495
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestProcessor.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.coronatest.type
+
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
+
+interface CoronaTestProcessor {
+
+    val type: CoronaTest.Type
+
+    suspend fun create(request: CoronaTestQRCode): CoronaTest
+
+    suspend fun create(request: CoronaTestTAN): CoronaTest
+
+    suspend fun pollServer(test: CoronaTest): CoronaTest
+
+    /**
+     * Called before a test is removed.
+     */
+    suspend fun onRemove(toBeRemoved: CoronaTest)
+
+    suspend fun markSubmitted(test: CoronaTest): CoronaTest
+
+    suspend fun markProcessing(test: CoronaTest, isProcessing: Boolean): CoronaTest
+
+    suspend fun markViewed(test: CoronaTest): CoronaTest
+
+    suspend fun updateConsent(test: CoronaTest, consented: Boolean): CoronaTest
+
+    suspend fun updateResultNotification(test: CoronaTest, sent: Boolean): CoronaTest
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestService.kt
similarity index 51%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestService.kt
index 1931b63c3dcf5bed27d07f442c267de620c863d9..f47d859b5a06c9eacb1531b4c1bf331d1c6b279b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestService.kt
@@ -1,15 +1,19 @@
-package de.rki.coronawarnapp.service.submission
+package de.rki.coronawarnapp.coronatest.type
 
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.server.VerificationKeyType
+import de.rki.coronawarnapp.deniability.NoiseScheduler
 import de.rki.coronawarnapp.playbook.Playbook
-import de.rki.coronawarnapp.util.formatter.TestResult
-import de.rki.coronawarnapp.verification.server.VerificationKeyType
+import de.rki.coronawarnapp.worker.BackgroundConstants
+import timber.log.Timber
 import javax.inject.Inject
 
-class SubmissionService @Inject constructor(
-    private val playbook: Playbook
+class CoronaTestService @Inject constructor(
+    private val playbook: Playbook,
+    private val noiseScheduler: NoiseScheduler,
 ) {
 
-    suspend fun asyncRequestTestResult(registrationToken: String): TestResult {
+    suspend fun asyncRequestTestResult(registrationToken: String): CoronaTestResult {
         return playbook.testResult(registrationToken)
     }
 
@@ -19,6 +23,10 @@ class SubmissionService @Inject constructor(
                 guid,
                 VerificationKeyType.GUID
             )
+
+        Timber.d("Scheduling background noise.")
+        scheduleDummyPattern()
+
         return RegistrationData(registrationToken, testResult)
     }
 
@@ -28,11 +36,21 @@ class SubmissionService @Inject constructor(
                 tan,
                 VerificationKeyType.TELETAN
             )
+
+        Timber.d("Scheduling background noise.")
+        scheduleDummyPattern()
+
         return RegistrationData(registrationToken, testResult)
     }
 
+    private fun scheduleDummyPattern() {
+        if (BackgroundConstants.NUMBER_OF_DAYS_TO_RUN_PLAYBOOK > 0) {
+            noiseScheduler.setPeriodicNoise(enabled = true)
+        }
+    }
+
     data class RegistrationData(
         val registrationToken: String,
-        val testResult: TestResult
+        val testResult: CoronaTestResult
     )
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/antigen/RapidAntigenCoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/antigen/RapidAntigenCoronaTest.kt
deleted file mode 100644
index 654a4cdb5701e51401ca60f12af99afd45011a5e..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/antigen/RapidAntigenCoronaTest.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.rki.coronawarnapp.coronatest.type.antigen
-
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestGUID
-import de.rki.coronawarnapp.coronatest.type.CoronaTest
-import de.rki.coronawarnapp.coronatest.type.RegistrationToken
-
-data class RapidAntigenCoronaTest(
-    override val testGUID: CoronaTestGUID,
-    override val registrationToken: RegistrationToken,
-    override val isRefreshing: Boolean,
-    override val isSubmissionAllowed: Boolean,
-    override val isSubmitted: Boolean,
-    val state: State,
-) : CoronaTest {
-
-    override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
-
-    enum class State {
-        PENDING,
-        INVALID,
-        POSITIVE,
-        NEGATIVE,
-        REDEEMED,
-        OUTDATED,
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt
index 9d4030826fe6fd3671757b5e1b1ecda8b23e4b40..ed6b4d56af89d29120f12ed7c1cf1f007be6d500 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt
@@ -1,20 +1,60 @@
 package de.rki.coronawarnapp.coronatest.type.pcr
 
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestGUID
+import com.google.gson.annotations.SerializedName
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.coronatest.type.RegistrationToken
+import de.rki.coronawarnapp.coronatest.type.TestIdentifier
+import org.joda.time.Instant
 
 data class PCRCoronaTest(
-    override val testGUID: CoronaTestGUID,
+    @SerializedName("identifier")
+    override val identifier: TestIdentifier,
+
+    @SerializedName("registeredAt")
+    override val registeredAt: Instant,
+
+    @SerializedName("registrationToken")
     override val registrationToken: RegistrationToken,
-    override val isRefreshing: Boolean,
-    override val isSubmissionAllowed: Boolean,
-    override val isSubmitted: Boolean,
-    val state: State,
+
+    @SerializedName("isSubmitted")
+    override val isSubmitted: Boolean = false,
+
+    @SerializedName("isViewed")
+    override val isViewed: Boolean = false,
+
+    @SerializedName("isAdvancedConsentGiven")
+    override val isAdvancedConsentGiven: Boolean = false,
+
+    @SerializedName("isJournalEntryCreated")
+    override val isJournalEntryCreated: Boolean = false,
+
+    @SerializedName("isResultAvailableNotificationSent")
+    override val isResultAvailableNotificationSent: Boolean = false,
+
+    @SerializedName("testResultReceivedAt")
+    override val testResultReceivedAt: Instant? = null,
+
+    @SerializedName("testResult")
+    override val testResult: CoronaTestResult,
+
+    @Transient override val isProcessing: Boolean = false,
+    @Transient override val lastError: Throwable? = null,
 ) : CoronaTest {
 
     override val type: CoronaTest.Type = CoronaTest.Type.PCR
 
+    override val isSubmissionAllowed: Boolean = testResult == CoronaTestResult.PCR_POSITIVE
+
+    val state: State = when (testResult) {
+        CoronaTestResult.PCR_OR_RAT_PENDING -> State.PENDING
+        CoronaTestResult.PCR_NEGATIVE -> State.NEGATIVE
+        CoronaTestResult.PCR_POSITIVE -> State.POSITIVE
+        CoronaTestResult.PCR_INVALID -> State.INVALID
+        CoronaTestResult.PCR_REDEEMED -> State.REDEEMED
+        else -> throw IllegalArgumentException("Invalid PCR test state $testResult")
+    }
+
     enum class State {
         PENDING,
         INVALID,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5ce1c65829e0f85eff22df70bb980e40dd264d1f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensions.kt
@@ -0,0 +1,121 @@
+package de.rki.coronawarnapp.coronatest.type.pcr
+
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.util.CWADebug
+import de.rki.coronawarnapp.util.DeviceUIState
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
+import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
+import timber.log.Timber
+
+fun PCRCoronaTest?.toSubmissionState(): SubmissionStatePCR {
+    if (this == null) return SubmissionStatePCR.NoTest
+
+    val uiState: DeviceUIState = when (state) {
+        PCRCoronaTest.State.PENDING -> DeviceUIState.PAIRED_NO_RESULT
+        PCRCoronaTest.State.INVALID -> DeviceUIState.PAIRED_ERROR
+        PCRCoronaTest.State.POSITIVE -> DeviceUIState.PAIRED_POSITIVE
+        PCRCoronaTest.State.NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE
+        PCRCoronaTest.State.REDEEMED -> DeviceUIState.PAIRED_REDEEMED
+    }
+
+    val networkWrapper = when {
+        isProcessing -> NetworkRequestWrapper.RequestStarted
+        lastError != null -> NetworkRequestWrapper.RequestFailed(lastError)
+        else -> NetworkRequestWrapper.RequestSuccessful(uiState)
+    }
+
+    val eval = Evaluation(
+        deviceUiState = networkWrapper,
+        isDeviceRegistered = registrationToken != null,
+        hasTestResultBeenSeen = this.isViewed
+    )
+    Timber.d("eval: %s", eval)
+    return when {
+        eval.isUnregistered() -> SubmissionStatePCR.NoTest
+        eval.isFetching() -> SubmissionStatePCR.FetchingResult
+        eval.isTestResultReady() -> SubmissionStatePCR.TestResultReady
+        eval.isResultPositive() -> SubmissionStatePCR.TestPositive
+        eval.isInvalid() -> SubmissionStatePCR.TestInvalid
+        eval.isError() -> SubmissionStatePCR.TestError
+        eval.isResultNegative() -> SubmissionStatePCR.TestNegative
+        eval.isSubmissionDone() -> SubmissionStatePCR.SubmissionDone(testRegisteredAt = registeredAt)
+        eval.isPending() -> SubmissionStatePCR.TestPending
+        else -> {
+            if (CWADebug.isDeviceForTestersBuild) throw IllegalStateException(eval.toString())
+            else SubmissionStatePCR.TestPending
+        }
+    }
+}
+
+// TODO Refactor this to be easier to understand, probably remove the "withSuccess" logic.
+private data class Evaluation(
+    val deviceUiState: NetworkRequestWrapper<DeviceUIState, Throwable>,
+    val isDeviceRegistered: Boolean,
+    val hasTestResultBeenSeen: Boolean
+) {
+
+    fun isUnregistered(): Boolean = !isDeviceRegistered
+
+    fun isTestResultReady(): Boolean = deviceUiState.withSuccess(false) {
+        when (it) {
+            DeviceUIState.PAIRED_POSITIVE,
+            DeviceUIState.PAIRED_POSITIVE_TELETAN -> !hasTestResultBeenSeen
+            else -> false
+        }
+    }
+
+    fun isFetching(): Boolean =
+        isDeviceRegistered && when (deviceUiState) {
+            is NetworkRequestWrapper.RequestFailed -> false
+            is NetworkRequestWrapper.RequestStarted -> true
+            is NetworkRequestWrapper.RequestIdle -> true
+            else -> false
+        }
+
+    fun isResultPositive(): Boolean =
+        deviceUiState.withSuccess(false) {
+            when (it) {
+                DeviceUIState.PAIRED_POSITIVE, DeviceUIState.PAIRED_POSITIVE_TELETAN -> hasTestResultBeenSeen
+                else -> false
+            }
+        }
+
+    fun isResultNegative(): Boolean =
+        deviceUiState.withSuccess(false) {
+            when (it) {
+                DeviceUIState.PAIRED_NEGATIVE -> true
+                else -> false
+            }
+        }
+
+    fun isSubmissionDone(): Boolean =
+        when (deviceUiState) {
+            is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == DeviceUIState.SUBMITTED_FINAL
+            else -> false
+        }
+
+    fun isInvalid(): Boolean =
+        isDeviceRegistered && when (deviceUiState) {
+            is NetworkRequestWrapper.RequestFailed -> deviceUiState.error !is CwaServerError
+            is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == DeviceUIState.PAIRED_REDEEMED
+            else -> false
+        }
+
+    fun isError(): Boolean =
+        deviceUiState.withSuccess(false) {
+            when (it) {
+                DeviceUIState.PAIRED_ERROR -> true
+                else -> false
+            }
+        }
+
+    fun isPending(): Boolean =
+        when (deviceUiState) {
+            is NetworkRequestWrapper.RequestFailed -> true
+            is NetworkRequestWrapper.RequestSuccessful -> {
+                deviceUiState.data == DeviceUIState.PAIRED_ERROR ||
+                    deviceUiState.data == DeviceUIState.PAIRED_NO_RESULT
+            }
+            else -> false
+        }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..90eee46fc2409ca41f3290abe4ff144c455e91b9
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt
@@ -0,0 +1,185 @@
+package de.rki.coronawarnapp.coronatest.type.pcr
+
+import dagger.Reusable
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
+import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
+import de.rki.coronawarnapp.coronatest.type.CoronaTestService
+import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
+import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.TestResultDataCollector
+import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.util.TimeStamper
+import timber.log.Timber
+import javax.inject.Inject
+
+@Reusable
+class PCRProcessor @Inject constructor(
+    private val timeStamper: TimeStamper,
+    private val submissionService: CoronaTestService,
+    private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
+    private val testResultDataCollector: TestResultDataCollector,
+    private val deadmanNotificationScheduler: DeadmanNotificationScheduler,
+    private val testResultScheduler: TestResultScheduler,
+) : CoronaTestProcessor {
+
+    override val type: CoronaTest.Type = CoronaTest.Type.PCR
+
+    override suspend fun create(request: CoronaTestQRCode): PCRCoronaTest {
+        Timber.tag(TAG).d("create(data=%s)", request)
+        request as CoronaTestQRCode.PCR
+
+        val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.qrCodeGUID)
+
+        testResultDataCollector.saveTestResultAnalyticsSettings(registrationData.testResult) // This saves received at
+
+        return createCoronaTest(request, registrationData)
+    }
+
+    override suspend fun create(request: CoronaTestTAN): CoronaTest {
+        Timber.tag(TAG).d("create(data=%s)", request)
+        request as CoronaTestTAN.PCR
+
+        val registrationData = submissionService.asyncRegisterDeviceViaTAN(request.tan)
+
+        analyticsKeySubmissionCollector.reportRegisteredWithTeleTAN()
+
+        return createCoronaTest(request, registrationData)
+    }
+
+    private suspend fun createCoronaTest(
+        request: TestRegistrationRequest,
+        response: CoronaTestService.RegistrationData
+    ): PCRCoronaTest {
+        analyticsKeySubmissionCollector.reset()
+        response.testResult.validOrThrow()
+
+        testResultDataCollector.updatePendingTestResultReceivedTime(response.testResult)
+
+        if (response.testResult == CoronaTestResult.PCR_POSITIVE) {
+            analyticsKeySubmissionCollector.reportPositiveTestResultReceived()
+            deadmanNotificationScheduler.cancelScheduledWork()
+        }
+
+        analyticsKeySubmissionCollector.reportTestRegistered()
+
+//        val currentTime = timeStamper.nowUTC
+//        submissionSettings.initialTestResultReceivedAt = currentTime
+//        testResultReceivedDateFlowInternal.value = currentTime.toDate()
+        if (response.testResult == CoronaTestResult.PCR_OR_RAT_PENDING) {
+//            riskWorkScheduler.setPeriodicRiskCalculation(enabled = true)
+
+            testResultScheduler.setPeriodicTestPolling(enabled = true)
+        }
+
+        return PCRCoronaTest(
+            identifier = request.identifier,
+            registeredAt = timeStamper.nowUTC,
+            registrationToken = response.registrationToken,
+            testResult = response.testResult,
+        )
+    }
+
+    override suspend fun pollServer(test: CoronaTest): CoronaTest {
+        return try {
+            Timber.tag(TAG).v("pollServer(test=%s)", test)
+            test as PCRCoronaTest
+
+            if (test.isSubmitted || test.isSubmissionAllowed) {
+                Timber.tag(TAG).w("Not refreshing already final test.")
+                return test
+            }
+
+            val testResult = submissionService.asyncRequestTestResult(test.registrationToken)
+            Timber.tag(TAG).d("Test result was %s", testResult)
+
+            testResult.validOrThrow()
+
+            testResultDataCollector.updatePendingTestResultReceivedTime(testResult)
+
+            if (testResult == CoronaTestResult.PCR_POSITIVE) {
+                analyticsKeySubmissionCollector.reportPositiveTestResultReceived()
+                deadmanNotificationScheduler.cancelScheduledWork()
+            }
+
+            test.copy(
+                testResult = testResult,
+                lastError = null
+            )
+        } catch (e: Exception) {
+            Timber.tag(TAG).e(e, "Failed to poll server for  %s", test)
+            if (e !is CwaWebException) e.report(ExceptionCategory.INTERNAL)
+
+            test as PCRCoronaTest
+            test.copy(lastError = e)
+        }
+    }
+
+    override suspend fun onRemove(toBeRemoved: CoronaTest) {
+        Timber.tag(TAG).v("onRemove(toBeRemoved=%s)", toBeRemoved)
+        testResultDataCollector.clear()
+    }
+
+    override suspend fun markSubmitted(test: CoronaTest): PCRCoronaTest {
+        Timber.tag(TAG).v("markSubmitted(test=%s)", test)
+        test as PCRCoronaTest
+
+        return test.copy(isSubmitted = true)
+    }
+
+    override suspend fun markProcessing(test: CoronaTest, isProcessing: Boolean): CoronaTest {
+        Timber.tag(TAG).v("markProcessing(test=%s, isProcessing=%b)", test, isProcessing)
+        test as PCRCoronaTest
+
+        return test.copy(isProcessing = isProcessing)
+    }
+
+    override suspend fun markViewed(test: CoronaTest): CoronaTest {
+        Timber.tag(TAG).v("markViewed(test=%s)", test)
+        test as PCRCoronaTest
+
+        return test.copy(isViewed = true)
+    }
+
+    override suspend fun updateConsent(test: CoronaTest, consented: Boolean): CoronaTest {
+        Timber.tag(TAG).v("updateConsent(test=%s, consented=%b)", test, consented)
+        test as PCRCoronaTest
+
+        return test.copy(isAdvancedConsentGiven = consented)
+    }
+
+    override suspend fun updateResultNotification(test: CoronaTest, sent: Boolean): CoronaTest {
+        Timber.tag(TAG).v("updateResultNotification(test=%s, sent=%b)", test, sent)
+        test as PCRCoronaTest
+
+        return test.copy(isResultAvailableNotificationSent = sent)
+    }
+
+    companion object {
+        private const val TAG = "PCRProcessor"
+    }
+}
+
+private fun CoronaTestResult.validOrThrow() {
+    val isValid = when (this) {
+        CoronaTestResult.PCR_OR_RAT_PENDING,
+        CoronaTestResult.PCR_NEGATIVE,
+        CoronaTestResult.PCR_POSITIVE,
+        CoronaTestResult.PCR_INVALID,
+        CoronaTestResult.PCR_REDEEMED -> true
+
+        CoronaTestResult.RAT_PENDING,
+        CoronaTestResult.RAT_NEGATIVE,
+        CoronaTestResult.RAT_POSITIVE,
+        CoronaTestResult.RAT_INVALID,
+        CoronaTestResult.RAT_REDEEMED -> false
+    }
+
+    if (!isValid) throw IllegalArgumentException("Invalid testResult $this")
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/SubmissionStatePCR.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/SubmissionStatePCR.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2a45ebb8a01fbd08b4ef2e0568163986539c28f0
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/SubmissionStatePCR.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.coronatest.type.pcr
+
+import de.rki.coronawarnapp.coronatest.type.CommonSubmissionStates
+import org.joda.time.Instant
+
+sealed class SubmissionStatePCR {
+
+    object NoTest : SubmissionStatePCR(), CommonSubmissionStates.TestUnregistered
+    object FetchingResult : SubmissionStatePCR(), CommonSubmissionStates.TestFetching
+    object TestResultReady : SubmissionStatePCR()
+    object TestPositive : SubmissionStatePCR()
+    object TestNegative : SubmissionStatePCR()
+    object TestError : SubmissionStatePCR()
+    object TestInvalid : SubmissionStatePCR()
+    object TestPending : SubmissionStatePCR()
+    data class SubmissionDone(
+        override val testRegisteredAt: Instant
+    ) : SubmissionStatePCR(), CommonSubmissionStates.SubmissionDone
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7cdf2cf5bcb094d3c11235626e7b2288a1737d3b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt
@@ -0,0 +1,75 @@
+package de.rki.coronawarnapp.coronatest.type.rapidantigen
+
+import com.google.gson.annotations.SerializedName
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.RegistrationToken
+import de.rki.coronawarnapp.coronatest.type.TestIdentifier
+import org.joda.time.Instant
+import org.joda.time.LocalDate
+
+data class RACoronaTest(
+    @SerializedName("identifier")
+    override val identifier: TestIdentifier,
+
+    @SerializedName("registeredAt")
+    override val registeredAt: Instant,
+
+    @SerializedName("registrationToken")
+    override val registrationToken: RegistrationToken,
+
+    @SerializedName("isSubmitted")
+    override val isSubmitted: Boolean = false,
+
+    @SerializedName("isViewed")
+    override val isViewed: Boolean = false,
+
+    @SerializedName("isAdvancedConsentGiven")
+    override val isAdvancedConsentGiven: Boolean = false,
+
+    @SerializedName("isJournalEntryCreated")
+    override val isJournalEntryCreated: Boolean = false,
+
+    @SerializedName("isResultAvailableNotificationSent")
+    override val isResultAvailableNotificationSent: Boolean = false,
+
+    @SerializedName("testResultReceivedAt")
+    override val testResultReceivedAt: Instant? = null,
+
+    @SerializedName("testResult")
+    override val testResult: CoronaTestResult,
+
+    @SerializedName("testedAt")
+    val testedAt: Instant,
+
+    @SerializedName("firstName")
+    val firstName: String?,
+
+    @SerializedName("lastName")
+    val lastName: String?,
+
+    @SerializedName("dateOfBirth")
+    val dateOfBirth: LocalDate?,
+
+    @Transient override val isProcessing: Boolean = false,
+    @Transient override val lastError: Throwable? = null,
+) : CoronaTest {
+
+    override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
+
+    fun getState(nowUTC: Instant): State {
+        // TODO
+        return State.PENDING
+    }
+
+    override val isSubmissionAllowed: Boolean = testResult == CoronaTestResult.RAT_POSITIVE
+
+    enum class State {
+        PENDING,
+        INVALID,
+        POSITIVE,
+        NEGATIVE,
+        REDEEMED,
+        OUTDATED,
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fa8ad42e982d75d0e766afd6ac74c2c7fe7adca5
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensions.kt
@@ -0,0 +1,7 @@
+package de.rki.coronawarnapp.coronatest.type.rapidantigen
+
+fun RACoronaTest?.toSubmissionState(): SubmissionStateRAT {
+    if (this == null) return SubmissionStateRAT.NoTest
+
+    return SubmissionStateRAT.FetchingResult
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6052fa0a06cc18df4e07161d807c579f3247792e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt
@@ -0,0 +1,138 @@
+package de.rki.coronawarnapp.coronatest.type.rapidantigen
+
+import dagger.Reusable
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
+import de.rki.coronawarnapp.coronatest.type.CoronaTestService
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.util.TimeStamper
+import timber.log.Timber
+import javax.inject.Inject
+
+@Reusable
+class RapidAntigenProcessor @Inject constructor(
+    private val timeStamper: TimeStamper,
+    private val submissionService: CoronaTestService,
+) : CoronaTestProcessor {
+
+    override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
+
+    override suspend fun create(request: CoronaTestQRCode): RACoronaTest {
+        Timber.tag(TAG).d("create(data=%s)", request)
+        request as CoronaTestQRCode.RapidAntigen
+
+        val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.registrationIdentifier)
+
+        registrationData.testResult.validOrThrow()
+
+        return RACoronaTest(
+            identifier = request.identifier,
+            registeredAt = timeStamper.nowUTC,
+            registrationToken = registrationData.registrationToken,
+            testResult = registrationData.testResult,
+            testedAt = request.createdAt,
+            firstName = request.firstName,
+            lastName = request.lastName,
+            dateOfBirth = request.dateOfBirth,
+        )
+    }
+
+    override suspend fun create(request: CoronaTestTAN): CoronaTest {
+        Timber.tag(TAG).d("create(data=%s)", request)
+        request as CoronaTestTAN.RapidAntigen
+        throw UnsupportedOperationException("There are no TAN based RATs")
+    }
+
+    override suspend fun pollServer(test: CoronaTest): CoronaTest {
+        return try {
+            Timber.tag(TAG).v("pollServer(test=%s)", test)
+            test as RACoronaTest
+
+            if (test.isSubmitted || test.isSubmissionAllowed) {
+                Timber.tag(TAG).w("Not refreshing already final test.")
+                return test
+            }
+
+            val testResult = submissionService.asyncRequestTestResult(test.registrationToken)
+            Timber.tag(TAG).d("Test result was %s", testResult)
+
+            test.copy(
+                testResult = testResult,
+                lastError = null
+            )
+        } catch (e: Exception) {
+            Timber.tag(TAG).e(e, "Failed to poll server for  %s", test)
+            if (e !is CwaWebException) e.report(ExceptionCategory.INTERNAL)
+
+            test as RACoronaTest
+            test.copy(lastError = e)
+        }
+    }
+
+    override suspend fun onRemove(toBeRemoved: CoronaTest) {
+        Timber.tag(TAG).v("onRemove(toBeRemoved=%s)", toBeRemoved)
+        // Currently nothing to do
+    }
+
+    override suspend fun markSubmitted(test: CoronaTest): RACoronaTest {
+        Timber.tag(TAG).d("markSubmitted(test=%s)", test)
+        test as RACoronaTest
+
+        return test.copy(isSubmitted = true)
+    }
+
+    override suspend fun markProcessing(test: CoronaTest, isProcessing: Boolean): CoronaTest {
+        Timber.tag(TAG).v("markProcessing(test=%s, isProcessing=%b)", test, isProcessing)
+        test as RACoronaTest
+
+        return test.copy(isProcessing = isProcessing)
+    }
+
+    override suspend fun markViewed(test: CoronaTest): CoronaTest {
+        Timber.tag(TAG).v("markViewed(test=%s)", test)
+        test as RACoronaTest
+
+        return test.copy(isViewed = true)
+    }
+
+    override suspend fun updateConsent(test: CoronaTest, consented: Boolean): CoronaTest {
+        Timber.tag(TAG).v("updateConsent(test=%s, consented=%b)", test, consented)
+        test as RACoronaTest
+
+        return test.copy(isAdvancedConsentGiven = consented)
+    }
+
+    override suspend fun updateResultNotification(test: CoronaTest, sent: Boolean): CoronaTest {
+        Timber.tag(TAG).v("updateResultNotification(test=%s, sent=%b)", test, sent)
+        test as RACoronaTest
+
+        return test.copy(isResultAvailableNotificationSent = sent)
+    }
+
+    companion object {
+        private const val TAG = "RapidAntigenProcessor"
+    }
+}
+
+private fun CoronaTestResult.validOrThrow() {
+    val isValid = when (this) {
+        CoronaTestResult.PCR_OR_RAT_PENDING,
+        CoronaTestResult.RAT_PENDING,
+        CoronaTestResult.RAT_NEGATIVE,
+        CoronaTestResult.RAT_POSITIVE,
+        CoronaTestResult.RAT_INVALID,
+        CoronaTestResult.RAT_REDEEMED -> true
+
+        CoronaTestResult.PCR_NEGATIVE,
+        CoronaTestResult.PCR_POSITIVE,
+        CoronaTestResult.PCR_INVALID,
+        CoronaTestResult.PCR_REDEEMED -> false
+    }
+
+    if (!isValid) throw IllegalArgumentException("Invalid testResult $this")
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/SubmissionStateRAT.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/SubmissionStateRAT.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bbb6cd6474fb89385c762be591e47a486df038e3
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/SubmissionStateRAT.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.coronatest.type.rapidantigen
+
+import de.rki.coronawarnapp.coronatest.type.CommonSubmissionStates
+import org.joda.time.Instant
+
+sealed class SubmissionStateRAT {
+
+    object NoTest : SubmissionStateRAT(), CommonSubmissionStates.TestUnregistered
+    object FetchingResult : SubmissionStateRAT(), CommonSubmissionStates.TestFetching
+    object TestResultReady : SubmissionStateRAT()
+    object TestPositive : SubmissionStateRAT()
+    object TestNegative : SubmissionStateRAT()
+    object TestError : SubmissionStateRAT()
+    object TestInvalid : SubmissionStateRAT()
+    object TestPending : SubmissionStateRAT()
+    data class SubmissionDone(
+        override val testRegisteredAt: Instant
+    ) : SubmissionStateRAT(), CommonSubmissionStates.SubmissionDone
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt
index 5335b72d1ad71c4e88d5806cc9ae4411331ea630..1bc2cb42d31db03d73f5cf36784b0f41bf5f093f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt
@@ -1,11 +1,11 @@
 package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest
 
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
 import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
 import de.rki.coronawarnapp.risk.tryLatestEwResultsWithDefaults
 import de.rki.coronawarnapp.util.TimeStamper
-import de.rki.coronawarnapp.util.formatter.TestResult
 import kotlinx.coroutines.flow.first
 import timber.log.Timber
 import javax.inject.Inject
@@ -21,11 +21,11 @@ class TestResultDataCollector @Inject constructor(
      *  Collect Test result registration only after user has given a consent.
      *  exclude any registered test result before giving a consent
      */
-    suspend fun saveTestResultAnalyticsSettings(testResult: TestResult) {
+    suspend fun saveTestResultAnalyticsSettings(testResult: CoronaTestResult) {
         val validTestResults = listOf(
-            TestResult.POSITIVE,
-            TestResult.PENDING,
-            TestResult.NEGATIVE
+            CoronaTestResult.PCR_POSITIVE,
+            CoronaTestResult.PCR_OR_RAT_PENDING,
+            CoronaTestResult.PCR_NEGATIVE
         )
 
         if (testResult !in validTestResults) return // Not interested in other values
@@ -42,16 +42,16 @@ class TestResultDataCollector @Inject constructor(
         }
     }
 
-    fun updatePendingTestResultReceivedTime(newTestResult: TestResult) {
+    fun updatePendingTestResultReceivedTime(newTestResult: CoronaTestResult) {
         // Analytics is enabled
         val shouldUpdate = analyticsSettings.analyticsEnabled.value &&
             // Test was scanned after giving consent and this a QR-Code Test registration
             // For TAN test registration this flag is not set
             testResultDonorSettings.testScannedAfterConsent.value &&
             // Result was Pending
-            testResultDonorSettings.testResultAtRegistration.value == TestResult.PENDING &&
+            testResultDonorSettings.testResultAtRegistration.value == CoronaTestResult.PCR_OR_RAT_PENDING &&
             // Final Test result received
-            newTestResult in listOf(TestResult.POSITIVE, TestResult.NEGATIVE)
+            newTestResult in listOf(CoronaTestResult.PCR_POSITIVE, CoronaTestResult.PCR_NEGATIVE)
         if (shouldUpdate) {
             val receivedAt = timeStamper.nowUTC
             Timber.d("updatePendingTestResultReceivedTime($newTestResult, $receivedAt")
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt
index 4a1344a4d0994d274aed67f6422909850eee0586..23e8c593739c9f92b7219153aa4a54aabb69469b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt
@@ -1,12 +1,14 @@
 package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest
 
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.latestPCRT
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.datadonation.analytics.common.calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration
 import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule
 import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings
 import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.util.TimeStamper
-import de.rki.coronawarnapp.util.formatter.TestResult
+import kotlinx.coroutines.flow.first
 import org.joda.time.Duration
 import org.joda.time.Instant
 import timber.log.Timber
@@ -17,7 +19,7 @@ import javax.inject.Singleton
 class TestResultDonor @Inject constructor(
     private val testResultDonorSettings: TestResultDonorSettings,
     private val timeStamper: TimeStamper,
-    private val submissionSettings: SubmissionSettings
+    private val coronaTestRepository: CoronaTestRepository,
 ) : DonorModule {
 
     override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution {
@@ -27,7 +29,7 @@ class TestResultDonor @Inject constructor(
             return TestResultMetadataNoContribution
         }
 
-        val timestampAtRegistration = submissionSettings.initialTestResultReceivedAt
+        val timestampAtRegistration = coronaTestRepository.latestPCRT.first()?.registeredAt
         if (timestampAtRegistration == null) {
             Timber.d("Skipping TestResultMetadata donation (timestampAtRegistration is missing)")
             return TestResultMetadataNoContribution
@@ -132,7 +134,7 @@ class TestResultDonor @Inject constructor(
 
     private fun pendingTestMetadataDonation(
         hoursSinceTestRegistrationTime: Int,
-        testResult: TestResult,
+        testResult: CoronaTestResult,
         daysSinceMostRecentDateAtRiskLevelAtTestRegistration: Int,
         hoursSinceHighRiskWarningAtTestRegistration: Int
     ): DonorModule.Contribution {
@@ -152,7 +154,7 @@ class TestResultDonor @Inject constructor(
 
     private fun finalTestMetadataDonation(
         registrationTime: Instant,
-        testResult: TestResult,
+        testResult: CoronaTestResult,
         daysSinceMostRecentDateAtRiskLevelAtTestRegistration: Int,
         hoursSinceHighRiskWarningAtTestRegistration: Int
     ): DonorModule.Contribution {
@@ -192,14 +194,18 @@ class TestResultDonor @Inject constructor(
         ).standardHours.toInt()
     }
 
-    private inline val TestResult.isFinal: Boolean get() = this in listOf(TestResult.POSITIVE, TestResult.NEGATIVE)
-    private inline val TestResult.isPending get() = this == TestResult.PENDING
+    private inline val CoronaTestResult.isFinal: Boolean
+        get() = this in listOf(
+            CoronaTestResult.PCR_POSITIVE,
+            CoronaTestResult.PCR_NEGATIVE
+        )
+    private inline val CoronaTestResult.isPending get() = this == CoronaTestResult.PCR_OR_RAT_PENDING
 
-    private fun TestResult.toPPATestResult(): PpaData.PPATestResult {
+    private fun CoronaTestResult.toPPATestResult(): PpaData.PPATestResult {
         return when (this) {
-            TestResult.PENDING -> PpaData.PPATestResult.TEST_RESULT_PENDING
-            TestResult.POSITIVE -> PpaData.PPATestResult.TEST_RESULT_POSITIVE
-            TestResult.NEGATIVE -> PpaData.PPATestResult.TEST_RESULT_NEGATIVE
+            CoronaTestResult.PCR_OR_RAT_PENDING -> PpaData.PPATestResult.TEST_RESULT_PENDING
+            CoronaTestResult.PCR_POSITIVE -> PpaData.PPATestResult.TEST_RESULT_POSITIVE
+            CoronaTestResult.PCR_NEGATIVE -> PpaData.PPATestResult.TEST_RESULT_NEGATIVE
             else -> PpaData.PPATestResult.TEST_RESULT_UNKNOWN
         }
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt
index 3cec71475ff6dd8801f73babdd62b8a340f5c32d..ebe80397f94a7efc88d8460bcf59f85c4028e104 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt
@@ -1,12 +1,12 @@
 package de.rki.coronawarnapp.datadonation.analytics.storage
 
 import android.content.Context
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.datadonation.analytics.common.toMetadataRiskLevel
 import de.rki.coronawarnapp.risk.EwRiskLevelResult
 import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.di.AppContext
-import de.rki.coronawarnapp.util.formatter.TestResult
 import de.rki.coronawarnapp.util.preferences.clearAndNotify
 import de.rki.coronawarnapp.util.preferences.createFlowPreference
 import org.joda.time.Instant
@@ -59,7 +59,7 @@ class TestResultDonorSettings @Inject constructor(
             if (value == -1) {
                 null
             } else {
-                TestResult.fromInt(value)
+                CoronaTestResult.fromInt(value)
             }
         },
         writer = { key, result ->
@@ -95,10 +95,10 @@ class TestResultDonorSettings @Inject constructor(
         }
     )
 
-    fun saveTestResultDonorDataAtRegistration(testResult: TestResult, lastEwRiskResult: EwRiskLevelResult) {
+    fun saveTestResultDonorDataAtRegistration(testResult: CoronaTestResult, lastEwRiskResult: EwRiskLevelResult) {
         testScannedAfterConsent.update { true }
         testResultAtRegistration.update { testResult }
-        if (testResult in listOf(TestResult.POSITIVE, TestResult.NEGATIVE)) {
+        if (testResult in listOf(CoronaTestResult.PCR_POSITIVE, CoronaTestResult.PCR_NEGATIVE)) {
             finalTestResultReceivedAt.update { timeStamper.nowUTC }
         }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoiseOneTimeWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/BackgroundNoiseOneTimeWorker.kt
similarity index 93%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoiseOneTimeWorker.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/BackgroundNoiseOneTimeWorker.kt
index ad5a57f87918c3dfdf5764b7f67206309c31afc7..91186227358718960d1b495094d8292348076999 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoiseOneTimeWorker.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/BackgroundNoiseOneTimeWorker.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.worker
+package de.rki.coronawarnapp.deniability
 
 import android.content.Context
 import androidx.work.CoroutineWorker
@@ -8,12 +8,11 @@ import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.playbook.Playbook
 import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory
+import de.rki.coronawarnapp.worker.BackgroundConstants
 import timber.log.Timber
 
 /**
  * One time background noise worker
- *
- * @see BackgroundWorkScheduler
  */
 class BackgroundNoiseOneTimeWorker @AssistedInject constructor(
     @Assisted val context: Context,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/BackgroundNoisePeriodicWorker.kt
similarity index 69%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/BackgroundNoisePeriodicWorker.kt
index 2168b02875a0b162f33c781faa418839b1dd000f..fc0852a0d8c8979e1aba380d233f9a549628f560 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/BackgroundNoisePeriodicWorker.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.worker
+package de.rki.coronawarnapp.deniability
 
 import android.content.Context
 import androidx.work.CoroutineWorker
@@ -6,9 +6,12 @@ import androidx.work.WorkerParameters
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.submission.SubmissionSettings
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory
+import de.rki.coronawarnapp.worker.BackgroundConstants
+import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
+import kotlinx.coroutines.flow.first
 import org.joda.time.Duration
 import org.joda.time.Instant
 import timber.log.Timber
@@ -21,9 +24,9 @@ import timber.log.Timber
 class BackgroundNoisePeriodicWorker @AssistedInject constructor(
     @Assisted val context: Context,
     @Assisted workerParams: WorkerParameters,
-    private val submissionSettings: SubmissionSettings,
     private val timeStamper: TimeStamper,
-    private val backgroundWorkScheduler: BackgroundWorkScheduler,
+    private val coronaTestRepository: CoronaTestRepository,
+    private val noiseScheduler: NoiseScheduler,
 ) : CoroutineWorker(context, workerParams) {
 
     /**
@@ -34,18 +37,20 @@ class BackgroundNoisePeriodicWorker @AssistedInject constructor(
 
         var result = Result.success()
         try {
-            val initialPairingDate = submissionSettings.devicePairingSuccessfulAt ?: Instant.ofEpochMilli(0)
+            val initialPairingDate = coronaTestRepository.coronaTests.first().maxByOrNull {
+                it.registeredAt
+            }?.registeredAt ?: Instant.ofEpochMilli(0)
 
             // Check if the numberOfDaysToRunPlaybook are over
-            if (initialPairingDate
-                .plus(Duration.standardDays(NUMBER_OF_DAYS_TO_RUN_PLAYBOOK))
-                .isBefore(timeStamper.nowUTC)
+            if (
+                initialPairingDate.plus(Duration.standardDays(NUMBER_OF_DAYS_TO_RUN_PLAYBOOK))
+                    .isBefore(timeStamper.nowUTC)
             ) {
                 stopWorker()
                 return result
             }
 
-            backgroundWorkScheduler.scheduleBackgroundNoiseOneTimeWork()
+            noiseScheduler.scheduleBackgroundNoiseOneTimeWork()
         } catch (e: Exception) {
             result = if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) {
                 Result.failure()
@@ -58,7 +63,7 @@ class BackgroundNoisePeriodicWorker @AssistedInject constructor(
     }
 
     private fun stopWorker() {
-        backgroundWorkScheduler.stopBackgroundNoisePeriodicWork()
+        noiseScheduler.setPeriodicNoise(enabled = false)
         Timber.tag(TAG).d("$id: worker stopped")
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/NoiseScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/NoiseScheduler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c7d8db4da0a6b70aae134a341db3b3d2cd8fdfba
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/deniability/NoiseScheduler.kt
@@ -0,0 +1,105 @@
+package de.rki.coronawarnapp.deniability
+
+import androidx.work.BackoffPolicy
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkManager
+import dagger.Reusable
+import de.rki.coronawarnapp.worker.BackgroundConstants
+import de.rki.coronawarnapp.worker.BackgroundWorkHelper
+import timber.log.Timber
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+@Reusable
+class NoiseScheduler @Inject constructor(
+    private val workManager: WorkManager,
+) {
+
+    fun setPeriodicNoise(enabled: Boolean) {
+        Timber.tag(TAG).d("setPeriodicNoise(enabled=$enabled)")
+        if (enabled) {
+            enqueueBackgroundNoisePeriodicWork()
+        } else {
+            workManager.cancelUniqueWork(BACKGROUND_NOISE_PERIODIC_WORK_NAME)
+        }
+    }
+
+    fun scheduleBackgroundNoiseOneTimeWork() {
+        workManager.enqueueUniqueWork(
+            BACKGROUND_NOISE_ONE_TIME_WORK_NAME,
+            ExistingWorkPolicy.REPLACE,
+            buildBackgroundNoiseOneTimeWork()
+        )
+    }
+
+    private fun enqueueBackgroundNoisePeriodicWork() = workManager.enqueueUniquePeriodicWork(
+        BACKGROUND_NOISE_PERIODIC_WORK_NAME,
+        ExistingPeriodicWorkPolicy.REPLACE,
+        buildBackgroundNoisePeriodicWork()
+    )
+
+    private fun buildBackgroundNoiseOneTimeWork() =
+        OneTimeWorkRequestBuilder<BackgroundNoiseOneTimeWorker>()
+            .addTag(BACKGROUND_NOISE_ONE_TIME_WORKER_TAG)
+            .setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork())
+            .setInitialDelay(
+                BackgroundWorkHelper.getBackgroundNoiseOneTimeWorkDelay(),
+                TimeUnit.HOURS
+            ).setBackoffCriteria(
+                BackoffPolicy.LINEAR,
+                BackgroundConstants.KIND_DELAY,
+                TimeUnit.MINUTES
+            )
+            .build()
+
+    /**
+     * Build background noise periodic work request
+     * Set "kind delay" for accessibility reason.
+     *
+     * @return PeriodicWorkRequest
+     *
+     * @see BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
+     */
+    private fun buildBackgroundNoisePeriodicWork() =
+        PeriodicWorkRequestBuilder<BackgroundNoisePeriodicWorker>(
+            BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION,
+            TimeUnit.HOURS
+        )
+            .addTag(BACKGROUND_NOISE_PERIODIC_WORKER_TAG)
+            .setInitialDelay(
+                BackgroundConstants.KIND_DELAY,
+                TimeUnit.SECONDS
+            ).setBackoffCriteria(
+                BackoffPolicy.LINEAR,
+                BackgroundConstants.KIND_DELAY,
+                TimeUnit.MINUTES
+            )
+            .build()
+
+    companion object {
+        /**
+         * Tag for background noise playbook periodic work
+         */
+        const val BACKGROUND_NOISE_PERIODIC_WORKER_TAG = "BACKGROUND_NOISE_PERIODIC_WORKER"
+
+        /**
+         * Tag for background noise playbook one time work
+         */
+        const val BACKGROUND_NOISE_ONE_TIME_WORKER_TAG = "BACKGROUND_NOISE_PERIODIC_WORKER"
+
+        /**
+         * Unique name for background noise playbook periodic work
+         */
+        const val BACKGROUND_NOISE_PERIODIC_WORK_NAME = "BackgroundNoisePeriodicWork"
+
+        /**
+         * Unique name for background noise playbook one time work
+         */
+        const val BACKGROUND_NOISE_ONE_TIME_WORK_NAME = "BackgroundNoiseOneTimeWork"
+
+        private const val TAG = "NoiseScheduler"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt
index 66ecae8a8c6a1d467ce314b3c71db8ab90ef3893..9c415cdc5e307527452650674c60ee45f184a5e8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt
@@ -3,12 +3,12 @@ package de.rki.coronawarnapp.diagnosiskeys.download
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.appconfig.ConfigData
 import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode
 import de.rki.coronawarnapp.environment.EnvironmentSetup
 import de.rki.coronawarnapp.nearby.ENFClient
 import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection
 import de.rki.coronawarnapp.risk.RollbackItem
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.task.Task
 import de.rki.coronawarnapp.task.TaskCancellationException
 import de.rki.coronawarnapp.task.TaskFactory
@@ -34,7 +34,7 @@ class DownloadDiagnosisKeysTask @Inject constructor(
     private val keyPackageSyncTool: KeyPackageSyncTool,
     private val timeStamper: TimeStamper,
     private val settings: DownloadDiagnosisKeysSettings,
-    private val submissionSettings: SubmissionSettings
+    private val coronaTestRepository: CoronaTestRepository,
 ) : Task<DownloadDiagnosisKeysTask.Progress, Task.Result> {
 
     private val internalProgress = ConflatedBroadcastChannel<Progress>()
@@ -114,7 +114,8 @@ class DownloadDiagnosisKeysTask @Inject constructor(
             // remember version code of this execution for next time
             settings.updateLastVersionCodeToCurrent()
 
-            if (submissionSettings.isAllowedToSubmitKeys) {
+            val isAllowedToSubmitKeys = coronaTestRepository.coronaTests.first().any { it.isSubmissionAllowed }
+            if (isAllowedToSubmitKeys) {
                 Timber.tag(TAG).i("task aborted, positive test result")
                 return object : Task.Result {}
             }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt
index a6d942933d7aa3a595e3e5efa3a69d00337a53db..c0b7a2943500af8bcc434d11c042a15bfb5f7de5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt
@@ -3,11 +3,11 @@ package de.rki.coronawarnapp.notification
 import android.content.Context
 import androidx.navigation.NavDeepLinkBuilder
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.main.CWASettings
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.util.device.ForegroundState
 import de.rki.coronawarnapp.util.di.AppContext
-import de.rki.coronawarnapp.util.formatter.TestResult
 import de.rki.coronawarnapp.util.notifications.setContentTextExpandable
 import kotlinx.coroutines.flow.first
 import timber.log.Timber
@@ -22,7 +22,7 @@ class TestResultAvailableNotificationService @Inject constructor(
     private val cwaSettings: CWASettings
 ) {
 
-    suspend fun showTestResultAvailableNotification(testResult: TestResult) {
+    suspend fun showTestResultAvailableNotification(testResult: CoronaTestResult) {
         Timber.d("showTestResultAvailableNotification(testResult=%s)", testResult)
 
         if (foregroundState.isInForeground.first()) {
@@ -67,5 +67,5 @@ class TestResultAvailableNotificationService @Inject constructor(
      * By letting the forwarding happen via the PendingResultFragment,
      * we have a common location to retrieve the test result.
      */
-    fun getNotificationDestination(testResult: TestResult): Int = R.id.submissionTestResultPendingFragment
+    fun getNotificationDestination(testResult: CoronaTestResult): Int = R.id.submissionTestResultPendingFragment
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt
index 57791940bf1a983cda707f6804b8c0d6aee22db4..4a0918b645e62d98680c0bf8a0a83b98bbc5ac25 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt
@@ -1,25 +1,20 @@
 package de.rki.coronawarnapp.playbook
 
-import de.rki.coronawarnapp.submission.SubmissionSettings
-import de.rki.coronawarnapp.worker.BackgroundConstants
-import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import kotlinx.coroutines.flow.first
 import javax.inject.Inject
 import javax.inject.Singleton
 import kotlin.random.Random
 
 @Singleton
 class BackgroundNoise @Inject constructor(
-    private val submissionSettings: SubmissionSettings,
+    private val coronaTestRepository: CoronaTestRepository,
     private val playbook: Playbook,
-    private val backgroundWorkScheduler: BackgroundWorkScheduler
 ) {
-    fun scheduleDummyPattern() {
-        if (BackgroundConstants.NUMBER_OF_DAYS_TO_RUN_PLAYBOOK > 0)
-            backgroundWorkScheduler.scheduleBackgroundNoisePeriodicWork()
-    }
 
     suspend fun foregroundScheduleCheck() {
-        if (submissionSettings.isAllowedToSubmitKeys) {
+        val isAllowedToSubmitKeys = coronaTestRepository.coronaTests.first().any { it.isSubmissionAllowed }
+        if (isAllowedToSubmitKeys) {
             val chance = Random.nextFloat() * 100
             if (chance < DefaultPlaybook.PROBABILITY_TO_EXECUTE_PLAYBOOK_ON_APP_OPEN) {
                 playbook.dummy()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt
index 3c432bc42d25dbde66ce663dc127508c11be47fc..a678e0686faa1ad1c067a43d7b140350094eb199 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt
@@ -1,11 +1,11 @@
 package de.rki.coronawarnapp.playbook
 
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.server.VerificationKeyType
+import de.rki.coronawarnapp.coronatest.server.VerificationServer
 import de.rki.coronawarnapp.exception.TanPairingException
 import de.rki.coronawarnapp.exception.http.BadRequestException
 import de.rki.coronawarnapp.submission.server.SubmissionServer
-import de.rki.coronawarnapp.util.formatter.TestResult
-import de.rki.coronawarnapp.verification.server.VerificationKeyType
-import de.rki.coronawarnapp.verification.server.VerificationServer
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
@@ -28,7 +28,7 @@ class DefaultPlaybook @Inject constructor(
     override suspend fun initialRegistration(
         key: String,
         keyType: VerificationKeyType
-    ): Pair<String, TestResult> {
+    ): Pair<String, CoronaTestResult> {
         Timber.i("[$uid] New Initial Registration Playbook")
 
         // real registration
@@ -43,7 +43,7 @@ class DefaultPlaybook @Inject constructor(
         // if the registration succeeded continue with the real test result retrieval
         // if it failed, execute a dummy request to satisfy the required playbook pattern
         val (testResult, testResultException) = if (registrationToken != null) {
-            executeCapturingExceptions { verificationServer.retrieveTestResults(registrationToken) }
+            executeCapturingExceptions { verificationServer.pollTestResult(registrationToken) }
         } else {
             ignoreExceptions { verificationServer.retrieveTanFake() }
             null to null
@@ -56,18 +56,18 @@ class DefaultPlaybook @Inject constructor(
 
         // if registration and test result retrieval succeeded, return the result
         if (registrationToken != null && testResult != null)
-            return registrationToken to TestResult.fromInt(testResult)
+            return registrationToken to testResult
 
         // else propagate the exception of either the first or the second step
         propagateException(registrationException, testResultException)
     }
 
-    override suspend fun testResult(registrationToken: String): TestResult {
+    override suspend fun testResult(registrationToken: String): CoronaTestResult {
         Timber.i("[$uid] New Test Result Playbook")
 
         // real test result
         val (testResult, exception) =
-            executeCapturingExceptions { verificationServer.retrieveTestResults(registrationToken) }
+            executeCapturingExceptions { verificationServer.pollTestResult(registrationToken) }
 
         // fake verification
         ignoreExceptions { verificationServer.retrieveTanFake() }
@@ -77,7 +77,7 @@ class DefaultPlaybook @Inject constructor(
 
         coroutineScope.launch { followUpPlaybooks() }
 
-        return testResult?.let { TestResult.fromInt(it) } ?: propagateException(exception)
+        return testResult ?: propagateException(exception)
     }
 
     override suspend fun submit(
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt
index 3da7b683d0611d94e64d51ecc0f4af5f75f80f4b..b46a6dda6ad0c9bd3cf8d502a40df4ae2a6e81c7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt
@@ -1,9 +1,9 @@
 package de.rki.coronawarnapp.playbook
 
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.server.VerificationKeyType
 import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass
 import de.rki.coronawarnapp.server.protocols.internal.pt.CheckInOuterClass
-import de.rki.coronawarnapp.util.formatter.TestResult
-import de.rki.coronawarnapp.verification.server.VerificationKeyType
 
 /**
  * The concept of Plausible Deniability aims to hide the existence of a positive test result by always using a defined
@@ -20,14 +20,14 @@ import de.rki.coronawarnapp.verification.server.VerificationKeyType
 interface Playbook {
 
     /**
-     * @return pair of Registration token [String] & [TestResult]
+     * @return pair of Registration token [String] & [CoronaTestResult]
      */
     suspend fun initialRegistration(
         key: String,
         keyType: VerificationKeyType
-    ): Pair<String, TestResult>
+    ): Pair<String, CoronaTestResult>
 
-    suspend fun testResult(registrationToken: String): TestResult
+    suspend fun testResult(registrationToken: String): CoronaTestResult
 
     suspend fun submit(data: SubmissionData)
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckIn.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckIn.kt
index 6330d8dd0d464fb13c05cd81ba5bb7fdc7c8fb52..04485e66cd82f0b0b628779ced082b203365b01b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckIn.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckIn.kt
@@ -24,6 +24,7 @@ data class CheckIn(
     val completed: Boolean,
     val createJournalEntry: Boolean,
     val isSubmitted: Boolean = false,
+    val hasSubmissionConsent: Boolean = false,
 ) {
     /**
      *  Returns SHA-256 hash of [traceLocationId] which itself may also be SHA-256 hash.
@@ -51,4 +52,5 @@ fun CheckIn.toEntity() = TraceLocationCheckInEntity(
     completed = completed,
     createJournalEntry = createJournalEntry,
     isSubmitted = isSubmitted,
+    hasSubmissionConsent = hasSubmissionConsent,
 )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepository.kt
index 4528fc1138c0c111aae3e822d569e730f472e046..6985100faabe2d8fce22e1551c7f5adbc8d96e57 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepository.kt
@@ -65,10 +65,14 @@ class CheckInRepository @Inject constructor(
         checkInDao.updateEntityById(checkInId, update)
     }
 
-    suspend fun markCheckInAsSubmitted(checkInId: Long) {
+    suspend fun updatePostSubmissionFlags(checkInId: Long) {
         Timber.d("markCheckInAsSubmitted(checkInId=$checkInId)")
         checkInDao.updateEntity(
-            TraceLocationCheckInEntity.SubmissionUpdate(checkInId = checkInId, isSubmitted = true)
+            TraceLocationCheckInEntity.SubmissionUpdate(
+                checkInId = checkInId,
+                isSubmitted = true,
+                hasSubmissionConsent = false,
+            )
         )
     }
 
@@ -88,4 +92,15 @@ class CheckInRepository @Inject constructor(
 
         return checkIn.toCheckIn()
     }
+
+    suspend fun updateSubmissionConsents(checkInIds: Collection<Long>, consent: Boolean) {
+        Timber.d("updateSubmissionConsents(checkInIds=%s, consent=%b)", checkInIds, consent)
+        val consentUpdates = checkInIds.map {
+            TraceLocationCheckInEntity.SubmissionConsentUpdate(
+                checkInId = it,
+                hasSubmissionConsent = consent
+            )
+        }
+        checkInDao.updateSubmissionConsents(consentUpdates)
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/common/CheckInRepositoryExtension.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/common/CheckInRepositoryExtension.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d146560bef7b273da74e3f020f838ca100cc63d2
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/common/CheckInRepositoryExtension.kt
@@ -0,0 +1,13 @@
+package de.rki.coronawarnapp.presencetracing.checkins.common
+
+import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
+import kotlinx.coroutines.flow.map
+
+/**
+ * Returns completed [CheckIn]s only
+ */
+val CheckInRepository.completedCheckIns
+    get() = checkInsWithinRetention.map { checkIns ->
+        checkIns.filter { checkIn -> checkIn.completed }
+    }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt
index 06b72ed4620c465999247db75079fae56aef49ee..f906c47bbd65bf57708ba0578b4105bcca44614f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt
@@ -6,22 +6,35 @@ import de.rki.coronawarnapp.risk.DefaultRiskLevels.Companion.inRange
 import de.rki.coronawarnapp.risk.RiskState
 import de.rki.coronawarnapp.risk.mapToRiskState
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import timber.log.Timber
 import javax.inject.Inject
+import javax.inject.Singleton
 
+@Singleton
 class PresenceTracingRiskMapper @Inject constructor(
     private val configProvider: AppConfigProvider
 ) {
     private var presenceTracingRiskCalculationParamContainer: PresenceTracingRiskCalculationParamContainer? = null
 
+    private val mutex = Mutex()
+
+    suspend fun clearConfig() {
+        mutex.withLock {
+            Timber.tag(TAG).i("Clearing config params.")
+            presenceTracingRiskCalculationParamContainer = null
+        }
+    }
+
     suspend fun lookupTransmissionRiskValue(transmissionRiskLevel: Int): Double {
-        return getTransmissionRiskValueMapping()?.find {
+        return getTransmissionRiskValueMapping().find {
             (it.transmissionRiskLevel == transmissionRiskLevel)
         }?.transmissionRiskValue ?: 0.0
     }
 
     suspend fun lookupRiskStatePerDay(normalizedTime: Double): RiskState {
-        return getNormalizedTimePerDayToRiskLevelMapping()?.find {
+        return getNormalizedTimePerDayToRiskLevelMapping().find {
             it.normalizedTimeRange.inRange(normalizedTime)
         }
             ?.riskLevel
@@ -29,7 +42,7 @@ class PresenceTracingRiskMapper @Inject constructor(
     }
 
     suspend fun lookupRiskStatePerCheckIn(normalizedTime: Double): RiskState {
-        return getNormalizedTimePerCheckInToRiskLevelMapping()?.find {
+        return getNormalizedTimePerCheckInToRiskLevelMapping().find {
             it.normalizedTimeRange.inRange(normalizedTime)
         }
             ?.riskLevel
@@ -37,20 +50,26 @@ class PresenceTracingRiskMapper @Inject constructor(
     }
 
     private suspend fun getTransmissionRiskValueMapping() =
-        getRiskCalculationParameters()?.transmissionRiskValueMapping
+        getRiskCalculationParameters().transmissionRiskValueMapping
 
     private suspend fun getNormalizedTimePerDayToRiskLevelMapping() =
-        getRiskCalculationParameters()?.normalizedTimePerDayToRiskLevelMapping
+        getRiskCalculationParameters().normalizedTimePerDayToRiskLevelMapping
 
     private suspend fun getNormalizedTimePerCheckInToRiskLevelMapping() =
-        getRiskCalculationParameters()?.normalizedTimePerCheckInToRiskLevelMapping
+        getRiskCalculationParameters().normalizedTimePerCheckInToRiskLevelMapping
 
-    private suspend fun getRiskCalculationParameters(): PresenceTracingRiskCalculationParamContainer? {
-        if (presenceTracingRiskCalculationParamContainer == null) {
-            presenceTracingRiskCalculationParamContainer =
-                configProvider.currentConfig.first().presenceTracing.riskCalculationParameters
-            Timber.d(presenceTracingRiskCalculationParamContainer.toString())
+    private suspend fun getRiskCalculationParameters(): PresenceTracingRiskCalculationParamContainer = mutex.withLock {
+        presenceTracingRiskCalculationParamContainer.let {
+            if (it == null) {
+                val newParams = configProvider.currentConfig.first().presenceTracing.riskCalculationParameters
+                Timber.d("New params %s", newParams)
+                presenceTracingRiskCalculationParamContainer = newParams
+                newParams
+            } else {
+                it
+            }
         }
-        return presenceTracingRiskCalculationParamContainer
     }
 }
+
+private const val TAG = "PtRiskMapper"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt
index 3cf08b5ca2ff59ea8492fe04e1d1dfbcc77ec718..b5c6b195dc3b54b186f8f5d71edd1c8a7a39fb64 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt
@@ -2,10 +2,11 @@ package de.rki.coronawarnapp.presencetracing.risk.execution
 
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.bugreporting.reportProblem
-import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import de.rki.coronawarnapp.presencetracing.risk.calculation.CheckInWarningMatcher
+import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingRiskMapper
 import de.rki.coronawarnapp.presencetracing.risk.storage.PresenceTracingRiskRepository
 import de.rki.coronawarnapp.presencetracing.warning.download.TraceWarningPackageSyncTool
 import de.rki.coronawarnapp.presencetracing.warning.storage.TraceWarningRepository
@@ -30,6 +31,7 @@ class PresenceTracingWarningTask @Inject constructor(
     private val presenceTracingRiskRepository: PresenceTracingRiskRepository,
     private val traceWarningRepository: TraceWarningRepository,
     private val checkInsRepository: CheckInRepository,
+    private val presenceTracingRiskMapper: PresenceTracingRiskMapper
 ) : Task<PresenceTracingWarningTaskProgress, PresenceTracingWarningTask.Result> {
 
     private val internalProgress = ConflatedBroadcastChannel<PresenceTracingWarningTaskProgress>()
@@ -60,6 +62,9 @@ class PresenceTracingWarningTask @Inject constructor(
         val nowUTC = timeStamper.nowUTC
         checkCancel()
 
+        Timber.tag(TAG).d("Resetting config to make sure latest changes are considered.")
+        presenceTracingRiskMapper.clearConfig()
+
         Timber.tag(TAG).d("Syncing packages.")
         internalProgress.send(PresenceTracingWarningTaskProgress.Downloading())
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt
index 98fcb83d2fbdc5c01473f1fff3250c5cb28d9341..5d0186469dea987b9fbb9d43b5bbc7d10a6ba045 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt
@@ -9,12 +9,12 @@ import androidx.room.OnConflictStrategy.REPLACE
 import androidx.room.PrimaryKey
 import androidx.room.Query
 import androidx.room.TypeConverter
-import de.rki.coronawarnapp.presencetracing.storage.entity.TraceLocationCheckInEntity
 import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult
 import de.rki.coronawarnapp.presencetracing.risk.TraceLocationCheckInRisk
 import de.rki.coronawarnapp.presencetracing.risk.calculation.CheckInWarningOverlap
 import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingDayRisk
 import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingRiskCalculator
+import de.rki.coronawarnapp.presencetracing.storage.entity.TraceLocationCheckInEntity
 import de.rki.coronawarnapp.risk.RiskState
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc
 import de.rki.coronawarnapp.util.TimeStamper
@@ -31,7 +31,7 @@ import javax.inject.Singleton
 class PresenceTracingRiskRepository @Inject constructor(
     private val presenceTracingRiskCalculator: PresenceTracingRiskCalculator,
     private val databaseFactory: PresenceTracingRiskDatabase.Factory,
-    private val timeStamper: TimeStamper,
+    private val timeStamper: TimeStamper
 ) {
 
     private val database by lazy {
@@ -144,9 +144,15 @@ class PresenceTracingRiskRepository @Inject constructor(
         get() = timeStamper.nowUTC.minus(Days.days(15).toStandardDuration())
 
     suspend fun clearAllTables() {
+        Timber.i("Deleting all matches and results.")
         traceTimeIntervalMatchDao.deleteAll()
         riskLevelResultDao.deleteAll()
     }
+
+    suspend fun clearResults() {
+        Timber.i("Deleting all results.")
+        riskLevelResultDao.deleteAll()
+    }
 }
 
 /*
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/TraceLocationDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/TraceLocationDatabase.kt
index 7e6facc03e154d5eeb5505ec3743cc8f6b51c61c..d17f1ccc6a1d5d0c687ad0e623d2384c4f85cb8b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/TraceLocationDatabase.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/TraceLocationDatabase.kt
@@ -10,6 +10,7 @@ import de.rki.coronawarnapp.presencetracing.storage.dao.TraceLocationDao
 import de.rki.coronawarnapp.presencetracing.storage.entity.TraceLocationCheckInEntity
 import de.rki.coronawarnapp.presencetracing.storage.entity.TraceLocationConverters
 import de.rki.coronawarnapp.presencetracing.storage.entity.TraceLocationEntity
+import de.rki.coronawarnapp.presencetracing.storage.migration.PresenceTracingDatabaseMigration1To2
 import de.rki.coronawarnapp.util.database.CommonConverters
 import de.rki.coronawarnapp.util.di.AppContext
 import javax.inject.Inject
@@ -19,7 +20,7 @@ import javax.inject.Inject
         TraceLocationCheckInEntity::class,
         TraceLocationEntity::class
     ],
-    version = 1,
+    version = 2,
     exportSchema = true
 )
 @TypeConverters(CommonConverters::class, TraceLocationConverters::class)
@@ -29,8 +30,9 @@ abstract class TraceLocationDatabase : RoomDatabase() {
     abstract fun traceLocationDao(): TraceLocationDao
 
     class Factory @Inject constructor(@AppContext private val context: Context) {
-        fun create() = Room
-            .databaseBuilder(context, TraceLocationDatabase::class.java, TRACE_LOCATIONS_DATABASE_NAME)
+        fun create(databaseName: String = TRACE_LOCATIONS_DATABASE_NAME): TraceLocationDatabase = Room
+            .databaseBuilder(context, TraceLocationDatabase::class.java, databaseName)
+            .addMigrations(PresenceTracingDatabaseMigration1To2)
             .build()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/dao/CheckInDao.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/dao/CheckInDao.kt
index 1d56e9455d19f8e7c9336692f0b6b588dea121f3..01261763e56279392b7b2ce8973ecfd69235ecc9 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/dao/CheckInDao.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/dao/CheckInDao.kt
@@ -40,6 +40,9 @@ interface CheckInDao {
     @Update(entity = TraceLocationCheckInEntity::class)
     suspend fun updateEntity(update: TraceLocationCheckInEntity.SubmissionUpdate)
 
+    @Update(entity = TraceLocationCheckInEntity::class)
+    suspend fun updateSubmissionConsents(update: Collection<TraceLocationCheckInEntity.SubmissionConsentUpdate>)
+
     @Query("DELETE FROM checkin")
     suspend fun deleteAll()
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/entity/TraceLocationCheckInEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/entity/TraceLocationCheckInEntity.kt
index 0d9fda64de57a889e6ca43212f32df7194a2a5ab..582d5f7ac85614fb82033861c65cea384c06a07c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/entity/TraceLocationCheckInEntity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/entity/TraceLocationCheckInEntity.kt
@@ -25,12 +25,20 @@ data class TraceLocationCheckInEntity(
     @ColumnInfo(name = "completed") val completed: Boolean,
     @ColumnInfo(name = "createJournalEntry") val createJournalEntry: Boolean,
     @ColumnInfo(name = "submitted") val isSubmitted: Boolean,
+    @ColumnInfo(name = "submissionConsent") val hasSubmissionConsent: Boolean,
 ) {
 
     @Entity
     data class SubmissionUpdate(
         @PrimaryKey @ColumnInfo(name = "id") val checkInId: Long,
         @ColumnInfo(name = "submitted") val isSubmitted: Boolean,
+        @ColumnInfo(name = "submissionConsent") val hasSubmissionConsent: Boolean,
+    )
+
+    @Entity
+    data class SubmissionConsentUpdate(
+        @PrimaryKey @ColumnInfo(name = "id") val checkInId: Long,
+        @ColumnInfo(name = "submissionConsent") val hasSubmissionConsent: Boolean,
     )
 }
 
@@ -50,5 +58,6 @@ fun TraceLocationCheckInEntity.toCheckIn() = CheckIn(
     checkInEnd = checkInEnd,
     completed = completed,
     createJournalEntry = createJournalEntry,
-    isSubmitted = isSubmitted
+    isSubmitted = isSubmitted,
+    hasSubmissionConsent = hasSubmissionConsent
 )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/migration/PresenceTracingDatabaseMigration1To2.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/migration/PresenceTracingDatabaseMigration1To2.kt
new file mode 100644
index 0000000000000000000000000000000000000000..97d046ec0f870729b54b13715a9bda1716c9e022
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/storage/migration/PresenceTracingDatabaseMigration1To2.kt
@@ -0,0 +1,38 @@
+package de.rki.coronawarnapp.presencetracing.storage.migration
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.reporting.report
+import timber.log.Timber
+
+/**
+ * Migrates the presence tracing database from version 1 to 2.
+ * The additional attribute:
+ * PresenceTracingCheckInEntity.submissionConsent was added
+ */
+object PresenceTracingDatabaseMigration1To2 : Migration(1, 2) {
+
+    override fun migrate(database: SupportSQLiteDatabase) {
+        try {
+            Timber.i("Attempting migration 1->2...")
+            performMigration(database)
+            Timber.i("Migration 1->2 successful.")
+        } catch (e: Exception) {
+            Timber.e(e, "Migration 1->2 failed")
+            e.report(ExceptionCategory.INTERNAL, "PresenceTracing database migration failed.")
+            throw e
+        }
+    }
+
+    private fun performMigration(database: SupportSQLiteDatabase) = with(database) {
+        Timber.d("Running MIGRATION_1_2")
+
+        migrateTraceLocationCheckInEntity()
+    }
+
+    private val migrateTraceLocationCheckInEntity: SupportSQLiteDatabase.() -> Unit = {
+        Timber.d("Table 'checkin': Add column 'submissionConsent'")
+        execSQL("ALTER TABLE `checkin` ADD COLUMN `submissionConsent` INTEGER NOT NULL DEFAULT '0'")
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt
index 123f45edceef714e0fefff56dff06bb8dc7f6f73..799cf04a61366a289895429d79a1fb9adfe50345 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt
@@ -4,13 +4,13 @@ import android.content.Context
 import androidx.annotation.VisibleForTesting
 import androidx.core.app.NotificationManagerCompat
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings
 import de.rki.coronawarnapp.datadonation.survey.Surveys
 import de.rki.coronawarnapp.notification.GeneralNotifications
 import de.rki.coronawarnapp.notification.NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
 import de.rki.coronawarnapp.storage.TracingSettings
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.util.coroutine.AppScope
 import de.rki.coronawarnapp.util.device.ForegroundState
 import de.rki.coronawarnapp.util.di.AppContext
@@ -35,7 +35,7 @@ class RiskLevelChangeDetector @Inject constructor(
     private val foregroundState: ForegroundState,
     private val notificationHelper: GeneralNotifications,
     private val surveys: Surveys,
-    private val submissionSettings: SubmissionSettings,
+    private val coronaTestRepository: CoronaTestRepository,
     private val tracingSettings: TracingSettings,
     private val testResultDonorSettings: TestResultDonorSettings
 ) {
@@ -146,7 +146,8 @@ class RiskLevelChangeDetector @Inject constructor(
         oldRiskState: RiskState,
         newRiskState: RiskState
     ) {
-        if (hasHighLowLevelChanged(oldRiskState, newRiskState) && !submissionSettings.isSubmissionSuccessful) {
+        val isSubmissionSuccessful = coronaTestRepository.coronaTests.first().any { it.isSubmitted }
+        if (hasHighLowLevelChanged(oldRiskState, newRiskState) && !isSubmissionSuccessful) {
             Timber.d("Notification Permission = ${notificationManagerCompat.areNotificationsEnabled()}")
 
             if (!foregroundState.isInForeground.first()) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt
index c79f789b7d07d76b8132f1adea7cb3fd3c114bb6..d81f7eff26394e0c622943efb89dde6e84aab444 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt
@@ -5,6 +5,7 @@ import com.google.android.gms.nearby.exposurenotification.ExposureWindow
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.appconfig.ConfigData
 import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.datadonation.analytics.modules.exposurewindows.AnalyticsExposureWindowCollector
 import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
 import de.rki.coronawarnapp.exception.ExceptionCategory
@@ -16,7 +17,6 @@ import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut
 import de.rki.coronawarnapp.risk.EwRiskLevelResult.FailureReason
 import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.task.Task
 import de.rki.coronawarnapp.task.TaskCancellationException
 import de.rki.coronawarnapp.task.TaskFactory
@@ -46,7 +46,7 @@ class RiskLevelTask @Inject constructor(
     private val appConfigProvider: AppConfigProvider,
     private val riskLevelStorage: RiskLevelStorage,
     private val keyCacheRepository: KeyCacheRepository,
-    private val submissionSettings: SubmissionSettings,
+    private val coronaTestRepository: CoronaTestRepository,
     private val analyticsExposureWindowCollector: AnalyticsExposureWindowCollector,
     private val autoCheckOut: AutoCheckOut,
 ) : Task<DefaultProgress, EwRiskLevelTaskResult> {
@@ -91,7 +91,9 @@ class RiskLevelTask @Inject constructor(
             Timber.d("The current time is %s", it)
         }
 
-        if (submissionSettings.isAllowedToSubmitKeys && submissionSettings.hasViewedTestResult.value) {
+        val isAllowedToSubmitKeys = coronaTestRepository.coronaTests.first().any { it.isSubmissionAllowed }
+        val hasViewedTestResult = coronaTestRepository.coronaTests.first().any { it.isViewed }
+        if (isAllowedToSubmitKeys && hasViewedTestResult) {
             Timber.i("Positive test result and user has seen it, skip risk calculation")
             return EwRiskLevelTaskResult(
                 calculatedAt = nowUTC,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt
index 0582cc3c9579abb25f40c3ecb13a79bbd617f2c9..800757c75b5c3e32104b6d769e6d1901d8ec6dbe 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt
@@ -224,9 +224,17 @@ abstract class BaseRiskLevelStorage constructor(
     override suspend fun clear() {
         Timber.w("clear() - Clearing stored risklevel/exposure-detection results.")
         database.clearAllTables()
+        Timber.w("clear() - Clearing stored presence tracing matches and results.")
         presenceTracingRiskRepository.clearAllTables()
     }
 
+    override suspend fun clearResults() {
+        Timber.w("clearResults() - Clearing stored risklevel/exposure-detection results.")
+        database.clearAllTables()
+        Timber.w("clearResults() - Clearing stored presence tracing results.")
+        presenceTracingRiskRepository.clearResults()
+    }
+
     companion object {
         private const val TAG = "RiskLevelStorage"
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt
index a72b458ba00fde938c47d01db704dc33001864d5..e0d42b72dbb3806ec60fbeca0e4aa060a336e957 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt
@@ -79,4 +79,6 @@ interface RiskLevelStorage {
     suspend fun storeResult(resultEw: EwRiskLevelResult)
 
     suspend fun clear()
+
+    suspend fun clearResults()
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/QRScanResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/QRScanResult.kt
deleted file mode 100644
index 2a054d0ebf1f23453ef26ca5d942235368fa8e63..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/QRScanResult.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package de.rki.coronawarnapp.service.submission
-
-import java.util.regex.Pattern
-
-data class QRScanResult(val rawResult: String) {
-
-    val isValid: Boolean
-        get() = guid != null
-    val guid: String? by lazy { extractGUID(rawResult) }
-
-    private fun extractGUID(rawResult: String): String? {
-        if (!QR_CODE_REGEX.toRegex().matches(rawResult)) return null
-
-        val matcher = QR_CODE_REGEX.matcher(rawResult)
-        return if (matcher.matches()) matcher.group(1) else null
-    }
-
-    companion object {
-        // regex pattern for scanned QR code URL
-        val QR_CODE_REGEX: Pattern = (
-            "^" + // Match start of string
-                "(?:https:\\/{2}localhost)" + // Match `https://localhost`
-                "(?:\\/{1}\\?)" + // Match the query param `/?`
-                "([a-f\\d]{6}[-][a-f\\d]{8}[-](?:[a-f\\d]{4}[-]){3}[a-f\\d]{12})" + // Match the UUID
-                "\$"
-            ).toPattern(Pattern.CASE_INSENSITIVE)
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingSettings.kt
index 63f4739e96fc9ebde92732f85661316e1d373638..4b632162771c9924ad35838a8b3aeb79e291230f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingSettings.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingSettings.kt
@@ -5,6 +5,7 @@ import androidx.core.content.edit
 import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.preferences.clearAndNotify
 import de.rki.coronawarnapp.util.preferences.createFlowPreference
+import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -21,13 +22,15 @@ class TracingSettings @Inject constructor(@AppContext private val context: Conte
             putBoolean(TRACING_ACTIVATION_TIMESTAMP, value)
         }
 
-    var initialPollingForTestResultTimeStamp: Long
+    @Deprecated("Use CoronaTestRepository")
+    var initialPollingForTestResultTimeStampMigration: Long
         get() = prefs.getLong(TRACING_POOLING_TIMESTAMP, 0L)
         set(value) = prefs.edit(true) {
             putLong(TRACING_POOLING_TIMESTAMP, value)
         }
 
-    var isTestResultAvailableNotificationSent: Boolean
+    @Deprecated("Use CoronaTestRepository")
+    var isTestResultAvailableNotificationSentMigration: Boolean
         get() = prefs.getBoolean(TEST_RESULT_NOTIFICATION_SENT, false)
         set(value) = prefs.edit(true) {
             putBoolean(TEST_RESULT_NOTIFICATION_SENT, value)
@@ -38,6 +41,31 @@ class TracingSettings @Inject constructor(@AppContext private val context: Conte
         defaultValue = false
     )
 
+    fun deleteLegacyTestData() {
+// Sourced from the behavior of SubmissionRepository.removeTestFromDevice()
+//        fun removeTestFromDevice() {
+//            submissionSettings.hasViewedTestResult.update { false }
+//            submissionSettings.hasGivenConsent.update { false }
+//            revokeConsentToSubmission()
+//            submissionSettings.registrationToken.update { null }
+//            submissionSettings.devicePairingSuccessfulAt = null
+//            tracingSettings.initialPollingForTestResultTimeStamp = 0L
+//            submissionSettings.initialTestResultReceivedAt = null
+//            submissionSettings.isAllowedToSubmitKeys = false
+//            tracingSettings.isTestResultAvailableNotificationSent = false
+//            submissionSettings.isSubmissionSuccessful = false
+//            testResultDataCollector.clear()
+//        }
+        Timber.d("deleteLegacyTestData()")
+        prefs.edit {
+            remove(TEST_RESULT_NOTIFICATION_SENT)
+            remove(TRACING_POOLING_TIMESTAMP)
+        }
+
+        // TODO No longer needed, was for worker control?
+        // tracingSettings.initialPollingForTestResultTimeStamp = 0L
+    }
+
     fun clear() = prefs.clearAndNotify()
 
     companion object {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt
index 29a64f31616a0e3b6f3e0ee5ca98b6d8b5007a8f..155f7c318ef9c79cc23ebee375b77792b0bfa56a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt
@@ -1,237 +1,128 @@
 package de.rki.coronawarnapp.submission
 
-import androidx.annotation.VisibleForTesting
-import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
-import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.TestResultDataCollector
-import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
-import de.rki.coronawarnapp.exception.ExceptionCategory
-import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
-import de.rki.coronawarnapp.exception.http.CwaWebException
-import de.rki.coronawarnapp.exception.reporting.report
-import de.rki.coronawarnapp.playbook.BackgroundNoise
-import de.rki.coronawarnapp.service.submission.SubmissionService
-import de.rki.coronawarnapp.storage.TracingSettings
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
-import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.coroutine.AppScope
-import de.rki.coronawarnapp.util.formatter.TestResult
-import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import timber.log.Timber
-import java.util.Date
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Suppress("LongParameterList")
 @Singleton
 class SubmissionRepository @Inject constructor(
-    private val submissionSettings: SubmissionSettings,
-    private val submissionService: SubmissionService,
     @AppScope private val scope: CoroutineScope,
-    private val timeStamper: TimeStamper,
+    private val submissionSettings: SubmissionSettings,
     private val tekHistoryStorage: TEKHistoryStorage,
-    private val deadmanNotificationScheduler: DeadmanNotificationScheduler,
-    private val backgroundNoise: BackgroundNoise,
-    private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
-    private val tracingSettings: TracingSettings,
-    private val testResultDataCollector: TestResultDataCollector,
-    private val backgroundWorkScheduler: BackgroundWorkScheduler,
+    private val coronaTestRepository: CoronaTestRepository,
 ) {
-    private val testResultReceivedDateFlowInternal =
-        MutableStateFlow((submissionSettings.initialTestResultReceivedAt ?: timeStamper.nowUTC).toDate())
-    val testResultReceivedDateFlow: Flow<Date> = testResultReceivedDateFlowInternal
-
-    private val deviceUIStateFlowInternal =
-        MutableStateFlow<NetworkRequestWrapper<DeviceUIState, Throwable>>(NetworkRequestWrapper.RequestIdle)
-    val deviceUIStateFlow: Flow<NetworkRequestWrapper<DeviceUIState, Throwable>> = deviceUIStateFlowInternal
 
-    // to be used by new submission flow screens
-    val hasGivenConsentToSubmission = submissionSettings.hasGivenConsent.flow
-    val hasViewedTestResult = submissionSettings.hasViewedTestResult.flow
-    val currentSymptoms = submissionSettings.symptoms
+    val pcrTest: Flow<PCRCoronaTest?> = coronaTestRepository.coronaTests.map { tests ->
+        tests.singleOrNull { it.type == CoronaTest.Type.PCR } as? PCRCoronaTest
+    }
 
-    private val testResultFlow = MutableStateFlow<TestResult?>(null)
+    val raTest: Flow<RACoronaTest?> = coronaTestRepository.coronaTests.map { tests ->
+        tests.singleOrNull { it.type == CoronaTest.Type.RAPID_ANTIGEN } as? RACoronaTest
+    }
 
-    // to be used by new submission flow screens
-    fun giveConsentToSubmission() {
-        Timber.d("giveConsentToSubmission()")
-        submissionSettings.hasGivenConsent.update {
-            true
-        }
+    fun testForType(type: CoronaTest.Type) = when (type) {
+        CoronaTest.Type.PCR -> pcrTest
+        CoronaTest.Type.RAPID_ANTIGEN -> raTest
     }
 
+    val currentSymptoms = submissionSettings.symptoms
+
     // to be used by new submission flow screens
-    fun revokeConsentToSubmission() {
-        Timber.d("revokeConsentToSubmission()")
-        submissionSettings.hasGivenConsent.update {
-            false
-        }
-    }
+    fun giveConsentToSubmission(type: CoronaTest.Type) {
+        Timber.tag(TAG).v("giveConsentToSubmission(type=%s)", type)
+        scope.launch {
+            val test = coronaTestRepository.coronaTests.first().singleOrNull { it.type == type }
+                ?: throw IllegalStateException("No test of type $type available")
 
-    // to be set to true once the user has opened and viewed their test result
-    fun setViewedTestResult() {
-        Timber.d("setViewedTestResult()")
-        submissionSettings.hasViewedTestResult.update {
-            true
+            coronaTestRepository.updateConsent(identifier = test.identifier, consented = true)
         }
     }
 
-    fun refreshDeviceUIState(refreshTestResult: Boolean = true) {
-        if (submissionSettings.isSubmissionSuccessful) {
-            deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL)
-            return
-        }
-
-        val registrationToken = submissionSettings.registrationToken.value
-        if (registrationToken == null) {
-            deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED)
-            return
-        }
+    // to be used by new submission flow screens
+    fun revokeConsentToSubmission(type: CoronaTest.Type) {
+        Timber.tag(TAG).v("revokeConsentToSubmission(type=%s)", type)
+        scope.launch {
+            val test = coronaTestRepository.coronaTests.first().singleOrNull { it.type == type }
+                ?: throw IllegalStateException("No test of type $type available")
 
-        if (submissionSettings.isAllowedToSubmitKeys) {
-            deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE)
-            return
+            coronaTestRepository.updateConsent(identifier = test.identifier, consented = false)
         }
+    }
 
-        var refresh = refreshTestResult
-        deviceUIStateFlowInternal.value.withSuccess {
-            if (it != DeviceUIState.PAIRED_NO_RESULT && it != DeviceUIState.UNPAIRED) {
-                refresh = false
-                Timber.d("refreshDeviceUIState: Change refresh, state ${it.name} doesn't require refresh")
-            }
-        }
+    // to be set to true once the user has opened and viewed their test result
+    fun setViewedTestResult(type: CoronaTest.Type) {
+        Timber.tag(TAG).v("setViewedTestResult(type=%s)", type)
+        scope.launch {
+            val test = coronaTestRepository.coronaTests.first().singleOrNull { it.type == type }
+                ?: throw IllegalStateException("No test of type $type available")
 
-        if (refresh) {
-            deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestStarted
-
-            scope.launch {
-                try {
-                    deviceUIStateFlowInternal.value =
-                        NetworkRequestWrapper.RequestSuccessful(fetchTestResult(registrationToken))
-                } catch (err: CwaWebException) {
-                    deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err)
-                } catch (err: Exception) {
-                    deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err)
-                    err.report(ExceptionCategory.INTERNAL)
-                }
-            }
-        } else {
-            deviceUIStateFlowInternal.value =
-                NetworkRequestWrapper.RequestSuccessful(deriveUiState(testResultFlow.value))
+            coronaTestRepository.markAsViewed(identifier = test.identifier)
         }
     }
 
-    suspend fun asyncRegisterDeviceViaTAN(tan: String) {
-        analyticsKeySubmissionCollector.reset()
-        val registrationData = submissionService.asyncRegisterDeviceViaTAN(tan)
-        // START - Fix for EXPOSUREAPP-4484 relies on this call order
-        submissionSettings.registrationToken.update {
-            registrationData.registrationToken
+    fun refreshTest(type: CoronaTest.Type) {
+        Timber.tag(TAG).v("refreshTest(type=%s)", type)
+
+        scope.launch {
+            coronaTestRepository.refresh(type = type)
         }
-        updateTestResult(registrationData.testResult)
-        submissionSettings.devicePairingSuccessfulAt = timeStamper.nowUTC
-        // END
-        backgroundNoise.scheduleDummyPattern()
-        analyticsKeySubmissionCollector.reportTestRegistered()
-        analyticsKeySubmissionCollector.reportRegisteredWithTeleTAN()
     }
 
-    suspend fun asyncRegisterDeviceViaGUID(guid: String): TestResult {
-        analyticsKeySubmissionCollector.reset()
-        val registrationData = submissionService.asyncRegisterDeviceViaGUID(guid)
-        // START - Fix for EXPOSUREAPP-4484 relies on this call order
-        submissionSettings.registrationToken.update {
-            registrationData.registrationToken
-        }
-        updateTestResult(registrationData.testResult) // This saves initial time
-        submissionSettings.devicePairingSuccessfulAt = timeStamper.nowUTC
-        // END
-        backgroundNoise.scheduleDummyPattern()
-        analyticsKeySubmissionCollector.reportTestRegistered()
-        testResultDataCollector.saveTestResultAnalyticsSettings(registrationData.testResult) // This saves received at
-        return registrationData.testResult
+    suspend fun registerTest(request: TestRegistrationRequest): CoronaTest {
+        Timber.tag(TAG).v("registerTest(request=%s)", request)
+        val coronaTest = coronaTestRepository.registerTest(request)
+        Timber.d("Registered test %s -> %s", request, coronaTest)
+        return coronaTest
     }
 
     suspend fun reset() {
-        deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestIdle
+        Timber.tag(TAG).v("reset()")
         tekHistoryStorage.clear()
         submissionSettings.clear()
     }
 
-    @VisibleForTesting
-    fun updateTestResult(testResult: TestResult) {
-        testResultFlow.value = testResult
+    fun removeTestFromDevice(type: CoronaTest.Type) {
+        Timber.tag(TAG).v("removeTestFromDevice(type=%s)", type)
 
-        testResultDataCollector.updatePendingTestResultReceivedTime(testResult)
-
-        if (testResult == TestResult.POSITIVE) {
-            submissionSettings.isAllowedToSubmitKeys = true
-            analyticsKeySubmissionCollector.reportPositiveTestResultReceived()
-            deadmanNotificationScheduler.cancelScheduledWork()
-        }
-
-        // https://jira-ibs.wbs.net.sap/browse/EXPOSUREAPP-4484
-        // User removed a test before 1.11 where due to a bug the timestamp was not removed.
-        if (submissionSettings.initialTestResultReceivedAt != null &&
-            submissionSettings.registrationToken.value != null &&
-            submissionSettings.devicePairingSuccessfulAt == null
-        ) {
-            Timber.tag(TAG).w("User has stale initialTestResultReceivedAt, fixing EXPOSUREAPP-4484.")
-            submissionSettings.initialTestResultReceivedAt = null
-        }
+        scope.launch {
+            val test = coronaTestRepository.coronaTests.first().singleOrNull { it.type == type }
+                ?: throw IllegalStateException("No test of type $type available")
 
-        val initialTestResultReceivedTimestamp = submissionSettings.initialTestResultReceivedAt
-
-        if (initialTestResultReceivedTimestamp == null) {
-            val currentTime = timeStamper.nowUTC
-            submissionSettings.initialTestResultReceivedAt = currentTime
-            testResultReceivedDateFlowInternal.value = currentTime.toDate()
-            if (testResult == TestResult.PENDING) {
-                backgroundWorkScheduler.startWorkScheduler()
-            }
-        } else {
-            testResultReceivedDateFlowInternal.value = initialTestResultReceivedTimestamp.toDate()
+            coronaTestRepository.removeTest(identifier = test.identifier)
         }
     }
 
-    private suspend fun fetchTestResult(registrationToken: String): DeviceUIState = try {
-        val testResult = submissionService.asyncRequestTestResult(registrationToken)
-        updateTestResult(testResult)
-        deriveUiState(testResult)
-    } catch (err: NoRegistrationTokenSetException) {
-        DeviceUIState.UNPAIRED
-    }
-
-    fun removeTestFromDevice() {
-        submissionSettings.hasViewedTestResult.update { false }
-        submissionSettings.hasGivenConsent.update { false }
-        revokeConsentToSubmission()
-        submissionSettings.registrationToken.update { null }
-        submissionSettings.devicePairingSuccessfulAt = null
-        tracingSettings.initialPollingForTestResultTimeStamp = 0L
-        submissionSettings.initialTestResultReceivedAt = null
-        submissionSettings.isAllowedToSubmitKeys = false
-        tracingSettings.isTestResultAvailableNotificationSent = false
-        submissionSettings.isSubmissionSuccessful = false
-        testResultDataCollector.clear()
-    }
-
-    private fun deriveUiState(testResult: TestResult?): DeviceUIState = when (testResult) {
-        TestResult.NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE
-        TestResult.POSITIVE -> DeviceUIState.PAIRED_POSITIVE
-        TestResult.PENDING -> DeviceUIState.PAIRED_NO_RESULT
-        TestResult.REDEEMED -> DeviceUIState.PAIRED_REDEEMED
-        TestResult.INVALID -> DeviceUIState.PAIRED_ERROR
-        null -> DeviceUIState.UNPAIRED
-    }
-
     companion object {
         private const val TAG = "SubmissionRepository"
     }
 }
+
+// TODO Temporary, mapping should be replaced with **[CoronaTest]**
+fun CoronaTestResult?.toDeviceUIState(): DeviceUIState = when (this) {
+    CoronaTestResult.PCR_NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE
+    CoronaTestResult.PCR_POSITIVE -> DeviceUIState.PAIRED_POSITIVE
+    CoronaTestResult.PCR_OR_RAT_PENDING -> DeviceUIState.PAIRED_NO_RESULT
+    CoronaTestResult.PCR_REDEEMED -> DeviceUIState.PAIRED_REDEEMED
+    CoronaTestResult.PCR_INVALID -> DeviceUIState.PAIRED_ERROR
+    CoronaTestResult.RAT_PENDING -> DeviceUIState.PAIRED_NO_RESULT
+    CoronaTestResult.RAT_NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE
+    CoronaTestResult.RAT_POSITIVE -> DeviceUIState.PAIRED_POSITIVE
+    CoronaTestResult.RAT_REDEEMED -> DeviceUIState.PAIRED_REDEEMED
+    CoronaTestResult.RAT_INVALID -> DeviceUIState.PAIRED_ERROR
+    null -> DeviceUIState.UNPAIRED
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt
index 1b362ef1a3eb843e762cba992a45ea33a5557c7f..4a508248a1fb715acca97519726e447db723ad6f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt
@@ -11,6 +11,7 @@ import de.rki.coronawarnapp.util.preferences.createFlowPreference
 import de.rki.coronawarnapp.util.serialization.BaseGson
 import de.rki.coronawarnapp.util.serialization.adapter.RuntimeTypeAdapterFactory
 import org.joda.time.Instant
+import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -37,36 +38,38 @@ class SubmissionSettings @Inject constructor(
         context.getSharedPreferences("submission_localdata", Context.MODE_PRIVATE)
     }
 
-    val registrationToken = prefs.createFlowPreference<String?>(
-        key = TEST_REGISTRATION_TOKEN,
-        defaultValue = null
-    )
+    @Deprecated("Only available for migration, use CoronaTestRepository!")
+    var registrationTokenMigration: String?
+        get() = prefs.getString(TEST_REGISTRATION_TOKEN, null)
+        set(value) = prefs.edit { putString(TEST_REGISTRATION_TOKEN, value) }
 
-    var initialTestResultReceivedAt: Instant?
+    @Deprecated("Only available for migration, use CoronaTestRepository!")
+    var initialTestResultReceivedAtMigration: Instant?
         get() = prefs.getLong(TEST_RESULT_RECEIVED_AT, 0L).toInstantOrNull()
         set(value) = prefs.edit { putLong(TEST_RESULT_RECEIVED_AT, value?.millis ?: 0L) }
 
-    var devicePairingSuccessfulAt: Instant?
+    @Deprecated("Only available for migration, use CoronaTestRepository!")
+    var devicePairingSuccessfulAtMigration: Instant?
         get() = prefs.getLong(TEST_PARING_SUCCESSFUL_AT, 0L).toInstantOrNull()
         set(value) = prefs.edit { putLong(TEST_PARING_SUCCESSFUL_AT, value?.millis ?: 0L) }
 
-    var isSubmissionSuccessful: Boolean
+    @Deprecated("Only available for migration, use CoronaTestRepository!")
+    var isSubmissionSuccessfulMigration: Boolean
         get() = prefs.getBoolean(IS_KEY_SUBMISSION_SUCCESSFUL, false)
         set(value) = prefs.edit { putBoolean(IS_KEY_SUBMISSION_SUCCESSFUL, value) }
 
-    var isAllowedToSubmitKeys: Boolean
+    @Deprecated("Only available for migration, use CoronaTestRepository!")
+    var isAllowedToSubmitKeysMigration: Boolean
         get() = prefs.getBoolean(IS_KEY_SUBMISSION_ALLOWED, false)
         set(value) = prefs.edit { putBoolean(IS_KEY_SUBMISSION_ALLOWED, value) }
 
-    val hasGivenConsent = prefs.createFlowPreference(
-        key = SUBMISSION_CONSENT_GIVEN,
-        defaultValue = false
-    )
+    @Deprecated("Only available for migration, use CoronaTestRepository!")
+    val hasGivenConsentMigration: Boolean
+        get() = prefs.getBoolean(SUBMISSION_CONSENT_GIVEN, false)
 
-    val hasViewedTestResult = prefs.createFlowPreference(
-        key = SUBMISSION_RESULT_VIEWED,
-        defaultValue = false
-    )
+    @Deprecated("Only available for migration, use CoronaTestRepository!")
+    val hasViewedTestResultMigration: Boolean
+        get() = prefs.getBoolean(SUBMISSION_RESULT_VIEWED, false)
 
     val symptoms: FlowPreference<Symptoms?> = FlowPreference(
         prefs,
@@ -105,6 +108,34 @@ class SubmissionSettings @Inject constructor(
         }
     )
 
+    fun deleteLegacyTestData() {
+        Timber.d("deleteLegacyTestData()")
+// Sourced from the behavior of SubmissionRepository.removeTestFromDevice()
+//        fun removeTestFromDevice() {
+//            submissionSettings.hasViewedTestResult.update { false }
+//            submissionSettings.hasGivenConsent.update { false }
+//            revokeConsentToSubmission()
+//            submissionSettings.registrationToken.update { null }
+//            submissionSettings.devicePairingSuccessfulAt = null
+//            tracingSettings.initialPollingForTestResultTimeStamp = 0L
+//            submissionSettings.initialTestResultReceivedAt = null
+//            submissionSettings.isAllowedToSubmitKeys = false
+//            tracingSettings.isTestResultAvailableNotificationSent = false
+//            submissionSettings.isSubmissionSuccessful = false
+//            testResultDataCollector.clear()
+//        }
+
+        prefs.edit {
+            remove(SUBMISSION_RESULT_VIEWED)
+            remove(TEST_REGISTRATION_TOKEN)
+            remove(TEST_PARING_SUCCESSFUL_AT)
+            remove(TEST_RESULT_RECEIVED_AT)
+            remove(IS_KEY_SUBMISSION_ALLOWED)
+            remove(IS_KEY_SUBMISSION_SUCCESSFUL)
+            remove(SUBMISSION_CONSENT_GIVEN)
+        }
+    }
+
     fun clear() = prefs.clearAndNotify()
 
     companion object {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt
index db59a8c793d0cfcc7fa320e2f7ba56387628d8fd..49c162c39401af971dd5e0135121b419c30f55f2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt
@@ -3,13 +3,15 @@ package de.rki.coronawarnapp.submission.task
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.bugreporting.reportProblem
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
-import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
-import de.rki.coronawarnapp.presencetracing.checkins.CheckInsTransformer
-import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
 import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.playbook.Playbook
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInsTransformer
+import de.rki.coronawarnapp.presencetracing.checkins.common.completedCheckIns
 import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.submission.Symptoms
 import de.rki.coronawarnapp.submission.auto.AutoSubmission
@@ -44,6 +46,7 @@ class SubmissionTask @Inject constructor(
     private val checkInsTransformer: CheckInsTransformer,
     private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
     private val backgroundWorkScheduler: BackgroundWorkScheduler,
+    private val coronaTestRepository: CoronaTestRepository,
 ) : Task<DefaultProgress, SubmissionTask.Result> {
 
     private val internalProgress = ConflatedBroadcastChannel<DefaultProgress>()
@@ -66,8 +69,8 @@ class SubmissionTask @Inject constructor(
                     inBackground = true
                 }
             }
-
-            if (!submissionSettings.hasGivenConsent.value) {
+            val hasGivenConsent = coronaTestRepository.coronaTests.first().any { it.isAdvancedConsentGiven }
+            if (!hasGivenConsent) {
                 Timber.tag(TAG).w("Consent unavailable. Skipping execution, disabling auto submission.")
                 autoSubmission.updateMode(AutoSubmission.Mode.DISABLED)
                 return Result(state = Result.State.SKIPPED)
@@ -124,8 +127,12 @@ class SubmissionTask @Inject constructor(
     }
 
     private suspend fun performSubmission(): Result {
-        val registrationToken = submissionSettings.registrationToken.value ?: throw NoRegistrationTokenSetException()
-        Timber.tag(TAG).d("Using registrationToken=$registrationToken")
+        val availableTests = coronaTestRepository.coronaTests.first()
+        Timber.tag(TAG).v("Available tests: %s", availableTests)
+        val coronaTest = availableTests.firstOrNull { it.isSubmissionAllowed && !it.isSubmitted }
+            ?: throw IllegalStateException("No valid test available to authorize submission")
+
+        Timber.tag(TAG).d("Submission is authorized by coronaTest=%s", coronaTest)
 
         val keys: List<TemporaryExposureKey> = try {
             tekHistoryStorage.tekData.first().flatMap { it.keys }
@@ -143,13 +150,15 @@ class SubmissionTask @Inject constructor(
         )
         Timber.tag(TAG).d("Transformed keys with symptoms %s from %s to %s", symptoms, keys, transformedKeys)
 
-        val checkIns = checkInsRepository.checkInsWithinRetention.first()
+        val checkIns = checkInsRepository.completedCheckIns.first().filter {
+            it.hasSubmissionConsent && !it.isSubmitted
+        }
         val transformedCheckIns = checkInsTransformer.transform(checkIns, symptoms)
 
         Timber.tag(TAG).d("Transformed CheckIns from: %s to: %s", checkIns, transformedCheckIns)
 
         val submissionData = Playbook.SubmissionData(
-            registrationToken = registrationToken,
+            registrationToken = coronaTest.registrationToken,
             temporaryExposureKeys = transformedKeys,
             consentToFederation = true,
             visitedCountries = getSupportedCountries(),
@@ -171,7 +180,7 @@ class SubmissionTask @Inject constructor(
         Timber.tag(TAG).d("Marking %d submitted CheckIns.", checkIns.size)
         checkIns.forEach { checkIn ->
             try {
-                checkInsRepository.markCheckInAsSubmitted(checkIn.id)
+                checkInsRepository.updatePostSubmissionFlags(checkIn.id)
             } catch (e: Exception) {
                 e.reportProblem(TAG, "CheckIn $checkIn could not be marked as submitted")
             }
@@ -179,15 +188,15 @@ class SubmissionTask @Inject constructor(
 
         autoSubmission.updateMode(AutoSubmission.Mode.DISABLED)
 
-        setSubmissionFinished()
+        setSubmissionFinished(coronaTest)
 
         return Result(state = Result.State.SUCCESSFUL)
     }
 
-    private fun setSubmissionFinished() {
+    private suspend fun setSubmissionFinished(coronaTest: CoronaTest) {
         Timber.tag(TAG).d("setSubmissionFinished()")
         backgroundWorkScheduler.stopWorkScheduler()
-        submissionSettings.isSubmissionSuccessful = true
+        coronaTestRepository.markAsSubmitted(coronaTest.identifier)
         backgroundWorkScheduler.startWorkScheduler()
 
         shareTestResultNotificationService.cancelSharePositiveTestResultNotification()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestErrorCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestErrorCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7e652c7c9bf62d2da01e6ce02fb5efa50dcf4c13
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestErrorCard.kt
@@ -0,0 +1,38 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.databinding.HomeSubmissionPcrStatusCardErrorBinding
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestErrorCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class PcrTestErrorCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionPcrStatusCardErrorBinding>(R.layout.home_card_container_layout, parent) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionPcrStatusCardErrorBinding.inflate(
+            layoutInflater,
+            itemView.findViewById(R.id.card_container),
+            true
+        )
+    }
+
+    override val onBindData: HomeSubmissionPcrStatusCardErrorBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, payloads ->
+        val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        itemView.setOnClickListener { curItem.onDeleteTest(item) }
+        showTestAction.setOnClickListener { itemView.performClick() }
+    }
+
+    data class Item(
+        val state: SubmissionStatePCR.TestError,
+        val onDeleteTest: (Item) -> Unit
+    ) : TestResultItem.PCR, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestInvalidCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestInvalidCard.kt
similarity index 51%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestInvalidCard.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestInvalidCard.kt
index 94d997b6a837972679991a405a0ca7ca7c29f5a2..28518361317f21ebf2e8781ae64a21389459bcba 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestInvalidCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestInvalidCard.kt
@@ -2,20 +2,25 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardInvalidBinding
-import de.rki.coronawarnapp.submission.ui.homecards.TestInvalidCard.Item
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.databinding.HomeSubmissionPcrStatusCardInvalidBinding
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestInvalidCard.Item
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
-class TestInvalidCard(
+class PcrTestInvalidCard(
     parent: ViewGroup
-) : HomeAdapter.HomeItemVH<Item, HomeSubmissionStatusCardInvalidBinding>(R.layout.home_card_container_layout, parent) {
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionPcrStatusCardInvalidBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
 
     override val viewBinding = lazy {
-        HomeSubmissionStatusCardInvalidBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+        HomeSubmissionPcrStatusCardInvalidBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
     }
 
-    override val onBindData: HomeSubmissionStatusCardInvalidBinding.(
+    override val onBindData: HomeSubmissionPcrStatusCardInvalidBinding.(
         item: Item,
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
@@ -24,9 +29,9 @@ class TestInvalidCard(
     }
 
     data class Item(
-        val state: TestInvalid,
+        val state: SubmissionStatePCR.TestInvalid,
         val onDeleteTest: (Item) -> Unit
-    ) : TestResultItem, HasPayloadDiffer {
+    ) : TestResultItem.PCR, HasPayloadDiffer {
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestNegativeCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestNegativeCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b9ae26372a44fea01df80384b7debbe5777396ab
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestNegativeCard.kt
@@ -0,0 +1,36 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.databinding.HomeSubmissionPcrStatusCardNegativeBinding
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestNegativeCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class PcrTestNegativeCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionPcrStatusCardNegativeBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionPcrStatusCardNegativeBinding.inflate(
+            layoutInflater,
+            itemView.findViewById(R.id.card_container),
+            true
+        )
+    }
+
+    override val onBindData: HomeSubmissionPcrStatusCardNegativeBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { _, _ -> }
+
+    data class Item(
+        val state: SubmissionStatePCR.TestNegative
+    ) : TestResultItem.PCR, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestPendingCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPendingCard.kt
similarity index 53%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestPendingCard.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPendingCard.kt
index ae2591be8e6752ab862dcef6588c2ab3a0efdb5f..67ae2a7acb3040e2b93c1dbf92cb0b750e90f2e9 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestPendingCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPendingCard.kt
@@ -2,20 +2,25 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardPendingBinding
-import de.rki.coronawarnapp.submission.ui.homecards.TestPendingCard.Item
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.databinding.HomeSubmissionPcrStatusCardPendingBinding
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPendingCard.Item
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
-class TestPendingCard(
+class PcrTestPendingCard(
     parent: ViewGroup
-) : HomeAdapter.HomeItemVH<Item, HomeSubmissionStatusCardPendingBinding>(R.layout.home_card_container_layout, parent) {
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionPcrStatusCardPendingBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
 
     override val viewBinding = lazy {
-        HomeSubmissionStatusCardPendingBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+        HomeSubmissionPcrStatusCardPendingBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
     }
 
-    override val onBindData: HomeSubmissionStatusCardPendingBinding.(
+    override val onBindData: HomeSubmissionPcrStatusCardPendingBinding.(
         item: Item,
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
@@ -25,9 +30,9 @@ class TestPendingCard(
     }
 
     data class Item(
-        val state: TestPending,
+        val state: SubmissionStatePCR.TestPending,
         val onClickAction: (Item) -> Unit
-    ) : TestResultItem, HasPayloadDiffer {
+    ) : TestResultItem.PCR, HasPayloadDiffer {
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestPositiveCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPositiveCard.kt
similarity index 58%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestPositiveCard.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPositiveCard.kt
index c0bcd6cb232c6db6dcc8909c242504c7b9ecef77..327ea83f7ae04c1f08fd1b950e8950beca79e9d7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestPositiveCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPositiveCard.kt
@@ -2,24 +2,28 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardPositiveBinding
-import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard.Item
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.databinding.HomeSubmissionPcrStatusCardPositiveNotSharedBinding
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPositiveCard.Item
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
-class TestPositiveCard(
+class PcrTestPositiveCard(
     parent: ViewGroup
-) : HomeAdapter.HomeItemVH<Item, HomeSubmissionStatusCardPositiveBinding>(R.layout.home_card_container_layout, parent) {
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionPcrStatusCardPositiveNotSharedBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
 
     override val viewBinding = lazy {
-        HomeSubmissionStatusCardPositiveBinding.inflate(
+        HomeSubmissionPcrStatusCardPositiveNotSharedBinding.inflate(
             layoutInflater,
             itemView.findViewById(R.id.card_container),
             true
         )
     }
 
-    override val onBindData: HomeSubmissionStatusCardPositiveBinding.(
+    override val onBindData: HomeSubmissionPcrStatusCardPositiveNotSharedBinding.(
         item: Item,
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
@@ -29,9 +33,9 @@ class TestPositiveCard(
     }
 
     data class Item(
-        val state: TestPositive,
+        val state: SubmissionStatePCR.TestPositive,
         val onClickAction: (Item) -> Unit
-    ) : TestResultItem, HasPayloadDiffer {
+    ) : TestResultItem.PCR, HasPayloadDiffer {
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestNegativeCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestReadyCard.kt
similarity index 54%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestNegativeCard.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestReadyCard.kt
index 0762cf9c77bc48d90604e9f9ff680fffa34bcfce..4793dc7cebb11539c9fabcd56ee7dfe9a519cda9 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestNegativeCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestReadyCard.kt
@@ -2,24 +2,22 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardNegativeBinding
-import de.rki.coronawarnapp.submission.ui.homecards.TestNegativeCard.Item
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.databinding.HomeSubmissionPcrStatusCardReadyBinding
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestReadyCard.Item
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
-class TestNegativeCard(
+class PcrTestReadyCard(
     parent: ViewGroup
-) : HomeAdapter.HomeItemVH<Item, HomeSubmissionStatusCardNegativeBinding>(R.layout.home_card_container_layout, parent) {
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionPcrStatusCardReadyBinding>(R.layout.home_card_container_layout, parent) {
 
     override val viewBinding = lazy {
-        HomeSubmissionStatusCardNegativeBinding.inflate(
-            layoutInflater,
-            itemView.findViewById(R.id.card_container),
-            true
-        )
+        HomeSubmissionPcrStatusCardReadyBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
     }
 
-    override val onBindData: HomeSubmissionStatusCardNegativeBinding.(
+    override val onBindData: HomeSubmissionPcrStatusCardReadyBinding.(
         item: Item,
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
@@ -29,9 +27,9 @@ class TestNegativeCard(
     }
 
     data class Item(
-        val state: TestNegative,
+        val state: SubmissionStatePCR.TestResultReady,
         val onClickAction: (Item) -> Unit
-    ) : TestResultItem, HasPayloadDiffer {
+    ) : TestResultItem.PCR, HasPayloadDiffer {
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestSubmissionDoneCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestSubmissionDoneCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..34ecfef89a27bc2c69769620493dc956f9eb07d6
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestSubmissionDoneCard.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.databinding.HomeSubmissionPcrStatusCardPositiveSharedBinding
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestSubmissionDoneCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+
+class PcrTestSubmissionDoneCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionPcrStatusCardPositiveSharedBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionPcrStatusCardPositiveSharedBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+    }
+
+    override val onBindData: HomeSubmissionPcrStatusCardPositiveSharedBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { _, _ -> }
+
+    data class Item(
+        val state: SubmissionStatePCR.SubmissionDone
+    ) : TestResultItem.PCR
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestErrorCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestErrorCard.kt
similarity index 61%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestErrorCard.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestErrorCard.kt
index a662b5e8e6bd6ac43a3efa10f61483ae2be65a02..2f0020df56626536d035874016adb097e1ef0ba4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestErrorCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestErrorCard.kt
@@ -2,24 +2,27 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardErrorBinding
-import de.rki.coronawarnapp.submission.ui.homecards.TestErrorCard.Item
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardErrorBinding
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
-class TestErrorCard(
+class RapidTestErrorCard(
     parent: ViewGroup
-) : HomeAdapter.HomeItemVH<Item, HomeSubmissionStatusCardErrorBinding>(R.layout.home_card_container_layout, parent) {
+) : HomeAdapter.HomeItemVH<RapidTestErrorCard.Item, HomeSubmissionRapidStatusCardErrorBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
 
     override val viewBinding = lazy {
-        HomeSubmissionStatusCardErrorBinding.inflate(
+        HomeSubmissionRapidStatusCardErrorBinding.inflate(
             layoutInflater,
             itemView.findViewById(R.id.card_container),
             true
         )
     }
 
-    override val onBindData: HomeSubmissionStatusCardErrorBinding.(
+    override val onBindData: HomeSubmissionRapidStatusCardErrorBinding.(
         item: Item,
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
@@ -29,9 +32,9 @@ class TestErrorCard(
     }
 
     data class Item(
-        val state: TestError,
+        val state: SubmissionStateRAT.TestError,
         val onDeleteTest: (Item) -> Unit
-    ) : TestResultItem, HasPayloadDiffer {
+    ) : TestResultItem.RA, HasPayloadDiffer {
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestInvalidCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestInvalidCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..729b65d5234877f1fb9e4feefde3f9fe680e6e34
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestInvalidCard.kt
@@ -0,0 +1,37 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardInvalidBinding
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestInvalidCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class RapidTestInvalidCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionRapidStatusCardInvalidBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionRapidStatusCardInvalidBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+    }
+
+    override val onBindData: HomeSubmissionRapidStatusCardInvalidBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, payloads ->
+        val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        deleteTestAction.setOnClickListener { curItem.onDeleteTest(item) }
+    }
+
+    data class Item(
+        val state: SubmissionStateRAT.TestInvalid,
+        val onDeleteTest: (Item) -> Unit
+    ) : TestResultItem.RA, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestNegativeCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestNegativeCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f8ae43ad4edb42a2d070f5cb21f3e250c36c8490
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestNegativeCard.kt
@@ -0,0 +1,36 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardNegativeBinding
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestNegativeCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class RapidTestNegativeCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionRapidStatusCardNegativeBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionRapidStatusCardNegativeBinding.inflate(
+            layoutInflater,
+            itemView.findViewById(R.id.card_container),
+            true
+        )
+    }
+
+    override val onBindData: HomeSubmissionRapidStatusCardNegativeBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { _, _ -> }
+
+    data class Item(
+        val state: SubmissionStateRAT.TestNegative
+    ) : TestResultItem.RA, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestOutdatedCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestOutdatedCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a35e11d70cfcd86a2560bf5b5cb0b996179f3279
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestOutdatedCard.kt
@@ -0,0 +1,37 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardOutdatedBinding
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestOutdatedCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class RapidTestOutdatedCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionRapidStatusCardOutdatedBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionRapidStatusCardOutdatedBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+    }
+
+    override val onBindData: HomeSubmissionRapidStatusCardOutdatedBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, payloads ->
+        val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        dontShowAnymoreButton.setOnClickListener { curItem.hideTest(item) }
+    }
+
+    data class Item(
+        val state: SubmissionStateRAT.TestInvalid,
+        val hideTest: (Item) -> Unit
+    ) : TestResultItem.RA, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPendingCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPendingCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..85617c340f55642744bfca594b66fe7ea035c79c
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPendingCard.kt
@@ -0,0 +1,38 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardPendingBinding
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPendingCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class RapidTestPendingCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionRapidStatusCardPendingBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionRapidStatusCardPendingBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+    }
+
+    override val onBindData: HomeSubmissionRapidStatusCardPendingBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, payloads ->
+        val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        itemView.setOnClickListener { curItem.onClickAction(item) }
+        showTestAction.setOnClickListener { itemView.performClick() }
+    }
+
+    data class Item(
+        val state: SubmissionStateRAT.TestPending,
+        val onClickAction: (Item) -> Unit
+    ) : TestResultItem.RA, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPositiveCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPositiveCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e9c021499f4ee97962892bbbd762a18e92d7d77e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPositiveCard.kt
@@ -0,0 +1,41 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardPositiveNotSharedBinding
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPositiveCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class RapidTestPositiveCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionRapidStatusCardPositiveNotSharedBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionRapidStatusCardPositiveNotSharedBinding.inflate(
+            layoutInflater,
+            itemView.findViewById(R.id.card_container),
+            true
+        )
+    }
+
+    override val onBindData: HomeSubmissionRapidStatusCardPositiveNotSharedBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, payloads ->
+        val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        itemView.setOnClickListener { curItem.onClickAction(item) }
+        submissionStatusCardPositiveButton.setOnClickListener { itemView.performClick() }
+    }
+
+    data class Item(
+        val state: SubmissionStateRAT.TestPositive,
+        val onClickAction: (Item) -> Unit
+    ) : TestResultItem.RA, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestReadyCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestReadyCard.kt
similarity index 52%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestReadyCard.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestReadyCard.kt
index 5dddc8c04dd3a617501d0f2e273d5fb1d9110edc..7509225d48d65e7cfdc5aad2a0bfa7ba7e344b34 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestReadyCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestReadyCard.kt
@@ -2,20 +2,25 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardReadyBinding
-import de.rki.coronawarnapp.submission.ui.homecards.TestReadyCard.Item
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardReadyBinding
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestReadyCard.Item
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
-class TestReadyCard(
+class RapidTestReadyCard(
     parent: ViewGroup
-) : HomeAdapter.HomeItemVH<Item, HomeSubmissionStatusCardReadyBinding>(R.layout.home_card_container_layout, parent) {
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionRapidStatusCardReadyBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
 
     override val viewBinding = lazy {
-        HomeSubmissionStatusCardReadyBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+        HomeSubmissionRapidStatusCardReadyBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
     }
 
-    override val onBindData: HomeSubmissionStatusCardReadyBinding.(
+    override val onBindData: HomeSubmissionRapidStatusCardReadyBinding.(
         item: Item,
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
@@ -25,9 +30,9 @@ class TestReadyCard(
     }
 
     data class Item(
-        val state: TestResultReady,
+        val state: SubmissionStateRAT.TestResultReady,
         val onClickAction: (Item) -> Unit
-    ) : TestResultItem, HasPayloadDiffer {
+    ) : TestResultItem.RA, HasPayloadDiffer {
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestSubmissionDoneCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestSubmissionDoneCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a96d2a531c24f998838019bf19bfa1cbfe1f5f09
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestSubmissionDoneCard.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.submission.ui.homecards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.databinding.HomeSubmissionRapidStatusCardPositiveSharedBinding
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestSubmissionDoneCard.Item
+import de.rki.coronawarnapp.ui.main.home.HomeAdapter
+
+class RapidTestSubmissionDoneCard(
+    parent: ViewGroup
+) : HomeAdapter.HomeItemVH<Item, HomeSubmissionRapidStatusCardPositiveSharedBinding>(
+    R.layout.home_card_container_layout,
+    parent
+) {
+
+    override val viewBinding = lazy {
+        HomeSubmissionRapidStatusCardPositiveSharedBinding
+            .inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+    }
+
+    override val onBindData: HomeSubmissionRapidStatusCardPositiveSharedBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { _, _ -> }
+
+    data class Item(
+        val state: SubmissionStateRAT.SubmissionDone
+    ) : TestResultItem.RA
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionState.kt
deleted file mode 100644
index 4daf23636f12def024f22f9b4d6721213836ccbf..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionState.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package de.rki.coronawarnapp.submission.ui.homecards
-
-import android.content.Context
-import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUIFormat
-import java.util.Date
-
-sealed class SubmissionState
-
-object NoTest : SubmissionState()
-object FetchingResult : SubmissionState()
-object TestResultReady : SubmissionState()
-object TestPositive : SubmissionState()
-object TestNegative : SubmissionState()
-object TestError : SubmissionState()
-object TestInvalid : SubmissionState()
-object TestPending : SubmissionState()
-data class SubmissionDone(val testRegisteredOn: Date) : SubmissionState() {
-
-    fun formatTestRegistrationText(context: Context): String =
-        context.getString(R.string.reenable_risk_card_test_registration_string)
-            .format(testRegisteredOn.toUIFormat(context))
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt
deleted file mode 100644
index 54f584682f70f72917c1c9805010ea5b3774e093..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-package de.rki.coronawarnapp.submission.ui.homecards
-
-import dagger.Reusable
-import de.rki.coronawarnapp.exception.http.CwaServerError
-import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.submission.SubmissionSettings
-import de.rki.coronawarnapp.util.CWADebug
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
-import timber.log.Timber
-import javax.inject.Inject
-
-@Reusable
-class SubmissionStateProvider @Inject constructor(
-    submissionRepository: SubmissionRepository,
-    submissionSettings: SubmissionSettings
-) {
-
-    val state: Flow<SubmissionState> = combine(
-        submissionRepository.deviceUIStateFlow,
-        submissionRepository.hasViewedTestResult,
-        submissionRepository.testResultReceivedDateFlow,
-        submissionSettings.registrationToken.flow
-    ) { uiState, hasTestBeenSeen, testRegistrationDate, registrationToken ->
-
-        val eval = Evaluation(
-            deviceUiState = uiState,
-            isDeviceRegistered = registrationToken != null,
-            hasTestResultBeenSeen = hasTestBeenSeen
-        )
-        Timber.d("eval: %s", eval)
-        when {
-            eval.isUnregistered() -> NoTest
-            eval.isFetching() -> FetchingResult
-            eval.isTestResultReady() -> TestResultReady
-            eval.isResultPositive() -> TestPositive
-            eval.isInvalid() -> TestInvalid
-            eval.isError() -> TestError
-            eval.isResultNegative() -> TestNegative
-            eval.isSubmissionDone() -> SubmissionDone(testRegisteredOn = testRegistrationDate)
-            eval.isPending() -> TestPending
-            else -> if (CWADebug.isDeviceForTestersBuild) throw IllegalStateException(eval.toString()) else TestPending
-        }
-    }
-        .onStart { Timber.v("SubmissionStateProvider FLOW start") }
-        .onEach { Timber.w("SubmissionStateProvider FLOW emission: %s", it) }
-        .onCompletion { Timber.v("SubmissionStateProvider FLOW completed.") }
-
-    // TODO Refactor this to be easier to understand, probably remove the "withSuccess" logic.
-    private data class Evaluation(
-        val deviceUiState: NetworkRequestWrapper<DeviceUIState, Throwable>,
-        val isDeviceRegistered: Boolean,
-        val hasTestResultBeenSeen: Boolean
-    ) {
-
-        fun isUnregistered(): Boolean = !isDeviceRegistered
-
-        fun isTestResultReady(): Boolean = deviceUiState.withSuccess(false) {
-            when (it) {
-                DeviceUIState.PAIRED_POSITIVE,
-                DeviceUIState.PAIRED_POSITIVE_TELETAN -> !hasTestResultBeenSeen
-                else -> false
-            }
-        }
-
-        fun isFetching(): Boolean =
-            isDeviceRegistered && when (deviceUiState) {
-                is NetworkRequestWrapper.RequestFailed -> false
-                is NetworkRequestWrapper.RequestStarted -> true
-                is NetworkRequestWrapper.RequestIdle -> true
-                else -> false
-            }
-
-        fun isResultPositive(): Boolean =
-            deviceUiState.withSuccess(false) {
-                when (it) {
-                    DeviceUIState.PAIRED_POSITIVE, DeviceUIState.PAIRED_POSITIVE_TELETAN -> hasTestResultBeenSeen
-                    else -> false
-                }
-            }
-
-        fun isResultNegative(): Boolean =
-            deviceUiState.withSuccess(false) {
-                when (it) {
-                    DeviceUIState.PAIRED_NEGATIVE -> true
-                    else -> false
-                }
-            }
-
-        fun isSubmissionDone(): Boolean =
-            when (deviceUiState) {
-                is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == DeviceUIState.SUBMITTED_FINAL
-                else -> false
-            }
-
-        fun isInvalid(): Boolean =
-            isDeviceRegistered && when (deviceUiState) {
-                is NetworkRequestWrapper.RequestFailed -> deviceUiState.error !is CwaServerError
-                is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == DeviceUIState.PAIRED_REDEEMED
-                else -> false
-            }
-
-        fun isError(): Boolean =
-            deviceUiState.withSuccess(false) {
-                when (it) {
-                    DeviceUIState.PAIRED_ERROR -> true
-                    else -> false
-                }
-            }
-
-        fun isPending(): Boolean =
-            when (deviceUiState) {
-                is NetworkRequestWrapper.RequestFailed -> true
-                is NetworkRequestWrapper.RequestSuccessful -> {
-                    deviceUiState.data == DeviceUIState.PAIRED_ERROR ||
-                        deviceUiState.data == DeviceUIState.PAIRED_NO_RESULT
-                }
-                else -> false
-            }
-
-        override fun toString() =
-            "Evaluation(deviceUiState=$deviceUiState, " +
-                "isDeviceRegistered=$isDeviceRegistered, " +
-                "hasTestResultBeenSeen=$hasTestResultBeenSeen)"
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestFetchingCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestFetchingCard.kt
index 42f3b7c828d4fbd41234d272e10f4f4b8c6fc61c..e367416f8bc80ae7c44e108d57908b51d8b4ac8c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestFetchingCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestFetchingCard.kt
@@ -2,6 +2,9 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.CommonSubmissionStates
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
 import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardFetchingBinding
 import de.rki.coronawarnapp.submission.ui.homecards.TestFetchingCard.Item
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
@@ -24,6 +27,13 @@ class TestFetchingCard(
     ) -> Unit = { _, _ -> }
 
     data class Item(
-        val state: FetchingResult
-    ) : TestResultItem
+        val state: CommonSubmissionStates.TestFetching
+    ) : TestResultItem {
+        override val stableId: Long
+            get() = when (state) {
+                is SubmissionStatePCR -> TestResultItem.PCR.LIST_ID
+                is SubmissionStateRAT -> TestResultItem.RA.LIST_ID
+                else -> throw IllegalArgumentException("Invalid card argument $state")
+            }
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt
index 14136421fc8ac8d26e6e25a2cc2050bcc587f311..2271042d492467547db54f49f2825fb3e875f328 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt
@@ -5,4 +5,22 @@ import de.rki.coronawarnapp.ui.main.home.items.HomeItem
 interface TestResultItem : HomeItem {
     override val stableId: Long
         get() = TestResultItem::class.java.name.hashCode().toLong()
+
+    interface RA : TestResultItem {
+        override val stableId: Long
+            get() = LIST_ID
+
+        companion object {
+            val LIST_ID = RA::class.java.name.hashCode().toLong()
+        }
+    }
+
+    interface PCR : TestResultItem {
+        override val stableId: Long
+            get() = LIST_ID
+
+        companion object {
+            val LIST_ID = PCR::class.java.name.hashCode().toLong()
+        }
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestSubmissionDoneCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestSubmissionDoneCard.kt
deleted file mode 100644
index 4203d6fd8fe1afbcf98594786bae2d13268041ec..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestSubmissionDoneCard.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package de.rki.coronawarnapp.submission.ui.homecards
-
-import android.view.ViewGroup
-import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardDoneBinding
-import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard.Item
-import de.rki.coronawarnapp.ui.main.home.HomeAdapter
-
-class TestSubmissionDoneCard(
-    parent: ViewGroup
-) : HomeAdapter.HomeItemVH<Item, HomeSubmissionStatusCardDoneBinding>(R.layout.home_card_container_layout, parent) {
-
-    override val viewBinding = lazy {
-        HomeSubmissionStatusCardDoneBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
-    }
-
-    override val onBindData: HomeSubmissionStatusCardDoneBinding.(
-        item: Item,
-        payloads: List<Any>
-    ) -> Unit = { _, _ -> }
-
-    data class Item(
-        val state: SubmissionDone
-    ) : TestResultItem
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestUnregisteredCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestUnregisteredCard.kt
index bbecf61dc124982d730d30d1edb3fff31dab8416..e6d2e9749377654d991d5d27aca45b7c87acf1b4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestUnregisteredCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestUnregisteredCard.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.submission.ui.homecards
 
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.CommonSubmissionStates
 import de.rki.coronawarnapp.databinding.HomeSubmissionStatusCardUnregisteredBinding
 import de.rki.coronawarnapp.submission.ui.homecards.TestUnregisteredCard.Item
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
@@ -33,7 +34,7 @@ class TestUnregisteredCard(
     }
 
     data class Item(
-        val state: NoTest,
+        val state: CommonSubmissionStates.TestUnregistered,
         val onClickAction: (Item) -> Unit
     ) : TestResultItem, HasPayloadDiffer {
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt
index e1037bf8281f2ca5309167b5eae7629a0b9f752e..34ca2c6cf8bb9e469804f2fd13cb75209129f210 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt
@@ -38,5 +38,9 @@ class InformationContactFragment : Fragment(R.layout.fragment_information_contac
             val number = getString(R.string.information_contact_phone_call_number)
             ExternalActionHelper.call(this, number)
         }
+        binding.informationContactNavigationRowInternationalPhone.navigationRow.setOnClickListener {
+            val number = getString(R.string.information_contact_button_international_phone)
+            ExternalActionHelper.call(this, number)
+        }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
index 177f1ce6f84d42e7f51bd02b98b229ec13cabe05..7d6fd992124a05732356a8b1bd4d5e4b9f977678 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
@@ -21,14 +21,13 @@ import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryWorkScheduler
 import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragmentDirections
 import de.rki.coronawarnapp.databinding.ActivityMainBinding
 import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler
-import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.ui.base.startActivitySafely
 import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.CheckInsFragment
 import de.rki.coronawarnapp.ui.setupWithNavController2
 import de.rki.coronawarnapp.util.AppShortcuts
 import de.rki.coronawarnapp.util.CWADebug
 import de.rki.coronawarnapp.util.ConnectivityHelper
+import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.device.PowerManagement
 import de.rki.coronawarnapp.util.di.AppInjector
@@ -76,10 +75,8 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
     private val navController by lazy { supportFragmentManager.findNavController(R.id.nav_host_fragment) }
 
     @Inject lateinit var powerManagement: PowerManagement
-    @Inject lateinit var deadmanScheduler: DeadmanNotificationScheduler
     @Inject lateinit var contactDiaryWorkScheduler: ContactDiaryWorkScheduler
     @Inject lateinit var dataDonationAnalyticsScheduler: DataDonationAnalyticsScheduler
-    @Inject lateinit var submissionSettings: SubmissionSettings
     @Inject lateinit var backgroundWorkScheduler: BackgroundWorkScheduler
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -118,7 +115,7 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
                 if (count > 0) {
                     val badge = getOrCreateBadge(targetId)
                     badge.number = count
-                    badge.badgeTextColor = getColor(android.R.color.white)
+                    badge.badgeTextColor = getColorCompat(android.R.color.white)
                 } else {
                     removeBadge(targetId)
                 }
@@ -195,9 +192,7 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
         vm.doBackgroundNoiseCheck()
         contactDiaryWorkScheduler.schedulePeriodic()
         dataDonationAnalyticsScheduler.schedulePeriodic()
-        if (!submissionSettings.isAllowedToSubmitKeys) {
-            deadmanScheduler.schedulePeriodic()
-        }
+        vm.checkDeadMan()
     }
 
     private fun showEnergyOptimizedEnabledForBackground() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt
index 08d5f165d9a5e5dd6ec550b1b49f10f84b04818f..1949db08144a868cd025f9a46b64e90fbab4a9d4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt
@@ -6,10 +6,12 @@ import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.environment.EnvironmentSetup
+import de.rki.coronawarnapp.playbook.BackgroundNoise
 import de.rki.coronawarnapp.presencetracing.TraceLocationSettings
 import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
-import de.rki.coronawarnapp.playbook.BackgroundNoise
 import de.rki.coronawarnapp.storage.OnboardingSettings
 import de.rki.coronawarnapp.util.CWADebug
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
@@ -19,7 +21,9 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
+import timber.log.Timber
 
+@Suppress("LongParameterList")
 class MainActivityViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     private val environmentSetup: EnvironmentSetup,
@@ -29,6 +33,8 @@ class MainActivityViewModel @AssistedInject constructor(
     private val onboardingSettings: OnboardingSettings,
     private val traceLocationSettings: TraceLocationSettings,
     private val checkInRepository: CheckInRepository,
+    private val deadmanScheduler: DeadmanNotificationScheduler,
+    private val coronaTestRepository: CoronaTestRepository,
 ) : CWAViewModel(
     dispatcherProvider = dispatcherProvider
 ) {
@@ -91,6 +97,16 @@ class MainActivityViewModel @AssistedInject constructor(
         }
     }
 
+    fun checkDeadMan() {
+        launch {
+            val isAllowedToSubmitKeys = coronaTestRepository.coronaTests.first().any { it.isSubmissionAllowed }
+            if (!isAllowedToSubmitKeys) {
+                Timber.v("We are not allowed to submit keys, scheduling deadman.")
+                deadmanScheduler.schedulePeriodic()
+            }
+        }
+    }
+
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<MainActivityViewModel>
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt
index 8b2e07957abd3b10bfc31393d0522f646b329ac5..be2720d7a0caf7f755e0370eec37e59e439ab549 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt
@@ -4,14 +4,22 @@ import android.view.ViewGroup
 import androidx.annotation.LayoutRes
 import androidx.viewbinding.ViewBinding
 import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestInvalidCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestNegativeCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPendingCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestReadyCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestSubmissionDoneCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestInvalidCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestNegativeCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestOutdatedCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPendingCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestReadyCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestSubmissionDoneCard
 import de.rki.coronawarnapp.submission.ui.homecards.TestFetchingCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestInvalidCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestNegativeCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestPendingCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestReadyCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard
 import de.rki.coronawarnapp.submission.ui.homecards.TestUnregisteredCard
 import de.rki.coronawarnapp.tracing.ui.homecards.IncreasedRiskCard
 import de.rki.coronawarnapp.tracing.ui.homecards.LowRiskCard
@@ -50,14 +58,22 @@ class HomeAdapter :
                 TypedVHCreatorMod({ data[it] is TracingFailedCard.Item }) { TracingFailedCard(it) },
                 TypedVHCreatorMod({ data[it] is TracingDisabledCard.Item }) { TracingDisabledCard(it) },
                 TypedVHCreatorMod({ data[it] is TracingProgressCard.Item }) { TracingProgressCard(it) },
-                TypedVHCreatorMod({ data[it] is TestSubmissionDoneCard.Item }) { TestSubmissionDoneCard(it) },
-                TypedVHCreatorMod({ data[it] is TestInvalidCard.Item }) { TestInvalidCard(it) },
-                TypedVHCreatorMod({ data[it] is TestErrorCard.Item }) { TestErrorCard(it) },
                 TypedVHCreatorMod({ data[it] is TestFetchingCard.Item }) { TestFetchingCard(it) },
-                TypedVHCreatorMod({ data[it] is TestPositiveCard.Item }) { TestPositiveCard(it) },
-                TypedVHCreatorMod({ data[it] is TestNegativeCard.Item }) { TestNegativeCard(it) },
-                TypedVHCreatorMod({ data[it] is TestReadyCard.Item }) { TestReadyCard(it) },
-                TypedVHCreatorMod({ data[it] is TestPendingCard.Item }) { TestPendingCard(it) },
+                TypedVHCreatorMod({ data[it] is PcrTestSubmissionDoneCard.Item }) { PcrTestSubmissionDoneCard(it) },
+                TypedVHCreatorMod({ data[it] is PcrTestInvalidCard.Item }) { PcrTestInvalidCard(it) },
+                TypedVHCreatorMod({ data[it] is PcrTestErrorCard.Item }) { PcrTestErrorCard(it) },
+                TypedVHCreatorMod({ data[it] is PcrTestPositiveCard.Item }) { PcrTestPositiveCard(it) },
+                TypedVHCreatorMod({ data[it] is PcrTestNegativeCard.Item }) { PcrTestNegativeCard(it) },
+                TypedVHCreatorMod({ data[it] is PcrTestReadyCard.Item }) { PcrTestReadyCard(it) },
+                TypedVHCreatorMod({ data[it] is PcrTestPendingCard.Item }) { PcrTestPendingCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestSubmissionDoneCard.Item }) { RapidTestSubmissionDoneCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestInvalidCard.Item }) { RapidTestInvalidCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestErrorCard.Item }) { RapidTestErrorCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestPositiveCard.Item }) { RapidTestPositiveCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestNegativeCard.Item }) { RapidTestNegativeCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestReadyCard.Item }) { RapidTestReadyCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestPendingCard.Item }) { RapidTestPendingCard(it) },
+                TypedVHCreatorMod({ data[it] is RapidTestOutdatedCard.Item }) { RapidTestOutdatedCard(it) },
                 TypedVHCreatorMod({ data[it] is TestUnregisteredCard.Item }) { TestUnregisteredCard(it) },
                 TypedVHCreatorMod({ data[it] is StatisticsHomeCard.Item }) { StatisticsHomeCard(it) },
                 SavedStateMod<HomeItemVH<HomeItem, ViewBinding>>() // For statistics card scroll position
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt
index f171d8a2fc0703a8eb0662ad300b8af3fb48f01e..6a761f77735f46cb759bc32d52cf1881e27c17da 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt
@@ -6,6 +6,17 @@ import androidx.navigation.NavDirections
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.latestPCRT
+import de.rki.coronawarnapp.coronatest.latestRAT
+import de.rki.coronawarnapp.coronatest.type.CommonSubmissionStates
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR
+import de.rki.coronawarnapp.coronatest.type.pcr.toSubmissionState
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.toSubmissionState
 import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.main.CWASettings
 import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
@@ -14,24 +25,22 @@ import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard
 import de.rki.coronawarnapp.storage.TracingRepository
 import de.rki.coronawarnapp.storage.TracingSettings
 import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.submission.ui.homecards.FetchingResult
-import de.rki.coronawarnapp.submission.ui.homecards.NoTest
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider
-import de.rki.coronawarnapp.submission.ui.homecards.TestError
-import de.rki.coronawarnapp.submission.ui.homecards.TestErrorCard
+import de.rki.coronawarnapp.submission.toDeviceUIState
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestInvalidCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestNegativeCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPendingCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestReadyCard
+import de.rki.coronawarnapp.submission.ui.homecards.PcrTestSubmissionDoneCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestErrorCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestInvalidCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestNegativeCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPendingCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestPositiveCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestReadyCard
+import de.rki.coronawarnapp.submission.ui.homecards.RapidTestSubmissionDoneCard
 import de.rki.coronawarnapp.submission.ui.homecards.TestFetchingCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestInvalid
-import de.rki.coronawarnapp.submission.ui.homecards.TestInvalidCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestNegative
-import de.rki.coronawarnapp.submission.ui.homecards.TestNegativeCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestPending
-import de.rki.coronawarnapp.submission.ui.homecards.TestPendingCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestPositive
-import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestReadyCard
-import de.rki.coronawarnapp.submission.ui.homecards.TestResultReady
-import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard
 import de.rki.coronawarnapp.submission.ui.homecards.TestUnregisteredCard
 import de.rki.coronawarnapp.tracing.GeneralTracingStatus
 import de.rki.coronawarnapp.tracing.states.IncreasedRisk
@@ -47,15 +56,14 @@ import de.rki.coronawarnapp.tracing.ui.homecards.TracingFailedCard
 import de.rki.coronawarnapp.tracing.ui.homecards.TracingProgressCard
 import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState
 import de.rki.coronawarnapp.tracing.ui.statusbar.toHeaderState
-import de.rki.coronawarnapp.ui.presencetracing.organizer.TraceLocationOrganizerSettings
 import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowErrorResetDialog
 import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowTracingExplanation
 import de.rki.coronawarnapp.ui.main.home.items.CreateTraceLocationCard
 import de.rki.coronawarnapp.ui.main.home.items.FAQCard
 import de.rki.coronawarnapp.ui.main.home.items.HomeItem
 import de.rki.coronawarnapp.ui.main.home.items.ReenableRiskCard
+import de.rki.coronawarnapp.ui.presencetracing.organizer.TraceLocationOrganizerSettings
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool
 import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper
@@ -74,7 +82,7 @@ class HomeFragmentViewModel @AssistedInject constructor(
     private val errorResetTool: EncryptionErrorResetTool,
     tracingStatus: GeneralTracingStatus,
     tracingStateProviderFactory: TracingStateProvider.Factory,
-    submissionStateProvider: SubmissionStateProvider,
+    private val coronaTestRepository: CoronaTestRepository,
     private val tracingRepository: TracingRepository,
     private val shareTestResultNotificationService: ShareTestResultNotificationService,
     private val submissionRepository: SubmissionRepository,
@@ -124,6 +132,7 @@ class HomeFragmentViewModel @AssistedInject constructor(
             }.launchInViewModel()
         }
     }
+
     private val tracingCardItems = tracingStateProvider.state.map { tracingState ->
         when (tracingState) {
             is TracingInProgress -> TracingProgressCard.Item(
@@ -165,68 +174,113 @@ class HomeFragmentViewModel @AssistedInject constructor(
         }
     }.distinctUntilChanged()
 
-    private val submissionCardItems = submissionStateProvider.state.map { state ->
-        when (state) {
-            is NoTest -> TestUnregisteredCard.Item(state) {
-                routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher())
-            }
-            is FetchingResult -> TestFetchingCard.Item(state)
-            is TestResultReady -> TestReadyCard.Item(state) {
-                routeToScreen.postValue(
-                    HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment()
-                )
-            }
-            is TestPositive -> TestPositiveCard.Item(state) {
-                routeToScreen.postValue(
-                    HomeFragmentDirections
-                        .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment()
-                )
-            }
-            is TestNegative -> TestNegativeCard.Item(state) {
-                routeToScreen.postValue(
-                    HomeFragmentDirections
-                        .actionMainFragmentToSubmissionTestResultNegativeFragment()
-                )
-            }
-            is TestInvalid -> TestInvalidCard.Item(state) {
-                popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog)
-            }
-            is TestError -> TestErrorCard.Item(state) {
-                routeToScreen.postValue(
-                    HomeFragmentDirections
-                        .actionMainFragmentToSubmissionTestResultPendingFragment()
-                )
-            }
-            is TestPending -> TestPendingCard.Item(state) {
-                routeToScreen.postValue(
-                    HomeFragmentDirections
-                        .actionMainFragmentToSubmissionTestResultPendingFragment()
-                )
-            }
-            is SubmissionDone -> TestSubmissionDoneCard.Item(state)
+    private fun PCRCoronaTest?.toTestCardItem() = when (val state = this.toSubmissionState()) {
+        is SubmissionStatePCR.NoTest -> TestUnregisteredCard.Item(state) {
+            routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher())
         }
-    }.distinctUntilChanged()
+        is SubmissionStatePCR.FetchingResult -> TestFetchingCard.Item(state)
+        is SubmissionStatePCR.TestResultReady -> PcrTestReadyCard.Item(state) {
+            routeToScreen.postValue(
+                HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment()
+            )
+        }
+        is SubmissionStatePCR.TestPositive -> PcrTestPositiveCard.Item(state) {
+            routeToScreen.postValue(
+                HomeFragmentDirections
+                    .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment()
+            )
+        }
+        is SubmissionStatePCR.TestNegative -> PcrTestNegativeCard.Item(state)
+        is SubmissionStatePCR.TestInvalid -> PcrTestInvalidCard.Item(state) {
+            popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog)
+        }
+        is SubmissionStatePCR.TestError -> PcrTestErrorCard.Item(state) {
+            routeToScreen.postValue(
+                HomeFragmentDirections
+                    .actionMainFragmentToSubmissionTestResultPendingFragment()
+            )
+        }
+        is SubmissionStatePCR.TestPending -> PcrTestPendingCard.Item(state) {
+            routeToScreen.postValue(
+                HomeFragmentDirections
+                    .actionMainFragmentToSubmissionTestResultPendingFragment()
+            )
+        }
+        is SubmissionStatePCR.SubmissionDone -> PcrTestSubmissionDoneCard.Item(state)
+    }
+
+    private fun RACoronaTest?.toTestCardItem() = when (val state = this.toSubmissionState()) {
+        is SubmissionStateRAT.NoTest -> TestUnregisteredCard.Item(state) {
+            // TODO
+//            routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher())
+        }
+        is SubmissionStateRAT.FetchingResult -> TestFetchingCard.Item(state)
+        is SubmissionStateRAT.TestResultReady -> RapidTestReadyCard.Item(state) {
+            // TODO
+//            routeToScreen.postValue(
+//                HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment()
+//            )
+        }
+        is SubmissionStateRAT.TestPositive -> RapidTestPositiveCard.Item(state) {
+            // TODO
+//            routeToScreen.postValue(
+//                HomeFragmentDirections
+//                    .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment()
+//            )
+        }
+        is SubmissionStateRAT.TestNegative -> RapidTestNegativeCard.Item(state)
+        is SubmissionStateRAT.TestInvalid -> RapidTestInvalidCard.Item(state) {
+            // TODO
+//            popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog)
+        }
+        is SubmissionStateRAT.TestError -> RapidTestErrorCard.Item(state) {
+            // TODO
+//            routeToScreen.postValue(
+//                HomeFragmentDirections
+//                    .actionMainFragmentToSubmissionTestResultPendingFragment()
+//            )
+        }
+        is SubmissionStateRAT.TestPending -> RapidTestPendingCard.Item(state) {
+            // TODO
+//            routeToScreen.postValue(
+//                HomeFragmentDirections
+//                    .actionMainFragmentToSubmissionTestResultPendingFragment()
+//            )
+        }
+        is SubmissionStateRAT.SubmissionDone -> RapidTestSubmissionDoneCard.Item(state)
+    }
 
     val homeItems: LiveData<List<HomeItem>> = combine(
         tracingCardItems,
-        submissionCardItems,
-        submissionStateProvider.state.distinctUntilChanged(),
+        coronaTestRepository.latestPCRT,
+        coronaTestRepository.latestRAT,
         statisticsProvider.current.distinctUntilChanged()
-    ) { tracingItem, submissionItem, submissionState, statsData ->
+    ) { tracingItem, testPCR, testRAT, statsData ->
+        val statePCR = testPCR.toSubmissionState()
+        val stateRAT = testRAT.toSubmissionState()
+        val bothTestStates = setOf(statePCR, stateRAT)
         mutableListOf<HomeItem>().apply {
-            when (submissionState) {
-                TestPositive, is SubmissionDone -> {
+            when {
+                statePCR is SubmissionStatePCR.TestPositive || statePCR is SubmissionStatePCR.SubmissionDone -> {
+                    // Don't show risk card
+                }
+                stateRAT is SubmissionStateRAT.TestPositive || stateRAT is SubmissionStateRAT.SubmissionDone -> {
                     // Don't show risk card
                 }
                 else -> add(tracingItem)
             }
 
-            add(submissionItem)
+            add(testPCR.toTestCardItem())
+
+            if (stateRAT != SubmissionStateRAT.NoTest || statePCR != SubmissionStatePCR.NoTest) {
+                add(testRAT.toTestCardItem())
+            }
 
-            if (submissionState is SubmissionDone) {
+            bothTestStates.firstOrNull { it is CommonSubmissionStates.SubmissionDone }?.let {
+                it as CommonSubmissionStates.SubmissionDone
                 add(
                     ReenableRiskCard.Item(
-                        state = submissionState,
+                        data = it,
                         onClickAction = { popupEvents.postValue(HomeFragmentEvents.ShowReactivateRiskCheckDialog) }
                     )
                 )
@@ -253,13 +307,14 @@ class HomeFragmentViewModel @AssistedInject constructor(
 
     private var isLoweredRiskLevelDialogBeingShown = false
     fun observeTestResultToSchedulePositiveTestResultReminder() = launch {
-        submissionRepository.deviceUIStateFlow
-            .first { state ->
-                state.withSuccess(false) {
-                    when (it) {
-                        DeviceUIState.PAIRED_POSITIVE, DeviceUIState.PAIRED_POSITIVE_TELETAN -> true
-                        else -> false
-                    }
+        submissionRepository.pcrTest
+            .first { test ->
+                when {
+                    test == null -> false
+                    test.lastError != null -> false
+                    test.testResult.toDeviceUIState() == DeviceUIState.PAIRED_POSITIVE -> true
+                    test.testResult.toDeviceUIState() == DeviceUIState.PAIRED_POSITIVE_TELETAN -> true
+                    else -> false
                 }
             }
             .also { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() }
@@ -292,7 +347,7 @@ class HomeFragmentViewModel @AssistedInject constructor(
 
     fun refreshRequiredData() {
         launch {
-            submissionRepository.refreshDeviceUIState()
+            submissionRepository.refreshTest(type = CoronaTest.Type.PCR)
             tracingRepository.refreshRiskLevel()
         }
     }
@@ -308,8 +363,8 @@ class HomeFragmentViewModel @AssistedInject constructor(
     }
 
     fun deregisterWarningAccepted() {
-        submissionRepository.removeTestFromDevice()
-        submissionRepository.refreshDeviceUIState()
+        submissionRepository.removeTestFromDevice(type = CoronaTest.Type.PCR)
+        submissionRepository.refreshTest(type = CoronaTest.Type.PCR)
     }
 
     fun userHasAcknowledgedTheLoweredRiskLevel() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/items/ReenableRiskCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/items/ReenableRiskCard.kt
index 2c181b509efa5f043ccfe2a7f2b4add8338fdba9..5c9df231a7c6a9106169323c436d11b1766f96b2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/items/ReenableRiskCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/items/ReenableRiskCard.kt
@@ -3,10 +3,11 @@ package de.rki.coronawarnapp.ui.main.home.items
 import android.view.ViewGroup
 import android.widget.Button
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.CommonSubmissionStates
 import de.rki.coronawarnapp.databinding.HomeReenableRiskCardLayoutBinding
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone
 import de.rki.coronawarnapp.ui.main.home.HomeAdapter
 import de.rki.coronawarnapp.ui.main.home.items.ReenableRiskCard.Item
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUIFormat
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
 class ReenableRiskCard(parent: ViewGroup) : HomeAdapter.HomeItemVH<Item, HomeReenableRiskCardLayoutBinding>(
@@ -19,15 +20,21 @@ class ReenableRiskCard(parent: ViewGroup) : HomeAdapter.HomeItemVH<Item, HomeRee
     }
 
     override val onBindData: HomeReenableRiskCardLayoutBinding.(Item, List<Any>) -> Unit = { item, payloads ->
+        reenableRiskCardTestRegistrationDate.text =
+            context.getString(R.string.reenable_risk_card_test_registration_string)
+                .format(item.data.testRegisteredAt.toDate().toUIFormat(context))
 
-        state = item.state
         itemView.findViewById<Button>(R.id.reenable_risk_card_button).setOnClickListener {
             val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
             curItem.onClickAction(item)
         }
     }
 
-    data class Item(val state: SubmissionDone, val onClickAction: (Item) -> Unit) : HomeItem, HasPayloadDiffer {
+    data class Item(
+        val data: CommonSubmissionStates.SubmissionDone,
+        val onClickAction: (Item) -> Unit
+    ) : HomeItem,
+        HasPayloadDiffer {
         override val stableId: Long = Item::class.java.name.hashCode().toLong()
 
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/PresenceTracingUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/PresenceTracingUIModule.kt
index b741e3d8820158a311a449f7a889fe0a0e7a35d7..092117efc9fb48cd7e7873c57ca54577a29a94df 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/PresenceTracingUIModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/PresenceTracingUIModule.kt
@@ -4,6 +4,8 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.CheckInsFragment
 import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.CheckInsModule
+import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent.CheckInsConsentFragment
+import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent.CheckInsConsentFragmentModule
 import de.rki.coronawarnapp.ui.presencetracing.attendee.confirm.ConfirmCheckInFragment
 import de.rki.coronawarnapp.ui.presencetracing.attendee.confirm.ConfirmCheckInModule
 import de.rki.coronawarnapp.ui.presencetracing.attendee.edit.EditCheckInFragment
@@ -60,4 +62,7 @@ internal abstract class PresenceTracingUIModule {
 
     @ContributesAndroidInjector(modules = [QrCodeDetailFragmentModule::class])
     abstract fun qrCodeDetailFragment(): QrCodeDetailFragment
+
+    @ContributesAndroidInjector(modules = [CheckInsConsentFragmentModule::class])
+    abstract fun checkInsConsentFragment(): CheckInsConsentFragment
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/TraceLocationPreferences.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/TraceLocationPreferences.kt
index d6451170caa4bf68d3085e92620787b042c21903..2297ac704807433eb989e4c36bfb0ff5d27dd84f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/TraceLocationPreferences.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/TraceLocationPreferences.kt
@@ -20,6 +20,11 @@ class TraceLocationPreferences @Inject constructor(
         defaultValue = false
     )
 
+    val createJournalEntryCheckedState = prefs.createFlowPreference(
+        key = "trace_location_create_journal_entry_checked_state",
+        defaultValue = true
+    )
+
     fun clear() {
         prefs.clearAndNotify()
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/TraceLocationAttendeeSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/TraceLocationAttendeeSettings.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1603fe47c8de43a6eb4e2ca5fc0c1fad4421abf1
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/TraceLocationAttendeeSettings.kt
@@ -0,0 +1,14 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee
+
+import dagger.Reusable
+import de.rki.coronawarnapp.ui.presencetracing.TraceLocationPreferences
+import javax.inject.Inject
+
+@Reusable
+class TraceLocationAttendeeSettings @Inject constructor(private val preferences: TraceLocationPreferences) {
+
+    val createJournalEntryCheckedState = preferences.createJournalEntryCheckedState.flow
+
+    fun setCreateJournalEntryCheckedState(isChecked: Boolean) =
+        preferences.createJournalEntryCheckedState.update { isChecked }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/common/CompletedCheckIn.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/common/CompletedCheckIn.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e5679346f5f9a930e8c768e298f24a25ec716514
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/common/CompletedCheckIn.kt
@@ -0,0 +1,16 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.common
+
+import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone
+import org.joda.time.format.DateTimeFormat
+
+inline val CheckIn.checkoutInfo: String
+    get() {
+        val checkInStartUserTZ = checkInStart.toUserTimeZone()
+        val checkInEndUserTZ = checkInEnd.toUserTimeZone()
+
+        val dayFormatted = checkInStartUserTZ.toLocalDate().toString(DateTimeFormat.mediumDate())
+        val startTimeFormatted = checkInStartUserTZ.toLocalTime().toString(DateTimeFormat.shortTime())
+        val endTimeFormatted = checkInEndUserTZ.toLocalTime().toString(DateTimeFormat.shortTime())
+        return "$dayFormatted, $startTimeFormatted - $endTimeFormatted"
+    }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fbd39ba15f070ad8f04c65d2de269c5c027e399d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentAdapter.kt
@@ -0,0 +1,37 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.viewbinding.ViewBinding
+import de.rki.coronawarnapp.util.lists.BindableVH
+import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffUtilAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffer
+import de.rki.coronawarnapp.util.lists.modular.ModularAdapter
+import de.rki.coronawarnapp.util.lists.modular.mods.DataBinderMod
+import de.rki.coronawarnapp.util.lists.modular.mods.StableIdMod
+import de.rki.coronawarnapp.util.lists.modular.mods.TypedVHCreatorMod
+
+class CheckInsConsentAdapter :
+    ModularAdapter<CheckInsConsentAdapter.ItemVH<CheckInsConsentItem, ViewBinding>>(),
+    AsyncDiffUtilAdapter<CheckInsConsentItem> {
+
+    override val asyncDiffer: AsyncDiffer<CheckInsConsentItem> = AsyncDiffer(adapter = this)
+
+    init {
+        modules.addAll(
+            listOf(
+                StableIdMod(data),
+                DataBinderMod<CheckInsConsentItem, ItemVH<CheckInsConsentItem, ViewBinding>>(data),
+                TypedVHCreatorMod({ data[it] is HeaderCheckInsVH.Item }) { HeaderCheckInsVH(it) },
+                TypedVHCreatorMod({ data[it] is SelectableCheckInVH.Item }) { SelectableCheckInVH(it) },
+            )
+        )
+    }
+
+    override fun getItemCount(): Int = data.size
+
+    abstract class ItemVH<Item : CheckInsConsentItem, VB : ViewBinding>(
+        @LayoutRes layoutRes: Int,
+        parent: ViewGroup
+    ) : ModularAdapter.VH(layoutRes, parent), BindableVH<Item, VB>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..33dfa460c14a79465c8fd39988dc15e0701b8d90
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentFragment.kt
@@ -0,0 +1,106 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import android.os.Bundle
+import android.view.View
+import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.Fragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.CheckInsConsentFragmentBinding
+import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.lists.diffutil.update
+import de.rki.coronawarnapp.util.ui.doNavigate
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
+import timber.log.Timber
+import javax.inject.Inject
+
+class CheckInsConsentFragment : Fragment(R.layout.check_ins_consent_fragment), AutoInject {
+
+    private val binding: CheckInsConsentFragmentBinding by viewBindingLazy()
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel: CheckInsConsentViewModel by cwaViewModelsAssisted(
+        factoryProducer = { viewModelFactory },
+        constructorCall = { factory, savedState ->
+            factory as CheckInsConsentViewModel.Factory
+            factory.create(
+                savedState = savedState
+            )
+        }
+    )
+
+    private val adapter = CheckInsConsentAdapter()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+
+        val backCallback = object : OnBackPressedCallback(true) {
+            override fun handleOnBackPressed() = viewModel.onCloseClick()
+        }
+        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
+
+        with(binding) {
+            checkInsRecycler.adapter = adapter
+            toolbar.setNavigationOnClickListener {
+                viewModel.onCloseClick()
+            }
+            skipButton.setOnClickListener { viewModel.onSkipClick() }
+            continueButton.setOnClickListener { viewModel.shareSelectedCheckIns() }
+        }
+
+        viewModel.checkIns.observe(viewLifecycleOwner) {
+            adapter.update(it)
+            binding.continueButton.isEnabled = it.any { item ->
+                item is SelectableCheckInVH.Item && item.checkIn.hasSubmissionConsent
+            }
+        }
+
+        viewModel.events.observe(viewLifecycleOwner) {
+            when (it) {
+                CheckInsConsentNavigation.OpenCloseDialog -> showCloseDialog()
+                CheckInsConsentNavigation.OpenSkipDialog -> showSkipDialog()
+                CheckInsConsentNavigation.ToHomeFragment -> doNavigate(
+                    CheckInsConsentFragmentDirections.actionCheckInsConsentFragmentToMainFragment()
+                )
+                CheckInsConsentNavigation.ToSubmissionResultReadyFragment -> doNavigate(
+                    CheckInsConsentFragmentDirections.actionCheckInsConsentFragmentToSubmissionResultReadyFragment()
+                )
+                CheckInsConsentNavigation.ToSubmissionTestResultConsentGivenFragment -> doNavigate(
+                    CheckInsConsentFragmentDirections
+                        .actionCheckInsConsentFragmentToSubmissionTestResultConsentGivenFragment()
+                )
+            }
+        }
+    }
+
+    private fun showSkipDialog() {
+        MaterialAlertDialogBuilder(requireContext())
+            .setTitle(R.string.trace_location_attendee_consent_dialog_title)
+            .setMessage(R.string.trace_location_attendee_consent_dialog_message)
+            .setPositiveButton(R.string.trace_location_attendee_consent_dialog_positive_button) { _, _ ->
+                Timber.d("showSkipDialog:Stay on CheckInsConsentFragment")
+            }
+            .setNegativeButton(R.string.trace_location_attendee_consent_dialog_negative_button) { _, _ ->
+                viewModel.doNotShareCheckIns()
+            }
+            .show()
+    }
+
+    private fun showCloseDialog() {
+        val closeDialogInstance = DialogHelper.DialogInstance(
+            context = requireActivity(),
+            title = R.string.submission_test_result_available_close_dialog_title_consent_given,
+            message = R.string.submission_test_result_available_close_dialog_body_consent_given,
+            positiveButton = R.string.submission_test_result_available_close_dialog_continue_button,
+            negativeButton = R.string.submission_test_result_available_close_dialog_cancel_button,
+            cancelable = true,
+            positiveButtonFunction = {
+                Timber.d("showCloseDialog:Stay on CheckInsConsentFragment")
+            },
+            negativeButtonFunction = { viewModel.onCancelConfirmed() }
+        )
+        DialogHelper.showDialog(closeDialogInstance)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d7fd545fd48907ffc4084221dcdb3a68ec5b579c
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentFragmentModule.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class CheckInsConsentFragmentModule {
+
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(CheckInsConsentViewModel::class)
+    abstract fun checkInsConsentFragment(
+        factory: CheckInsConsentViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentItem.kt
new file mode 100644
index 0000000000000000000000000000000000000000..53f9a7859bcc43c86587bef3beca68bb67965f2e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentItem.kt
@@ -0,0 +1,5 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import de.rki.coronawarnapp.util.lists.HasStableId
+
+interface CheckInsConsentItem : HasStableId
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentNavigation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentNavigation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ff3bbef59c0b267c038ab61d9c8be54afce1ac7f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentNavigation.kt
@@ -0,0 +1,9 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+sealed class CheckInsConsentNavigation {
+    object OpenCloseDialog : CheckInsConsentNavigation()
+    object OpenSkipDialog : CheckInsConsentNavigation()
+    object ToHomeFragment : CheckInsConsentNavigation()
+    object ToSubmissionTestResultConsentGivenFragment : CheckInsConsentNavigation()
+    object ToSubmissionResultReadyFragment : CheckInsConsentNavigation()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..698969365ff832c50599f13f811632e2ca08142f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentViewModel.kt
@@ -0,0 +1,171 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.asLiveData
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
+import de.rki.coronawarnapp.presencetracing.checkins.common.completedCheckIns
+import de.rki.coronawarnapp.submission.SubmissionRepository
+import de.rki.coronawarnapp.submission.auto.AutoSubmission
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import timber.log.Timber
+
+class CheckInsConsentViewModel @AssistedInject constructor(
+    @Assisted private val savedState: SavedStateHandle,
+    dispatcherProvider: DispatcherProvider,
+    private val checkInRepository: CheckInRepository,
+    private val submissionRepository: SubmissionRepository,
+    private val autoSubmission: AutoSubmission
+) : CWAViewModel(dispatcherProvider) {
+
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
+    private val selectedSetFlow = MutableStateFlow(initialSet())
+    private val coronaTest = submissionRepository.testForType(coronaTestType).filterNotNull()
+
+    val checkIns: LiveData<List<CheckInsConsentItem>> = combine(
+        checkInRepository.completedCheckIns,
+        selectedSetFlow
+    ) { checkIns, ids ->
+        mutableListOf<CheckInsConsentItem>().apply {
+            add(headerItem(checkIns))
+            addAll(mapCheckIns(checkIns, ids))
+        }
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    val events = SingleLiveEvent<CheckInsConsentNavigation>()
+
+    fun shareSelectedCheckIns() = launch {
+        // Reset selected check-ins from previous selection
+        resetPreviousSubmissionConsents()
+
+        Timber.d("Navigate to shareSelectedCheckIns")
+        autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+
+        // Update CheckIns for new submission
+        val idsWithConsent = selectedSetFlow.value
+        checkInRepository.updateSubmissionConsents(
+            checkInIds = idsWithConsent,
+            consent = true,
+        )
+
+        val event = if (coronaTest.first().isViewed) {
+            Timber.d("Navigate to SubmissionResultReadyFragment")
+            CheckInsConsentNavigation.ToSubmissionResultReadyFragment
+        } else {
+            Timber.d("Navigate to SubmissionTestResultConsentGivenFragment")
+            CheckInsConsentNavigation.ToSubmissionTestResultConsentGivenFragment
+        }
+        events.postValue(event)
+    }
+
+    fun doNotShareCheckIns() = launch {
+        // Reset selected check-ins from previous selection
+        resetPreviousSubmissionConsents()
+
+        Timber.d("Navigate to doNotShareCheckIns")
+        autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+        val event = if (coronaTest.first().isViewed) {
+            Timber.d("Navigate to SubmissionResultReadyFragment")
+            CheckInsConsentNavigation.ToSubmissionResultReadyFragment
+        } else {
+            Timber.d("Navigate to SubmissionTestResultConsentGivenFragment")
+            CheckInsConsentNavigation.ToSubmissionTestResultConsentGivenFragment
+        }
+        events.postValue(event)
+    }
+
+    fun onCloseClick() = launch {
+        val event = if (coronaTest.first().isViewed) {
+            Timber.d("openSkipDialog")
+            CheckInsConsentNavigation.OpenSkipDialog
+        } else {
+            Timber.d("openCloseDialog")
+            CheckInsConsentNavigation.OpenCloseDialog
+        }
+        events.postValue(event)
+    }
+
+    fun onCancelConfirmed() {
+        Timber.d("onCancelConfirmed")
+        events.postValue(CheckInsConsentNavigation.ToHomeFragment)
+    }
+
+    fun onSkipClick() {
+        Timber.d("onSkipClick")
+        events.postValue(CheckInsConsentNavigation.OpenSkipDialog)
+    }
+
+    private fun headerItem(checkIns: List<CheckIn>) = HeaderCheckInsVH.Item(
+        selectAll = {
+            val ids = checkIns.map { it.id }
+            if (!selectedSetFlow.value.containsAll(ids)) {
+                selectedSetFlow.value = updateSet(ids)
+            }
+        }
+    )
+
+    private fun mapCheckIns(checkIns: List<CheckIn>, ids: Set<Long>): List<CheckInsConsentItem> =
+        checkIns.sortedByDescending { it.checkInEnd }
+            .map { checkIn ->
+                SelectableCheckInVH.Item(
+                    checkIn = checkIn.copy(hasSubmissionConsent = ids.contains(checkIn.id)),
+                    onItemSelected = { selectedSetFlow.value = updateSet(listOf(it.id)) }
+                )
+            }
+
+    private fun updateSet(ids: List<Long>) =
+        mutableSetOf<Long>().apply {
+            if (!selectedSetFlow.value.containsAll(ids)) {
+                addAll(ids) // New Ids
+                addAll(selectedSetFlow.value) // Existing Ids
+            } else {
+                addAll(
+                    selectedSetFlow.value.toMutableSet().apply { removeAll(ids) }
+                )
+            }
+        }.also {
+            savedState.set(SET_KEY, it)
+            Timber.d("SelectedCheckIns=$it")
+        }
+
+    private fun initialSet(): Set<Long> = savedState.get(SET_KEY) ?: emptySet()
+
+    private fun resetPreviousSubmissionConsents() = launch {
+        try {
+            Timber.d("Trying to reset submission consents")
+            checkInRepository.apply {
+                val ids = completedCheckIns.first().filter { it.hasSubmissionConsent }.map { it.id }
+                updateSubmissionConsents(ids, consent = false)
+            }
+
+            Timber.d("Resetting submission consents was successful")
+        } catch (error: Exception) {
+            Timber.e(error, "Failed to reset SubmissionConsents")
+        }
+    }
+
+    @AssistedFactory
+    interface Factory : CWAViewModelFactory<CheckInsConsentViewModel> {
+        fun create(
+            savedState: SavedStateHandle,
+        ): CheckInsConsentViewModel
+    }
+
+    companion object {
+        private const val SET_KEY = "selected_checkIn_set"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/HeaderCheckInsVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/HeaderCheckInsVH.kt
new file mode 100644
index 0000000000000000000000000000000000000000..397b621ac0264b1f6695b4e711ad6990e1b697a7
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/HeaderCheckInsVH.kt
@@ -0,0 +1,31 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.TraceLocationAttendeeConsentHeaderBinding
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class HeaderCheckInsVH(parent: ViewGroup) :
+    CheckInsConsentAdapter.ItemVH<HeaderCheckInsVH.Item, TraceLocationAttendeeConsentHeaderBinding>(
+        layoutRes = R.layout.trace_location_attendee_consent_header,
+        parent = parent
+    ) {
+
+    override val viewBinding: Lazy<TraceLocationAttendeeConsentHeaderBinding> = lazy {
+        TraceLocationAttendeeConsentHeaderBinding.bind(itemView)
+    }
+
+    override val onBindData: TraceLocationAttendeeConsentHeaderBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, _ ->
+        selectAllButton.setOnClickListener { item.selectAll() }
+    }
+
+    data class Item(
+        val selectAll: () -> Unit
+    ) : CheckInsConsentItem, HasPayloadDiffer {
+        override val stableId: Long = Item::class.simpleName.hashCode().toLong()
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/SelectableCheckInVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/SelectableCheckInVH.kt
new file mode 100644
index 0000000000000000000000000000000000000000..98178f9378b24aa8da9509c45a2c4f90ec9f7850
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/SelectableCheckInVH.kt
@@ -0,0 +1,44 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.TraceLocationAttendeeConsentSelectableCheckInBinding
+import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
+import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.common.checkoutInfo
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+
+class SelectableCheckInVH(parent: ViewGroup) :
+    CheckInsConsentAdapter.ItemVH<SelectableCheckInVH.Item, TraceLocationAttendeeConsentSelectableCheckInBinding>(
+        layoutRes = R.layout.trace_location_attendee_consent_selectable_check_in,
+        parent = parent
+    ) {
+
+    override val viewBinding: Lazy<TraceLocationAttendeeConsentSelectableCheckInBinding> = lazy {
+        TraceLocationAttendeeConsentSelectableCheckInBinding.bind(itemView)
+    }
+
+    override val onBindData: TraceLocationAttendeeConsentSelectableCheckInBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, payloads ->
+        val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        val checkIn = curItem.checkIn
+        val imageResource = if (checkIn.hasSubmissionConsent) R.drawable.ic_selected else R.drawable.ic_unselected
+
+        checkbox.setImageResource(imageResource)
+        title.text = checkIn.description
+        subtitle.text = checkIn.address
+        checkoutInfo.text = checkIn.checkoutInfo
+
+        checkbox.setOnClickListener { item.onItemSelected(checkIn) }
+        itemView.setOnClickListener { item.onItemSelected(checkIn) }
+    }
+
+    data class Item(
+        val checkIn: CheckIn,
+        val onItemSelected: (CheckIn) -> Unit
+    ) : CheckInsConsentItem, HasPayloadDiffer {
+        override val stableId: Long = checkIn.id
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt
index 392de2548ba9c038e928fa23ce336ca1c5a138b1..106b09f13341fddceb6265d968b8ab136b5ef922 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt
@@ -4,10 +4,9 @@ import android.view.ViewGroup
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsItemPastBinding
 import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
-import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone
+import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.common.checkoutInfo
 import de.rki.coronawarnapp.util.list.SwipeConsumer
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
-import org.joda.time.format.DateTimeFormat
 
 class PastCheckInVH(parent: ViewGroup) :
     BaseCheckInVH<PastCheckInVH.Item, TraceLocationAttendeeCheckinsItemPastBinding>(
@@ -24,20 +23,10 @@ class PastCheckInVH(parent: ViewGroup) :
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
         val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
-
-        val checkInStartUserTZ = curItem.checkin.checkInStart.toUserTimeZone()
-        val checkInEndUserTZ = curItem.checkin.checkInEnd.toUserTimeZone()
-
         description.text = curItem.checkin.description
         address.text = curItem.checkin.address
 
-        checkoutInfo.text = run {
-            val dayFormatted = checkInStartUserTZ.toLocalDate().toString(DateTimeFormat.mediumDate())
-            val startTimeFormatted = checkInStartUserTZ.toLocalTime().toString(DateTimeFormat.shortTime())
-            val endTimeFormatted = checkInEndUserTZ.toLocalTime().toString(DateTimeFormat.shortTime())
-
-            "$dayFormatted, $startTimeFormatted - $endTimeFormatted"
-        }
+        checkoutInfo.text = curItem.checkin.checkoutInfo
 
         menuAction.setupMenu(R.menu.menu_trace_location_attendee_checkin_item) {
             when (it.itemId) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/confirm/ConfirmCheckInViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/confirm/ConfirmCheckInViewModel.kt
index ae6338f35bae7c8dea56f31b8466f1e2a54381aa..4acce9986eb10ac33a056e211927cf0b1c068b87 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/confirm/ConfirmCheckInViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/confirm/ConfirmCheckInViewModel.kt
@@ -11,6 +11,7 @@ import de.rki.coronawarnapp.presencetracing.checkins.qrcode.VerifiedTraceLocatio
 import de.rki.coronawarnapp.presencetracing.checkins.qrcode.getDefaultAutoCheckoutLengthInMinutes
 import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat
 import de.rki.coronawarnapp.ui.durationpicker.toReadableDuration
+import de.rki.coronawarnapp.ui.presencetracing.attendee.TraceLocationAttendeeSettings
 import de.rki.coronawarnapp.ui.presencetracing.organizer.category.adapter.category.mapTraceLocationToTitleRes
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
@@ -18,6 +19,7 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
 import org.joda.time.Duration
 import org.joda.time.Instant
 import org.joda.time.format.DateTimeFormat
@@ -25,10 +27,11 @@ import org.joda.time.format.DateTimeFormat
 class ConfirmCheckInViewModel @AssistedInject constructor(
     @Assisted private val verifiedTraceLocation: VerifiedTraceLocation,
     private val checkInRepository: CheckInRepository,
-    private val timeStamper: TimeStamper
+    private val timeStamper: TimeStamper,
+    private val traceLocationAttendeeSettings: TraceLocationAttendeeSettings
 ) : CWAViewModel() {
     private val traceLocation = MutableStateFlow(verifiedTraceLocation.traceLocation)
-    private val createJournalEntry = MutableStateFlow(true)
+    private val createJournalEntry = traceLocationAttendeeSettings.createJournalEntryCheckedState
 
     private val autoCheckOutLength = MutableStateFlow(
         Duration.standardMinutes(
@@ -64,7 +67,7 @@ class ConfirmCheckInViewModel @AssistedInject constructor(
             checkInRepository.addCheckIn(
                 verifiedTraceLocation.toCheckIn(
                     checkInStart = now,
-                    createJournalEntry = createJournalEntry.value,
+                    createJournalEntry = createJournalEntry.first(),
                     checkInEnd = now + autoCheckOutLength.value
                 )
             )
@@ -73,7 +76,7 @@ class ConfirmCheckInViewModel @AssistedInject constructor(
     }
 
     fun createJournalEntryToggled(state: Boolean) {
-        createJournalEntry.value = state
+        traceLocationAttendeeSettings.setCreateJournalEntryCheckedState(state)
     }
 
     fun dateSelectorClicked() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt
index b85dea9f8730792b52d0ad473b44e0a6e14d0203..2607499099a7fe0790bb63e57acfb8b70dd3a318 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt
@@ -138,7 +138,7 @@ class QrCodeDetailFragment : Fragment(R.layout.trace_location_organizer_qr_code_
                     binding.progressBar.hide()
                     binding.qrCodeImage.apply {
                         val resourceId = RoundedBitmapDrawableFactory.create(resources, it)
-                        resourceId.cornerRadius = it.width * 0.1f
+                        resourceId.cornerRadius = 15f
                         setImageDrawable(resourceId)
                     }
                 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt
index 5bd94db299550124fb07ce5bec5682034b0a36cb..1cfcb3edcb2063e3b657184d3ad01764fa00d885 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionContactFragment.kt
@@ -45,10 +45,6 @@ class SubmissionContactFragment : Fragment(R.layout.fragment_submission_contact)
                     )
             }
         }
-
-        viewModel.dial.observe2(this) {
-            dial()
-        }
     }
 
     override fun onResume() {
@@ -60,19 +56,18 @@ class SubmissionContactFragment : Fragment(R.layout.fragment_submission_contact)
         binding.submissionContactHeader.headerButtonBack.buttonIcon.setOnClickListener {
             viewModel.onBackPressed()
         }
-        binding.submissionContactButtonCall.setOnClickListener {
-            viewModel.onDialPressed()
-        }
-        binding.includeSubmissionContact.submissionContactStep1Number.setOnClickListener {
-            viewModel.onDialPressed()
-        }
+        binding.includeSubmissionContact.submissionContactNavigationRowPhone.navigationRow
+            .setOnClickListener {
+                val number = getString(R.string.submission_contact_number_display)
+                ExternalActionHelper.call(this, number)
+            }
+        binding.includeSubmissionContact.submissionContactNavigationRowInternationalPhone.navigationRow
+            .setOnClickListener {
+                val number = getString(R.string.submission_contact_button_international_phone)
+                ExternalActionHelper.call(this, number)
+            }
         binding.submissionContactButtonEnter.setOnClickListener {
             viewModel.onEnterTanPressed()
         }
     }
-
-    private fun dial() = context?.let {
-        val number = getString(R.string.submission_contact_number_dial)
-        ExternalActionHelper.call(this, number)
-    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt
index 1428e0f4fc1d1a3862db37a91653c4a934ae0704..459ea67f482258a9e58aa664e74dec82f37587d0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt
@@ -29,7 +29,8 @@ class SubmissionConsentViewModel @AssistedInject constructor(
         .asLiveData(context = dispatcherProvider.Default)
 
     fun onConsentButtonClick() {
-        submissionRepository.giveConsentToSubmission()
+        // TODO Do we have a Test registered at this time? We need to forward the decission with navargs?
+//        submissionRepository.giveConsentToSubmission(type = CoronaTest.Type.PCR)
         analyticsKeySubmissionCollector.reportAdvancedConsentGiven()
         launch {
             try {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
index 855e5810489aef55471e6980f336ea3aa506ad83..f19296ecba2b0a05766d8ddd40b6ae8d4546ee30 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
@@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
 import com.google.zxing.BarcodeFormat
 import com.journeyapps.barcodescanner.DefaultDecoderFactory
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding
 import de.rki.coronawarnapp.exception.http.BadRequestException
 import de.rki.coronawarnapp.exception.http.CwaClientError
@@ -20,7 +21,6 @@ import de.rki.coronawarnapp.ui.submission.ScanStatus
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
-import de.rki.coronawarnapp.util.formatter.TestResult
 import de.rki.coronawarnapp.util.permission.CameraPermissionHelper
 import de.rki.coronawarnapp.util.ui.doNavigate
 import de.rki.coronawarnapp.util.ui.observe2
@@ -86,7 +86,7 @@ class SubmissionQRCodeScanFragment :
                 else -> View.GONE
             }
             if (ApiRequestState.SUCCESS == state.apiRequestState) {
-                if (state.testResult == TestResult.POSITIVE) {
+                if (state.testResult == CoronaTestResult.PCR_POSITIVE) {
                     doNavigate(
                         SubmissionQRCodeScanFragmentDirections
                             .actionSubmissionQRCodeScanFragmentToSubmissionTestResultAvailableFragment()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt
index 8ebceb7827da23d3fd9d2adab78675332da5991b..ff300dd094c8610d70ccd2ec42bc7231d4114c37 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt
@@ -5,16 +5,19 @@ import androidx.lifecycle.MutableLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator
+import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.TransactionException
 import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.exception.reporting.report
-import de.rki.coronawarnapp.service.submission.QRScanResult
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.ui.submission.ScanStatus
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
-import de.rki.coronawarnapp.util.formatter.TestResult
 import de.rki.coronawarnapp.util.permission.CameraSettings
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
@@ -23,21 +26,21 @@ import timber.log.Timber
 
 class SubmissionQRCodeScanViewModel @AssistedInject constructor(
     private val submissionRepository: SubmissionRepository,
-    private val cameraSettings: CameraSettings
+    private val cameraSettings: CameraSettings,
+    private val qrCodeValidator: CoronaTestQrCodeValidator
 ) : CWAViewModel() {
     val routeToScreen = SingleLiveEvent<SubmissionNavigationEvents>()
     val showRedeemedTokenWarning = SingleLiveEvent<Unit>()
     val scanStatusValue = SingleLiveEvent<ScanStatus>()
 
-    open class InvalidQRCodeException : Exception("error in qr code")
-
     fun validateTestGUID(rawResult: String) {
-        val scanResult = QRScanResult(rawResult)
-        if (scanResult.isValid) {
-            QRCodeCensor.lastGUID = scanResult.guid
+        try {
+            val coronaTestQRCode = qrCodeValidator.validate(rawResult)
+            // TODO this needs to be adapted to work for different types
+            QRCodeCensor.lastGUID = coronaTestQRCode.registrationIdentifier
             scanStatusValue.postValue(ScanStatus.SUCCESS)
-            doDeviceRegistration(scanResult)
-        } else {
+            doDeviceRegistration(coronaTestQRCode)
+        } catch (err: InvalidQRCodeException) {
             scanStatusValue.postValue(ScanStatus.INVALID)
         }
     }
@@ -47,16 +50,17 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
 
     data class RegistrationState(
         val apiRequestState: ApiRequestState,
-        val testResult: TestResult? = null
+        val testResult: CoronaTestResult? = null
     )
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    internal fun doDeviceRegistration(scanResult: QRScanResult) = launch {
+    internal fun doDeviceRegistration(coronaTestQRCode: CoronaTestQRCode) = launch {
         try {
             registrationState.postValue(RegistrationState(ApiRequestState.STARTED))
-            val testResult = submissionRepository.asyncRegisterDeviceViaGUID(scanResult.guid!!)
-            checkTestResult(testResult)
-            registrationState.postValue(RegistrationState(ApiRequestState.SUCCESS, testResult))
+            val coronaTest = submissionRepository.registerTest(coronaTestQRCode)
+            submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type)
+            checkTestResult(coronaTest.testResult)
+            registrationState.postValue(RegistrationState(ApiRequestState.SUCCESS, coronaTest.testResult))
         } catch (err: CwaWebException) {
             registrationState.postValue(RegistrationState(ApiRequestState.FAILED))
             registrationError.postValue(err)
@@ -77,8 +81,8 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
         }
     }
 
-    private fun checkTestResult(testResult: TestResult) {
-        if (testResult == TestResult.REDEEMED) {
+    private fun checkTestResult(testResult: CoronaTestResult) {
+        if (testResult == CoronaTestResult.PCR_REDEEMED) {
             throw InvalidQRCodeException()
         }
     }
@@ -87,7 +91,7 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
         launch {
             Timber.d("deregisterTestFromDevice()")
 
-            submissionRepository.removeTestFromDevice()
+            submissionRepository.removeTestFromDevice(type = CoronaTest.Type.PCR)
 
             routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity)
         }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt
index 3e5d0a7cbaf823d3ec6cdf162fa0a64992ffbc39..069dbdbe54906de32218bb61202cd0d263a996ef 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt
@@ -7,9 +7,12 @@ import androidx.navigation.NavDirections
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
+import de.rki.coronawarnapp.presencetracing.checkins.common.completedCheckIns
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.auto.AutoSubmission
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater
@@ -17,20 +20,28 @@ import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
 import timber.log.Timber
 
 class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory,
     submissionRepository: SubmissionRepository,
+    private val checkInRepository: CheckInRepository,
     private val autoSubmission: AutoSubmission,
     private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
+    // TODO Use navargs to supply this?
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
     val routeToScreen = SingleLiveEvent<NavDirections>()
 
-    val consentFlow = submissionRepository.hasGivenConsentToSubmission
+    private val consentFlow = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .map { it.isAdvancedConsentGiven }
     val consent = consentFlow.asLiveData(dispatcherProvider.Default)
     val showPermissionRequest = SingleLiveEvent<(Activity) -> Unit>()
     val showCloseDialog = SingleLiveEvent<Unit>()
@@ -39,14 +50,21 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
 
     private val tekHistoryUpdater = tekHistoryUpdaterFactory.create(
         object : TEKHistoryUpdater.Callback {
-            override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
-                Timber.d("onTEKAvailable(teks.size=%d)", teks.size)
-                autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+            override fun onTEKAvailable(teks: List<TemporaryExposureKey>) = launch {
+                Timber.tag(TAG).d("onTEKAvailable(teks.size=%d)", teks.size)
                 showKeysRetrievalProgress.postValue(false)
-                routeToScreen.postValue(
+                val completedCheckInsExist = checkInRepository.completedCheckIns.first().isNotEmpty()
+                val navDirections = if (completedCheckInsExist) {
+                    Timber.tag(TAG).d("Navigate to CheckInsConsentFragment")
+                    SubmissionTestResultAvailableFragmentDirections
+                        .actionSubmissionTestResultAvailableFragmentToCheckInsConsentFragment()
+                } else {
+                    autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+                    Timber.tag(TAG).d("Navigate to SubmissionTestResultConsentGivenFragment")
                     SubmissionTestResultAvailableFragmentDirections
                         .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultConsentGivenFragment()
-                )
+                }
+                routeToScreen.postValue(navDirections)
             }
 
             override fun onTEKPermissionDeclined() {
@@ -59,13 +77,13 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
             }
 
             override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) {
-                Timber.d("onTracingConsentRequired")
+                Timber.tag(TAG).d("onTracingConsentRequired")
                 showKeysRetrievalProgress.postValue(false)
                 showTracingConsentDialog.postValue(onConsentResult)
             }
 
             override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) {
-                Timber.d("onPermissionRequired")
+                Timber.tag(TAG).d("onPermissionRequired")
                 showKeysRetrievalProgress.postValue(false)
                 showPermissionRequest.postValue(permissionRequest)
             }
@@ -82,7 +100,7 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
     )
 
     init {
-        submissionRepository.refreshDeviceUIState(refreshTestResult = false)
+        submissionRepository.refreshTest(type = CoronaTest.Type.PCR)
     }
 
     fun goBack() {
@@ -109,10 +127,10 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
         showKeysRetrievalProgress.value = true
         launch {
             if (consentFlow.first()) {
-                Timber.d("tekHistoryUpdater.updateTEKHistoryOrRequestPermission")
+                Timber.tag(TAG).d("tekHistoryUpdater.updateTEKHistoryOrRequestPermission")
                 tekHistoryUpdater.updateTEKHistoryOrRequestPermission()
             } else {
-                Timber.d("routeToScreen:SubmissionTestResultNoConsentFragment")
+                Timber.tag(TAG).d("routeToScreen:SubmissionTestResultNoConsentFragment")
                 analyticsKeySubmissionCollector.reportConsentWithdrawn()
                 showKeysRetrievalProgress.postValue(false)
                 routeToScreen.postValue(
@@ -130,4 +148,8 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
 
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<SubmissionTestResultAvailableViewModel>
+
+    companion object {
+        private const val TAG = "TestAvailableViewModel"
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt
index 84b91e5b5d1600042d7f7fbfa33b28ce305570e6..ed42c6abe15548d9258ed11a022cfc54c8030bd0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt
@@ -4,6 +4,8 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.TransactionException
 import de.rki.coronawarnapp.exception.http.CwaWebException
@@ -51,7 +53,8 @@ class SubmissionTanViewModel @AssistedInject constructor(
         launch {
             try {
                 registrationState.postValue(ApiRequestState.STARTED)
-                submissionRepository.asyncRegisterDeviceViaTAN(teletan.value)
+                val request = CoronaTestTAN.PCR(tan = teletan.value)
+                submissionRepository.registerTest(request)
                 registrationState.postValue(ApiRequestState.SUCCESS)
             } catch (err: CwaWebException) {
                 registrationState.postValue(ApiRequestState.FAILED)
@@ -67,7 +70,8 @@ class SubmissionTanViewModel @AssistedInject constructor(
                 registrationState.postValue(ApiRequestState.FAILED)
                 err.report(ExceptionCategory.INTERNAL)
             } finally {
-                submissionRepository.refreshDeviceUIState(refreshTestResult = false)
+                // TODO Should not be necessary? What new data would we
+                submissionRepository.refreshTest(type = CoronaTest.Type.PCR)
             }
         }
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt
index a34a02cad13d8a16e1c550ef47aa4c2ce27dfd2d..d9a2808b6eeaeb5c83781d89b52d4b8b44af5c24 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt
@@ -1,10 +1,7 @@
 package de.rki.coronawarnapp.ui.submission.testresult
 
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import java.util.Date
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 
 data class TestResultUIState(
-    val deviceUiState: NetworkRequestWrapper<DeviceUIState, Throwable>,
-    val testResultReceivedDate: Date?
+    val coronaTest: CoronaTest
 )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidFragment.kt
index e4fcde50e12fc1d1c91af144a7f58ba3306fbe09..8ee096b5f6da43022e8b30083fbd8775f0c97924 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidFragment.kt
@@ -34,7 +34,7 @@ class SubmissionTestResultInvalidFragment : Fragment(R.layout.fragment_submissio
         }
 
         viewModel.testResult.observe2(this) {
-            binding.submissionTestResultSection.setTestResultSection(it.deviceUiState, it.testResultReceivedDate)
+            binding.submissionTestResultSection.setTestResultSection(it.coronaTest)
         }
 
         viewModel.routeToScreen.observe2(this) { navDirections ->
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt
index 870df2853cfdc629cf13f2873ec5cd39a39519ad..81a4ebbc057e1effeed28b8b0f1505ef35b8cbfc 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt
@@ -5,45 +5,48 @@ import androidx.lifecycle.asLiveData
 import androidx.navigation.NavDirections
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
-import de.rki.coronawarnapp.util.flow.combine
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
 import timber.log.Timber
 
 class SubmissionTestResultInvalidViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     private val submissionRepository: SubmissionRepository,
-    private val testResultAvailableNotificationService: TestResultAvailableNotificationService
+    private val testResultAvailableNotificationService: TestResultAvailableNotificationService,
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
+    init {
+        Timber.v("init() coronaTestType=%s", coronaTestType)
+    }
 
     val routeToScreen = SingleLiveEvent<NavDirections?>()
 
-    val testResult: LiveData<TestResultUIState> = combine(
-        submissionRepository.deviceUIStateFlow,
-        submissionRepository.testResultReceivedDateFlow
-    ) { deviceUiState, resultDate ->
-        TestResultUIState(
-            deviceUiState = deviceUiState,
-            testResultReceivedDate = resultDate
-        )
-    }.asLiveData(context = dispatcherProvider.Default)
-
-    fun deregisterTestFromDevice() {
+    val testResult: LiveData<TestResultUIState> = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .map { test ->
+            TestResultUIState(coronaTest = test)
+        }.asLiveData(context = dispatcherProvider.Default)
+
+    fun deregisterTestFromDevice() = launch {
         Timber.d("deregisterTestFromDevice()")
-        launch {
-            submissionRepository.removeTestFromDevice()
 
-            routeToScreen.postValue(null)
-        }
+        submissionRepository.removeTestFromDevice(type = coronaTestType)
+        routeToScreen.postValue(null)
     }
 
-    fun onTestOpened() {
-        submissionRepository.setViewedTestResult()
+    fun onTestOpened() = launch {
+        Timber.d("onTestOpened()")
+        submissionRepository.setViewedTestResult(type = coronaTestType)
         testResultAvailableNotificationService.cancelTestResultAvailableNotification()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt
index 609cec522f708fd4171355299dcb72fce81670b3..3372b725f72f8731e29955ac4a37d8e49b3af704 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt
@@ -34,7 +34,7 @@ class SubmissionTestResultNegativeFragment : Fragment(R.layout.fragment_submissi
         }
 
         viewModel.testResult.observe2(this) {
-            binding.submissionTestResultSection.setTestResultSection(it.deviceUiState, it.testResultReceivedDate)
+            binding.submissionTestResultSection.setTestResultSection(it.coronaTest)
         }
 
         viewModel.routeToScreen.observe2(this) { navDirections ->
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt
index b1d734482f18730dee076c93430059264ab5f0d9..e7f6ebec332e3ad125f57f9dc7164cebff9ae569 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt
@@ -5,14 +5,16 @@ import androidx.lifecycle.asLiveData
 import androidx.navigation.NavDirections
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
-import de.rki.coronawarnapp.util.flow.combine
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
 import timber.log.Timber
 
 class SubmissionTestResultNegativeViewModel @AssistedInject constructor(
@@ -20,29 +22,30 @@ class SubmissionTestResultNegativeViewModel @AssistedInject constructor(
     private val submissionRepository: SubmissionRepository,
     private val testResultAvailableNotificationService: TestResultAvailableNotificationService
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
+    init {
+        Timber.v("init() coronaTestType=%s", coronaTestType)
+    }
 
     val routeToScreen = SingleLiveEvent<NavDirections?>()
-    val testResult: LiveData<TestResultUIState> = combine(
-        submissionRepository.deviceUIStateFlow,
-        submissionRepository.testResultReceivedDateFlow
-    ) { deviceUiState, resultDate ->
-        TestResultUIState(
-            deviceUiState = deviceUiState,
-            testResultReceivedDate = resultDate
-        )
-    }.asLiveData(context = dispatcherProvider.Default)
-
-    fun deregisterTestFromDevice() {
-        launch {
-            Timber.tag(TAG).d("deregisterTestFromDevice()")
-            submissionRepository.removeTestFromDevice()
-
-            routeToScreen.postValue(null)
-        }
+    val testResult: LiveData<TestResultUIState> = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .map { test ->
+            TestResultUIState(coronaTest = test)
+        }.asLiveData(context = dispatcherProvider.Default)
+
+    fun deregisterTestFromDevice() = launch {
+        Timber.tag(TAG).d("deregisterTestFromDevice()")
+        submissionRepository.removeTestFromDevice(type = coronaTestType)
+
+        routeToScreen.postValue(null)
     }
 
-    fun onTestOpened() {
-        submissionRepository.setViewedTestResult()
+    fun onTestOpened() = launch {
+        Timber.tag(TAG).d("onTestOpened()")
+        submissionRepository.setViewedTestResult(type = coronaTestType)
         testResultAvailableNotificationService.cancelTestResultAvailableNotification()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt
index 864629916a35ab0a185955bbbe351502c80e48c3..2c2ed4a3ca78d308b9dfefdea4cd66dcc85e4fec 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt
@@ -11,7 +11,6 @@ import de.rki.coronawarnapp.exception.http.CwaClientError
 import de.rki.coronawarnapp.exception.http.CwaServerError
 import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat
 import de.rki.coronawarnapp.util.DialogHelper
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.doNavigate
 import de.rki.coronawarnapp.util.ui.observe2
@@ -42,9 +41,9 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio
         }
 
         pendingViewModel.testState.observe2(this) { result ->
-            val hasResult = result.deviceUiState is NetworkRequestWrapper.RequestSuccessful
+            val hasResult = !result.coronaTest.isProcessing
             binding.apply {
-                submissionTestResultSection.setTestResultSection(result.deviceUiState, result.testResultReceivedDate)
+                submissionTestResultSection.setTestResultSection(result.coronaTest)
                 submissionTestResultSpinner.setInvisible(hasResult)
                 submissionTestResultContent.setInvisible(!hasResult)
                 buttonContainer.setInvisible(!hasResult)
@@ -89,7 +88,7 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio
     override fun onResume() {
         super.onResume()
         binding.submissionTestResultContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
-        pendingViewModel.refreshDeviceUIState(refreshTestResult = !skipInitialTestResultRefresh)
+        pendingViewModel.refreshDeviceUIState()
         skipInitialTestResultRefresh = false
         pendingViewModel.cwaWebExceptionLiveData.observeOnce(this.viewLifecycleOwner) { exception ->
             handleError(exception)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt
index 9c82958cca133c04f554e1affcfb2362f19de084..b01b89606ed2a5b22e16efeacf5164f9b077abf5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt
@@ -5,19 +5,18 @@ import androidx.lifecycle.asLiveData
 import androidx.navigation.NavDirections
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
+import de.rki.coronawarnapp.submission.toDeviceUIState
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
-import de.rki.coronawarnapp.util.flow.combine
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -30,60 +29,58 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
     private val shareTestResultNotificationService: ShareTestResultNotificationService,
     private val submissionRepository: SubmissionRepository
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
+    init {
+        Timber.v("init() coronaTestType=%s", coronaTestType)
+    }
 
     val routeToScreen = SingleLiveEvent<NavDirections?>()
 
     val showRedeemedTokenWarning = SingleLiveEvent<Unit>()
-    val consentGiven = submissionRepository.hasGivenConsentToSubmission.asLiveData()
+    val consentGiven = submissionRepository.testForType(type = coronaTestType).map {
+        it?.isAdvancedConsentGiven ?: false
+    }.asLiveData()
 
     private var wasRedeemedTokenErrorShown = false
     private val tokenErrorMutex = Mutex()
 
-    private val testResultFlow = combine(
-        submissionRepository.deviceUIStateFlow,
-        submissionRepository.testResultReceivedDateFlow
-    ) { deviceUiState, resultDate ->
-
-        tokenErrorMutex.withLock {
-            if (!wasRedeemedTokenErrorShown) {
-                deviceUiState.withSuccess {
-                    if (it == DeviceUIState.PAIRED_REDEEMED) {
+    private val testResultFlow = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .map { test ->
+            tokenErrorMutex.withLock {
+                if (!wasRedeemedTokenErrorShown) {
+                    if (test.testResult.toDeviceUIState() == DeviceUIState.PAIRED_REDEEMED) {
                         wasRedeemedTokenErrorShown = true
                         showRedeemedTokenWarning.postValue(Unit)
                     }
                 }
             }
+            TestResultUIState(coronaTest = test)
         }
 
-        TestResultUIState(
-            deviceUiState = deviceUiState,
-            testResultReceivedDate = resultDate
-        )
-    }
     val testState: LiveData<TestResultUIState> = testResultFlow
         .onEach { testResultUIState ->
-            testResultUIState.deviceUiState.withSuccess { deviceState ->
-                when (deviceState) {
-                    DeviceUIState.PAIRED_POSITIVE ->
-                        SubmissionTestResultPendingFragmentDirections
-                            .actionSubmissionTestResultPendingFragmentToSubmissionTestResultAvailableFragment()
-                    DeviceUIState.PAIRED_NEGATIVE ->
-                        SubmissionTestResultPendingFragmentDirections
-                            .actionSubmissionTestResultPendingFragmentToSubmissionTestResultNegativeFragment()
-                    DeviceUIState.PAIRED_REDEEMED,
-                    DeviceUIState.PAIRED_ERROR ->
-                        SubmissionTestResultPendingFragmentDirections
-                            .actionSubmissionTestResultPendingFragmentToSubmissionTestResultInvalidFragment()
-                    else -> {
-                        Timber.w("Unknown success state: %s", deviceState)
-                        null
-                    }
-                }?.let { routeToScreen.postValue(it) }
-            }
+            when (val deviceState = testResultUIState.coronaTest.testResult.toDeviceUIState()) {
+                DeviceUIState.PAIRED_POSITIVE ->
+                    SubmissionTestResultPendingFragmentDirections
+                        .actionSubmissionTestResultPendingFragmentToSubmissionTestResultAvailableFragment()
+                DeviceUIState.PAIRED_NEGATIVE ->
+                    SubmissionTestResultPendingFragmentDirections
+                        .actionSubmissionTestResultPendingFragmentToSubmissionTestResultNegativeFragment()
+                DeviceUIState.PAIRED_REDEEMED,
+                DeviceUIState.PAIRED_ERROR ->
+                    SubmissionTestResultPendingFragmentDirections
+                        .actionSubmissionTestResultPendingFragmentToSubmissionTestResultInvalidFragment()
+                else -> {
+                    Timber.w("Unknown success state: %s", deviceState)
+                    null
+                }
+            }?.let { routeToScreen.postValue(it) }
         }
-        .filter {
-            val isPositiveTest = it.deviceUiState is NetworkRequestWrapper.RequestSuccessful &&
-                it.deviceUiState.data == DeviceUIState.PAIRED_POSITIVE
+        .filter { testResultUIState ->
+            val isPositiveTest = testResultUIState.coronaTest.isSubmissionAllowed
             if (isPositiveTest) {
                 Timber.w("Filtering out positive test emission as we don't display this here.")
             }
@@ -91,31 +88,27 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
         }
         .asLiveData(context = dispatcherProvider.Default)
 
-    val cwaWebExceptionLiveData = submissionRepository.deviceUIStateFlow
-        .filterIsInstance<NetworkRequestWrapper.RequestFailed<DeviceUIState, Throwable>>()
-        .map { it.error }
+    val cwaWebExceptionLiveData = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .filter { it.lastError != null }
+        .map { it.lastError!! }
         .asLiveData()
 
     fun observeTestResultToSchedulePositiveTestResultReminder() = launch {
-        submissionRepository.deviceUIStateFlow
-            .first { request ->
-                request.withSuccess(false) {
-                    it == DeviceUIState.PAIRED_POSITIVE || it == DeviceUIState.PAIRED_POSITIVE_TELETAN
-                }
-            }
+        submissionRepository.testForType(type = coronaTestType)
+            .first { request -> request?.isSubmissionAllowed ?: false }
             .also { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() }
     }
 
-    fun deregisterTestFromDevice() {
+    fun deregisterTestFromDevice() = launch {
         Timber.d("deregisterTestFromDevice()")
-        launch {
-            submissionRepository.removeTestFromDevice()
-            routeToScreen.postValue(null)
-        }
+        submissionRepository.removeTestFromDevice(type = coronaTestType)
+        routeToScreen.postValue(null)
     }
 
-    fun refreshDeviceUIState(refreshTestResult: Boolean = true) {
-        submissionRepository.refreshDeviceUIState(refreshTestResult)
+    fun refreshDeviceUIState() = launch {
+        Timber.v("refreshDeviceUIState()")
+        submissionRepository.refreshTest(type = coronaTestType)
     }
 
     fun onConsentClicked() {
@@ -127,8 +120,4 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
 
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<SubmissionTestResultPendingViewModel>
-
-    companion object {
-        private const val TAG = "SubmissionTestResult:VM"
-    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt
index 4b61145a714f16401e56cc941d5e6ad282729d82..411733b90aebaa7f501eeba3f8f40036222d0690 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenFragment.kt
@@ -47,7 +47,7 @@ class SubmissionTestResultConsentGivenFragment :
         viewModel.uiState.observe2(this) {
             binding.apply {
                 uiState = it
-                submissionTestResultSection.setTestResultSection(it.deviceUiState, it.testResultReceivedDate)
+                submissionTestResultSection.setTestResultSection(it.coronaTest)
             }
         }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt
index a1277d4c81ad3c6ffd3e2a4df94e520c82891496..053baa300837faac283f575edda2cd5633f30e78 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt
@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.Screen
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
@@ -12,11 +13,12 @@ import de.rki.coronawarnapp.submission.auto.AutoSubmission
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
-import de.rki.coronawarnapp.util.flow.combine
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
 import timber.log.Timber
 
 class SubmissionTestResultConsentGivenViewModel @AssistedInject constructor(
@@ -26,26 +28,29 @@ class SubmissionTestResultConsentGivenViewModel @AssistedInject constructor(
     private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
     dispatcherProvider: DispatcherProvider
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
+    init {
+        Timber.v("init() coronaTestType=%s", coronaTestType)
+    }
 
     val showUploadDialog = autoSubmission.isSubmissionRunning
         .asLiveData(context = dispatcherProvider.Default)
 
-    val uiState: LiveData<TestResultUIState> = combine(
-        submissionRepository.deviceUIStateFlow,
-        submissionRepository.testResultReceivedDateFlow
-    ) { deviceUiState, resultDate ->
-        TestResultUIState(
-            deviceUiState = deviceUiState,
-            testResultReceivedDate = resultDate
-        )
-    }.asLiveData(context = Dispatchers.Default)
+    val uiState: LiveData<TestResultUIState> = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .map { test ->
+            TestResultUIState(coronaTest = test)
+        }.asLiveData(context = Dispatchers.Default)
 
     val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
 
     val showCancelDialog = SingleLiveEvent<Unit>()
 
-    fun onTestOpened() {
-        submissionRepository.setViewedTestResult()
+    fun onTestOpened() = launch {
+        Timber.d("onTestOpened()")
+        submissionRepository.setViewedTestResult(type = coronaTestType)
         testResultAvailableNotificationService.cancelTestResultAvailableNotification()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt
index 2b1fa0d5a3a26617ed28f3031f18c4e5ef0fb68a..04374d1b4d688bfe8e66003f303e30dd16312d45 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt
@@ -41,8 +41,7 @@ class SubmissionTestResultNoConsentFragment :
         requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
 
         viewModel.uiState.observe2(this) {
-            binding.submissionTestResultSection
-                .setTestResultSection(it.deviceUiState, it.testResultReceivedDate)
+            binding.submissionTestResultSection.setTestResultSection(it.coronaTest)
         }
 
         binding.apply {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt
index 49b07dec8537509cf4040c211f5203004102b380..8161c5e817c77b091843d54556f7425ec76fa7c8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt
@@ -4,36 +4,41 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.Screen
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
-import de.rki.coronawarnapp.util.flow.combine
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import timber.log.Timber
 
 class SubmissionTestResultNoConsentViewModel @AssistedInject constructor(
     private val submissionRepository: SubmissionRepository,
     private val testResultAvailableNotificationService: TestResultAvailableNotificationService,
     private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
 ) : CWAViewModel() {
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
 
-    val uiState: LiveData<TestResultUIState> = combine(
-        submissionRepository.deviceUIStateFlow,
-        submissionRepository.testResultReceivedDateFlow
-    ) { deviceUiState, resultDate ->
+    init {
+        Timber.v("init() coronaTestType=%s", coronaTestType)
+    }
 
-        TestResultUIState(
-            deviceUiState = deviceUiState,
-            testResultReceivedDate = resultDate
-        )
-    }.asLiveData(context = Dispatchers.Default)
+    val uiState: LiveData<TestResultUIState> = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .map { test ->
+            TestResultUIState(coronaTest = test)
+        }.asLiveData(context = Dispatchers.Default)
 
-    fun onTestOpened() {
+    fun onTestOpened() = launch {
+        Timber.v("onTestOpened()")
         analyticsKeySubmissionCollector.reportLastSubmissionFlowScreen(Screen.TEST_RESULT)
-        submissionRepository.setViewedTestResult()
+        submissionRepository.setViewedTestResult(type = coronaTestType)
         testResultAvailableNotificationService.cancelTestResultAvailableNotification()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionContactViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionContactViewModel.kt
index 37840183038353ac054037063d756fed0ee4e855..dc290b19fe035ca2cd2dd8caf049a6a957012ea0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionContactViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionContactViewModel.kt
@@ -9,16 +9,11 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 class SubmissionContactViewModel @AssistedInject constructor() : CWAViewModel() {
 
     val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
-    val dial = SingleLiveEvent<Unit>()
 
     fun onBackPressed() {
         routeToScreen.postValue(SubmissionNavigationEvents.NavigateToDispatcher)
     }
 
-    fun onDialPressed() {
-        dial.postValue(Unit)
-    }
-
     fun onEnterTanPressed() {
         routeToScreen.postValue(SubmissionNavigationEvents.NavigateToTAN)
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt
index da6c4e5148d24e6535c49def2d4de866d86047c9..8dad6e8631dcf73bd095391cfe2b28d55b537bb4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt
@@ -7,11 +7,14 @@ import androidx.navigation.NavDirections
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.Screen
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
+import de.rki.coronawarnapp.presencetracing.checkins.common.completedCheckIns
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.auto.AutoSubmission
@@ -30,8 +33,15 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
     tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory,
     interoperabilityRepository: InteroperabilityRepository,
     private val submissionRepository: SubmissionRepository,
+    private val checkInRepository: CheckInRepository,
     private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
+    init {
+        Timber.v("init() coronaTestType=%s", coronaTestType)
+    }
 
     val routeToScreen = SingleLiveEvent<NavDirections>()
 
@@ -48,36 +58,44 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
 
     private val tekHistoryUpdater = tekHistoryUpdaterFactory.create(
         object : TEKHistoryUpdater.Callback {
-            override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
-                Timber.d("onTEKAvailable(tek.size=%d)", teks.size)
-                autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+            override fun onTEKAvailable(teks: List<TemporaryExposureKey>) = launch {
+                Timber.tag(TAG).d("onTEKAvailable(tek.size=%d)", teks.size)
                 showKeysRetrievalProgress.postValue(false)
-                routeToScreen.postValue(
+
+                val completedCheckInsExist = checkInRepository.completedCheckIns.first().isNotEmpty()
+                val navDirections = if (completedCheckInsExist) {
+                    Timber.tag(TAG).d("Navigate to CheckInsConsentFragment")
+                    SubmissionResultPositiveOtherWarningNoConsentFragmentDirections
+                        .actionSubmissionResultPositiveOtherWarningNoConsentFragmentToCheckInsConsentFragment()
+                } else {
+                    autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+                    Timber.tag(TAG).d("Navigate to SubmissionResultReadyFragment")
                     SubmissionResultPositiveOtherWarningNoConsentFragmentDirections
                         .actionSubmissionResultPositiveOtherWarningNoConsentFragmentToSubmissionResultReadyFragment()
-                )
+                }
+                routeToScreen.postValue(navDirections)
             }
 
             override fun onTEKPermissionDeclined() {
-                Timber.d("onTEKPermissionDeclined")
+                Timber.tag(TAG).d("onTEKPermissionDeclined")
                 showKeysRetrievalProgress.postValue(false)
                 // stay on screen
             }
 
             override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) {
-                Timber.d("onTracingConsentRequired")
+                Timber.tag(TAG).d("onTracingConsentRequired")
                 showKeysRetrievalProgress.postValue(false)
                 showTracingConsentDialog.postValue(onConsentResult)
             }
 
             override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) {
-                Timber.d("onPermissionRequired")
+                Timber.tag(TAG).d("onPermissionRequired")
                 showKeysRetrievalProgress.postValue(false)
                 showPermissionRequest.postValue(permissionRequest)
             }
 
             override fun onError(error: Throwable) {
-                Timber.e(error, "Couldn't access temporary exposure key history.")
+                Timber.tag(TAG).e(error, "Couldn't access temporary exposure key history.")
                 showKeysRetrievalProgress.postValue(false)
                 error.report(ExceptionCategory.EXPOSURENOTIFICATION, "Failed to obtain TEKs.")
             }
@@ -91,15 +109,15 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
         )
     }
 
-    fun onConsentButtonClicked() {
-        showKeysRetrievalProgress.value = true
-        submissionRepository.giveConsentToSubmission()
+    fun onConsentButtonClicked() = launch {
+        showKeysRetrievalProgress.postValue(true)
+        submissionRepository.giveConsentToSubmission(type = coronaTestType)
         launch {
             if (enfClient.isTracingEnabled.first()) {
-                Timber.d("tekHistoryUpdater.updateTEKHistoryOrRequestPermission()")
+                Timber.tag(TAG).d("tekHistoryUpdater.updateTEKHistoryOrRequestPermission()")
                 tekHistoryUpdater.updateTEKHistoryOrRequestPermission()
             } else {
-                Timber.d("showEnableTracingEvent:Unit")
+                Timber.tag(TAG).d("showEnableTracingEvent:Unit")
                 showKeysRetrievalProgress.postValue(false)
                 showEnableTracingEvent.postValue(Unit)
             }
@@ -107,6 +125,7 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
     }
 
     fun onDataPrivacyClick() {
+        Timber.tag(TAG).d("onDataPrivacyClick")
         routeToScreen.postValue(
             SubmissionResultPositiveOtherWarningNoConsentFragmentDirections
                 .actionSubmissionResultPositiveOtherWarningNoConsentFragmentToInformationPrivacyFragment()
@@ -114,6 +133,7 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
     }
 
     fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        Timber.tag(TAG).d("handleActivityResult($resultCode)")
         showKeysRetrievalProgress.value = true
         tekHistoryUpdater.handleActivityResult(requestCode, resultCode, data)
     }
@@ -126,4 +146,8 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
     interface Factory : CWAViewModelFactory<SubmissionResultPositiveOtherWarningNoConsentViewModel> {
         fun create(): SubmissionResultPositiveOtherWarningNoConsentViewModel
     }
+
+    companion object {
+        private const val TAG = "WarnNoConsentViewModel"
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt
index 8e76e60416cfd52d94eb293d41942a579f7c85d1..10e46bb07917a4188943464085ef60fd9d28257c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt
@@ -3,22 +3,36 @@ package de.rki.coronawarnapp.ui.submission.yourconsent
 import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import timber.log.Timber
 
 class SubmissionYourConsentViewModel @AssistedInject constructor(
     val dispatcherProvider: DispatcherProvider,
     interoperabilityRepository: InteroperabilityRepository,
     val submissionRepository: SubmissionRepository
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+    // TODO Use navargs to supply this
+    private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR
+
+    init {
+        Timber.v("init() coronaTestType=%s", coronaTestType)
+    }
 
     val clickEvent: SingleLiveEvent<SubmissionYourConsentEvents> = SingleLiveEvent()
-    val consent = submissionRepository.hasGivenConsentToSubmission.asLiveData()
+    private val consentFlow = submissionRepository.testForType(type = coronaTestType)
+        .filterNotNull()
+        .map { it.isAdvancedConsentGiven }
+    val consent = consentFlow.asLiveData(context = dispatcherProvider.Default)
+
     val countryList = interoperabilityRepository.countryList
         .asLiveData(context = dispatcherProvider.Default)
 
@@ -26,13 +40,13 @@ class SubmissionYourConsentViewModel @AssistedInject constructor(
         clickEvent.postValue(SubmissionYourConsentEvents.GoBack)
     }
 
-    fun switchConsent() {
-        launch {
-            if (submissionRepository.hasGivenConsentToSubmission.first()) {
-                submissionRepository.revokeConsentToSubmission()
-            } else {
-                submissionRepository.giveConsentToSubmission()
-            }
+    fun switchConsent() = launch {
+        if (consentFlow.first()) {
+            Timber.v("revokeConsentToSubmission()")
+            submissionRepository.revokeConsentToSubmission(type = coronaTestType)
+        } else {
+            Timber.v("giveConsentToSubmission()")
+            submissionRepository.giveConsentToSubmission(type = coronaTestType)
         }
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt
index 2170fe780f40ff8847bb557fc3d63aaad4e3904f..11c140a9ff906b736dbafec4afda1f2c1aeddbc3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt
@@ -9,14 +9,14 @@ import androidx.appcompat.content.res.AppCompatResources.getDrawable
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.content.withStyledAttributes
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.databinding.ViewTestResultSectionBinding
+import de.rki.coronawarnapp.submission.toDeviceUIState
 import de.rki.coronawarnapp.util.ContextExtensions.getDrawableCompat
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUIFormat
 import de.rki.coronawarnapp.util.formatter.formatTestResult
-import java.util.Date
+import org.joda.time.Instant
 
 /**
  * The [TestResultSectionView] Displays the appropriate test result.
@@ -47,49 +47,46 @@ constructor(
         }
     }
 
-    fun setTestResultSection(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?, registeredAt: Date?) {
+    fun setTestResultSection(coronaTest: CoronaTest?) {
         binding.apply {
             testResultSectionHeadline.text = context.getString(R.string.test_result_card_headline)
-            testResultSectionRegisteredAtText.text = formatTestResultRegisteredAtText(registeredAt)
-            val testResultIcon = formatTestStatusIcon(uiState)
+            testResultSectionRegisteredAtText.text = formatTestResultRegisteredAtText(coronaTest?.registeredAt)
+            val testResultIcon = formatTestStatusIcon(coronaTest)
             testResultSectionStatusIcon.setImageDrawable(testResultIcon)
-            testResultSectionContent.text = formatTestResultSectionContent(uiState)
+            testResultSectionContent.text = formatTestResultSectionContent(coronaTest)
         }
     }
 
-    private fun formatTestStatusIcon(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Drawable? {
-        return uiState.withSuccess(R.drawable.ic_test_result_illustration_invalid) {
-            when (it) {
-                DeviceUIState.PAIRED_NO_RESULT -> R.drawable.ic_test_result_illustration_pending
-                DeviceUIState.PAIRED_POSITIVE_TELETAN,
-                DeviceUIState.PAIRED_POSITIVE -> R.drawable.ic_test_result_illustration_positive
-                DeviceUIState.PAIRED_NEGATIVE -> R.drawable.ic_test_result_illustration_negative
-                DeviceUIState.PAIRED_ERROR,
-                DeviceUIState.PAIRED_REDEEMED -> R.drawable.ic_test_result_illustration_invalid
-                else -> R.drawable.ic_test_result_illustration_invalid
-            }
-        }.let { context.getDrawableCompat(it) }
+    private fun formatTestStatusIcon(coronaTest: CoronaTest?): Drawable? {
+        val drawable = when (coronaTest?.testResult.toDeviceUIState()) {
+            DeviceUIState.PAIRED_NO_RESULT -> R.drawable.ic_test_result_illustration_pending
+            DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            DeviceUIState.PAIRED_POSITIVE -> R.drawable.ic_test_result_illustration_positive
+            DeviceUIState.PAIRED_NEGATIVE -> R.drawable.ic_test_result_illustration_negative
+            DeviceUIState.PAIRED_ERROR,
+            DeviceUIState.PAIRED_REDEEMED -> R.drawable.ic_test_result_illustration_invalid
+            else -> R.drawable.ic_test_result_illustration_invalid
+        }
+        return context.getDrawableCompat(drawable)
     }
 
-    private fun formatTestResultRegisteredAtText(registeredAt: Date?): String {
+    private fun formatTestResultRegisteredAtText(registeredAt: Instant?): String {
         return context.getString(R.string.test_result_card_registered_at_text)
-            .format(registeredAt?.toUIFormat(context))
+            .format(registeredAt?.toDate()?.toUIFormat(context))
     }
 
-    private fun formatTestResultSectionContent(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Spannable {
-        return uiState.withSuccess(SpannableString("")) {
-            when (it) {
-                DeviceUIState.PAIRED_NO_RESULT ->
-                    SpannableString(context.getString(R.string.test_result_card_status_pending))
-                DeviceUIState.PAIRED_ERROR,
-                DeviceUIState.PAIRED_REDEEMED ->
-                    SpannableString(context.getString(R.string.test_result_card_status_invalid))
+    private fun formatTestResultSectionContent(coronaTest: CoronaTest?): Spannable {
+        return when (val uiState = coronaTest?.testResult.toDeviceUIState()) {
+            DeviceUIState.PAIRED_NO_RESULT ->
+                SpannableString(context.getString(R.string.test_result_card_status_pending))
+            DeviceUIState.PAIRED_ERROR,
+            DeviceUIState.PAIRED_REDEEMED ->
+                SpannableString(context.getString(R.string.test_result_card_status_invalid))
 
-                DeviceUIState.PAIRED_POSITIVE,
-                DeviceUIState.PAIRED_POSITIVE_TELETAN,
-                DeviceUIState.PAIRED_NEGATIVE -> SpannableString(formatTestResult(context, uiState))
-                else -> SpannableString("")
-            }
+            DeviceUIState.PAIRED_POSITIVE,
+            DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            DeviceUIState.PAIRED_NEGATIVE -> SpannableString(formatTestResult(context, uiState))
+            else -> SpannableString("")
         }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
index 9f43056902f79eba4ee9652a49d09fcf2a7fc151..6bcdf97ff9844aa616c2b6b62c890a3447cdd9bd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
@@ -6,6 +6,7 @@ import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.bugreporting.BugReportingSettings
 import de.rki.coronawarnapp.contactdiary.storage.ContactDiaryPreferences
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.datadonation.analytics.Analytics
 import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
 import de.rki.coronawarnapp.datadonation.survey.SurveySettings
@@ -60,6 +61,7 @@ class DataReset @Inject constructor(
     private val checkInRepository: CheckInRepository,
     private val traceLocationSettings: TraceLocationSettings,
     private val traceWarningRepository: TraceWarningRepository,
+    private val coronaTestRepository: CoronaTestRepository,
 ) {
 
     private val mutex = Mutex()
@@ -101,6 +103,7 @@ class DataReset @Inject constructor(
         traceWarningRepository.clear()
         traceLocationRepository.deleteAllTraceLocations()
         checkInRepository.clear()
+        coronaTestRepository.clear()
 
         Timber.w("CWA LOCAL DATA DELETION COMPLETED.")
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt
index f7f9f38cfecdbeb0fe260e791236ceb6d3cb8e9b..64dcf305d5544431a1ffd6e5e5a118a5a8d303a2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt
@@ -93,18 +93,6 @@ object TimeAndDateExtensions {
         TimeUnit.MILLISECONDS.toSeconds(this) % TimeUnit.MINUTES.toSeconds(1)
     )
 
-    /**
-     * Calculates the difference between two timestamps in Days Units
-     *
-     * @return Long
-     *
-     * @see TimeUnit
-     */
-    fun calculateDays(firstDate: Long, secondDate: Long): Long {
-        val millionSeconds = secondDate - firstDate
-        return TimeUnit.MILLISECONDS.toDays(millionSeconds)
-    }
-
     fun LocalDate.ageInDays(now: LocalDate) = Days.daysBetween(this, now).days
 
     fun Instant.toLocalDateUtc(): LocalDate = this.toDateTime(DateTimeZone.UTC).toLocalDate()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
index 9ddda9b3ec68c4068a7577798d6b3dfeb7d6377d..fafb7e29de202995224714daf4bbcfe5178112b8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
@@ -12,6 +12,7 @@ import de.rki.coronawarnapp.bugreporting.BugReportingModule
 import de.rki.coronawarnapp.bugreporting.BugReportingSharedModule
 import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
 import de.rki.coronawarnapp.coronatest.CoronaTestModule
+import de.rki.coronawarnapp.coronatest.server.VerificationModule
 import de.rki.coronawarnapp.datadonation.DataDonationModule
 import de.rki.coronawarnapp.diagnosiskeys.DiagnosisKeysModule
 import de.rki.coronawarnapp.diagnosiskeys.DownloadDiagnosisKeysTaskModule
@@ -43,7 +44,6 @@ import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool
 import de.rki.coronawarnapp.util.security.SecurityModule
 import de.rki.coronawarnapp.util.serialization.SerializationModule
 import de.rki.coronawarnapp.util.worker.WorkerBinder
-import de.rki.coronawarnapp.verification.VerificationModule
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import javax.inject.Singleton
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt
index 64929458b059e6c40a2ea3e1705d438707916f80..b2eea5c60ce439ad2bb43140aabacaf9e1a4a151 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt
@@ -64,22 +64,21 @@ class EncryptedPreferencesMigration @Inject constructor(
             }
             onboardingSettings.isBackgroundCheckDone = isBackgroundCheckDone()
         }
-
+        @Suppress("DEPRECATION")
         TracingLocalData(encryptedSharedPreferences).apply {
-            tracingSettings.initialPollingForTestResultTimeStamp = initialPollingForTestResultTimeStamp()
-            tracingSettings.isTestResultAvailableNotificationSent = isTestResultAvailableNotificationSent()
+            tracingSettings.initialPollingForTestResultTimeStampMigration = initialPollingForTestResultTimeStamp()
+            tracingSettings.isTestResultAvailableNotificationSentMigration = isTestResultAvailableNotificationSent()
             tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel.update { isUserToBeNotifiedOfLoweredRiskLevel() }
             tracingSettings.isConsentGiven = initialTracingActivationTimestamp() != 0L
         }
-
+        @Suppress("DEPRECATION")
         SubmissionLocalData(encryptedSharedPreferences).apply {
-            submissionSettings.registrationToken.update {
-                registrationToken()
-            }
-            submissionSettings.initialTestResultReceivedAt = initialTestResultReceivedTimestamp().toInstantOrNull()
-            submissionSettings.devicePairingSuccessfulAt = devicePairingSuccessfulTimestamp().toInstantOrNull()
-            submissionSettings.isSubmissionSuccessful = numberOfSuccessfulSubmissions() >= 1
-            submissionSettings.isAllowedToSubmitKeys = isAllowedToSubmitDiagnosisKeys()
+            submissionSettings.registrationTokenMigration = registrationToken()
+            submissionSettings.initialTestResultReceivedAtMigration =
+                initialTestResultReceivedTimestamp().toInstantOrNull()
+            submissionSettings.devicePairingSuccessfulAtMigration = devicePairingSuccessfulTimestamp().toInstantOrNull()
+            submissionSettings.isSubmissionSuccessfulMigration = numberOfSuccessfulSubmissions() >= 1
+            submissionSettings.isAllowedToSubmitKeysMigration = isAllowedToSubmitDiagnosisKeys()
         }
         Timber.i("copyData(): EncryptedPreferences have been copied.")
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt
index 9957c98680e872732a2d0bdde917a15bb0a8c5f6..4983861085b09dbad197b772a0847b2a331a4faf 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt
@@ -8,14 +8,11 @@ import android.text.Spannable
 import android.text.SpannableString
 import android.text.SpannableStringBuilder
 import android.text.style.ForegroundColorSpan
-import android.view.View
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.submission.Symptoms
 import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat
 import de.rki.coronawarnapp.util.ContextExtensions.getDrawableCompat
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUIFormat
 import java.util.Date
 import java.util.Locale
@@ -68,29 +65,23 @@ fun formatSymptomBackgroundButtonStyleByState(
         R.color.colorCalendarBackgroundUnselected
     )
 
-fun formatTestResultStatusText(context: Context, uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): String =
-    uiState.withSuccess(R.string.test_result_card_status_invalid) {
-        when (it) {
-            DeviceUIState.PAIRED_NEGATIVE -> R.string.test_result_card_status_negative
-            DeviceUIState.PAIRED_POSITIVE,
-            DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.string.test_result_card_status_positive
-            else -> R.string.test_result_card_status_invalid
-        }
-    }.let { context.getString(it) }
-
-fun formatTestResultStatusColor(context: Context, uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
-    uiState.withSuccess(R.color.colorTextSemanticRed) {
-        when (it) {
-            DeviceUIState.PAIRED_NEGATIVE -> R.color.colorTextSemanticGreen
-            DeviceUIState.PAIRED_POSITIVE,
-            DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.color.colorTextSemanticRed
-            else -> R.color.colorTextSemanticRed
-        }
-    }.let { context.getColorCompat(it) }
+fun formatTestResultStatusText(context: Context, uiState: DeviceUIState): String = when (uiState) {
+    DeviceUIState.PAIRED_NEGATIVE -> R.string.test_result_card_status_negative
+    DeviceUIState.PAIRED_POSITIVE,
+    DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.string.test_result_card_status_positive
+    else -> R.string.test_result_card_status_invalid
+}.let { context.getString(it) }
+
+fun formatTestResultStatusColor(context: Context, uiState: DeviceUIState): Int = when (uiState) {
+    DeviceUIState.PAIRED_NEGATIVE -> R.color.colorTextSemanticGreen
+    DeviceUIState.PAIRED_POSITIVE,
+    DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.color.colorTextSemanticRed
+    else -> R.color.colorTextSemanticRed
+}.let { context.getColorCompat(it) }
 
 fun formatTestResult(
     context: Context,
-    uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?
+    uiState: DeviceUIState
 ): Spannable {
     return SpannableStringBuilder()
         .append(context.getString(R.string.test_result_card_virus_name_text))
@@ -104,51 +95,37 @@ fun formatTestResult(
 
 fun formatTestResultCardContent(
     context: Context,
-    uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?
+    uiState: DeviceUIState
 ): Spannable {
-    return uiState.withSuccess(SpannableString("")) {
-        when (it) {
-            DeviceUIState.PAIRED_NO_RESULT ->
-                SpannableString(context.getString(R.string.test_result_card_status_pending))
-            DeviceUIState.PAIRED_ERROR,
-            DeviceUIState.PAIRED_REDEEMED ->
-                SpannableString(context.getString(R.string.test_result_card_status_invalid))
-
-            DeviceUIState.PAIRED_POSITIVE,
-            DeviceUIState.PAIRED_POSITIVE_TELETAN,
-            DeviceUIState.PAIRED_NEGATIVE -> SpannableString(formatTestResult(context, uiState))
-            else -> SpannableString("")
-        }
+    return when (uiState) {
+        DeviceUIState.PAIRED_NO_RESULT ->
+            SpannableString(context.getString(R.string.test_result_card_status_pending))
+        DeviceUIState.PAIRED_ERROR,
+        DeviceUIState.PAIRED_REDEEMED ->
+            SpannableString(context.getString(R.string.test_result_card_status_invalid))
+
+        DeviceUIState.PAIRED_POSITIVE,
+        DeviceUIState.PAIRED_POSITIVE_TELETAN,
+        DeviceUIState.PAIRED_NEGATIVE -> SpannableString(formatTestResult(context, uiState))
+        else -> SpannableString("")
     }
 }
 
-fun formatTestStatusIcon(context: Context, uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Drawable? {
-    return uiState.withSuccess(R.drawable.ic_test_result_illustration_invalid) {
-        when (it) {
-            DeviceUIState.PAIRED_NO_RESULT -> R.drawable.ic_test_result_illustration_pending
-            DeviceUIState.PAIRED_POSITIVE_TELETAN,
-            DeviceUIState.PAIRED_POSITIVE -> R.drawable.ic_test_result_illustration_positive
-            DeviceUIState.PAIRED_NEGATIVE -> R.drawable.ic_test_result_illustration_negative
-            DeviceUIState.PAIRED_ERROR,
-            DeviceUIState.PAIRED_REDEEMED -> R.drawable.ic_test_result_illustration_invalid
-            else -> R.drawable.ic_test_result_illustration_invalid
-        }
-    }.let { context.getDrawableCompat(it) }
-}
+fun formatTestStatusIcon(context: Context, uiState: DeviceUIState): Drawable? = when (uiState) {
+    DeviceUIState.PAIRED_NO_RESULT -> R.drawable.ic_test_result_illustration_pending
+    DeviceUIState.PAIRED_POSITIVE_TELETAN,
+    DeviceUIState.PAIRED_POSITIVE -> R.drawable.ic_test_result_illustration_positive
+    DeviceUIState.PAIRED_NEGATIVE -> R.drawable.ic_test_result_illustration_negative
+    DeviceUIState.PAIRED_ERROR,
+    DeviceUIState.PAIRED_REDEEMED -> R.drawable.ic_test_result_illustration_invalid
+    else -> R.drawable.ic_test_result_illustration_invalid
+}.let { context.getDrawableCompat(it) }
 
 fun formatTestResultRegisteredAtText(context: Context, registeredAt: Date?): String {
     return context.getString(R.string.test_result_card_registered_at_text)
         .format(registeredAt?.toUIFormat(context))
 }
 
-fun formatTestResultPendingStepsVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
-    uiState.withSuccess(View.GONE) { formatVisibility(it == DeviceUIState.PAIRED_NO_RESULT) }
-
-fun formatTestResultInvalidStepsVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
-    uiState.withSuccess(View.GONE) {
-        formatVisibility(it == DeviceUIState.PAIRED_ERROR || it == DeviceUIState.PAIRED_REDEEMED)
-    }
-
 fun formatCountryIsoTagToLocalizedName(isoTag: String?): String {
     val country = if (isoTag != null) Locale("", isoTag).displayCountry else ""
     return country
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/TestResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/TestResult.kt
deleted file mode 100644
index 912799429291f7b7e5c99cc9d3f0629b3acbad94..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/TestResult.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package de.rki.coronawarnapp.util.formatter
-
-enum class TestResult(val value: Int) {
-    PENDING(0),
-    NEGATIVE(1),
-    POSITIVE(2),
-    INVALID(3),
-    REDEEMED(4);
-
-    companion object {
-        fun fromInt(value: Int) = values().first { it.value == value }
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt
index 400286b19a31baefe0423f273f64fc5d4b8bd839..90dc9a5a50241e1c52314f239eaece15e33a28cd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt
@@ -8,14 +8,14 @@ import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryRetentionWorker
 import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsPeriodicWorker
 import de.rki.coronawarnapp.deadman.DeadmanNotificationOneTimeWorker
 import de.rki.coronawarnapp.deadman.DeadmanNotificationPeriodicWorker
+import de.rki.coronawarnapp.deniability.BackgroundNoiseOneTimeWorker
+import de.rki.coronawarnapp.deniability.BackgroundNoisePeriodicWorker
 import de.rki.coronawarnapp.diagnosiskeys.execution.DiagnosisKeyRetrievalWorker
 import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpPeriodicWorker
 import de.rki.coronawarnapp.nearby.ExposureStateUpdateWorker
 import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOutWorker
 import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningWorker
 import de.rki.coronawarnapp.submission.auto.SubmissionWorker
-import de.rki.coronawarnapp.worker.BackgroundNoiseOneTimeWorker
-import de.rki.coronawarnapp.worker.BackgroundNoisePeriodicWorker
 import de.rki.coronawarnapp.worker.DiagnosisTestResultRetrievalPeriodicWorker
 
 @Module
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundConstants.kt
index 7e7340ebb38c7a76df1ccdbfdc4059cadf3b6057..b250c13b7b41292d63406c65e60ded636630426d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundConstants.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundConstants.kt
@@ -9,36 +9,6 @@ import java.util.concurrent.TimeUnit
  */
 object BackgroundConstants {
 
-    /**
-     * Tag for background polling tp check test result periodic work
-     */
-    const val DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG = "DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER"
-
-    /**
-     * Tag for background noise playbook periodic work
-     */
-    const val BACKGROUND_NOISE_PERIODIC_WORKER_TAG = "BACKGROUND_NOISE_PERIODIC_WORKER"
-
-    /**
-     * Tag for background noise playbook one time work
-     */
-    const val BACKGROUND_NOISE_ONE_TIME_WORKER_TAG = "BACKGROUND_NOISE_PERIODIC_WORKER"
-
-    /**
-     * Unique name for diagnosis test result retrieval periodic work
-     */
-    const val DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME = "DiagnosisTestResultBackgroundPeriodicWork"
-
-    /**
-     * Unique name for background noise playbook periodic work
-     */
-    const val BACKGROUND_NOISE_PERIODIC_WORK_NAME = "BackgroundNoisePeriodicWork"
-
-    /**
-     * Unique name for background noise playbook one time work
-     */
-    const val BACKGROUND_NOISE_ONE_TIME_WORK_NAME = "BackgroundNoiseOneTimeWork"
-
     /**
      * Total minutes in one day
      */
@@ -57,13 +27,6 @@ object BackgroundConstants {
      */
     const val KIND_DELAY = 1L
 
-    /**
-     * Kind initial delay in minutes for periodic work for accessibility reason
-     *
-     * @see TimeUnit.SECONDS
-     */
-    const val DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY = 10L
-
     /**
      * Retries before work would set as FAILED
      */
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkBuilder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkBuilder.kt
deleted file mode 100644
index 2c03e9544d7eb89824da44430afdf47ad39f0eb8..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkBuilder.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package de.rki.coronawarnapp.worker
-
-import androidx.work.BackoffPolicy
-import androidx.work.OneTimeWorkRequestBuilder
-import androidx.work.PeriodicWorkRequestBuilder
-import de.rki.coronawarnapp.worker.BackgroundWorkScheduler.WorkTag
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class BackgroundWorkBuilder @Inject constructor() {
-
-    /**
-     * Build diagnosis Test Result periodic work request
-     * Set "kind delay" for accessibility reason.
-     *
-     * @return PeriodicWorkRequest
-     *
-     * @see WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER
-     * @see BackgroundConstants.KIND_DELAY
-     */
-    fun buildDiagnosisTestResultRetrievalPeriodicWork() =
-        PeriodicWorkRequestBuilder<DiagnosisTestResultRetrievalPeriodicWorker>(
-            BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(),
-            TimeUnit.MINUTES
-        )
-            .addTag(WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER.tag)
-            .setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork())
-            .setInitialDelay(
-                BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY,
-                TimeUnit.SECONDS
-            ).setBackoffCriteria(
-                BackoffPolicy.LINEAR,
-                BackgroundConstants.KIND_DELAY,
-                TimeUnit.MINUTES
-            )
-            .build()
-
-    /**
-     * Build background noise one time work request
-     * Set BackgroundNoiseOneTimeWorkDelay for timing randomness.
-     *
-     * @return PeriodicWorkRequest
-     *
-     * @see WorkTag.BACKGROUND_NOISE_ONE_TIME_WORKER
-     * @see BackgroundWorkHelper.getBackgroundNoiseOneTimeWorkDelay
-     */
-    fun buildBackgroundNoiseOneTimeWork() =
-        OneTimeWorkRequestBuilder<BackgroundNoiseOneTimeWorker>()
-            .addTag(WorkTag.BACKGROUND_NOISE_ONE_TIME_WORKER.tag)
-            .setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork())
-            .setInitialDelay(
-                BackgroundWorkHelper.getBackgroundNoiseOneTimeWorkDelay(),
-                TimeUnit.HOURS
-            ).setBackoffCriteria(
-                BackoffPolicy.LINEAR,
-                BackgroundConstants.KIND_DELAY,
-                TimeUnit.MINUTES
-            )
-            .build()
-
-    /**
-     * Build background noise periodic work request
-     * Set "kind delay" for accessibility reason.
-     *
-     * @return PeriodicWorkRequest
-     *
-     * @see BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
-     * @see WorkTag.BACKGROUND_NOISE_PERIODIC_WORKER
-     * @see BackgroundConstants.KIND_DELAY
-     */
-    fun buildBackgroundNoisePeriodicWork() =
-        PeriodicWorkRequestBuilder<BackgroundNoisePeriodicWorker>(
-            BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION,
-            TimeUnit.HOURS
-        )
-            .addTag(WorkTag.BACKGROUND_NOISE_PERIODIC_WORKER.tag)
-            .setInitialDelay(
-                BackgroundConstants.KIND_DELAY,
-                TimeUnit.SECONDS
-            ).setBackoffCriteria(
-                BackoffPolicy.LINEAR,
-                BackgroundConstants.KIND_DELAY,
-                TimeUnit.MINUTES
-            )
-            .build()
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt
index 4f83979c39c974861e80c04df100c1439be3b50e..706db5566173d3b84763594291e1e025f2fe07f6 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt
@@ -1,16 +1,12 @@
 package de.rki.coronawarnapp.worker
 
-import androidx.work.ExistingPeriodicWorkPolicy
-import androidx.work.ExistingWorkPolicy
-import androidx.work.Operation
-import androidx.work.WorkInfo
-import androidx.work.WorkManager
-import de.rki.coronawarnapp.CoronaWarnApplication
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler
+import de.rki.coronawarnapp.deniability.NoiseScheduler
 import de.rki.coronawarnapp.risk.execution.RiskWorkScheduler
-import de.rki.coronawarnapp.storage.TracingSettings
-import de.rki.coronawarnapp.submission.SubmissionSettings
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 import timber.log.Timber
-import java.util.concurrent.ExecutionException
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -23,228 +19,31 @@ import javax.inject.Singleton
  */
 @Singleton
 class BackgroundWorkScheduler @Inject constructor(
-    private val backgroundWorkBuilder: BackgroundWorkBuilder,
-    private val submissionSettings: SubmissionSettings,
-    private val tracingSettings: TracingSettings,
-    private val riskWorkScheduler: RiskWorkScheduler
+    private val riskWorkScheduler: RiskWorkScheduler,
+    private val coronaTestRepository: CoronaTestRepository,
+    private val testResultScheduler: TestResultScheduler,
+    private val noiseScheduler: NoiseScheduler,
 ) {
 
-    /**
-     * Enum class for work tags
-     *
-     * @param tag the tag of the worker
-     *
-     * @see BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG
-     * @see BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORKER_TAG
-     * @see BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORKER_TAG
-     */
-    enum class WorkTag(val tag: String) {
-        DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG),
-        BACKGROUND_NOISE_ONE_TIME_WORKER(BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORKER_TAG),
-        BACKGROUND_NOISE_PERIODIC_WORKER(BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORKER_TAG)
-    }
-
-    /**
-     * Enum class for work type
-     *
-     * @param uniqueName the unique name of specified work
-     *
-     * @see BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME
-     * @see BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORK_NAME
-     * @see BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORK_NAME
-     */
-    enum class WorkType(val uniqueName: String) {
-        DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME),
-        BACKGROUND_NOISE_PERIODIC_WORK(BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORK_NAME),
-        BACKGROUND_NOISE_ONE_TIME_WORK(BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORK_NAME)
-    }
-
-    /**
-     * Work manager instance
-     */
-    private val workManager by lazy { WorkManager.getInstance(CoronaWarnApplication.getAppContext()) }
-
-    /**
-     * Start work scheduler
-     * Checks if periodic worker was already scheduled. If not - reschedule it again.
-     * For [WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER] also checks if User is Registered
-     *
-     * @see de.rki.coronawarnapp.submission.SubmissionSettings.registrationToken
-     * @see isWorkActive
-     */
     fun startWorkScheduler() {
         Timber.d("startWorkScheduler()")
         riskWorkScheduler.setPeriodicRiskCalculation(enabled = true)
 
-        if (!submissionSettings.isSubmissionSuccessful) {
-            if (!isWorkActive(WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER.tag) &&
-                submissionSettings.registrationToken.value != null &&
-                !tracingSettings.isTestResultAvailableNotificationSent
-            ) {
-                WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.start()
-                tracingSettings.initialPollingForTestResultTimeStamp = System.currentTimeMillis()
-                Timber.d("Starting DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER")
-            }
+        // TODO Blocking isn't very nice here...
+        val coronatests = runBlocking { coronaTestRepository.coronaTests.first() }
+
+        val isSubmissionSuccessful = coronatests.any { it.isSubmitted }
+        val hasPendingTests = coronatests.any { !it.isResultAvailableNotificationSent }
+
+        if (!isSubmissionSuccessful && hasPendingTests) {
+            testResultScheduler.setPeriodicTestPolling(enabled = true)
         }
     }
 
-    /**
-     * Stop work scheduler
-     * Stops all background work by tag.
-     */
     fun stopWorkScheduler() {
-        WorkTag.values().map { workTag: WorkTag ->
-            workManager.cancelAllWorkByTag(workTag.tag)
-                .also { it.logOperationCancelByTag(workTag) }
-        }
+        noiseScheduler.setPeriodicNoise(enabled = false)
         riskWorkScheduler.setPeriodicRiskCalculation(enabled = false)
+        testResultScheduler.setPeriodicTestPolling(enabled = false)
         Timber.d("All Background Jobs Stopped")
     }
-
-    /**
-     * Stop work by unique name
-     *
-     * @return Operation
-     *
-     * @see WorkType
-     */
-    fun WorkType.stop(): Operation =
-        workManager.cancelUniqueWork(this.uniqueName)
-
-    /**
-     * Checks if defined work is active
-     * Non-active means worker was Cancelled, Failed or have not been enqueued at all
-     *
-     * @param tag String tag of the worker
-     *
-     * @return Boolean
-     *
-     * @see WorkInfo.State.CANCELLED
-     * @see WorkInfo.State.FAILED
-     */
-    private fun isWorkActive(tag: String): Boolean {
-        val workStatus = workManager.getWorkInfosByTag(tag)
-        var result = true
-        try {
-            val workInfoList = workStatus.get()
-            if (workInfoList.size == 0) result = false
-            for (info in workInfoList) {
-                if (info.state == WorkInfo.State.CANCELLED || info.state == WorkInfo.State.FAILED) {
-                    result = false
-                }
-            }
-        } catch (e: ExecutionException) {
-            result = false
-        } catch (e: InterruptedException) {
-            result = false
-        }
-        return result
-    }
-
-    fun scheduleDiagnosisTestResultPeriodicWork() {
-        WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.start()
-    }
-
-    fun stopDiagnosisTestResultPeriodicWork() {
-        WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.stop()
-    }
-
-    /**
-     * Schedule background noise periodic work
-     *
-     * @see WorkType.BACKGROUND_NOISE_PERIODIC_WORK
-     */
-    fun scheduleBackgroundNoisePeriodicWork() {
-        WorkType.BACKGROUND_NOISE_PERIODIC_WORK.start()
-    }
-
-    fun stopBackgroundNoisePeriodicWork() {
-        WorkType.BACKGROUND_NOISE_PERIODIC_WORK.start()
-    }
-
-    /**
-     * Schedule background noise one time work
-     *
-     * @see WorkType.BACKGROUND_NOISE_ONE_TIME_WORK
-     */
-    fun scheduleBackgroundNoiseOneTimeWork() {
-        WorkType.BACKGROUND_NOISE_ONE_TIME_WORK.start()
-    }
-
-    /**
-     * Enqueue operation for work type defined in WorkType enum class
-     *
-     * @return Operation
-     *
-     * @see WorkType
-     */
-    private fun WorkType.start(): Operation = when (this) {
-        WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER -> enqueueDiagnosisTestResultBackgroundPeriodicWork()
-        WorkType.BACKGROUND_NOISE_PERIODIC_WORK -> enqueueBackgroundNoisePeriodicWork()
-        WorkType.BACKGROUND_NOISE_ONE_TIME_WORK -> enqueueBackgroundNoiseOneTimeWork()
-    }
-
-    /**
-     * Enqueue diagnosis Test Result periodic
-     * Show a Notification when new Test Results are in.
-     * Replace with new if older work exists.
-     *
-     * @return Operation
-     *
-     * @see WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER
-     */
-    private fun enqueueDiagnosisTestResultBackgroundPeriodicWork() =
-        workManager.enqueueUniquePeriodicWork(
-            WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.uniqueName,
-            ExistingPeriodicWorkPolicy.REPLACE,
-            backgroundWorkBuilder.buildDiagnosisTestResultRetrievalPeriodicWork()
-        ).also { it.logOperationSchedule(WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER) }
-
-    /**
-     * Enqueue background noise periodic
-     * Replace with new if older work exists.
-     *
-     * @return Operation
-     *
-     * @see WorkType.BACKGROUND_NOISE_PERIODIC_WORK
-     */
-    private fun enqueueBackgroundNoisePeriodicWork() =
-        workManager.enqueueUniquePeriodicWork(
-            WorkType.BACKGROUND_NOISE_PERIODIC_WORK.uniqueName,
-            ExistingPeriodicWorkPolicy.REPLACE,
-            backgroundWorkBuilder.buildBackgroundNoisePeriodicWork()
-        ).also { it.logOperationSchedule(WorkType.BACKGROUND_NOISE_PERIODIC_WORK) }
-
-    /**
-     * Enqueue background noise one time
-     * Replace with new if older work exists.
-     *
-     * @return Operation
-     *
-     * @see WorkType.BACKGROUND_NOISE_ONE_TIME_WORK
-     */
-    private fun enqueueBackgroundNoiseOneTimeWork() =
-        workManager.enqueueUniqueWork(
-            WorkType.BACKGROUND_NOISE_ONE_TIME_WORK.uniqueName,
-            ExistingWorkPolicy.REPLACE,
-            backgroundWorkBuilder.buildBackgroundNoiseOneTimeWork()
-        ).also { it.logOperationSchedule(WorkType.BACKGROUND_NOISE_ONE_TIME_WORK) }
-
-    /**
-     * Log operation schedule
-     */
-    private fun Operation.logOperationSchedule(workType: WorkType) =
-        this.result.addListener(
-            { Timber.d("${workType.uniqueName} completed.") },
-            { it.run() }
-        ).also { Timber.d("${workType.uniqueName} scheduled.") }
-
-    /**
-     * Log operation cancellation
-     */
-    private fun Operation.logOperationCancelByTag(workTag: WorkTag) =
-        this.result.addListener(
-            { Timber.d("All work with tag ${workTag.tag} canceled.") },
-            { it.run() }
-        ).also { Timber.d("Canceling all work with tag ${workTag.tag}") }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt
index 0b719ddd8021c35549a621f4c051c423cdf07c26..0d2b9c43a0cfa7841f4cfed0583b117d72075c71 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt
@@ -6,17 +6,20 @@ import androidx.work.WorkerParameters
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler
+import de.rki.coronawarnapp.coronatest.latestPCRT
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
 import de.rki.coronawarnapp.notification.GeneralNotifications
 import de.rki.coronawarnapp.notification.NotificationConstants
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
-import de.rki.coronawarnapp.service.submission.SubmissionService
-import de.rki.coronawarnapp.storage.TracingSettings
-import de.rki.coronawarnapp.submission.SubmissionSettings
-import de.rki.coronawarnapp.util.TimeAndDateExtensions
 import de.rki.coronawarnapp.util.TimeStamper
-import de.rki.coronawarnapp.util.formatter.TestResult
 import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory
+import kotlinx.coroutines.flow.first
+import org.joda.time.Duration
+import org.joda.time.Instant
 import timber.log.Timber
 
 /**
@@ -29,11 +32,9 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
     @Assisted workerParams: WorkerParameters,
     private val testResultAvailableNotificationService: TestResultAvailableNotificationService,
     private val notificationHelper: GeneralNotifications,
-    private val submissionSettings: SubmissionSettings,
-    private val submissionService: SubmissionService,
+    private val coronaTestRepository: CoronaTestRepository,
     private val timeStamper: TimeStamper,
-    private val tracingSettings: TracingSettings,
-    private val backgroundWorkScheduler: BackgroundWorkScheduler,
+    private val testResultScheduler: TestResultScheduler,
 ) : CoroutineWorker(context, workerParams) {
 
     override suspend fun doWork(): Result {
@@ -42,37 +43,39 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
         if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) {
             Timber.tag(TAG).d("$id doWork() failed after $runAttemptCount attempts. Rescheduling")
 
-            backgroundWorkScheduler.scheduleDiagnosisTestResultPeriodicWork()
+            testResultScheduler.setPeriodicTestPolling(enabled = true)
             Timber.tag(TAG).d("$id Rescheduled background worker")
 
             return Result.failure()
         }
         var result = Result.success()
         try {
-
-            if (abortConditionsMet(timeStamper.nowUTC.millis)) {
+            if (abortConditionsMet(timeStamper.nowUTC)) {
                 Timber.tag(TAG).d(" $id Stopping worker.")
                 stopWorker()
             } else {
                 Timber.tag(TAG).d(" $id Running worker.")
 
-                val registrationToken =
-                    submissionSettings.registrationToken.value ?: throw NoRegistrationTokenSetException()
-                val testResult = submissionService.asyncRequestTestResult(registrationToken)
+                val coronaTest = coronaTestRepository.refresh(
+                    type = CoronaTest.Type.PCR
+                ).single() as PCRCoronaTest
+                val testResult = coronaTest.testResult
+
                 Timber.tag(TAG).d("$id: Test Result retrieved is $testResult")
 
-                if (testResult == TestResult.NEGATIVE ||
-                    testResult == TestResult.POSITIVE ||
-                    testResult == TestResult.INVALID
+                if (testResult == CoronaTestResult.PCR_NEGATIVE ||
+                    testResult == CoronaTestResult.PCR_POSITIVE ||
+                    testResult == CoronaTestResult.PCR_INVALID
                 ) {
-                    sendTestResultAvailableNotification(testResult)
+                    sendTestResultAvailableNotification(coronaTest)
                     cancelRiskLevelScoreNotification()
                     Timber.tag(TAG)
                         .d("$id: Test Result available - notification sent & risk level notification canceled")
                     stopWorker()
                 }
             }
-        } catch (ex: Exception) {
+        } catch (e: Exception) {
+            Timber.tag(TAG).e(e, "Test result retrieval worker failed.")
             result = Result.retry()
         }
 
@@ -81,34 +84,37 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
         return result
     }
 
-    private fun abortConditionsMet(currentMillis: Long): Boolean {
-        if (tracingSettings.isTestResultAvailableNotificationSent) {
+    private suspend fun abortConditionsMet(nowUTC: Instant): Boolean {
+        val pcrTest = coronaTestRepository.latestPCRT.first()
+        if (pcrTest == null) {
+            Timber.tag(TAG).w("There is no PCR test available!?")
+            return true
+        }
+
+        if (pcrTest.isResultAvailableNotificationSent) {
             Timber.tag(TAG).d("$id: Notification already sent.")
             return true
         }
-        if (submissionSettings.hasViewedTestResult.value) {
+
+        if (pcrTest.isViewed) {
             Timber.tag(TAG).d("$id: Test result has already been viewed.")
             return true
         }
 
-        val calculateDays = TimeAndDateExtensions.calculateDays(
-            tracingSettings.initialPollingForTestResultTimeStamp,
-            currentMillis
-        )
+        val calculateDays = Duration(pcrTest.registeredAt, nowUTC).standardDays
         Timber.tag(TAG).d("Calculated days: %d", calculateDays)
 
         if (calculateDays >= BackgroundConstants.POLLING_VALIDITY_MAX_DAYS) {
-            Timber.tag(TAG)
-                .d(" $id Maximum of ${BackgroundConstants.POLLING_VALIDITY_MAX_DAYS} days for polling exceeded.")
+            Timber.tag(TAG).d("$id $calculateDays is exceeding the maximum polling duration")
             return true
         }
 
         return false
     }
 
-    private suspend fun sendTestResultAvailableNotification(testResult: TestResult) {
-        testResultAvailableNotificationService.showTestResultAvailableNotification(testResult)
-        tracingSettings.isTestResultAvailableNotificationSent = true
+    private suspend fun sendTestResultAvailableNotification(coronaTest: CoronaTest) {
+        testResultAvailableNotificationService.showTestResultAvailableNotification(coronaTest.testResult)
+        coronaTestRepository.updateResultNotification(identifier = coronaTest.identifier, sent = true)
     }
 
     private fun cancelRiskLevelScoreNotification() {
@@ -118,8 +124,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
     }
 
     private fun stopWorker() {
-        tracingSettings.initialPollingForTestResultTimeStamp = 0L
-        backgroundWorkScheduler.stopDiagnosisTestResultPeriodicWork()
+        testResultScheduler.setPeriodicTestPolling(enabled = false)
         Timber.tag(TAG).d("$id: Background worker stopped")
     }
 
diff --git a/Corona-Warn-App/src/main/res/drawable-hdpi/ic_splash_logo_round.png b/Corona-Warn-App/src/main/res/drawable-hdpi/ic_splash_logo_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..98fb80f06506fc7aad0a1191ef4b22f6fef1e40d
Binary files /dev/null and b/Corona-Warn-App/src/main/res/drawable-hdpi/ic_splash_logo_round.png differ
diff --git a/Corona-Warn-App/src/main/res/drawable-ldpi/ic_splash_logo_round.png b/Corona-Warn-App/src/main/res/drawable-ldpi/ic_splash_logo_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..1a6ce4d2a116010ba5dcf91bc01dee2be58d7abc
Binary files /dev/null and b/Corona-Warn-App/src/main/res/drawable-ldpi/ic_splash_logo_round.png differ
diff --git a/Corona-Warn-App/src/main/res/drawable-mdpi/ic_splash_logo_round.png b/Corona-Warn-App/src/main/res/drawable-mdpi/ic_splash_logo_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..3afd257ea151f25b3f6c7aa20e105ff502787042
Binary files /dev/null and b/Corona-Warn-App/src/main/res/drawable-mdpi/ic_splash_logo_round.png differ
diff --git a/Corona-Warn-App/src/main/res/drawable-xhdpi/ic_splash_logo_round.png b/Corona-Warn-App/src/main/res/drawable-xhdpi/ic_splash_logo_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..29cd5074fc4ef500f69160a91d4d0dace6fa9db5
Binary files /dev/null and b/Corona-Warn-App/src/main/res/drawable-xhdpi/ic_splash_logo_round.png differ
diff --git a/Corona-Warn-App/src/main/res/drawable-xxhdpi/ic_splash_logo_round.png b/Corona-Warn-App/src/main/res/drawable-xxhdpi/ic_splash_logo_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d7cde1f43ec085d9b295b405b9f6f06a431cd61
Binary files /dev/null and b/Corona-Warn-App/src/main/res/drawable-xxhdpi/ic_splash_logo_round.png differ
diff --git a/Corona-Warn-App/src/main/res/drawable-xxxhdpi/ic_splash_logo_round.png b/Corona-Warn-App/src/main/res/drawable-xxxhdpi/ic_splash_logo_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..17eb8e49327a109b903a0f4f5061fdf3c0b403e1
Binary files /dev/null and b/Corona-Warn-App/src/main/res/drawable-xxxhdpi/ic_splash_logo_round.png differ
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_international_phone.xml b/Corona-Warn-App/src/main/res/drawable/ic_international_phone.xml
new file mode 100644
index 0000000000000000000000000000000000000000..622119eaa5e1d0e09d2d0179eac8de65266e02fc
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_international_phone.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="23dp"
+    android:viewportWidth="24"
+    android:viewportHeight="23">
+  <path
+      android:pathData="M12.1484,22.4805C18.3037,22.4805 23.374,17.3994 23.374,11.2441C23.374,5.0889 18.293,0.0186 12.1484,0.0186C5.9932,0.0186 0.9229,5.0889 0.9229,11.2441C0.9229,17.3994 5.9932,22.4805 12.1484,22.4805ZM5.6494,4.6807C6.7021,3.6387 8.0127,2.833 9.4629,2.3926C8.6895,3.1553 8.0342,4.2402 7.54,5.5723C6.7988,5.3359 6.165,5.0352 5.6494,4.6807ZM14.8447,2.3926C16.2949,2.8438 17.5947,3.6387 18.6475,4.6914C18.1426,5.0352 17.498,5.3359 16.7676,5.5723C16.2734,4.251 15.6182,3.1553 14.8447,2.3926ZM9.1191,5.9482C9.6992,4.4229 10.4941,3.2842 11.3643,2.8545V6.1846C10.5693,6.1523 9.8174,6.0771 9.1191,5.9482ZM12.9434,2.8545C13.8135,3.2842 14.6084,4.4229 15.1777,5.9482C14.4902,6.0771 13.7383,6.1523 12.9434,6.1846V2.8545ZM2.9209,10.4492C3.0713,8.752 3.6836,7.1836 4.6396,5.8623C5.2734,6.3457 6.1113,6.7432 7.0889,7.0547C6.831,8.0967 6.6699,9.2353 6.6162,10.4492H2.9209ZM17.6807,10.4492C17.6377,9.2353 17.4658,8.0967 17.2188,7.0547C18.1963,6.7432 19.0342,6.3457 19.668,5.8731C20.6133,7.1836 21.2256,8.7627 21.376,10.4492H17.6807ZM12.9434,10.4492V7.7637C13.8887,7.7207 14.8018,7.624 15.6396,7.4521C15.8652,8.3867 16.0156,9.3965 16.0693,10.4492H12.9434ZM8.2275,10.4492C8.2813,9.3965 8.4316,8.3867 8.6572,7.4521C9.5059,7.624 10.4189,7.7207 11.3643,7.7637V10.4492H8.2275ZM2.9209,12.0283H6.6162C6.6699,13.2637 6.831,14.4238 7.0889,15.4766C6.1221,15.7881 5.2949,16.1855 4.6611,16.6582C3.6943,15.3262 3.0713,13.7471 2.9209,12.0283ZM8.2275,12.0283H11.3643V14.7783C10.4189,14.8105 9.5059,14.918 8.668,15.0791C8.4316,14.1338 8.2813,13.1025 8.2275,12.0283ZM12.9434,14.7783V12.0283H16.0693C16.0264,13.1025 15.8652,14.1338 15.6289,15.0791C14.791,14.918 13.8887,14.8105 12.9434,14.7783ZM17.208,15.4766C17.4766,14.4238 17.6377,13.2637 17.6807,12.0283H21.376C21.2363,13.7471 20.6025,15.3262 19.6465,16.6582C19.0127,16.1855 18.1855,15.7881 17.208,15.4766ZM9.1406,16.583C9.8281,16.4648 10.5693,16.3896 11.3643,16.3574V19.6553C10.4941,19.2148 9.71,18.0869 9.1406,16.583ZM12.9434,16.3574C13.7275,16.3896 14.4688,16.4648 15.167,16.583C14.5977,18.0869 13.8027,19.2148 12.9434,19.6553V16.3574ZM5.6709,17.8398C6.1865,17.4854 6.8203,17.1953 7.5508,16.9697C8.0342,18.2695 8.6895,19.3438 9.4522,20.0957C8.0127,19.6553 6.7236,18.8711 5.6709,17.8398ZM16.7568,16.9697C17.4873,17.1953 18.1211,17.4854 18.626,17.8398C17.5732,18.8711 16.2842,19.6553 14.8555,20.0957C15.6182,19.3438 16.2627,18.2695 16.7568,16.9697Z"
+      android:fillColor="#17191A"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_phone.xml b/Corona-Warn-App/src/main/res/drawable/ic_phone.xml
new file mode 100644
index 0000000000000000000000000000000000000000..80f7fca27287063af17dc13fd122a852f46197b6
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_phone.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="23dp"
+    android:height="23dp"
+    android:viewportWidth="23"
+    android:viewportHeight="23">
+  <path
+      android:pathData="M16.6592,22.6309C17.5615,22.6309 18.3708,22.459 19.0869,22.1152C19.8102,21.7786 20.512,21.2738 21.1924,20.6006C21.2282,20.5648 21.2604,20.529 21.2891,20.4932C21.3249,20.4645 21.3571,20.4287 21.3857,20.3857C21.6436,20.0778 21.8548,19.7269 22.0195,19.333C22.1842,18.9391 22.2666,18.5667 22.2666,18.2158C22.2666,17.7718 22.1735,17.3672 21.9873,17.002C21.8011,16.6296 21.4788,16.2822 21.0205,15.96L17.6475,13.6182C17.0674,13.2171 16.4873,13.0238 15.9072,13.0381C15.3343,13.0452 14.8294,13.2744 14.3926,13.7256L13.7803,14.3594C13.5654,14.5742 13.3327,14.6852 13.082,14.6924C12.8385,14.6924 12.5521,14.5527 12.2227,14.2734C12.0365,14.1159 11.8359,13.944 11.6211,13.7578C11.4134,13.5716 11.2057,13.3818 10.998,13.1885C10.7904,12.9951 10.597,12.8125 10.418,12.6406C10.2819,12.4974 10.1315,12.3398 9.9668,12.168C9.8092,11.9889 9.6481,11.8099 9.4834,11.6309C9.3258,11.4447 9.179,11.2656 9.043,11.0938C8.7852,10.7643 8.6527,10.4779 8.6455,10.2344C8.6455,9.9909 8.7529,9.7617 8.9678,9.5469L9.6016,8.9238C10.0456,8.487 10.2747,7.9821 10.2891,7.4092C10.3034,6.8291 10.1064,6.2526 9.6982,5.6797L7.0986,2.0381C6.8408,1.68 6.5042,1.4043 6.0889,1.2109C5.6735,1.0176 5.2366,0.9209 4.7783,0.9209C4.0049,0.9209 3.3066,1.1716 2.6836,1.6729C2.6478,1.6943 2.612,1.7194 2.5762,1.748C2.5475,1.7767 2.526,1.7982 2.5117,1.8125C1.7813,2.5501 1.237,3.2878 0.8789,4.0254C0.5208,4.763 0.3418,5.6869 0.3418,6.7969C0.3418,7.9499 0.5173,9.1136 0.8682,10.2881C1.2191,11.4554 1.7848,12.6406 2.5654,13.8438C3.346,15.0469 4.3809,16.2715 5.6699,17.5176C6.9518,18.7637 8.1872,19.7591 9.376,20.5039C10.5648,21.2559 11.7536,21.7965 12.9424,22.126C14.1312,22.4626 15.3701,22.6309 16.6592,22.6309ZM16.5732,20.2783C15.7139,20.2783 14.8223,20.1315 13.8984,19.8379C12.9746,19.5514 11.9863,19.0645 10.9336,18.377C9.888,17.6823 8.7493,16.737 7.5176,15.541C6.2786,14.3379 5.3154,13.2135 4.6279,12.168C3.9404,11.1152 3.4606,10.1198 3.1885,9.1816C2.9163,8.2435 2.7803,7.3447 2.7803,6.4854C2.7803,5.9984 2.8698,5.54 3.0488,5.1104C3.2279,4.6807 3.4785,4.3262 3.8008,4.0469C3.8509,4.0039 3.9046,3.9609 3.9619,3.918C4.0192,3.8678 4.0729,3.8249 4.1231,3.7891C4.2305,3.7103 4.3379,3.653 4.4453,3.6172C4.5527,3.5742 4.6602,3.5527 4.7676,3.5527C5.0755,3.5527 5.3369,3.7067 5.5518,4.0146L7.1738,6.3779C7.3529,6.6429 7.4281,6.9186 7.3994,7.2051C7.3708,7.4915 7.2311,7.7529 6.9805,7.9893L6.5508,8.376C6.0065,8.8701 5.7236,9.3499 5.7021,9.8154C5.6878,10.2809 5.7845,10.6963 5.9922,11.0615C6.1856,11.4124 6.5365,11.8851 7.0449,12.4795C7.5606,13.0667 8.137,13.6719 8.7744,14.2949C9.3044,14.8105 9.8021,15.2796 10.2676,15.7021C10.7331,16.1247 11.1377,16.4792 11.4814,16.7656C11.8324,17.0521 12.0902,17.2383 12.2549,17.3242C12.6273,17.5319 13.0462,17.6286 13.5117,17.6143C13.9772,17.5928 14.457,17.3135 14.9512,16.7764L15.3379,16.3467C15.5742,16.0889 15.832,15.9492 16.1113,15.9277C16.3978,15.9062 16.6735,15.9779 16.9385,16.1426L19.0439,17.5068C19.2158,17.6214 19.334,17.7432 19.3984,17.8721C19.4701,18.001 19.5059,18.1335 19.5059,18.2695C19.5059,18.513 19.4271,18.735 19.2695,18.9355C19.2337,18.9857 19.1908,19.0394 19.1406,19.0967C19.0977,19.154 19.0547,19.2077 19.0117,19.2578C18.7324,19.5801 18.3779,19.8307 17.9482,20.0098C17.5186,20.1888 17.0602,20.2783 16.5732,20.2783Z"
+      android:fillColor="#17191A"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_details_pcr.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_details_pcr.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7c2fcb55f53139b5a296e2dc47dfe1c404527d86
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_risk_details_pcr.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="22dp"
+    android:viewportWidth="20"
+    android:viewportHeight="22">
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M6,0L14,0A1,1 0,0 1,15 1L15,1A1,1 0,0 1,14 2L6,2A1,1 0,0 1,5 1L5,1A1,1 0,0 1,6 0z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M7.2449,6.1667V3.1579H12.7551V6.1667V6.3816L12.8323,6.5822L15.8949,14.5486C17.0611,17.5821 14.8218,20.8421 11.5717,20.8421H8.4283C5.1782,20.8421 2.9389,17.5821 4.1051,14.5486L7.1677,6.5822L7.2449,6.3816V6.1667Z"
+        android:strokeWidth="2.31579"
+        android:strokeColor="#ffffff" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M3.5,16.5295L4.5,19.5295L5.5,20.5304L10,21.0304L15.5,20.0304L16.5,16.5295C16.8333,15.5295 16.4,11.4011 14,13.0011C11.6,14.6011 8,17.9875 4,16L3.5,16.5295Z" />
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_submission_share_cards.xml b/Corona-Warn-App/src/main/res/drawable/ic_submission_share_cards.xml
new file mode 100644
index 0000000000000000000000000000000000000000..78495b12be958b064888f28b3a3584477621c027
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_submission_share_cards.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="25dp"
+    android:viewportWidth="24"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M12,4.5C15.64,4.5 18.67,7.09 19.35,10.54C21.95,10.72 24,12.86 24,15.5C24,18.26 21.76,20.5 19,20.5H6C2.69,20.5 0,17.81 0,14.5C0,11.41 2.34,8.86 5.35,8.54C6.6,6.14 9.11,4.5 12,4.5ZM14,17.5V13.5H17L12.36,8.85C12.16,8.65 11.85,8.65 11.65,8.85L7,13.5H10V17.5H14Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_negative_card.xml b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_negative_card.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fa73b136ca20edcec54e80ee3bb8f6cec9a516e9
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_negative_card.xml
@@ -0,0 +1,49 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="87dp"
+    android:height="110dp"
+    android:viewportWidth="87"
+    android:viewportHeight="110">
+  <path
+      android:pathData="M15.211,15.749V109.852H86.414V33.676L69.317,15.749H15.211Z"
+      android:fillColor="#657888"
+      android:fillAlpha="0.1"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M69.361,33.676H86.357L69.361,15.763V33.676Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M42.763,96.966H36.275C35.168,96.966 34.272,96.075 34.272,94.974V88.519C34.272,87.417 35.168,86.527 36.275,86.527H42.763C43.868,86.527 44.764,87.417 44.764,88.519V94.974C44.764,96.075 43.868,96.966 42.763,96.966Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M68.099,96.966H61.608C60.503,96.966 59.607,96.075 59.607,94.975V88.517C59.607,87.417 60.503,86.527 61.608,86.527H68.099C69.204,86.527 70.1,87.417 70.1,88.517V94.975C70.1,96.075 69.204,96.966 68.099,96.966Z"
+      android:fillColor="#2E854B"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M32.397,56.782H71.431C72.197,56.842 72.677,57.376 72.677,58.109C72.677,58.752 72.197,59.469 71.431,59.435H32.397C31.392,59.469 30.912,58.841 30.912,58.109C30.912,57.376 31.392,56.842 32.397,56.782Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M32.397,42.454H71.431C72.197,42.513 72.677,43.048 72.677,43.78C72.677,44.423 72.197,45.14 71.431,45.105H32.397C31.392,45.14 30.912,44.511 30.912,43.78C30.912,43.048 31.392,42.513 32.397,42.454Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M32.397,70.874H71.431C72.197,70.933 72.677,71.468 72.677,72.2C72.677,72.843 72.197,73.56 71.431,73.525H32.397C31.392,73.56 30.912,72.931 30.912,72.2C30.912,71.468 31.392,70.933 32.397,70.874Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M68.062,89.099C67.942,89.091 67.825,89.142 67.751,89.237L63.815,94.055L61.813,91.936C61.729,91.828 61.59,91.777 61.456,91.805C61.322,91.831 61.214,91.93 61.177,92.062C61.141,92.193 61.181,92.333 61.283,92.426L63.572,94.852C63.644,94.926 63.745,94.967 63.849,94.963C63.954,94.959 64.051,94.91 64.116,94.829L68.319,89.694C68.409,89.588 68.43,89.443 68.375,89.316C68.321,89.191 68.2,89.107 68.062,89.099Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M68.062,89.099C67.942,89.091 67.825,89.142 67.751,89.237L63.815,94.055L61.813,91.936C61.729,91.828 61.59,91.777 61.456,91.805C61.322,91.831 61.214,91.93 61.177,92.062C61.141,92.193 61.181,92.333 61.283,92.426L63.572,94.852C63.644,94.926 63.745,94.967 63.849,94.963C63.954,94.959 64.051,94.91 64.116,94.829L68.319,89.694C68.409,89.588 68.43,89.443 68.375,89.316C68.321,89.191 68.2,89.107 68.062,89.099Z"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M22.538,7.105L22.596,7.136C24.156,7.956 25.455,9.076 26.461,10.388C26.725,10.428 26.988,10.438 27.221,10.395C27.695,10.308 29.009,9.734 29.292,9.101C29.575,8.468 30.139,7.25 31.479,9.23L31.493,9.251C31.789,9.687 32.712,11.05 30.849,10.972C29.085,10.897 28.177,11.979 27.801,12.606C28.519,14.158 28.897,15.858 28.895,17.581C29.267,17.841 29.802,18.032 30.565,18.02C31.842,17.999 32.603,18.021 33.023,18.044C33.295,18.059 34.301,18.052 34.525,17.898C35.077,17.519 36.143,17.031 35.986,19.06C35.854,20.76 35.453,21.406 34.648,20.759C34.237,20.429 33.035,20.113 32.524,19.986C31.452,19.721 29.717,19.447 28.622,20.08C28.4,21.086 28.042,22.08 27.54,23.036C27.467,23.174 27.392,23.31 27.315,23.444L11.007,7.492C11.63,7.115 12.284,6.797 12.962,6.542C13.104,6.288 13.228,5.912 13.139,5.44C13.127,5.378 13.116,5.305 13.103,5.224C13.037,4.797 12.937,4.154 12.574,3.887C12.176,3.596 11.349,2.621 13.682,2.288C13.74,2.28 13.797,2.271 13.854,2.262C14.442,2.169 15.011,2.078 14.733,3.093C14.626,3.48 14.608,3.854 14.639,4.198C14.71,4.961 14.939,5.431 15.435,5.904C17.067,5.667 18.758,5.77 20.399,6.248C20.773,5.977 21.137,5.544 21.416,4.863C21.735,4.085 22.005,3.224 22.189,2.639L22.19,2.639L22.19,2.639L22.19,2.638C22.264,2.403 22.323,2.212 22.367,2.089C22.482,1.762 22.53,1.415 22.502,1.069C22.452,0.446 22.618,-0.588 24.559,0.427C26.809,1.605 26.099,2.169 25.351,2.466C25.049,2.586 24.783,2.784 24.565,3.026C23.937,3.729 22.716,5.583 22.538,7.105ZM6.887,11.731L23.18,27.654C22.579,28.013 21.949,28.318 21.298,28.565C21.273,28.874 21.281,29.242 21.341,29.68C21.473,30.632 21.991,31.966 22.292,32.74C22.391,32.995 22.466,33.19 22.497,33.29C22.564,33.514 22.691,33.602 22.867,33.723C22.895,33.743 22.924,33.763 22.954,33.784C23.499,34.171 24.324,35.005 22.365,35.554C20.723,36.013 19.979,35.858 20.309,34.881C20.479,34.382 20.558,34.011 20.502,33.488C20.394,32.492 19.768,30.265 18.899,29.197C17.302,29.442 15.644,29.361 14.031,28.921C13.594,29.075 13.121,29.298 12.827,29.602C12.491,29.948 11.755,31.179 11.893,31.858C12.03,32.538 12.278,33.857 10.037,33.023L10.013,33.014C9.52,32.831 7.976,32.259 9.539,31.24C11.612,29.888 10.996,27.663 10.996,27.663L10.972,27.6C9.584,26.753 8.429,25.656 7.533,24.399C7.224,24.505 6.894,24.677 6.548,24.94C4.578,26.436 4.191,26.853 4.191,26.853C4.191,26.853 2.583,30.117 0.895,27.529C-0.793,24.941 0.595,24.328 2.441,24.757C2.441,24.757 5.2,23.982 6.422,22.479C5.765,21.049 5.397,19.495 5.348,17.913C5.193,17.805 5.035,17.708 4.876,17.632C4.378,17.396 2.848,17.183 2.104,17.487C1.3,17.815 -0.181,18.285 0.018,16.452C0.178,14.985 0.795,14.281 2.104,15.191C3.31,16.029 4.605,15.767 5.552,15.338C5.766,14.229 6.144,13.131 6.696,12.08C6.758,11.962 6.822,11.846 6.887,11.731ZM2.79,5.225L29.162,31.022C29.765,31.611 30.618,31.717 31.065,31.258C31.513,30.8 31.386,29.95 30.784,29.361L4.411,3.565C3.808,2.976 2.956,2.871 2.508,3.329C2.061,3.787 2.187,4.637 2.79,5.225Z"
+      android:fillColor="#2E854B"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_outdated_card.xml b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_outdated_card.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6136cb130644d155c77c5f673a523b19ced9e3b7
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_outdated_card.xml
@@ -0,0 +1,49 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="87dp"
+    android:height="110dp"
+    android:viewportWidth="87"
+    android:viewportHeight="110">
+  <path
+      android:pathData="M15.211,15.749V109.852H86.414V33.676L69.317,15.749H15.211Z"
+      android:fillColor="#657888"
+      android:fillAlpha="0.1"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M69.361,33.676H86.357L69.361,15.763V33.676Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M42.763,96.966H36.275C35.168,96.966 34.272,96.075 34.272,94.974V88.519C34.272,87.417 35.168,86.527 36.275,86.527H42.763C43.868,86.527 44.764,87.417 44.764,88.519V94.974C44.764,96.075 43.868,96.966 42.763,96.966Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M68.099,96.966H61.608C60.503,96.966 59.607,96.075 59.607,94.975V88.517C59.607,87.417 60.503,86.527 61.608,86.527H68.099C69.204,86.527 70.1,87.417 70.1,88.517V94.975C70.1,96.075 69.204,96.966 68.099,96.966Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M32.397,56.782H71.431C72.197,56.842 72.677,57.376 72.677,58.109C72.677,58.752 72.197,59.469 71.431,59.435H32.397C31.392,59.469 30.912,58.841 30.912,58.109C30.912,57.376 31.392,56.842 32.397,56.782Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M32.397,42.454H71.431C72.197,42.513 72.677,43.048 72.677,43.78C72.677,44.423 72.197,45.14 71.431,45.105H32.397C31.392,45.14 30.912,44.511 30.912,43.78C30.912,43.048 31.392,42.513 32.397,42.454Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M32.397,70.874H71.431C72.197,70.933 72.677,71.468 72.677,72.2C72.677,72.843 72.197,73.56 71.431,73.525H32.397C31.392,73.56 30.912,72.931 30.912,72.2C30.912,71.468 31.392,70.933 32.397,70.874Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M68.062,89.099C67.942,89.091 67.825,89.142 67.751,89.237L63.815,94.055L61.813,91.936C61.729,91.828 61.59,91.777 61.456,91.805C61.322,91.831 61.214,91.93 61.177,92.062C61.141,92.193 61.181,92.333 61.283,92.426L63.572,94.852C63.644,94.926 63.745,94.967 63.849,94.963C63.954,94.959 64.051,94.91 64.116,94.829L68.319,89.694C68.409,89.588 68.43,89.443 68.375,89.316C68.321,89.191 68.2,89.107 68.062,89.099Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M68.062,89.099C67.942,89.091 67.825,89.142 67.751,89.237L63.815,94.055L61.813,91.936C61.729,91.828 61.59,91.777 61.456,91.805C61.322,91.831 61.214,91.93 61.177,92.062C61.141,92.193 61.181,92.333 61.283,92.426L63.572,94.852C63.644,94.926 63.745,94.967 63.849,94.963C63.954,94.959 64.051,94.91 64.116,94.829L68.319,89.694C68.409,89.588 68.43,89.443 68.375,89.316C68.321,89.191 68.2,89.107 68.062,89.099Z"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M22.538,7.105L22.596,7.136C24.156,7.956 25.455,9.076 26.461,10.388C26.725,10.428 26.988,10.438 27.221,10.395C27.695,10.308 29.009,9.734 29.292,9.101C29.575,8.468 30.139,7.25 31.479,9.23L31.493,9.251C31.789,9.687 32.712,11.05 30.849,10.972C29.085,10.897 28.177,11.979 27.801,12.606C28.519,14.158 28.897,15.858 28.895,17.581C29.267,17.841 29.802,18.032 30.565,18.02C31.842,17.999 32.603,18.021 33.023,18.044C33.295,18.059 34.301,18.052 34.525,17.898C35.077,17.519 36.143,17.031 35.986,19.06C35.854,20.76 35.453,21.406 34.648,20.759C34.237,20.429 33.035,20.113 32.524,19.986C31.452,19.721 29.717,19.447 28.622,20.08C28.4,21.086 28.042,22.08 27.54,23.036C27.467,23.174 27.392,23.31 27.315,23.444L11.007,7.492C11.63,7.115 12.284,6.797 12.962,6.542C13.104,6.288 13.228,5.912 13.139,5.44C13.127,5.378 13.116,5.305 13.103,5.224C13.037,4.797 12.937,4.154 12.574,3.887C12.176,3.596 11.349,2.621 13.682,2.288C13.74,2.28 13.797,2.271 13.854,2.262C14.442,2.169 15.011,2.078 14.733,3.093C14.626,3.48 14.608,3.854 14.639,4.198C14.71,4.961 14.939,5.431 15.435,5.904C17.067,5.667 18.758,5.77 20.399,6.248C20.773,5.977 21.137,5.544 21.416,4.863C21.735,4.085 22.005,3.224 22.189,2.639L22.19,2.639L22.19,2.639L22.19,2.638C22.264,2.403 22.323,2.212 22.367,2.089C22.482,1.762 22.53,1.415 22.502,1.069C22.452,0.446 22.618,-0.588 24.559,0.427C26.809,1.605 26.099,2.169 25.351,2.466C25.049,2.586 24.783,2.784 24.565,3.026C23.937,3.729 22.716,5.583 22.538,7.105ZM6.887,11.731L23.18,27.654C22.579,28.013 21.949,28.318 21.298,28.565C21.273,28.874 21.281,29.242 21.341,29.68C21.473,30.632 21.991,31.966 22.292,32.74C22.391,32.995 22.466,33.19 22.497,33.29C22.564,33.514 22.691,33.602 22.867,33.723C22.895,33.743 22.924,33.763 22.954,33.784C23.499,34.171 24.324,35.005 22.365,35.554C20.723,36.013 19.979,35.858 20.309,34.881C20.479,34.382 20.558,34.011 20.502,33.488C20.394,32.492 19.768,30.265 18.899,29.197C17.302,29.442 15.644,29.361 14.031,28.921C13.594,29.075 13.121,29.298 12.827,29.602C12.491,29.948 11.755,31.179 11.893,31.858C12.03,32.538 12.278,33.857 10.037,33.023L10.013,33.014C9.52,32.831 7.976,32.259 9.539,31.24C11.612,29.888 10.996,27.663 10.996,27.663L10.972,27.6C9.584,26.753 8.429,25.656 7.533,24.399C7.224,24.505 6.894,24.677 6.548,24.94C4.578,26.436 4.191,26.853 4.191,26.853C4.191,26.853 2.583,30.117 0.895,27.529C-0.793,24.941 0.595,24.328 2.441,24.757C2.441,24.757 5.2,23.982 6.422,22.479C5.765,21.049 5.397,19.495 5.348,17.913C5.193,17.805 5.035,17.708 4.876,17.632C4.378,17.396 2.848,17.183 2.104,17.487C1.3,17.815 -0.181,18.285 0.018,16.452C0.178,14.985 0.795,14.281 2.104,15.191C3.31,16.029 4.605,15.767 5.552,15.338C5.766,14.229 6.144,13.131 6.696,12.08C6.758,11.962 6.822,11.846 6.887,11.731ZM2.79,5.225L29.162,31.022C29.765,31.611 30.618,31.717 31.065,31.258C31.513,30.8 31.386,29.95 30.784,29.361L4.411,3.565C3.808,2.976 2.956,2.871 2.508,3.329C2.061,3.787 2.187,4.637 2.79,5.225Z"
+      android:fillColor="#657888"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml
index 6cddd5383ec531388ccad2d578fd3428c1cafeae..2277fe672d8fe4b98a599c31289f0bed4ad0c11e 100644
--- a/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml
@@ -127,7 +127,7 @@
                 android:layout_marginVertical="8dp"
                 android:layout_marginStart="16dp"
                 android:importantForAccessibility="no"
-                android:src="@drawable/ic_debug_log_indicator_deactivated"
+                app:srcCompat="@drawable/ic_debug_log_indicator_deactivated"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/check_ins_consent_fragment.xml b/Corona-Warn-App/src/main/res/layout/check_ins_consent_fragment.xml
new file mode 100644
index 0000000000000000000000000000000000000000..37cd803b92a2764b00f7c1e51b2b5626fd3d35e9
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/check_ins_consent_fragment.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 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"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.presencetracing.attendee.checkins.consent.CheckInsConsentFragment">
+
+    <com.google.android.material.appbar.MaterialToolbar
+        android:id="@+id/toolbar"
+        style="@style/CWAToolbar.Close"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:title="@string/trace_location_attendee_consent_title" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/checkInsRecycler"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginBottom="16dp"
+        android:orientation="vertical"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+        app:layout_constraintBottom_toTopOf="@id/continue_button"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/toolbar"
+        tools:listitem="@layout/trace_location_attendee_consent_selectable_check_in" />
+
+    <Button
+        android:id="@+id/continue_button"
+        style="@style/buttonPrimary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="24dp"
+        android:layout_marginBottom="16dp"
+        android:text="@string/trace_location_attendee_consent_continue"
+        app:layout_constraintBottom_toTopOf="@id/skip_button"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <Button
+        android:id="@+id/skip_button"
+        style="@style/buttonPrimary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="24dp"
+        android:layout_marginBottom="24dp"
+        android:text="@string/trace_location_attendee_consent_skip"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_add_location_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_add_location_fragment.xml
index 36df6ecdea2e1afe02d3c5a25cf6558a85fd2bef..efca1f88d350e7d38ed0d7e08f356eee58941548 100644
--- a/Corona-Warn-App/src/main/res/layout/contact_diary_add_location_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/contact_diary_add_location_fragment.xml
@@ -20,7 +20,7 @@
             android:layout_marginTop="@dimen/spacing_tiny"
             android:contentDescription="@string/accessibility_close"
             android:padding="@dimen/spacing_mega_tiny"
-            android:src="@drawable/ic_close"
+            app:srcCompat="@drawable/ic_close"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
 
@@ -45,7 +45,7 @@
             android:layout_marginEnd="@dimen/spacing_tiny"
             android:contentDescription="@string/contact_diary_delete_icon_content_description"
             android:padding="@dimen/button_icon_padding"
-            android:src="@drawable/ic_baseline_delete"
+            app:srcCompat="@drawable/ic_baseline_delete"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_add_person_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_add_person_fragment.xml
index 254bed1eeb31ff2c3bbbb31ab9f8b7a69b60ef4f..c77fce4cadefa04a6f52993edc5a02541b5ee0e0 100644
--- a/Corona-Warn-App/src/main/res/layout/contact_diary_add_person_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/contact_diary_add_person_fragment.xml
@@ -20,7 +20,7 @@
             android:layout_marginTop="@dimen/spacing_tiny"
             android:contentDescription="@string/accessibility_close"
             android:padding="@dimen/spacing_mega_tiny"
-            android:src="@drawable/ic_close"
+            app:srcCompat="@drawable/ic_close"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
 
@@ -44,7 +44,7 @@
             android:layout_marginEnd="@dimen/spacing_tiny"
             android:contentDescription="@string/contact_diary_delete_icon_content_description"
             android:padding="@dimen/button_icon_padding"
-            android:src="@drawable/ic_baseline_delete"
+            app:srcCompat="@drawable/ic_baseline_delete"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml
index 172335e1c41f698ca0d0ef66628dfec44aecc814..e14e44081fe32f09bf569aa9a878bea1abe06f1c 100644
--- a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml
+++ b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml
@@ -29,7 +29,7 @@
         android:layout_height="wrap_content"
         android:contentDescription="@string/contact_diary_edit_icon_content_description"
         android:padding="@dimen/spacing_small"
-        android:src="@drawable/ic_baseline_edit_24"
+        app:srcCompat="@drawable/ic_baseline_edit_24"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml
index 18a6ca07ae46636da2f3faf085f0cdfe88394ae6..e726b8d0cc6036164b243272ae9d2242f4d81892 100644
--- a/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml
@@ -40,7 +40,7 @@
                     android:layout_height="wrap_content"
                     android:contentDescription="@string/contact_diary_onboarding_image_content_description"
                     android:focusable="true"
-                    android:src="@drawable/ic_contact_diary_illustration_onboarding"
+                    app:srcCompat="@drawable/ic_contact_diary_illustration_onboarding"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml
index 3378d49878f8e3dcd0f2bd47405f85c0d3635192..ae153228eedec1a0163d72c7bc918aa09314f894 100644
--- a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml
+++ b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml
@@ -19,7 +19,7 @@
         android:layout_marginStart="@dimen/spacing_small"
         android:importantForAccessibility="no"
         android:scaleType="centerInside"
-        android:src="@drawable/ic_contact_diary_person_item"
+        app:srcCompat="@drawable/ic_contact_diary_person_item"
         app:layout_constraintBaseline_toBaselineOf="@id/contact_diary_overview_element_name"
         app:layout_constraintEnd_toStartOf="@id/contact_diary_overview_element_name"
         app:layout_constraintStart_toStartOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_information_contact.xml b/Corona-Warn-App/src/main/res/layout/fragment_information_contact.xml
index f0cb4058e4d53fad5afb91f9fe538ca989cfe5c0..75163b421f29afa948d220349347a3c74e1ab7d4 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_information_contact.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_information_contact.xml
@@ -6,8 +6,8 @@
         android:id="@+id/information_contact_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:focusable="true"
-        android:contentDescription="@string/information_contact_title">
+        android:contentDescription="@string/information_contact_title"
+        android:focusable="true">
 
         <include
             android:id="@+id/information_contact_header"
@@ -52,43 +52,62 @@
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="@dimen/spacing_medium"
-                    app:layout_constraintEnd_toEndOf="@+id/guideline_end"
+                    app:layout_constraintEnd_toEndOf="@id/guideline_end"
                     app:layout_constraintStart_toStartOf="@id/guideline_start"
-                    app:layout_constraintTop_toBottomOf="@+id/information_contact_header_details" />
+                    app:layout_constraintTop_toBottomOf="@id/information_contact_header_details" />
 
                 <TextView
                     android:id="@+id/information_contact_subtitle_phone"
                     style="@style/headline6"
-                    android:accessibilityHeading="true"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="@dimen/spacing_small"
-                    android:text="@string/information_contact_subtitle_phone"
+                    android:accessibilityHeading="true"
                     android:focusable="true"
-                    app:layout_constraintEnd_toStartOf="@+id/guideline_end"
-                    app:layout_constraintStart_toEndOf="@+id/guideline_start"
-                    app:layout_constraintTop_toBottomOf="@+id/divider" />
+                    android:text="@string/information_contact_subtitle_phone"
+                    app:layout_constraintEnd_toStartOf="@id/guideline_end"
+                    app:layout_constraintStart_toEndOf="@id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@id/divider" />
 
                 <include
                     android:id="@+id/information_contact_navigation_row_phone"
                     layout="@layout/include_navigation_row"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_normal"
+                    android:layout_marginEnd="@dimen/spacing_normal"
+                    app:icon="@{@drawable/ic_phone}"
                     app:layout_constraintEnd_toEndOf="parent"
-                    app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@+id/information_contact_subtitle_phone"
-                    app:subtitle="@{@string/information_contact_button_phone}" />
+                    app:layout_constraintStart_toStartOf="@id/information_contact_subtitle_phone"
+                    app:layout_constraintTop_toBottomOf="@id/information_contact_subtitle_phone"
+                    app:subtitle="@{@string/information_contact_button_phone_description}"
+                    app:title="@{@string/information_contact_button_phone}" />
+
+                <include
+                    android:id="@+id/information_contact_navigation_row_international_phone"
+                    layout="@layout/include_navigation_row"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_normal"
+                    android:layout_marginEnd="@dimen/spacing_normal"
+                    app:icon="@{@drawable/ic_international_phone}"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="@id/information_contact_subtitle_phone"
+                    app:layout_constraintTop_toBottomOf="@id/information_contact_navigation_row_phone"
+                    app:subtitle="@{@string/information_contact_button_international_phone_description}"
+                    app:title="@{@string/information_contact_button_international_phone}" />
 
                 <TextView
                     android:id="@+id/information_contact_body_phone"
                     style="@style/body2"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
-                    android:text="@string/information_contact_body_phone"
                     android:focusable="true"
-                    app:layout_constraintEnd_toEndOf="@+id/guideline_end"
-                    app:layout_constraintStart_toStartOf="@+id/guideline_start"
-                    app:layout_constraintTop_toBottomOf="@+id/information_contact_navigation_row_phone" />
+                    android:layout_marginTop="@dimen/spacing_normal"
+                    android:text="@string/information_contact_body_phone"
+                    app:layout_constraintEnd_toEndOf="@id/guideline_end"
+                    app:layout_constraintStart_toStartOf="@id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@id/information_contact_navigation_row_international_phone" />
 
                 <TextView
                     android:id="@+id/information_contact_body_open"
@@ -96,11 +115,11 @@
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="@dimen/spacing_normal"
-                    android:text="@string/information_contact_body_open"
                     android:focusable="true"
-                    app:layout_constraintEnd_toEndOf="@+id/guideline_end"
-                    app:layout_constraintStart_toStartOf="@+id/guideline_start"
-                    app:layout_constraintTop_toBottomOf="@+id/information_contact_body_phone" />
+                    android:text="@string/information_contact_body_open"
+                    app:layout_constraintEnd_toEndOf="@id/guideline_end"
+                    app:layout_constraintStart_toStartOf="@id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@id/information_contact_body_phone" />
 
                 <TextView
                     android:id="@+id/information_contact_body_other"
@@ -108,12 +127,12 @@
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="@dimen/spacing_normal"
-                    android:text="@string/information_contact_body_other"
                     android:focusable="true"
+                    android:text="@string/information_contact_body_other"
                     android:textColorLink="@color/colorTextTint"
-                    app:layout_constraintEnd_toEndOf="@+id/guideline_end"
-                    app:layout_constraintStart_toStartOf="@+id/guideline_start"
-                    app:layout_constraintTop_toBottomOf="@+id/information_contact_body_open" />
+                    app:layout_constraintEnd_toEndOf="@id/guideline_end"
+                    app:layout_constraintStart_toStartOf="@id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@id/information_contact_body_open" />
 
                 <androidx.constraintlayout.widget.Guideline
                     android:id="@+id/guideline_start"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_interoperability_configuration.xml b/Corona-Warn-App/src/main/res/layout/fragment_interoperability_configuration.xml
index 1aeb9f6e56af982cfbb97f9baea1e5918d6bcef2..be2c804d65a220a405505ab73be2466327cf7dc4 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_interoperability_configuration.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_interoperability_configuration.xml
@@ -48,7 +48,7 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:contentDescription="@{@string/interoperability_eu_illustration_description}"
-                    android:src="@drawable/ic_illustration_interoperability"
+                    app:srcCompat="@drawable/ic_illustration_interoperability"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toTopOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_onboarding.xml b/Corona-Warn-App/src/main/res/layout/fragment_onboarding.xml
index b9efe5256f307b04c36963dbd0dcab79d36059e5..a34da0c9424fbd812f17a364ab83371d645a6a1e 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_onboarding.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_onboarding.xml
@@ -47,25 +47,25 @@
                     android:text="@string/onboarding_headline"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@+id/onboarding_illustration"
+                    app:layout_constraintTop_toBottomOf="@id/onboarding_illustration"
                     tools:text="@string/onboarding_headline" />
 
                 <TextView
-                    android:id="@+id/onboarding_subtitle"
+                    android:id="@+id/onboarding_body_1"
                     style="@style/subtitle"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_marginHorizontal="@dimen/spacing_normal"
                     android:layout_marginTop="@dimen/spacing_normal"
                     android:focusable="true"
-                    android:text="@string/onboarding_subtitle"
+                    android:text="@string/onboarding_body_1"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@+id/onboarding_headline"
-                    tools:text="@string/onboarding_subtitle" />
+                    app:layout_constraintTop_toBottomOf="@id/onboarding_headline"
+                    tools:text="@string/onboarding_body_1" />
 
                 <TextView
-                    android:id="@+id/onboarding_body"
+                    android:id="@+id/onboarding_body_2"
                     style="@style/subtitle"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
@@ -73,26 +73,58 @@
                     android:layout_marginTop="@dimen/spacing_normal"
                     android:autoLink="web|email"
                     android:focusable="true"
-                    android:text="@string/onboarding_body"
+                    android:text="@string/onboarding_body_2"
                     android:textColorLink="@color/colorTextTint"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@+id/onboarding_subtitle"
-                    tools:text="@string/onboarding_body" />
+                    app:layout_constraintTop_toBottomOf="@id/onboarding_body_1"
+                    tools:text="@string/onboarding_body_2" />
 
                 <TextView
-                    android:id="@+id/onboarding_body_emphasized"
+                    android:id="@+id/onboarding_body_3"
+                    style="@style/subtitle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="@dimen/spacing_normal"
+                    android:layout_marginTop="@dimen/spacing_normal"
+                    android:autoLink="web|email"
+                    android:focusable="true"
+                    android:text="@string/onboarding_body_3"
+                    android:textColorLink="@color/colorTextTint"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@id/onboarding_body_2"
+                    tools:text="@string/onboarding_body_3" />
+
+                <TextView
+                    android:id="@+id/onboarding_body_4"
+                    style="@style/subtitle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="@dimen/spacing_normal"
+                    android:layout_marginTop="@dimen/spacing_normal"
+                    android:autoLink="web|email"
+                    android:focusable="true"
+                    android:text="@string/onboarding_body_4"
+                    android:textColorLink="@color/colorTextTint"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@id/onboarding_body_3"
+                    tools:text="@string/onboarding_body_4" />
+
+                <TextView
+                    android:id="@+id/onboarding_body_5"
                     style="@style/subtitle"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_marginHorizontal="@dimen/spacing_normal"
                     android:layout_marginTop="@dimen/spacing_normal"
                     android:focusable="true"
-                    android:text="@string/onboarding_body_emphasized"
+                    android:text="@string/onboarding_body_5"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@id/onboarding_body"
-                    tools:text="@string/onboarding_body_emphasized" />
+                    app:layout_constraintTop_toBottomOf="@id/onboarding_body_4"
+                    tools:text="@string/onboarding_body_5" />
 
                 <TextView
                     android:id="@+id/onboarding_easy_language"
@@ -108,7 +140,7 @@
                     android:textColorLink="@color/colorTextTint"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@id/onboarding_body_emphasized"
+                    app:layout_constraintTop_toBottomOf="@id/onboarding_body_5"
                     tools:text="@string/onboarding_tracing_easy_language_explanation" />
 
             </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml
index 5ea4a2968ef5df16c6c964c020a64a3e34bc3b7e..e176c094ac61800d6f5df5ae9bd68ea5f377a666 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml
@@ -46,7 +46,7 @@
                     android:id="@+id/submission_consent_illustration"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
-                    android:src="@drawable/ic_submission_consent"
+                    app:srcCompat="@drawable/ic_submission_consent"
                     app:layout_constraintTop_toTopOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintEnd_toEndOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_contact.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_contact.xml
index a8b7ca3fd3c2c12eca992a6f6bef5377bb092e55..a4f2fb2e3e7a40bc9fdb90c2fd4ac8a4c613b2a3 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_contact.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_contact.xml
@@ -32,21 +32,9 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/submission_contact_header" />
 
-        <Button
-            android:id="@+id/submission_contact_button_call"
-            style="@style/buttonPrimary"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:text="@string/submission_contact_button_call"
-            android:textAllCaps="true"
-            app:layout_constraintBottom_toTopOf="@+id/submission_contact_button_enter"
-            app:layout_constraintEnd_toStartOf="@+id/guideline_end"
-            app:layout_constraintStart_toStartOf="@id/guideline_start"
-            app:layout_constraintTop_toBottomOf="@+id/guideline_bottom" />
-
         <Button
             android:id="@+id/submission_contact_button_enter"
-            style="@style/buttonLight"
+            style="@style/buttonPrimary"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:text="@string/submission_contact_button_enter"
@@ -54,14 +42,14 @@
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@+id/guideline_end"
             app:layout_constraintStart_toStartOf="@id/guideline_start"
-            app:layout_constraintTop_toBottomOf="@+id/submission_contact_button_call" />
+            app:layout_constraintTop_toBottomOf="@+id/include_submission_contact" />
 
         <androidx.constraintlayout.widget.Guideline
             android:id="@+id/guideline_bottom"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="horizontal"
-            app:layout_constraintGuide_end="@dimen/guideline_action_large" />
+            app:layout_constraintGuide_end="@dimen/guideline_action" />
 
         <include layout="@layout/merge_guidelines_side" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_dispatcher.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_dispatcher.xml
index f769e6fe74599e776179e946c7da286a1299f129..c7f1068de61ae287278824a91ba383edf3604e82 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_dispatcher.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_dispatcher.xml
@@ -43,7 +43,7 @@
                     android:contentDescription="@string/submission_intro_illustration_description"
                     android:focusable="true"
                     android:layout_height="wrap_content"
-                    android:src="@drawable/ic_illustration_test"
+                    app:srcCompat="@drawable/ic_illustration_test"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toTopOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_no_consent_positive_other_warning.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_no_consent_positive_other_warning.xml
index e011e4b3ed20d4560b530fe257b61a1142707ea0..080cab6b67d87c6305648406bb8aaad6cffd7a75 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_no_consent_positive_other_warning.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_no_consent_positive_other_warning.xml
@@ -46,7 +46,7 @@
                     android:id="@+id/submission_positive_other_warning_hero_illustration"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
-                    android:src="@drawable/ic_submission_illustration_other_warning"
+                    app:srcCompat="@drawable/ic_submission_illustration_other_warning"
                     android:contentDescription="@string/submission_positive_other_illustration_description"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_result_ready.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_result_ready.xml
index 7ef34d33e1ad6d7a77fe5d928d417c12675d3828..19af93c4224f7a2ae6ab6c335c12a2fb7921bede 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_result_ready.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_result_ready.xml
@@ -45,7 +45,7 @@
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:focusable="true"
-                    android:src="@drawable/ic_illustration_together"
+                    app:srcCompat="@drawable/ic_illustration_together"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_available.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_available.xml
index 494201687e24ae17343f7fbfe52b09a59c38dedd..f99c53fd0e19a574434d0d59aaaff60ff5e476b4 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_available.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_available.xml
@@ -43,7 +43,7 @@
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:focusable="true"
-                    android:src="@drawable/ic_test_result_illustration_result_available"
+                    app:srcCompat="@drawable/ic_test_result_illustration_result_available"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_positive_no_consent.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_positive_no_consent.xml
index d320ca471a02a0d43ce56a4e6af72d956acdfcef..22c8a25e0c3c164f43102b69b0f7481dc9ba3dcf 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_positive_no_consent.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_positive_no_consent.xml
@@ -71,7 +71,7 @@
                     android:backgroundTint="@color/button_red"
                     android:importantForAccessibility="no"
                     android:padding="@dimen/circle_icon_padding"
-                    android:src="@drawable/ic_submission_share"
+                    app:srcCompat="@drawable/ic_submission_share"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toBottomOf="@+id/submission_test_result_positive_no_consent_subtitle"
                     app:tint="@color/colorStableLight" />
@@ -96,7 +96,7 @@
                     android:layout_marginTop="@dimen/spacing_normal"
                     android:background="@drawable/circle"
                     android:backgroundTint="@color/button_red"
-                    android:src="@drawable/ic_lock"
+                    app:srcCompat="@drawable/ic_lock"
                     android:importantForAccessibility="no"
                     android:padding="@dimen/circle_icon_big_padding"
                     app:layout_constraintTop_toBottomOf="@+id/submission_test_result_positive_no_consent_text_1"
@@ -121,7 +121,7 @@
                     android:layout_marginTop="@dimen/spacing_normal"
                     android:background="@drawable/circle"
                     android:backgroundTint="@color/button_red"
-                    android:src="@drawable/ic_risk_details_home"
+                    app:srcCompat="@drawable/ic_risk_details_home"
                     android:importantForAccessibility="no"
                     android:padding="@dimen/circle_icon_big_padding"
                     app:layout_constraintTop_toBottomOf="@+id/submission_test_result_positive_no_consent_text_2"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_test_home_test_cards_layout.xml b/Corona-Warn-App/src/main/res/layout/fragment_test_home_test_cards_layout.xml
new file mode 100644
index 0000000000000000000000000000000000000000..03465872a364efac9d328977fc27308d20df4190
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/fragment_test_home_test_cards_layout.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:bind="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <data>
+        <variable
+            name="tracingHeader"
+            type="de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState" />
+    </data>
+
+    <LinearLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:contentDescription="@string/main_title"
+        android:orientation="vertical"
+        tools:context=".ui.main.MainActivity">
+
+        <Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/CWAToolbar"
+            app:popupTheme="@style/CWAToolbar.Menu">
+            <ImageView
+                android:id="@+id/main_header_logo"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="start|center_vertical"
+                android:focusable="true"
+                android:scaleType="fitStart"
+                android:src="@drawable/ic_main_header"
+                bind:cwaContentDescription="@{@string/accessibility_logo}"
+                tools:ignore="ContentDescription" />
+        </Toolbar>
+
+        <androidx.coordinatorlayout.widget.CoordinatorLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <com.google.android.material.appbar.AppBarLayout
+                android:layout_width="match_parent"
+                android:theme="@style/CWAToolbar"
+                android:layout_height="wrap_content">
+
+                <Toolbar
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:elevation="8dp"
+                    style="@style/CWAToolbar"
+                    android:id="@+id/main_tracing"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:foreground="?selectableItemBackground"
+                    app:layout_scrollFlags="scroll|snap|enterAlways">
+
+                    <TextView
+                        android:id="@+id/main_tracing_headline"
+                        style="@style/bodyButton"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginEnd="72dp"
+                        android:contentDescription="@{tracingHeader.getTracingContentDescription(context)}"
+                        android:focusable="false"
+                        android:gravity="start|center_vertical"
+                        android:text="@{tracingHeader.getTracingDescription(context)}"
+                        tools:text="@string/settings_tracing_body_inactive_location" />
+
+                    <com.airbnb.lottie.LottieAnimationView
+                        android:id="@+id/main_tracing_icon"
+                        android:layout_width="@dimen/icon_size_main_card"
+                        android:layout_height="@dimen/icon_size_main_card"
+                        android:layout_gravity="end|center_vertical"
+                        android:layout_marginEnd="@dimen/spacing_small"
+                        android:importantForAccessibility="no"
+                        app:animation="@{tracingHeader.getTracingAnimation(context)}"
+                        app:animation_tint="@{tracingHeader.getTracingTint(context)}" />
+
+                </Toolbar>
+
+            </com.google.android.material.appbar.AppBarLayout>
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/recycler_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                app:layout_behavior="@string/appbar_scrolling_view_behavior"
+                tools:itemCount="3"
+                tools:listitem="@layout/home_submission_status_card_unregistered" />
+
+        </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+    </LinearLayout>
+</layout>
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml b/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml
index 3d8399ed798a5a067a043be3e65539c5df86eccb..fd7e77dd2b97a4de774bf6831549a61990609b76 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml
@@ -46,7 +46,7 @@
                 android:layout_marginHorizontal="24dp"
                 android:layout_marginTop="4dp"
                 android:contentDescription="@string/trace_location_onboarding_content_description"
-                android:src="@drawable/trace_location_onboarding"
+                app:srcCompat="@drawable/trace_location_onboarding"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />
@@ -83,7 +83,7 @@
                 android:layout_marginStart="@dimen/spacing_normal"
                 android:layout_marginTop="@dimen/spacing_large"
                 android:importantForAccessibility="no"
-                android:src="@drawable/ic_qr_tracing_static"
+                app:srcCompat="@drawable/ic_qr_tracing_static"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_subtitle" />
 
@@ -105,7 +105,7 @@
                 android:layout_marginStart="@dimen/spacing_normal"
                 android:layout_marginTop="@dimen/spacing_medium"
                 android:importantForAccessibility="no"
-                android:src="@drawable/ic_qr_time"
+                app:srcCompat="@drawable/ic_qr_time"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_warning" />
 
@@ -158,7 +158,7 @@
                     android:layout_width="8dp"
                     android:layout_height="8dp"
                     android:layout_marginTop="24dp"
-                    android:src="@drawable/bullet_point"
+                    app:srcCompat="@drawable/bullet_point"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_card_subtitle" />
 
@@ -194,7 +194,7 @@
                     android:layout_width="8dp"
                     android:layout_height="8dp"
                     android:layout_marginTop="22dp"
-                    android:src="@drawable/bullet_point"
+                    app:srcCompat="@drawable/bullet_point"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body3" />
 
@@ -217,7 +217,7 @@
                     android:layout_width="8dp"
                     android:layout_height="8dp"
                     android:layout_marginTop="22dp"
-                    android:src="@drawable/bullet_point"
+                    app:srcCompat="@drawable/bullet_point"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body4" />
 
@@ -261,4 +261,4 @@
         </androidx.constraintlayout.widget.ConstraintLayout>
     </androidx.core.widget.NestedScrollView>
 
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_create_trace_location_card.xml b/Corona-Warn-App/src/main/res/layout/home_create_trace_location_card.xml
index bd13f263097bac37c72691edcc6641fd6e7054f5..e63bb3580bb19b981148fbe005306bdb3dc81cde 100644
--- a/Corona-Warn-App/src/main/res/layout/home_create_trace_location_card.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_create_trace_location_card.xml
@@ -36,7 +36,7 @@
         android:id="@+id/create_trace_location_card_icon"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:src="@drawable/ic_event_organizer"
+        app:srcCompat="@drawable/ic_event_organizer"
         android:importantForAccessibility="no"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="@id/create_trace_location_card_subtitle" />
diff --git a/Corona-Warn-App/src/main/res/layout/home_faq_card_layout.xml b/Corona-Warn-App/src/main/res/layout/home_faq_card_layout.xml
index 55e41cb3cfde7f09bd5169f55931a5be4028b1d2..aeb33f5ad66e44f8153b2b01d6859aca5c03bd68 100644
--- a/Corona-Warn-App/src/main/res/layout/home_faq_card_layout.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_faq_card_layout.xml
@@ -15,7 +15,7 @@
             android:layout_width="@dimen/icon_size_button"
             android:layout_height="@dimen/icon_size_button"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_main_about"
+            app:srcCompat="@drawable/ic_main_about"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@+id/main_card_header_headline"
             app:layout_constraintStart_toStartOf="parent"
@@ -42,7 +42,7 @@
             android:layout_width="@dimen/icon_size_external_link"
             android:layout_height="@dimen/icon_size_external_link"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_link"
+            app:srcCompat="@drawable/ic_link"
             app:layout_constraintBottom_toBottomOf="@+id/main_card_header_headline"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@+id/main_card_header_headline" />
diff --git a/Corona-Warn-App/src/main/res/layout/home_fragment_layout.xml b/Corona-Warn-App/src/main/res/layout/home_fragment_layout.xml
index 03465872a364efac9d328977fc27308d20df4190..683de0e8d2f457d3cfe850a568d1d6439032a072 100644
--- a/Corona-Warn-App/src/main/res/layout/home_fragment_layout.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_fragment_layout.xml
@@ -31,7 +31,7 @@
                 android:layout_gravity="start|center_vertical"
                 android:focusable="true"
                 android:scaleType="fitStart"
-                android:src="@drawable/ic_main_header"
+                app:srcCompat="@drawable/ic_main_header"
                 bind:cwaContentDescription="@{@string/accessibility_logo}"
                 tools:ignore="ContentDescription" />
         </Toolbar>
diff --git a/Corona-Warn-App/src/main/res/layout/home_reenable_risk_card_layout.xml b/Corona-Warn-App/src/main/res/layout/home_reenable_risk_card_layout.xml
index 9ea36f1e033de8ad1f81be69ffed907d820e7a80..34fcc456137c95fbce1b2a6d5a5b3a242f5fb89f 100644
--- a/Corona-Warn-App/src/main/res/layout/home_reenable_risk_card_layout.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_reenable_risk_card_layout.xml
@@ -2,12 +2,6 @@
 <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">
-    
-    <data>
-        <variable
-            name="state"
-            type="de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone" />
-    </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
@@ -66,7 +60,6 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginEnd="@dimen/spacing_small"
-            android:text="@{state.formatTestRegistrationText(context)}"
             tools:text="@string/reenable_risk_card_test_registration_string"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/reenable_risk_card_positive_body" />
diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml
index a2bd07c8f2264973bfb2a1e84db6985393cc5e08..7eee9560c166dc4b7e507df6d0fd25f7b1a25c92 100644
--- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml
@@ -18,7 +18,7 @@
             android:layout_height="wrap_content"
             android:importantForAccessibility="no"
             android:contentDescription="@null"
-            android:src="@drawable/ic_statistics_incidence"
+            app:srcCompat="@drawable/ic_statistics_incidence"
             android:paddingStart="0dp"
             android:paddingEnd="@dimen/spacing_small"
             app:layout_constraintEnd_toEndOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml
index f44904922ab4c86fe30b4e7141d40e5deb3897c9..1d54ca078e19c2008ac9b13851aaa11bb7d8fe20 100644
--- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml
@@ -18,7 +18,7 @@
             android:layout_height="wrap_content"
             android:importantForAccessibility="no"
             android:contentDescription="@null"
-            android:src="@drawable/ic_main_illustration_infection"
+            app:srcCompat="@drawable/ic_main_illustration_infection"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/flow_layout" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml
index 781e99cdf4a48cc807509c0b6f227212d8d5a91d..1d486842a6fee8dbac82c1637f95608e5b6e3114 100644
--- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml
@@ -18,7 +18,7 @@
             android:layout_height="wrap_content"
             android:importantForAccessibility="no"
             android:contentDescription="@null"
-            android:src="@drawable/ic_main_illustration_warnende_personen"
+            app:srcCompat="@drawable/ic_main_illustration_warnende_personen"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/flow_layout" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml
index feca8a674d5c9f9583b3c1bd2a67794507d57128..57f7e82475116fe8e63ca7d7a8eab9beb0c464b0 100644
--- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml
@@ -17,7 +17,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_7_day_r_value"
+            app:srcCompat="@drawable/ic_7_day_r_value"
             android:paddingStart="@dimen/spacing_normal"
             android:paddingEnd="@dimen/spacing_normal"
             app:layout_constraintEnd_toEndOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_error.xml b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_error.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c49ecb6fb79e3c97427dfdd60238de2e7cf77e75
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_error.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="8dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_error"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_error"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_invalid" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/show_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_show_results"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_invalid.xml b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_invalid.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6c03a73581718fbcb1cc1ae1141b536c37c3d49f
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_invalid.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="8dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_invalid"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_not_valid_test"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_invalid" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/delete_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_failed"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_negative.xml b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_negative.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6b23023977d63eda004770d1cf3ba16fa93d96d8
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_negative.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/findings"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_findings"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/corona_name"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_name_of_the_cause_of_this_app"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/findings" />
+
+    <TextView
+        android:id="@+id/status"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_negative"
+        android:textColor="@color/colorTextSemanticGreen"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/corona_name" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="@dimen/spacing_tiny"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_negative"
+        app:layout_constraintBottom_toTopOf="@id/date"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/status" />
+
+    <TextView
+        android:id="@+id/date"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:layout_marginBottom="32dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_body_result_date"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/body" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="24dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_test_result_illustration_negative_card" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_pending.xml b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_pending.xml
new file mode 100644
index 0000000000000000000000000000000000000000..da6cb6b82918c50ac40cc55550cf4f550ff4f7f9
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_pending.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="20dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_no_result"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_body_no_result"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_pending" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/show_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_show_results"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_positive_not_shared.xml b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_positive_not_shared.xml
new file mode 100644
index 0000000000000000000000000000000000000000..65812e9d4cd43430cba8cc75fd50b5139125c4f6
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_positive_not_shared.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/card_padding">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/findings"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_findings"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/corona_name"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_name_of_the_cause_of_this_app"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/findings" />
+
+    <TextView
+        android:id="@+id/status"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_positiv"
+        android:textColor="@color/colorTextSemanticRed"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/corona_name" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_tiny"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_positive"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/status" />
+
+    <TextView
+        android:id="@+id/date"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:layout_marginBottom="32dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_body_result_date"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/body" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_test_result_illustration_positive_card" />
+
+    <include
+        android:id="@+id/divider"
+        layout="@layout/include_divider"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/date" />
+
+    <TextView
+        android:id="@+id/result_subtitle"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_normal"
+        android:accessibilityHeading="true"
+        android:text="@string/submission_status_card_positive_result_subtitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contact_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_contact"
+        app:layout_constraintEnd_toStartOf="@id/contact_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/result_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contact_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_contact"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contact_icon"
+        app:layout_constraintTop_toTopOf="@+id/contact_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contagious_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_home"
+        app:layout_constraintEnd_toStartOf="@id/contagious_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/contact_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contagious_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_contagious"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contagious_icon"
+        app:layout_constraintTop_toTopOf="@+id/contagious_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/share_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_submission_share_cards"
+        app:layout_constraintEnd_toStartOf="@id/share_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/contagious_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/share_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_share"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/share_icon"
+        app:layout_constraintTop_toTopOf="@+id/share_icon" />
+
+    <Button
+        android:id="@+id/submission_status_card_positive_button"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        android:text="@string/submission_test_result_positive_no_consent_button_warn_others"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/share_subtitle" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_positive_shared.xml b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_positive_shared.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bc0dd85a63b74a3bc6535adf01720ef3b162ce55
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_positive_shared.xml
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/card_padding">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/findings"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_findings"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/corona_name"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_name_of_the_cause_of_this_app"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/findings" />
+
+    <TextView
+        android:id="@+id/status"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_positiv"
+        android:textColor="@color/colorTextSemanticRed"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/corona_name" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_tiny"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_positive"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/status" />
+
+    <TextView
+        android:id="@+id/date"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:layout_marginBottom="32dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_body_result_date"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/body" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_test_result_illustration_positive_card" />
+
+    <include
+        android:id="@+id/divider"
+        layout="@layout/include_divider"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/date" />
+
+    <TextView
+        android:id="@+id/result_subtitle"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_normal"
+        android:accessibilityHeading="true"
+        android:text="@string/submission_status_card_positive_result_subtitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contact_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_contact"
+        app:layout_constraintEnd_toStartOf="@id/contact_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/result_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contact_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_contact"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contact_icon"
+        app:layout_constraintTop_toTopOf="@+id/contact_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contagious_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_home"
+        app:layout_constraintEnd_toStartOf="@id/contagious_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/contact_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contagious_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginBottom="12dp"
+        android:text="@string/submission_status_card_positive_result_contagious"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contagious_icon"
+        app:layout_constraintTop_toTopOf="@+id/contagious_icon" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_ready.xml b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_ready.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b4d9ecbbc923c8b46b325a88aee703e881bc1484
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_pcr_status_card_ready.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_pcr_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="8dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_result_available"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_available"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_invalid" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/show_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_show_results"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/button_barrier" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_error.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_error.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eec3d36c7890f062905f402dea4b655fb337c9cd
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_error.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="8dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_error"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_error"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_invalid" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/show_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_show_results"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_invalid.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_invalid.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5ee0fdbae1dff0e9e2f1e858e0c6c246cc3d1c0e
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_invalid.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="8dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_invalid"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_not_valid_test"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_invalid" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/delete_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_failed"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_negative.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_negative.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c08d9fef1b9255c8f777133500ac06f80c87b5cc
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_negative.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/findings"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_findings"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/corona_name"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_name_of_the_cause_of_this_app"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/findings" />
+
+    <TextView
+        android:id="@+id/status"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_negative"
+        android:textColor="@color/colorTextSemanticGreen"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/corona_name" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="@dimen/spacing_tiny"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_negative"
+        app:layout_constraintBottom_toTopOf="@id/date"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/status" />
+
+    <TextView
+        android:id="@+id/date"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:layout_marginBottom="32dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapid_body_result_date"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/body" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="24dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_test_result_illustration_negative_card" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_outdated.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_outdated.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8ba510968c65f48c5a71a07cf4fd0832fe514ff7
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_outdated.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="2dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_status_outdated_test"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_body_outdated_test"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="24dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_test_result_illustration_outdated_card" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/dont_show_anymore_button"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/ag_homescreen_card_rapidtest_dont_show_anymore_button"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_pending.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_pending.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5c9eb6e5541b817f82f7dfaa98704166d77bf1bc
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_pending.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="8dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_no_result"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_body_no_result"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_pending" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/show_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_show_results"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_positive_not_shared.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_positive_not_shared.xml
new file mode 100644
index 0000000000000000000000000000000000000000..33bf2d3e74a207b9d98019a3d909ac701ceb5355
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_positive_not_shared.xml
@@ -0,0 +1,242 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/card_padding">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/findings"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_findings"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/corona_name"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_name_of_the_cause_of_this_app"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/findings" />
+
+    <TextView
+        android:id="@+id/status"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_positiv"
+        android:textColor="@color/colorTextSemanticRed"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/corona_name" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_tiny"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_positive"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/status" />
+
+    <TextView
+        android:id="@+id/date"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:layout_marginBottom="32dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapid_body_result_date"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/body" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_test_result_illustration_positive_card" />
+
+    <include
+        android:id="@+id/divider"
+        layout="@layout/include_divider"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/date" />
+
+    <TextView
+        android:id="@+id/result_subtitle"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_normal"
+        android:accessibilityHeading="true"
+        android:text="@string/submission_status_card_positive_result_subtitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/prc_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:paddingHorizontal="12dp"
+        android:paddingVertical="11dp"
+        android:src="@drawable/ic_risk_details_pcr"
+        app:layout_constraintEnd_toStartOf="@id/pcr_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/result_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/pcr_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/ag_homescreen_card_rapidtest_body_result_positive_pcr_check"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/prc_icon"
+        app:layout_constraintTop_toTopOf="@+id/prc_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contact_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_contact"
+        app:layout_constraintEnd_toStartOf="@id/contact_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/pcr_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contact_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_contact"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contact_icon"
+        app:layout_constraintTop_toTopOf="@+id/contact_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contagious_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_home"
+        app:layout_constraintEnd_toStartOf="@id/contagious_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/contact_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contagious_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_contagious"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contagious_icon"
+        app:layout_constraintTop_toTopOf="@+id/contagious_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/share_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_submission_share_cards"
+        app:layout_constraintEnd_toStartOf="@id/share_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/contagious_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/share_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_share"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/share_icon"
+        app:layout_constraintTop_toTopOf="@+id/share_icon" />
+
+    <Button
+        android:id="@+id/submission_status_card_positive_button"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        android:text="@string/submission_test_result_positive_no_consent_button_warn_others"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/share_subtitle" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_positive_shared.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_positive_shared.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9ecf98e1234564db8b62a7b6c85f92dd1929c866
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_positive_shared.xml
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/card_padding">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/findings"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_findings"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/corona_name"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_name_of_the_cause_of_this_app"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/findings" />
+
+    <TextView
+        android:id="@+id/status"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_positiv"
+        android:textColor="@color/colorTextSemanticRed"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/corona_name" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_tiny"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_positive"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/status" />
+
+    <TextView
+        android:id="@+id/date"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:layout_marginBottom="32dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapid_body_result_date"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/body" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_test_result_illustration_positive_card" />
+
+    <include
+        android:id="@+id/divider"
+        layout="@layout/include_divider"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/date" />
+
+    <TextView
+        android:id="@+id/result_subtitle"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_normal"
+        android:accessibilityHeading="true"
+        android:text="@string/submission_status_card_positive_result_subtitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/prc_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:paddingHorizontal="12dp"
+        android:paddingVertical="11dp"
+        android:src="@drawable/ic_risk_details_pcr"
+        app:layout_constraintEnd_toStartOf="@id/pcr_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/result_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/pcr_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/ag_homescreen_card_rapidtest_body_result_positive_pcr_check"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/prc_icon"
+        app:layout_constraintTop_toTopOf="@+id/prc_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contact_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_contact"
+        app:layout_constraintEnd_toStartOf="@id/contact_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/pcr_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contact_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/submission_status_card_positive_result_contact"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contact_icon"
+        app:layout_constraintTop_toTopOf="@+id/contact_icon" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/contagious_icon"
+        style="@style/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="28dp"
+        android:background="@drawable/circle"
+        android:backgroundTint="@color/colorSemanticHighRisk"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:padding="10dp"
+        android:src="@drawable/ic_risk_details_home"
+        app:layout_constraintEnd_toStartOf="@id/contagious_subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/contact_subtitle"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/contagious_subtitle"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginBottom="12dp"
+        android:text="@string/submission_status_card_positive_result_contagious"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/contagious_icon"
+        app:layout_constraintTop_toTopOf="@+id/contagious_icon" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_ready.xml b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_ready.xml
new file mode 100644
index 0000000000000000000000000000000000000000..26747ccb991403571436853831e9c862d6b917f0
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_rapid_status_card_ready.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_rapidtest_title"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/headline6"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="8dp"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_status_result_available"
+        android:textColor="@color/colorTextSemanticNeutral"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitleMedium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="@dimen/spacing_small"
+        android:focusable="false"
+        android:text="@string/ag_homescreen_card_body_result_available"
+        app:layout_constraintEnd_toStartOf="@+id/icon"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/title"
+        app:srcCompat="@drawable/ic_main_illustration_invalid" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/button_barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="icon,body" />
+
+    <Button
+        android:id="@+id/show_test_action"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/card_padding"
+        android:text="@string/submission_status_card_button_show_results"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/button_barrier" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_done.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_done.xml
index 7d43888b64c917b0c02fa946b95a41ec52ba7034..0e7f27076e6d6dd165c763adeba1d2769e9ab5e0 100644
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_done.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_done.xml
@@ -29,7 +29,7 @@
             android:layout_marginTop="@dimen/spacing_tiny"
             android:contentDescription="@string/submission_done_illustration_description"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_illustration_together"
+            app:srcCompat="@drawable/ic_illustration_together"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/submission_done_card_title" />
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_error.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_error.xml
deleted file mode 100644
index 63aa89dc006172f5cdb9f4abd4945d877388f53c..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_error.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<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">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:showIn="@layout/home_card_container_layout">
-
-        <TextView
-            android:id="@+id/title"
-            style="@style/headline5"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/card_padding"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:accessibilityHeading="true"
-            android:focusable="false"
-            android:text="@string/submission_status_card_title_available"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <TextView
-            android:id="@+id/subtitle"
-            style="@style/headline6"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_subtitle_invalid"
-            android:textColor="@color/colorTextSemanticNeutral"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/title" />
-
-        <TextView
-            android:id="@+id/body"
-            style="@style/subtitleMedium"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_tiny"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_body_invalid"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/subtitle" />
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:importantForAccessibility="no"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/title"
-            app:srcCompat="@drawable/ic_main_illustration_invalid" />
-
-        <androidx.constraintlayout.widget.Barrier
-            android:id="@+id/button_barrier"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:barrierDirection="bottom"
-            app:constraint_referenced_ids="icon,body" />
-
-        <Button
-            android:id="@+id/show_test_action"
-            style="@style/buttonPrimary"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/card_padding"
-            android:layout_marginBottom="@dimen/card_padding"
-            android:text="@string/submission_status_card_button_show_results"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-</layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_fetching.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_fetching.xml
index bc6badd3cc05e4d9b4cec32cd376d78254756055..8cab6e02e053f0ff116905fecf3341fb4d8eacea 100644
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_fetching.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_fetching.xml
@@ -18,7 +18,15 @@
             android:text="@string/submission_status_card_title_fetching"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@+id/header_barrier"/>
+
+        <androidx.constraintlayout.widget.Barrier
+            android:id="@+id/header_barrier"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:barrierDirection="top"
+            app:constraint_referenced_ids="submission_status_card_fetching_spinner,submission_status_card_fetching_body" />
 
         <ProgressBar
             android:id="@+id/submission_status_card_fetching_spinner"
@@ -27,7 +35,7 @@
             android:layout_height="wrap_content"
             android:layout_marginVertical="@dimen/spacing_normal"
             app:layout_constraintStart_toStartOf="@+id/submission_status_card_fetching_title"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_fetching_title" />
+            app:layout_constraintTop_toBottomOf="@+id/header_barrier" />
 
         <TextView
             android:id="@+id/submission_status_card_fetching_body"
@@ -41,6 +49,13 @@
             app:layout_constraintStart_toEndOf="@+id/submission_status_card_fetching_spinner"
             app:layout_constraintTop_toTopOf="@+id/submission_status_card_fetching_spinner" />
 
+        <androidx.constraintlayout.widget.Barrier
+            android:id="@+id/button_barrier"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:barrierDirection="bottom"
+            app:constraint_referenced_ids="submission_status_card_fetching_spinner,submission_status_card_fetching_body" />
+
         <Button
             android:id="@+id/show_test_action"
             style="@style/buttonPrimary"
@@ -51,7 +66,7 @@
             android:text="@string/submission_status_card_button_show_results"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_fetching_spinner" />
+            app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_invalid.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_invalid.xml
deleted file mode 100644
index 2c16e8dfc436d69b8fee51894c38694a415da11b..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_invalid.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<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">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:showIn="@layout/home_card_container_layout">
-
-        <TextView
-            android:id="@+id/title"
-            style="@style/headline5"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/card_padding"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:accessibilityHeading="true"
-            android:focusable="false"
-            android:text="@string/submission_status_card_title_failed"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <TextView
-            android:id="@+id/body"
-            style="@style/subtitleMedium"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_body_failed"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="@+id/title"
-            app:layout_constraintTop_toBottomOf="@+id/title" />
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:importantForAccessibility="no"
-            app:layout_constraintBottom_toBottomOf="@+id/body"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@+id/body"
-            app:srcCompat="@drawable/ic_main_illustration_invalid" />
-
-        <androidx.constraintlayout.widget.Barrier
-            android:id="@+id/button_barrier"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:barrierDirection="bottom"
-            app:constraint_referenced_ids="icon,body" />
-
-        <Button
-            android:id="@+id/delete_test_action"
-            style="@style/buttonPrimary"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/card_padding"
-            android:layout_marginBottom="@dimen/card_padding"
-            android:text="@string/submission_status_card_button_failed"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-</layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_negative.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_negative.xml
deleted file mode 100644
index 74b0a58c7f43aeb1b90d54c380820aa6f6f8ab65..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_negative.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<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">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:showIn="@layout/home_card_container_layout">
-
-        <TextView
-            android:id="@+id/title"
-            style="@style/headline5"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/card_padding"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:accessibilityHeading="true"
-            android:focusable="false"
-            android:text="@string/submission_status_card_title_available"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <TextView
-            android:id="@+id/subtitle"
-            style="@style/headline6"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_subtitle_negative"
-            android:textColor="@color/colorTextSemanticGreen"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/title" />
-
-        <TextView
-            android:id="@+id/body"
-            style="@style/subtitleMedium"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_tiny"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_body_negative"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/subtitle" />
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:srcCompat="@drawable/ic_main_illustration_negative"
-            android:importantForAccessibility="no"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/title" />
-
-        <androidx.constraintlayout.widget.Barrier
-            android:id="@+id/button_barrier"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:barrierDirection="bottom"
-            app:constraint_referenced_ids="icon,body" />
-
-        <Button
-            android:id="@+id/show_test_action"
-            style="@style/buttonPrimary"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/card_padding"
-            android:layout_marginBottom="@dimen/card_padding"
-            android:text="@string/submission_status_card_button_show_results"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_pending.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_pending.xml
deleted file mode 100644
index 9a2257a3cc6c8559d8fb024905bb8b786a8ab7b0..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_pending.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<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">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:showIn="@layout/home_card_container_layout">
-
-        <TextView
-            android:id="@+id/title"
-            style="@style/headline5"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/card_padding"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:accessibilityHeading="true"
-            android:focusable="false"
-            android:text="@string/submission_status_card_title_pending"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <TextView
-            android:id="@+id/body"
-            style="@style/subtitleMedium"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_tiny"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_body_pending"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/title" />
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:importantForAccessibility="no"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/title"
-            app:srcCompat="@drawable/ic_main_illustration_pending" />
-
-        <androidx.constraintlayout.widget.Barrier
-            android:id="@+id/button_barrier"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:barrierDirection="bottom"
-            app:constraint_referenced_ids="icon,body" />
-
-        <Button
-            android:id="@+id/show_test_action"
-            style="@style/buttonPrimary"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_small"
-            android:layout_marginEnd="@dimen/card_padding"
-            android:layout_marginBottom="@dimen/card_padding"
-            android:text="@string/submission_status_card_button_show_results"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/button_barrier" />
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_positive.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_positive.xml
deleted file mode 100644
index dc381249a1cffde20e1a393f25ed9049ad63a06b..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_positive.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<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">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="@dimen/card_padding"
-        tools:showIn="@layout/home_card_container_layout">
-
-        <TextView
-            android:id="@+id/submission_status_card_positive_title"
-            style="@style/headline5"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:accessibilityHeading="true"
-            android:text="@string/submission_status_card_title_positive"
-            app:layout_constraintEnd_toStartOf="@id/submission_status_card_positive_next"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <include
-            android:id="@+id/submission_status_card_positive_next"
-            layout="@layout/include_button_icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:icon="@{@drawable/ic_forward}"
-            app:iconDescription="@{@string/accessibility_next}"
-            app:layout_constraintBottom_toBottomOf="@+id/submission_status_card_positive_title"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@+id/submission_status_card_positive_title" />
-
-        <include
-            android:id="@+id/submission_status_card_positive_result_card"
-            layout="@layout/include_test_result_card_positive"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_positive_title" />
-
-        <include
-            android:id="@+id/divider"
-            layout="@layout/include_divider"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_positive_result_card" />
-
-        <TextView
-            android:id="@+id/submission_status_card_positive_result_subtitle"
-            style="@style/headline5"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:accessibilityHeading="true"
-            android:text="@string/submission_status_card_positive_result_subtitle"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/divider" />
-
-        <include
-            android:id="@+id/submission_status_card_positive_result_contact"
-            layout="@layout/include_submission_behaviour_row"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            app:body="@{@string/submission_status_card_positive_result_contact}"
-            app:icon="@{@drawable/ic_risk_details_contact}"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_positive_result_subtitle" />
-
-        <include
-            android:id="@+id/submission_status_card_positive_result_contagious"
-            layout="@layout/include_submission_behaviour_row"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            app:body="@{@string/submission_status_card_positive_result_contagious}"
-            app:icon="@{@drawable/ic_submission_home}"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_positive_result_contact" />
-
-        <include
-            android:id="@+id/submission_status_card_positive_result_share"
-            layout="@layout/include_submission_behaviour_row"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            app:body="@{@string/submission_status_card_positive_result_share}"
-            app:icon="@{@drawable/ic_submission_share}"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_positive_result_contagious" />
-
-        <Button
-            android:id="@+id/submission_status_card_positive_button"
-            style="@style/buttonPrimary"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:text="@string/submission_test_result_positive_no_consent_button_warn_others"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_status_card_positive_result_share" />
-
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_ready.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_ready.xml
deleted file mode 100644
index 4898df7f38d0af23fa707f8ca168bd6bf189d1ad..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_ready.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<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">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:showIn="@layout/home_card_container_layout">
-
-        <TextView
-            android:id="@+id/title"
-            style="@style/headline5"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/card_padding"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:accessibilityHeading="true"
-            android:focusable="false"
-            android:text="@string/submission_status_card_title_ready"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <TextView
-            android:id="@+id/subtitle"
-            style="@style/headline6Sixteen"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_subtitle_ready"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="@+id/title"
-            app:layout_constraintTop_toBottomOf="@+id/title" />
-
-        <TextView
-            android:id="@+id/body"
-            style="@style/subtitleMedium"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/spacing_small"
-            android:focusable="false"
-            android:text="@string/submission_status_card_body_ready"
-            app:layout_constraintEnd_toStartOf="@+id/icon"
-            app:layout_constraintStart_toStartOf="@+id/subtitle"
-            app:layout_constraintTop_toBottomOf="@+id/subtitle" />
-
-        <ImageView
-            android:id="@+id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:importantForAccessibility="no"
-            app:layout_constraintBottom_toBottomOf="@+id/body"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@+id/subtitle"
-            app:srcCompat="@drawable/ic_main_illustration_invalid" />
-
-        <androidx.constraintlayout.widget.Barrier
-            android:id="@+id/button_barrier"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:barrierDirection="bottom"
-            app:constraint_referenced_ids="icon,body" />
-
-        <Button
-            android:id="@+id/show_test_action"
-            style="@style/buttonPrimary"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/card_padding"
-            android:layout_marginTop="@dimen/spacing_normal"
-            android:layout_marginEnd="@dimen/card_padding"
-            android:layout_marginBottom="@dimen/card_padding"
-            android:text="@string/submission_status_card_retrieve_test_result"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/button_barrier" />
-
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_unregistered.xml b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_unregistered.xml
index e7f47cc6b0a30c5f92dda088514df2bcf89fc3d0..4004baa2d329e216b4ca2f88560529a381f8bfed 100644
--- a/Corona-Warn-App/src/main/res/layout/home_submission_status_card_unregistered.xml
+++ b/Corona-Warn-App/src/main/res/layout/home_submission_status_card_unregistered.xml
@@ -18,7 +18,7 @@
             android:layout_marginEnd="@dimen/spacing_small"
             android:accessibilityHeading="true"
             android:focusable="false"
-            android:text="@string/submission_status_card_title_unregistered"
+            android:text="@string/ag_homescreen_card_test_register_title"
             app:layout_constraintEnd_toStartOf="@+id/icon"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
@@ -32,7 +32,7 @@
             android:layout_marginTop="@dimen/spacing_normal"
             android:layout_marginEnd="@dimen/spacing_small"
             android:focusable="false"
-            android:text="@string/submission_status_card_body_unregistered"
+            android:text="@string/ag_homescreen_card_test_register_body"
             app:layout_constraintEnd_toStartOf="@+id/icon"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/title" />
diff --git a/Corona-Warn-App/src/main/res/layout/include_bullet_point.xml b/Corona-Warn-App/src/main/res/layout/include_bullet_point.xml
index 099185c7ef900b417cb1b5382f195da98da6e0dc..38b441f371c276918ee45353ed30c9538eae575d 100644
--- a/Corona-Warn-App/src/main/res/layout/include_bullet_point.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_bullet_point.xml
@@ -7,7 +7,7 @@
         android:layout_width="@dimen/bullet_point_size"
         android:layout_height="@dimen/bullet_point_size"
         android:baseline="@dimen/bullet_point_baseline_offset"
-        android:src="@drawable/bullet_point"
+        app:srcCompat="@drawable/bullet_point"
         android:importantForAccessibility="no"
         app:layout_constraintStart_toStartOf="parent"
         tools:showIn="@layout/include_submission_consent_body" />
diff --git a/Corona-Warn-App/src/main/res/layout/include_dispatcher_card.xml b/Corona-Warn-App/src/main/res/layout/include_dispatcher_card.xml
index 968a7b08957fc02c7274e65190138e541dcad47a..d2a3907c5506158b2d26d2d037fe2d87be67dbe1 100644
--- a/Corona-Warn-App/src/main/res/layout/include_dispatcher_card.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_dispatcher_card.xml
@@ -50,7 +50,7 @@
             android:layout_marginTop="@dimen/spacing_small"
             android:layout_marginEnd="@dimen/spacing_normal"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_forward"
+            app:srcCompat="@drawable/ic_forward"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent"
             app:tint="@color/colorTextPrimary1" />
diff --git a/Corona-Warn-App/src/main/res/layout/include_interop_riskdetails_no_countries_infoview.xml b/Corona-Warn-App/src/main/res/layout/include_interop_riskdetails_no_countries_infoview.xml
index 5038e7ba3ced6952a41d33f711c8e59c3992452c..1ade9dc9d3b8c8a400d2b80d7c1760582af92cd0 100644
--- a/Corona-Warn-App/src/main/res/layout/include_interop_riskdetails_no_countries_infoview.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_interop_riskdetails_no_countries_infoview.xml
@@ -17,7 +17,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_interop_no_network"
+            app:srcCompat="@drawable/ic_interop_no_network"
             android:visibility="visible"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/include_interoperability.xml b/Corona-Warn-App/src/main/res/layout/include_interoperability.xml
index 092efae0ac1754aeb5b7901f6372ee8fd26081a8..9a9dac88001cac1b8d79bc51eecd80df9bb26c3d 100644
--- a/Corona-Warn-App/src/main/res/layout/include_interoperability.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_interoperability.xml
@@ -86,7 +86,7 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:contentDescription="@{@string/interoperability_eu_illustration_description}"
-                    android:src="@drawable/ic_illustration_interoperability"
+                    app:srcCompat="@drawable/ic_illustration_interoperability"
                     android:visibility="@{FormatterHelper.formatVisibility(!isOnboarding)}"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/include_navigation_row.xml b/Corona-Warn-App/src/main/res/layout/include_navigation_row.xml
index 1daa4173899acc7a5080e99384755e5e8b9c0462..192d8b7ed6df2f8d27f20aadfdf923240d7359e0 100644
--- a/Corona-Warn-App/src/main/res/layout/include_navigation_row.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_navigation_row.xml
@@ -13,52 +13,56 @@
             name="icon"
             type="android.graphics.drawable.Drawable" />
 
+        <variable
+            name="title"
+            type="String" />
+
         <variable
             name="subtitle"
             type="String" />
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/navigation_row"
+        style="@style/phoneNumber"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="wrap_content"
+        android:focusable="true">
 
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/navigation_row"
-            style="@style/row"
+        <ImageView
+            android:id="@+id/navigation_row_icon"
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_marginEnd="@dimen/spacing_small"
+            android:importantForAccessibility="no"
+            android:src="@{icon}"
+            android:visibility="@{FormatterHelper.formatVisibilityIcon(icon)}"
+            app:layout_constraintBottom_toBottomOf="@id/navigation_row_title"
+            app:layout_constraintEnd_toStartOf="@+id/navigation_row_title"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@id/navigation_row_title" />
+
+        <TextView
+            android:id="@+id/navigation_row_title"
+            style="@style/headline5Tint"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:focusable="true"
+            android:text="@{title}"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent">
+            app:layout_constraintStart_toEndOf="@+id/navigation_row_icon"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="@string/settings_notifications_button_open_settings" />
 
-            <ImageView
-                android:id="@+id/navigation_row_icon"
-                style="@style/icon"
-                android:layout_width="@dimen/icon_size_settings"
-                android:layout_height="@dimen/icon_size_settings"
-                android:layout_marginEnd="@dimen/spacing_small"
-                android:importantForAccessibility="no"
-                android:src="@{icon}"
-                android:visibility="@{FormatterHelper.formatVisibilityIcon(icon)}"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toStartOf="@+id/navigation_row_subtitle"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-            <TextView
-                android:id="@+id/navigation_row_subtitle"
-                style="@style/headline5Tint"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:text="@{subtitle}"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@+id/navigation_row_icon"
-                app:layout_constraintTop_toTopOf="parent"
-                tools:text="@string/settings_notifications_button_open_settings" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
+        <TextView
+            android:id="@+id/navigation_row_subtitle"
+            style="@style/body2"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/spacing_tiny"
+            android:text="@{subtitle}"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@id/navigation_row_title"
+            app:layout_constraintTop_toBottomOf="@id/navigation_row_title" />
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 </layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_consent_body.xml b/Corona-Warn-App/src/main/res/layout/include_submission_consent_body.xml
index c9475f94df2f31f1f10e6fba2e5f28fec1b052cf..dcef17ee05da26127ee138b5e4eb435c384628d9 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_consent_body.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_consent_body.xml
@@ -15,7 +15,7 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@id/submission_consent_your_consent_subsection_headline"
-        app:layout_constraintBottom_toBottomOf="@id/submission_consent_your_consent_subsection_third_point"
+        app:layout_constraintBottom_toBottomOf="@id/submission_consent_your_consent_subsection_fourth_point"
         style="@style/GreyCard" />
 
     <TextView
@@ -40,32 +40,32 @@
         android:text="@string/submission_consent_your_consent_subsection_tapping_agree"
         style="@style/subtitle" />
 
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/submission_consent_your_consent_subsection_first_point"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:paddingVertical="@dimen/spacing_normal"
-        app:layout_constraintTop_toBottomOf="@id/submission_consent_your_consent_subsection_tapping_agree"
-        app:layout_constraintEnd_toEndOf="@id/guideline_end"
-        app:layout_constraintStart_toStartOf="@id/guideline_start">
-
-        <include layout="@layout/include_bullet_point"
-            android:layout_width="@dimen/bullet_point_size"
-            android:layout_height="@dimen/bullet_point_size"
-            app:layout_constraintBaseline_toBaselineOf="@id/submission_consent_your_consent_subsection_first_point_text"/>
-
-        <TextView
-            android:id="@+id/submission_consent_your_consent_subsection_first_point_text"
-            style="@style/subtitle"
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/submission_consent_your_consent_subsection_first_point"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/bullet_point_spacing_after"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toEndOf="@id/bullet_point"
-            app:layout_constraintTop_toTopOf="parent"
-            android:text="@string/submission_consent_your_consent_subsection_first_point" />
+            android:paddingVertical="@dimen/spacing_normal"
+            app:layout_constraintTop_toBottomOf="@id/submission_consent_your_consent_subsection_tapping_agree"
+            app:layout_constraintEnd_toEndOf="@id/guideline_end"
+            app:layout_constraintStart_toStartOf="@id/guideline_start">
+
+            <include layout="@layout/include_bullet_point"
+                android:layout_width="@dimen/bullet_point_size"
+                android:layout_height="@dimen/bullet_point_size"
+                app:layout_constraintBaseline_toBaselineOf="@id/submission_consent_your_consent_subsection_first_point_text"/>
+
+            <TextView
+                android:id="@+id/submission_consent_your_consent_subsection_first_point_text"
+                style="@style/subtitle"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/bullet_point_spacing_after"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/bullet_point"
+                app:layout_constraintTop_toTopOf="parent"
+                android:text="@string/submission_consent_your_consent_subsection_first_point" />
 
-    </androidx.constraintlayout.widget.ConstraintLayout>
+        </androidx.constraintlayout.widget.ConstraintLayout>
 
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/submission_consent_your_consent_subsection_second_point"
@@ -94,8 +94,35 @@
 
         </androidx.constraintlayout.widget.ConstraintLayout>
 
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/submission_consent_your_consent_subsection_third_point"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/spacing_normal"
+            app:layout_constraintTop_toBottomOf="@id/submission_consent_your_consent_subsection_second_point"
+            app:layout_constraintEnd_toEndOf="@id/guideline_end"
+            app:layout_constraintStart_toStartOf="@id/guideline_start">
+
+            <include layout="@layout/include_bullet_point"
+                android:layout_width="@dimen/bullet_point_size"
+                android:layout_height="@dimen/bullet_point_size"
+                app:layout_constraintBaseline_toBaselineOf="@id/submission_consent_your_consent_subsection_third_point_text"/>
+
+            <TextView
+                android:id="@+id/submission_consent_your_consent_subsection_third_point_text"
+                style="@style/subtitle"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/bullet_point_spacing_after"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/bullet_point"
+                app:layout_constraintTop_toTopOf="parent"
+                android:text="@string/submission_consent_your_consent_subsection_third_point" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
     <TextView
-        android:id="@+id/submission_consent_your_consent_subsection_third_point"
+        android:id="@+id/submission_consent_your_consent_subsection_fourth_point"
         style="@style/subtitle"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -103,8 +130,8 @@
         android:paddingBottom="@dimen/spacing_normal"
         app:layout_constraintEnd_toEndOf="@id/guideline_end"
         app:layout_constraintStart_toStartOf="@id/guideline_start"
-        app:layout_constraintTop_toBottomOf="@id/submission_consent_your_consent_subsection_second_point"
-        android:text="@string/submission_consent_your_consent_subsection_third_point"
+        app:layout_constraintTop_toBottomOf="@id/submission_consent_your_consent_subsection_third_point"
+        android:text="@string/submission_consent_your_consent_subsection_fourth_point"
         tools:ignore="RtlSymmetry" />
 
     <include layout="@layout/view_bullet_point_text"
@@ -114,7 +141,7 @@
         android:layout_marginTop="@dimen/spacing_normal"
         app:layout_constraintEnd_toEndOf="@id/guideline_end"
         app:layout_constraintStart_toStartOf="@id/guideline_start"
-        app:layout_constraintTop_toBottomOf="@id/submission_consent_your_consent_subsection_third_point"
+        app:layout_constraintTop_toBottomOf="@id/submission_consent_your_consent_subsection_fourth_point"
         app:itemText="@{@string/submission_consent_main_first_point}" />
 
     <include layout="@layout/view_bullet_point_text"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_consent_intro.xml b/Corona-Warn-App/src/main/res/layout/include_submission_consent_intro.xml
index 26d2702fea7740b3b8aa6a6a5554e93a9119984a..4bd8c6d0bb6afbbebfa7a735be2f716da5dd4def 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_consent_intro.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_consent_intro.xml
@@ -33,7 +33,7 @@
             android:layout_width="@dimen/circle_icon"
             android:layout_height="@dimen/circle_icon"
             android:layout_marginTop="@dimen/spacing_normal"
-            android:src="@drawable/ic_qr_code"
+            app:srcCompat="@drawable/ic_qr_code"
             android:background="@drawable/circle"
             android:backgroundTint="@color/card_dark"
             android:padding="@dimen/circle_icon_padding"
@@ -60,7 +60,7 @@
             android:layout_marginTop="@dimen/spacing_normal"
             android:background="@drawable/circle"
             android:backgroundTint="@color/card_dark"
-            android:src="@drawable/ic_qr_1x_test"
+            app:srcCompat="@drawable/ic_qr_1x_test"
             android:importantForAccessibility="no"
             android:padding="8dp"
             app:layout_constraintTop_toBottomOf="@+id/submission_consent_call_test_result_scan_your_test_only"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_contact.xml b/Corona-Warn-App/src/main/res/layout/include_submission_contact.xml
index 803f64096c2751a5f945e042d9214780fd30ace5..fa7bc987503ed860b8f32ca10412e8ebce046b66 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_contact.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_contact.xml
@@ -16,8 +16,8 @@
                 android:id="@+id/submission_contact_illustration"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:src="@drawable/ic_submission_illustration_hotline"
                 android:focusable="true"
+                app:srcCompat="@drawable/ic_submission_illustration_hotline"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
@@ -30,8 +30,8 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_small"
-                android:text="@string/submission_contact_body"
                 android:focusable="true"
+                android:text="@string/submission_contact_body"
                 app:layout_constraintEnd_toStartOf="@+id/guideline_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@id/submission_contact_illustration" />
@@ -39,12 +39,12 @@
             <TextView
                 android:id="@+id/submission_contact_headline"
                 style="@style/headline5"
-                android:accessibilityHeading="true"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_medium"
-                android:text="@string/submission_contact_headline"
+                android:accessibilityHeading="true"
                 android:focusable="true"
+                android:text="@string/submission_contact_headline"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="@id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@+id/submission_contact_body" />
@@ -54,8 +54,8 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
-                android:focusable="true"
                 android:contentDescription="@string/submission_contact_step_1_content"
+                android:focusable="true"
                 app:layout_constraintEnd_toStartOf="@+id/guideline_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@id/submission_contact_headline"
@@ -77,30 +77,40 @@
                         app:layout_constraintStart_toStartOf="parent"
                         app:layout_constraintTop_toTopOf="parent" />
 
-                    <TextView
-                        android:id="@+id/submission_contact_step_1_number"
-                        style="@style/headline5"
-                        android:accessibilityHeading="true"
+                    <include
+                        android:id="@+id/submission_contact_navigation_row_phone"
+                        layout="@layout/include_navigation_row"
                         android:layout_width="0dp"
                         android:layout_height="wrap_content"
-                        android:layout_marginTop="@dimen/spacing_small"
-                        android:text="@string/submission_contact_number_display"
-                        android:textColor="@color/colorTextTint"
-                        android:clickable="true"
+                        android:layout_marginTop="@dimen/spacing_normal"
                         app:layout_constraintEnd_toEndOf="parent"
                         app:layout_constraintStart_toStartOf="parent"
-                        app:layout_constraintTop_toBottomOf="@id/submission_contact_step_1_body" />
+                        app:layout_constraintTop_toBottomOf="@id/submission_contact_step_1_body"
+                        app:subtitle="@{@string/information_contact_button_phone_description}"
+                        app:title="@{@string/submission_contact_number_display}" />
+
+                    <include
+                        android:id="@+id/submission_contact_navigation_row_international_phone"
+                        layout="@layout/include_navigation_row"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/spacing_normal"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toBottomOf="@id/submission_contact_navigation_row_phone"
+                        app:subtitle="@{@string/information_contact_button_international_phone_description}"
+                        app:title="@{@string/submission_contact_button_international_phone}" />
 
                     <TextView
                         android:id="@+id/submission_contact_operating_hours_body"
                         style="@style/body2"
                         android:layout_width="0dp"
                         android:layout_height="wrap_content"
-                        android:layout_marginTop="@dimen/spacing_small"
+                        android:layout_marginTop="@dimen/spacing_normal"
                         android:text="@string/submission_contact_operating_hours_body"
                         app:layout_constraintEnd_toEndOf="parent"
                         app:layout_constraintStart_toStartOf="parent"
-                        app:layout_constraintTop_toBottomOf="@id/submission_contact_step_1_number" />
+                        app:layout_constraintTop_toBottomOf="@id/submission_contact_navigation_row_international_phone" />
 
                     <TextView
                         android:id="@+id/submission_contact_body_other"
@@ -121,8 +131,8 @@
                 android:id="@+id/submission_contact_step_2"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:focusable="true"
                 android:contentDescription="@string/submission_contact_step_2_content"
+                android:focusable="true"
                 app:layout_constraintEnd_toStartOf="@+id/guideline_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@+id/submission_contact_step_1"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_done.xml b/Corona-Warn-App/src/main/res/layout/include_submission_done.xml
index b199de4cec6a04d42d224a849c4b02a8664c3850..3e6226cf040a033607934d78954bfe81eae9b50f 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_done.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_done.xml
@@ -18,7 +18,7 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:focusable="true"
-                android:src="@drawable/ic_illustration_together"
+                app:srcCompat="@drawable/ic_illustration_together"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml b/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml
index 688adae687beb54590ddaf5bf1186b08d013c7d2..f8b15560263ab87d295a4164ecc0ff76bfc2fb0a 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_positive_other_warning.xml
@@ -25,7 +25,7 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 bind:cwaContentDescription="@{@string/submission_positive_other_illustration_description}"
-                android:src="@drawable/ic_submission_illustration_other_warning"
+                app:srcCompat="@drawable/ic_submission_illustration_other_warning"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml b/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml
index 54e8aaf5becc0597eb9667956d84bd4633243a29..e3c6daffd7549badb95fa1bcf755e7efe0c5e39d 100644
--- a/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml
@@ -11,7 +11,7 @@
 
         <variable
             name="deviceUIState"
-            type="de.rki.coronawarnapp.util.NetworkRequestWrapper&lt;de.rki.coronawarnapp.util.DeviceUIState,java.lang.Throwable>" />
+            type="de.rki.coronawarnapp.util.DeviceUIState" />
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
diff --git a/Corona-Warn-App/src/main/res/layout/new_release_info_item.xml b/Corona-Warn-App/src/main/res/layout/new_release_info_item.xml
index c5a0c49a6aac2f350c82a45119862e36e49c3e0a..a1b010aae990ccdc95a6fba9af35dc10f6f69244 100644
--- a/Corona-Warn-App/src/main/res/layout/new_release_info_item.xml
+++ b/Corona-Warn-App/src/main/res/layout/new_release_info_item.xml
@@ -25,7 +25,7 @@
             android:layout_marginStart="@dimen/spacing_normal"
             android:layout_marginTop="@dimen/spacing_tiny"
             android:importantForAccessibility="no"
-            android:src="@drawable/bullet_point"
+            app:srcCompat="@drawable/bullet_point"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="@id/title" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml b/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml
index 78182d1514903c3832529ac05a92e3234d932629..9761fcdcf4c4c0ac5dc8596a25ea536ce56538fc 100644
--- a/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml
@@ -40,7 +40,7 @@
                     android:contentDescription="@string/release_info_header"
                     android:focusable="true"
                     android:layout_marginTop="@dimen/spacing_normal"
-                    android:src="@drawable/ic_new_release_info"
+                    app:srcCompat="@drawable/ic_new_release_info"
                     android:importantForAccessibility="no"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/statistics_trend_view.xml b/Corona-Warn-App/src/main/res/layout/statistics_trend_view.xml
index d9c7fca9a466a4cd532a5facac95eb95f768c1e2..28b4dfaa304ef0674d6a93f56d7db2397ab3d0ac 100644
--- a/Corona-Warn-App/src/main/res/layout/statistics_trend_view.xml
+++ b/Corona-Warn-App/src/main/res/layout/statistics_trend_view.xml
@@ -1,11 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 
     <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
         android:id="@+id/trend"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:padding="1dp"
         android:background="@drawable/bg_statistics_trend_neutral"
         android:scaleType="center"
-        android:src="@drawable/ic_trend_stable" />
+        app:srcCompat="@drawable/ic_trend_stable" />
 
diff --git a/Corona-Warn-App/src/main/res/layout/survey_consent_fragment.xml b/Corona-Warn-App/src/main/res/layout/survey_consent_fragment.xml
index 114e09de6df2f0c9a191818f4a189186033db836..fe1c3c7ef61715c5c5e095444fe87ac18213bc71 100644
--- a/Corona-Warn-App/src/main/res/layout/survey_consent_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/survey_consent_fragment.xml
@@ -39,7 +39,7 @@
                     android:layout_height="wrap_content"
                     android:contentDescription="@string/contact_diary_onboarding_image_content_description"
                     android:focusable="true"
-                    android:src="@drawable/ic_illustration_datenspende"
+                    app:srcCompat="@drawable/ic_illustration_datenspende"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml
index 1e665b70bab77733c13522cf12757475f33c3306..b2934dbf99a1385ba0ce5a991093769fe4e9b70a 100644
--- a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml
@@ -49,7 +49,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:contentDescription="@string/trace_location_checkins_empty_illustration_accessibility"
-            android:src="@drawable/trace_location_my_check_ins_empty_illustration" />
+            app:srcCompat="@drawable/trace_location_my_check_ins_empty_illustration" />
 
         <TextView
             style="@style/subtitleMedium"
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_past.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_past.xml
index 8a08c8cae5c1d37737e064dc3d05774e3fca87bd..3ead1f5c9f3a44c01dab82dc8354845bf80d0520 100644
--- a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_past.xml
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_past.xml
@@ -17,7 +17,7 @@
         android:layout_marginTop="16dp"
         android:layout_marginBottom="16dp"
         android:orientation="vertical"
-        android:src="@drawable/ic_old_checkin"
+        app:srcCompat="@drawable/ic_old_checkin"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_consent_header.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_consent_header.xml
new file mode 100644
index 0000000000000000000000000000000000000000..04f98963fdd96a10a3b35d32d63c2b6791bde0f0
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_consent_header.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="24dp"
+    android:paddingTop="18dp"
+    android:paddingEnd="24dp"
+    android:paddingBottom="8dp">
+
+    <TextView
+        style="@style/subtitleMedium"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/trace_location_attendee_consent_header_description" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/select_all_button"
+        style="@style/materialTextButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_marginTop="8dp"
+        android:text="@string/trace_location_attendee_consent_header_button" />
+</LinearLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_consent_selectable_check_in.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_consent_selectable_check_in.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7037299401d7b52e5a6c3c3ed248bbd79a9d7618
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_consent_selectable_check_in.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 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"
+    style="@style/contactDiaryCardRipple"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="16dp"
+    android:layout_marginVertical="8dp"
+    android:focusable="true">
+
+    <ImageView
+        android:id="@+id/checkbox"
+        android:layout_width="@dimen/spacing_medium"
+        android:layout_height="@dimen/spacing_medium"
+        android:layout_marginStart="@dimen/spacing_small"
+        android:layout_marginTop="13dp"
+        android:background="?selectableItemBackgroundBorderless"
+        android:clickable="false"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/ic_unselected"
+        tools:srcCompat="@drawable/ic_selected" />
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/materialSubtitleSixteen"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="15dp"
+        android:layout_marginTop="13dp"
+        android:layout_marginEnd="10dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/checkbox"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Hairdresser" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        style="@style/body2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginEnd="10dp"
+        android:textColor="@color/colorTextPrimary2"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@id/title"
+        app:layout_constraintTop_toBottomOf="@id/title"
+        tools:text="Berlin" />
+
+    <TextView
+        android:id="@+id/checkoutInfo"
+        style="@style/body2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginEnd="10dp"
+        android:layout_marginBottom="10dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@id/title"
+        app:layout_constraintTop_toBottomOf="@id/subtitle"
+        tools:text="21.01.21, 18:01 - 21:00 Uhr" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml
index 9396dc1576dfc32de53f628d19d795c828a87d19..36589681fc501f9e38582a7790969b2071e1dab8 100644
--- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml
@@ -36,7 +36,7 @@
                     android:id="@+id/expandedImage"
                     android:layout_width="match_parent"
                     android:layout_height="match_parent"
-                    android:src="@drawable/trace_location_view_cardhighlight_gradient"
+                    app:srcCompat="@drawable/trace_location_view_cardhighlight_gradient"
                     app:layout_collapseMode="parallax" />
 
                 <LinearLayout
@@ -95,7 +95,7 @@
                             android:layout_width="match_parent"
                             android:layout_height="wrap_content"
                             android:layout_marginEnd="72dp"
-                            android:src="@drawable/ic_cwa_logo_white" />
+                            app:srcCompat="@drawable/ic_cwa_logo_white" />
 
                     </LinearLayout>
 
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml
index c106bb4ddcea1a87288b686c7b5d3415f985b7df..de5948e1fe8de67d48496593342c7b4bda2dde40 100644
--- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml
@@ -39,7 +39,7 @@
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_huge"
                 android:contentDescription="@string/trace_location_qr_info_content_description"
-                android:src="@drawable/ic_qr_info"
+                app:srcCompat="@drawable/ic_qr_info"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />
@@ -76,7 +76,7 @@
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_large"
                 android:importantForAccessibility="no"
-                android:src="@drawable/ic_qr_tracing_static"
+                app:srcCompat="@drawable/ic_qr_tracing_static"
                 app:layout_constraintStart_toStartOf="@id/trace_location_qr_info_subtitle"
                 app:layout_constraintTop_toBottomOf="@id/trace_location_qr_info_subtitle" />
 
@@ -98,7 +98,7 @@
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_medium"
                 android:importantForAccessibility="no"
-                android:src="@drawable/ic_qr_code_illustration"
+                app:srcCompat="@drawable/ic_qr_code_illustration"
                 app:layout_constraintStart_toStartOf="@id/trace_location_tracing_icon"
                 app:layout_constraintTop_toBottomOf="@id/trace_location_qr_info_tracing" />
 
@@ -120,7 +120,7 @@
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_medium"
                 android:importantForAccessibility="no"
-                android:src="@drawable/ic_qr_time"
+                app:srcCompat="@drawable/ic_qr_time"
                 app:layout_constraintTop_toBottomOf="@id/trace_location_qr_code_illustration_text"
                 app:layout_constraintStart_toStartOf="@id/trace_location_qr_code_icon" />
 
@@ -166,7 +166,7 @@
         </androidx.constraintlayout.widget.ConstraintLayout>
     </ScrollView>
 
-    <android.widget.Button
+    <Button
         android:id="@+id/trace_location_qr_info_understand_button"
         style="@style/buttonPrimary"
         android:layout_width="0dp"
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml
index 3b0d989a634d8d026167b26298a3e64c6c9bb7f7..89a547a8123b56d1b249532b67451b21a864dbdd 100644
--- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml
@@ -23,7 +23,7 @@
             android:layout_width="42dp"
             android:layout_height="42dp"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_qr_code_list_item_icon"
+            app:srcCompat="@drawable/ic_qr_code_list_item_icon"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_disabled_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_disabled_view.xml
index 827d81c0156ad03c52f36737ba861c7949a6938c..b5ab40269e5bcc93d7b540ce596642d053fd03b1 100644
--- a/Corona-Warn-App/src/main/res/layout/tracing_content_disabled_view.xml
+++ b/Corona-Warn-App/src/main/res/layout/tracing_content_disabled_view.xml
@@ -34,7 +34,7 @@
             android:layout_width="@dimen/icon_size_risk_card"
             android:layout_height="@dimen/icon_size_risk_card"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_forward"
+            app:srcCompat="@drawable/ic_forward"
             app:tint="@color/colorTextPrimary1"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_failed_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_failed_view.xml
index 101480a1308bbae54cce8732059f937b7f994380..31da8067055d0218e12fe0675f01f10b53026c44 100644
--- a/Corona-Warn-App/src/main/res/layout/tracing_content_failed_view.xml
+++ b/Corona-Warn-App/src/main/res/layout/tracing_content_failed_view.xml
@@ -33,7 +33,7 @@
             android:layout_width="@dimen/icon_size_risk_card"
             android:layout_height="@dimen/icon_size_risk_card"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_forward"
+            app:srcCompat="@drawable/ic_forward"
             app:tint="@color/colorTextPrimary1"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml
index 12af62996f2fc7360995f144a1f974461e5fe820..79688669a658d2ce286d953b6c04897b3dc5cc3c 100644
--- a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml
+++ b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml
@@ -35,7 +35,7 @@
             android:layout_width="@dimen/icon_size_risk_card"
             android:layout_height="@dimen/icon_size_risk_card"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_forward"
+            app:srcCompat="@drawable/ic_forward"
             app:tint="@color/colorStableLight"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml
index 1fbb229efc0de19a2b00f8c9ebd378da8cffd902..0c569562d3d1fe3ad86431eb15aa91f29f59327d 100644
--- a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml
+++ b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml
@@ -37,7 +37,7 @@
             android:layout_width="@dimen/icon_size_risk_card"
             android:layout_height="@dimen/icon_size_risk_card"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_forward"
+            app:srcCompat="@drawable/ic_forward"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent"
             app:tint="@color/colorStableLight"
diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_progress_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_progress_view.xml
index b425d258b53994166fe68cea2109ff11a51aedcf..10154cbf3cecfb6b580dac8e08dc49c4b288f87d 100644
--- a/Corona-Warn-App/src/main/res/layout/tracing_content_progress_view.xml
+++ b/Corona-Warn-App/src/main/res/layout/tracing_content_progress_view.xml
@@ -35,7 +35,7 @@
             android:layout_width="@dimen/icon_size_risk_card"
             android:layout_height="@dimen/icon_size_risk_card"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_forward"
+            app:srcCompat="@drawable/ic_forward"
             app:tint="@{state.getStableIconColor(context)}"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml
index 62b3800457ba532756f6d8dc31ea351e0c0bc290..2d200a6d28b5737e5e0a71e59e46440234a5b20b 100644
--- a/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml
+++ b/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml
@@ -71,7 +71,7 @@
                 android:layout_marginStart="@dimen/spacing_small"
                 android:layout_marginTop="@dimen/spacing_mega_tiny"
                 android:importantForAccessibility="no"
-                android:src="@drawable/bullet_point"
+                app:srcCompat="@drawable/bullet_point"
                 app:tint="@color/colorSemanticHighRisk"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="@id/risk_details_behavior_bullet_point_1" />
@@ -97,7 +97,7 @@
                 android:layout_marginStart="@dimen/spacing_small"
                 android:layout_marginTop="@dimen/spacing_mega_tiny"
                 android:importantForAccessibility="no"
-                android:src="@drawable/bullet_point"
+                app:srcCompat="@drawable/bullet_point"
                 app:tint="@color/colorSemanticHighRisk"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="@id/risk_details_behavior_bullet_point_2" />
@@ -124,7 +124,7 @@
                 android:layout_marginStart="@dimen/spacing_small"
                 android:layout_marginTop="@dimen/spacing_mega_tiny"
                 android:importantForAccessibility="no"
-                android:src="@drawable/bullet_point"
+                app:srcCompat="@drawable/bullet_point"
                 app:tint="@color/colorSemanticHighRisk"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="@id/risk_details_behavior_bullet_point_3" />
diff --git a/Corona-Warn-App/src/main/res/layout/view_bullet_point_entry.xml b/Corona-Warn-App/src/main/res/layout/view_bullet_point_entry.xml
index dd70a00c8d45f6a7ab3a04aa457f28a7949e7afc..8be17fc926adfb4d93b3a0e321929d6d4fa05a1d 100644
--- a/Corona-Warn-App/src/main/res/layout/view_bullet_point_entry.xml
+++ b/Corona-Warn-App/src/main/res/layout/view_bullet_point_entry.xml
@@ -12,7 +12,7 @@
         android:layout_height="@dimen/bullet_point_size"
         android:layout_marginStart="@dimen/bullet_point_spacing_before"
         android:baseline="@dimen/bullet_point_baseline_offset"
-        android:src="@drawable/bullet_point"
+        app:srcCompat="@drawable/bullet_point"
         android:importantForAccessibility="no"
         app:layout_constraintBaseline_toBaselineOf="@+id/bullet_point_content"
         app:layout_constraintStart_toStartOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/view_bullet_point_text.xml b/Corona-Warn-App/src/main/res/layout/view_bullet_point_text.xml
index 776045ec7dc7532c1e64e97f1a32fff1581a66b0..65bf6f10070a81e8d9d15f30f9fa3026b28bf4cd 100644
--- a/Corona-Warn-App/src/main/res/layout/view_bullet_point_text.xml
+++ b/Corona-Warn-App/src/main/res/layout/view_bullet_point_text.xml
@@ -19,7 +19,7 @@
             android:layout_width="@dimen/bullet_point_size"
             android:layout_height="@dimen/bullet_point_size"
             android:baseline="@dimen/bullet_point_baseline_offset"
-            android:src="@drawable/bullet_point"
+            app:srcCompat="@drawable/bullet_point"
             android:importantForAccessibility="no"
             app:layout_constraintBaseline_toBaselineOf="@+id/bullet_point_content"
             app:layout_constraintStart_toStartOf="parent" />
diff --git a/Corona-Warn-App/src/main/res/layout/view_circle_progress.xml b/Corona-Warn-App/src/main/res/layout/view_circle_progress.xml
index 209542e7621e609b83451ce49d1c0ab4b3d1f4cf..f5b023d5db5e8290d642dc22dd86a725d7f37484 100644
--- a/Corona-Warn-App/src/main/res/layout/view_circle_progress.xml
+++ b/Corona-Warn-App/src/main/res/layout/view_circle_progress.xml
@@ -22,7 +22,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:importantForAccessibility="no"
-            android:src="@drawable/ic_risk_card_saved_days"
+            app:srcCompat="@drawable/ic_risk_card_saved_days"
             app:tint="@color/colorAccentTintIcon"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/view_consent_status.xml b/Corona-Warn-App/src/main/res/layout/view_consent_status.xml
index 8f533776c8ebf58cdd2888e1df32d25814fa8671..4bbcf160f22e3e82960aae722126b940c4f915ac 100644
--- a/Corona-Warn-App/src/main/res/layout/view_consent_status.xml
+++ b/Corona-Warn-App/src/main/res/layout/view_consent_status.xml
@@ -27,7 +27,7 @@
         android:focusable="false"
         android:importantForAccessibility="no"
         android:paddingTop="4dp"
-        android:src="@drawable/ic_consent_status_view_icon"
+        app:srcCompat="@drawable/ic_consent_status_view_icon"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintVertical_bias="0.0" />
diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
index f8b5895d66971e5a7352055e161eaad7677f69c1..5ff1d99cff64e4f55a40b3bcde41772a920a595b 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -278,6 +278,11 @@
             app:destination="@id/submissionResultReadyFragment"
             app:popUpTo="@id/mainFragment"
             app:popUpToInclusive="false" />
+        <action
+            android:id="@+id/action_submissionResultPositiveOtherWarningNoConsentFragment_to_checkInsConsentFragment"
+            app:destination="@id/checkInsConsentFragment"
+            app:popUpTo="@id/mainFragment"
+            app:popUpToInclusive="false" />
     </fragment>
     <fragment
         android:id="@+id/submissionTestResultPendingFragment"
@@ -488,6 +493,11 @@
             app:destination="@id/submissionTestResultConsentGivenFragment"
             app:popUpTo="@id/mainFragment"
             app:popUpToInclusive="false" />
+        <action
+            android:id="@+id/action_submissionTestResultAvailableFragment_to_checkInsConsentFragment"
+            app:destination="@id/checkInsConsentFragment"
+            app:popUpTo="@id/mainFragment"
+            app:popUpToInclusive="false" />
         <action
             android:id="@+id/action_submissionTestResultAvailableFragment_to_submissionTestResultNoConsentFragment"
             app:destination="@id/submissionTestResultNoConsentFragment"
@@ -609,4 +619,28 @@
         android:name="de.rki.coronawarnapp.bugreporting.debuglog.ui.legal.DebugLogLegalFragment"
         android:label="DebugLogLegalFragment"
         tools:layout="@layout/bugreporting_legal_fragment" />
+
+    <fragment
+        android:id="@+id/checkInsConsentFragment"
+        android:name="de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent.CheckInsConsentFragment"
+        android:label="check_ins_consent_fragment"
+        tools:layout="@layout/check_ins_consent_fragment">
+        <action
+            android:id="@+id/action_checkInsConsentFragment_to_mainFragment"
+            app:destination="@id/mainFragment"
+            app:popUpTo="@id/nav_graph"
+            app:popUpToInclusive="true" />
+
+        <action
+            android:id="@+id/action_checkInsConsentFragment_to_submissionResultReadyFragment"
+            app:destination="@id/submissionResultReadyFragment"
+            app:popUpTo="@id/mainFragment"
+            app:popUpToInclusive="false" />
+
+        <action
+            android:id="@+id/action_checkInsConsentFragment_to_submissionTestResultConsentGivenFragment"
+            app:destination="@id/submissionTestResultConsentGivenFragment"
+            app:popUpTo="@id/mainFragment"
+            app:popUpToInclusive="false" />
+    </fragment>
 </navigation>
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 646797d6076689e0bb7ec0078da5c49ab7d18f6e..3b78ec9880e3753956cb89e11d5bc8bcd4960ee7 100644
--- a/Corona-Warn-App/src/main/res/values-bg/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml
@@ -1973,4 +1973,22 @@
     <string name="trace_location_organiser_poster_share">"Споделяне"</string>
     <!-- XHED: Trace location poster title -->
     <string name="trace_location_organiser_poster_title">"Версия за печат"</string>
+    <!-- XHED: Trace location check-ins consent screen title -->
+    <string name="trace_location_attendee_consent_title">"Ще споделите ли своите регистрации от локациите, които сте посетили?"</string>
+    <!-- XTXT: Trace location check-ins consent screen header description -->
+    <string name="trace_location_attendee_consent_header_description">"Предупредете останалите, които също са се регистрирали в близост до вас. Вашите лични данни няма да бъдат споделени."</string>
+    <!-- XBUT: Trace location check-ins consent screen header button -->
+    <string name="trace_location_attendee_consent_header_button">"Избери всички"</string>
+    <!-- XBUT: Trace location check-ins consent screen continue button -->
+    <string name="trace_location_attendee_consent_continue">"Предаване"</string>
+    <!-- XBUT: Trace location check-ins consent screen skip button -->
+    <string name="trace_location_attendee_consent_skip">"Пропускане"</string>
+    <!-- XHED: Trace location check-ins consent screen dialog title -->
+    <string name="trace_location_attendee_consent_dialog_title">"Наистина ли не желаете да споделите своите регистрации?"</string>
+    <!-- XTXT: Trace location check-ins consent screen dialog message -->
+    <string name="trace_location_attendee_consent_dialog_message">"Като пропускате споделянето, вие не изпращате предупреждение на други контактни лица, регистрирани в близост до вас."</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog positive button -->
+    <string name="trace_location_attendee_consent_dialog_positive_button">"Споделяне"</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog negative button -->
+    <string name="trace_location_attendee_consent_dialog_negative_button">"Да не се споделя"</string>
 </resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3f69d7869cbb2f30d4ed86edf20e0273a6fe2135
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation">
+
+    <!-- ####################################
+        Homescreen cards - register test card
+    ###################################### -->
+    <!-- XHED: Register test homescreen card: title -->
+    <string name="ag_homescreen_card_test_register_title">"Test registrieren"</string>
+    <!-- XTXT: Register test homescreen card: body -->
+    <string name="ag_homescreen_card_test_register_body">"Nutzen Sie die App, um Ihre Testergebnisse abrufen und schneller warnen zu können."</string>
+
+    <!-- ####################################
+        Homescreen cards - common buttons
+    ###################################### -->
+    <!-- XBUT: Homescreen card: show test button -->
+    <string name="ag_homescreen_card_show_button">"Test anzeigen"</string>
+    <!-- XBUT: Homescreen card: warn others button -->
+    <string name="ag_homescreen_card_warn_others_button">"Andere warnen"</string>
+
+    <!-- ####################################
+        Homescreen cards - common status
+    ###################################### -->
+    <!-- XTXT: Homescreen card: status subtitle - no result -->
+    <string name="ag_homescreen_card_status_no_result">"Ergebnis liegt noch nicht vor"</string>
+    <!-- XTXT: Homescreen card: status subtitle - result available -->
+    <string name="ag_homescreen_card_status_result_available">"Testergebnis abrufen"</string>
+    <!-- XTXT: Homescreen card: status subtitle - error -->
+    <string name="ag_homescreen_card_status_error">"Fehlerhafter Test"</string>
+    <!-- XTXT: Homescreen card: status subtitle - invalid -->
+    <string name="ag_homescreen_card_status_invalid">"Nicht mehr gültig"</string>
+    <!-- XTXT: Homescreen card: status subtitle - findings -->
+    <string name="ag_homescreen_card_status_findings">"Befund"</string>
+    <!-- XTXT: Homescreen card: subtitle - corona official name -->
+    <string name="ag_homescreen_card_status_name_of_the_cause_of_this_app">"SARS-CoV-2"</string>
+    <!-- XTXT: Homescreen card: status - negative -->
+    <string name="ag_homescreen_card_status_negative">"Negativ"</string>
+    <!-- XTXT: Homescreen card: status - positiv -->
+    <string name="ag_homescreen_card_status_positiv">"Positiv"</string>
+
+    <!-- ####################################
+       Homescreen cards - common body
+   ###################################### -->
+    <!-- XTXT: homescreen card: body - result available -->
+    <string name="ag_homescreen_card_body_result_available">"Warnen Sie Ihre Mitmenschen, wenn Sie positiv auf Corona getestet wurden."</string>
+    <!-- XTXT: homescreen card: body - error -->
+    <string name="ag_homescreen_card_body_error">"Ihr Test konnte nicht ausgewertet werden."</string>
+    <!-- XTXT: homescreen card: body - not valid test -->
+    <string name="ag_homescreen_card_body_not_valid_test">"Ihr Test liegt länger als 21 Tage zurück und ist daher nicht länger relevant. Bitte löschen Sie den Test. Danach können Sie einen neuen Test hinzufügen. "</string>
+    <!-- XTXT: homescreen card: body - negative -->
+    <string name="ag_homescreen_card_body_result_negative">"Das Virus SARS-CoV-2 wurde bei Ihnen nicht nachgewiesen."</string>
+    <!-- XTXT: homescreen card: body - positive -->
+    <string name="ag_homescreen_card_body_result_positive">"Das Virus SARS-CoV-2 wurde bei Ihnen nachgewiesen."</string>
+
+    <!-- ####################################
+        Homescreen cards - rapid test
+    ###################################### -->
+    <!-- XHED: rapid test homescreen card: title -->
+    <string name="ag_homescreen_card_rapidtest_title">"Schnelltest"</string>
+    <!-- XBUT: rapid test homescreen card: dont show anymore button -->
+    <string name="ag_homescreen_card_rapidtest_dont_show_anymore_button">"Nicht mehr anzeigen"</string>
+    <!-- XTXT: rapid test homescreen card: status subtitle - outdated test -->
+    <string name="ag_homescreen_card_rapidtest_status_outdated_test">"Test nicht mehr aktuell"</string>
+    <!-- XTXT: rapid test homescreen card: result positive - check results with PCR test -->
+    <string name="ag_homescreen_card_rapidtest_body_result_positive_pcr_check">"Machen Sie einen PCR-Test, um dieses Test-Ergebnis zu verifizieren."</string>
+
+    <!-- Body -->
+    <!-- XTXT: rapid test homescreen card: body - no result -->
+    <string name="ag_homescreen_card_rapidtest_body_no_result">"Die Auswertung Ihres Schnelltests ist noch nicht abgeschlossen."</string>
+    <!-- XTXT: rapid test homescreen card: body - outdated test -->
+    <string name="ag_homescreen_card_rapidtest_body_outdated_test">"Ihr Schnelltest ist älter als 48 Stunden und wird hier nicht mehr angezeigt."</string>
+    <!-- XTXT: homescreen card: body - negative -->
+    <string name="ag_homescreen_card_rapid_body_result_date">"Durchgeführt am %1$s"</string>
+
+    <!-- ####################################
+        Homescreen cards - PCR
+    ###################################### -->
+    <!-- XHED: PCR homescreen card: title -->
+    <string name="ag_homescreen_card_pcr_title">"PCR-Test"</string>
+    <!-- XBUT: PCR homescreen card: clear test button -->
+    <string name="ag_homescreen_card_pcr_clear_test_button">"TEST LÖSCHEN"</string>
+
+    <!-- Body -->
+    <!-- XTXT: PCR homescreen card: body - no result -->
+    <string name="ag_homescreen_card_pcr_body_no_result">"Die Auswertung Ihres PCR-Tests ist noch nicht abgeschlossen."</string>
+    <!-- XTXT: homescreen card: body - negative -->
+    <string name="ag_homescreen_card_pcr_body_result_date">"Test registriert am %1$s"</string>
+
+</resources>
diff --git a/Corona-Warn-App/src/main/res/values-de/legal_strings.xml b/Corona-Warn-App/src/main/res/values-de/legal_strings.xml
index 1c1f0d484b10de9bb196b8474f3fad7e0a1e7a13..707f0e5913aa59fc7bc7a475794604b724890733 100644
--- a/Corona-Warn-App/src/main/res/values-de/legal_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/legal_strings.xml
@@ -16,16 +16,20 @@
     <string name="submission_consent_your_consent_subsection_headline">"Ihr Einverständnis"</string>
     <!-- YTXT: Body for consent sub section your consent subtext -->
     <string name="submission_consent_your_consent_subsection_tapping_agree">"Durch Antippen von „Einverstanden“ willigen Sie in folgende Schritte ein:"</string>
+
     <!-- YTXT: Body for consent sub section your consent subtext first point  -->
     <string name="submission_consent_your_consent_subsection_first_point">"<b>Die App ruft Ihr Testergebnis ab.</b> Wenn Sie es sich später anders überlegen, können Sie den Test in der App entfernen."</string>
     <!-- YTXT: Body for consent sub section your consent subtext second point  -->
-    <string name="submission_consent_your_consent_subsection_second_point">"<b>Wenn Sie positiv auf Corona getestet wurden, teilt die App Ihr Testergebnis, um Nutzer, denen Sie begegnet sind, zu warnen. Dies betrifft Nutzer von Corona-Apps der oben genannten Länder und Nutzer, die zeitgleich am selben Event oder Ort wie Sie eingecheckt waren.\n\nWenn Sie zusätzlich Angaben zum Beginn Ihrer Symptome machen, werden auch diese geteilt.</b>"</string>
+    <string name="submission_consent_your_consent_subsection_second_point">"<b>Wenn Sie positiv auf Corona getestet wurden, teilt die App Ihr Testergebnis, um Nutzer, denen Sie begegnet sind, zu warnen. Dies betrifft Nutzer von Corona-Apps der oben genannten Länder und Nutzer, die zeitgleich am selben Event oder Ort wie Sie eingecheckt waren. Im Falle von Schnelltests werden nur Nutzer der Corona-Warn-App gewarnt.</b>"</string>
     <!-- YTXT: Body for consent sub section your consent subtext third point  -->
-    <string name="submission_consent_your_consent_subsection_third_point">"Sie können Ihr Einverständnis jederzeit zurücknehmen. Die Einstellung hierfür finden Sie unter „Test anzeigen“. Vor dem Teilen werden Sie nochmal auf Ihr Einverständnis hingewiesen."</string>
+    <string name="submission_consent_your_consent_subsection_third_point">"<b>Wenn Sie zusätzlich Angaben zum Beginn Ihrer Symptome machen, werden auch diese geteilt.</b>"</string>
+    <!-- YTXT: Body for consent sub section your consent subtext fourth point  -->
+    <string name="submission_consent_your_consent_subsection_fourth_point">"Sie können Ihr Einverständnis jederzeit zurücknehmen. Die Einstellung hierfür finden Sie unter „Test anzeigen“. Vor dem Teilen werden Sie nochmal auf Ihr Einverständnis hingewiesen."</string>
     <!-- YTXT: Body for your consent screen agreement share test results -->
-    <string name="submission_your_consent_agreement_share_test_results">"<b>Wenn Sie positiv auf Corona getestet wurden, teilt die App Ihr Testergebnis, um Nutzer zu warnen, denen Sie begegnet sind.</b>\n\n<b>Die Warnung erreicht Nutzer von offiziellen Corona-Apps der folgenden teilnehmenden Länder:</b>"</string>
+    <string name="submission_your_consent_agreement_share_test_results">"<b>Wenn Sie positiv auf Corona getestet wurden, teilt die App Ihr Testergebnis, um Nutzer, denen Sie begegnet sind, zu warnen.</b>\n\n<b>Die Warnung betrifft Nutzer, die zeitgleich am selben Event oder Ort wie Sie eingecheckt waren und Nutzer der Corona-Apps der folgenden Länder:</b>"</string>
     <!-- YTXT: Body for your consent screen agreement share symptoms -->
-    <string name="submission_your_consent_agreement_share_symptoms">"<b>Wenn Sie zusätzlich Angaben zum Beginn Ihrer Symptome machen, werden auch diese geteilt.</b>\n\nSie können Ihr Einverständnis zurücknehmen, indem Sie oben „Andere warnen“ deaktivieren."</string>
+    <string name="submission_your_consent_agreement_share_symptoms">"<b>Im Falle von Schnelltests werden nur Nutzer der Corona-Warn-App gewarnt. Wenn Sie zusätzlich Angaben zum Beginn Ihrer Symptome machen, werden auch diese Daten geteilt.</b>\n\nSie können Ihr Einverständnis zurücknehmen, indem Sie oben „Andere warnen“ deaktivieren."</string>
+
     <!-- YTXT: Body for your consent screen agreement share symptoms with additional hint for test result-->
     <string name="submission_your_consent_agreement_share_symptoms_2"><b>Wenn Sie zusätzlich Angaben zum Beginn Ihrer Symptome machen, werden auch diese geteilt.</b>\n\nSie können Ihr Einverständnis zurücknehmen, indem Sie oben „Andere warnen“ deaktivieren. Ihr Testergebnis wird Ihnen anschließend angezeigt.</string>
     <!-- YTXT: Body for keys submission no consent text first part-->
diff --git a/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml
index b1493e50e3f95cd0b0fa920142b2f9b4a9e49baa..9bdb4e9ea0f2db79e0535267cac45f49f94d5015 100644
--- a/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml
@@ -17,22 +17,34 @@
 
     <!-- XHED: Titles for the release info screen bullet points -->
     <string-array name="new_release_title">
-        <item>Check-in per QR-Code für effiziente Rückverfolgbarkeit von Infektionsketten</item>
+        <item>Registrierung von Antigen-Schnelltests</item>
+        <item>Anzeige von Nachweisen</item>
+        <item>Technische Hotline nun auch aus dem Ausland erreichbar</item>
+        <item>TAN-Hotline nun auch aus dem Ausland erreichbar</item>
     </string-array>
 
     <!-- XTXT: Text bodies for the release info screen bullet points -->
     <string-array name="new_release_body">
-        <item>Die CWA ermöglicht nun einen Check-in für Events und Orte. Personen, die Events veranstalten oder ein Geschäft haben, können über die App einen QR-Code erstellen. Durch Scannen des QR-Codes können sich Gäste bei Ankunft einchecken, um so ihre Anwesenheit zu registrieren. Auf Wunsch legt die App außerdem einen entsprechenden Tagebuch-Eintrag an. Wird eine eingecheckte Person später positiv auf das Coronavirus getestet, können andere Personen gewarnt werden, die zur selben Zeit eingescheckt waren.</item>
+        <item>Sie können sich nun zusätzlich zu Ergebnissen von PCR-Tests auch Ergebnisse von Antigen-Schnelltests in der App anzeigen lassen und nutzen, um andere zu warnen.</item>
+        <item>Auf Wunsch können Sie über die App Ihren persönlichen Infektionsstatus nachweisen (z.B. negativer Schnelltest). Bitte beachten Sie, dass Sie grundsätzlich nicht zum Nachweis Ihres Infektionsstatus per App verpflichtet sind. Sie können Ihren Infektionsstatus im Rahmen der rechtlichen Bestimmungen an Ihrem Aufenthaltsort auch auf andere Weise nachweisen.</item>
+        <item>Sie können die technische Hotline nun auch aus dem Ausland unter der Nummer +49 30 498 75401 erreichen. Es fallen Gebühren des jeweiligen Telefonanbieters an.</item>
+        <item>Sie können bei einem positiven Befund nun auch aus dem Ausland eine TAN anfordern unter der Nummer +49 30 498 75402. Es fallen Gebühren des jeweiligen Telefonanbieters an.</item>
     </string-array>
 
     <!-- XTXT: Text labels that will be converted to Links -->
     <string-array name="new_release_linkified_labels">
         <item></item>
+        <item></item>
+        <item></item>
+        <item></item>
     </string-array>
 
     <!-- XTXT: URL destinations for the lables in new_release_linkified_labels -->
     <string-array name="new_release_target_urls">
         <item></item>
+        <item></item>
+        <item></item>
+        <item></item>
     </string-array>
 
 </resources>
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 74344fff12023323d98b03dfa9982c49bac570a5..c18d91172304704774f9953b876dd814312fb630 100644
--- a/Corona-Warn-App/src/main/res/values-de/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/strings.xml
@@ -399,12 +399,16 @@
     <string name="onboarding_onboarding_accessibility_title">"Einführung Seite 1 von 6: Gemeinsam Corona bekämpfen"</string>
     <!-- XHED: onboarding(together) - fight corona -->
     <string name="onboarding_headline">"Gemeinsam Corona bekämpfen"</string>
-    <!-- XHED: onboarding(together) - two/three line headline under an illustration -->
-    <string name="onboarding_subtitle">"Mehr Schutz für Sie und uns alle. Mit der Corona-Warn-App durchbrechen wir Infektionsketten schneller."</string>
-    <!-- YTXT: onboarding(together) - inform about the app -->
-    <string name="onboarding_body">"Machen Sie Ihr Smartphone zum Corona-Warn-System. Überblicken Sie Ihren Risikostatus und erfahren Sie, ob in den letzten 14 Tagen Corona-positiv getestete Personen in Ihrer Nähe waren."</string>
-    <!-- YTXT: onboarding(together) - explain application -->
-    <string name="onboarding_body_emphasized">"Die App merkt sich Begegnungen zwischen Menschen, indem ihre Smartphones verschlüsselte Zufalls-IDs austauschen. Persönliche Daten werden dabei nicht ausgetauscht."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_1">"Mehr Schutz für Sie und uns alle. Mit der Corona-Warn-App durchbrechen wir Infektionsketten schneller."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_2">"Machen Sie Ihr Smartphone zum Corona-Warn-System. Überblicken Sie Ihren Risikostatus und erfahren Sie, ob in den letzten 14 Tagen Corona-positiv getestete Personen in Ihrer Nähe waren."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_3">"Lassen Sie sich Ihre Testergebnisse (PCR-Test oder Antigen-Schnelltest) in der App anzeigen und warnen Sie andere, wenn Sie ein positives Testergebnis erhalten."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_4">"Auf Wunsch können Sie mit der App Ihren persönlichen Infektionsstatus nachweisen (z.B. negativer Schnelltest). Bitte beachten Sie, dass Sie grundsätzlich nicht zum Nachweis Ihres Infektionsstatus per App verpflichtet sind. Sie können Ihren Infektionsstatus im Rahmen der rechtlichen Bestimmungen an Ihrem Aufenthaltsort auch auf andere Weise nachweisen."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_5">"Die App merkt sich Begegnungen zwischen Menschen, indem ihre Smartphones verschlüsselte Zufalls-IDs austauschen. Persönliche Daten werden dabei nicht ausgetauscht."</string>
     <!-- XACT: onboarding(together) - illustraction description, header image -->
     <string name="onboarding_illustration_description">"Eine vielfältige Gruppe in einer Stadt benutzt Smartphones."</string>
     <!-- XACT: Onboarding (privacy) page title -->
@@ -784,12 +788,17 @@
     <string name="information_contact_subtitle_phone">"Technische Hotline:"</string>
     <!-- XLNK: Button / hyperlink to phone call for technical contact and hotline information page -->
     <string name="information_contact_button_phone">"0800 7540001"</string>
+    <string name="information_contact_button_international_phone">"+49 30 498 75401"</string>
+    <!-- XLNK: Description for national technical contact and hotline information page -->
+    <string name="information_contact_button_phone_description">"Für Anrufe innerhalb Deutschlands. Der Anruf ist kostenfrei."</string>
+    <!-- XLNK: Description for international technical contact and hotline information page -->
+    <string name="information_contact_button_international_phone_description">"Für Anrufe aus dem Ausland. Es fallen die Gebühren des jeweiligen Telefonanbieters an."</string>
     <!-- XBUT: CAUTION - ONLY UPDATE THE NUMBER IF NEEDED, ONLY NUMBERS AND NO SPECIAL CHARACTERS EXCEPT "+" and "space" ALLOWED IN THIS FIELD; -->
     <string name="information_contact_phone_call_number">"0800 7540001"</string>
     <!-- XTXT: Body text for technical contact and hotline information page -->
-    <string name="information_contact_body_phone">"Unser Kundenservice ist für Sie da."</string>
+    <string name="information_contact_body_phone">"Unser Kundenservice ist in den folgenden Sprachen für Sie da:"</string>
     <!-- YTXT: Body text for technical contact and hotline information page -->
-    <string name="information_contact_body_open">"Sprachen: Deutsch, Englisch, Türkisch \nErreichbarkeit:"<xliff:g id="line_break">"\n"</xliff:g>"Mo - Sa: 07:00 - 22:00 Uhr"<xliff:g id="line_break">"\n(außer an bundesweiten Feiertagen)"</xliff:g><xliff:g id="line_break">"\nDer Anruf ist kostenfrei."</xliff:g></string>
+    <string name="information_contact_body_open">"Deutsch, Englisch, Türkisch\n\nErreichbarkeit:\nMo – Sa: 07:00 – 22:00 Uhr\n(außer an bundesweiten Feiertagen)</string>
     <!-- YTXT: Body text for technical contact and hotline information page -->
     <string name="information_contact_body_other">"Für gesundheitliche Fragen wenden Sie sich bitte an Ihre Hausarztpraxis oder die Hotline des ärztlichen Bereitschaftsdienstes 116 117."</string>
     <!-- XACT: describes illustration -->
@@ -1014,11 +1023,11 @@
     <!-- YTXT:  Body sub text 1 for Submission Consent call test result   -->
     <string name="submission_consent_call_test_result_scan_your_test_only">"Scannen Sie nun per QR-Code Ihren eigenen Test und rufen Sie Ihr Testergebnis ab."</string>
     <!-- YTXT:  Body sub text 2 for Submission Consent call test result   -->
-    <string name="submission_consent_call_test_result_scan_test_only_once">"Ihr Test kann nur einmal gescannt werden. Die App kann nicht gleichzeitig mehrere Tests verwalten."</string>
+    <string name="submission_consent_call_test_result_scan_test_only_once">"Jeder Test kann nur einmal gescannt werden. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten."</string>
     <!-- XHED: Page subheadline for consent help by warning others  -->
     <string name="submission_consent_help_by_warning_others_headline">"Helfen Sie mit, indem Sie andere warnen, denen Sie begegnet sind!"</string>
     <!-- YTXT: Body for consent help by warning others -->
-    <string name="submission_consent_help_by_warning_others_body">"Wenn Sie positiv auf Corona getestet wurden, können Sie Ihre Mitmenschen über die App warnen. Die Warnung funktioniert in mehreren Ländern. Derzeit nehmen folgende Länder teil:"</string>
+    <string name="submission_consent_help_by_warning_others_body">"Wenn Sie positiv auf Corona getestet wurden, können Sie Ihre Mitmenschen über die App warnen. Bei einem Schnelltest funktioniert die Warnung nur in Deutschland, im Falle eines PCR-Tests funktioniert die Warnung in folgenden Ländern:"</string>
     <!-- YTXT: Body for consent help by warning others - info about events -->
     <string name="submission_consent_help_by_warning_others_body_event">"Es werden auch Ihre Mitmenschen gewarnt, die zeitgleich mit Ihnen an denselben Events oder Orten eingecheckt waren."</string>
     <!-- YTXT: Page bottom text for consent screen -->
@@ -1284,7 +1293,7 @@
     <!-- YTXT: Body text for step 2 of contact page-->
     <string name="submission_contact_step_2_body">"Geben Sie die TAN in der App ein, um Ihren Test zu registrieren."</string>
     <!-- YTXT: Body text for operating hours in contact page-->
-    <string name="submission_contact_operating_hours_body">"Sprachen:\nDeutsch, Englisch, Türkisch\n\nErreichbarkeit:\ntäglich 24 Stunden\n\nDer Anruf ist kostenfrei."</string>
+    <string name="submission_contact_operating_hours_body">"Unser Kundenservice ist in den folgenden Sprachen für Sie da: Deutsch, Englisch, Türkisch \n\nErreichbarkeit:\nMo-So: Täglich 24 Stunden"</string>
     <!-- YTXT: Body text for technical contact and hotline information page -->
     <string name="submission_contact_body_other">"Für gesundheitliche Fragen wenden Sie sich bitte an Ihre Hausarztpraxis oder die Hotline des ärztlichen Bereitschaftsdienstes 116 117."</string>
 
@@ -1991,5 +2000,22 @@
     <string name="trace_location_organiser_poster_share">"Teilen"</string>
     <!-- XHED: Trace location poster title -->
     <string name="trace_location_organiser_poster_title">"Druckversion"</string>
-
+    <!-- XHED: Trace location check-ins consent screen title -->
+    <string name="trace_location_attendee_consent_title">Check-ins für diese Orte teilen?</string>
+    <!-- XTXT: Trace location check-ins consent screen header description -->
+    <string name="trace_location_attendee_consent_header_description">Teilen Sie Ihre Check-ins, um andere zu warnen, die in Ihrer Nähe waren. Ihre Identität bleibt geheim.</string>
+    <!-- XBUT: Trace location check-ins consent screen header button -->
+    <string name="trace_location_attendee_consent_header_button">Alle auswählen</string>
+    <!-- XBUT: Trace location check-ins consent screen continue button -->
+    <string name="trace_location_attendee_consent_continue">Weiter</string>
+    <!-- XBUT: Trace location check-ins consent screen skip button -->
+    <string name="trace_location_attendee_consent_skip">Überspringen</string>
+    <!-- XHED: Trace location check-ins consent screen dialog title -->
+    <string name="trace_location_attendee_consent_dialog_title">Sind Sie sicher, dass Sie Ihre Check-Ins nicht teilen wollen?</string>
+    <!-- XTXT: Trace location check-ins consent screen dialog message -->
+    <string name="trace_location_attendee_consent_dialog_message">Dadurch werden andere, die in Ihrer Nähe waren, nicht gewarnt.</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog positive button -->
+    <string name="trace_location_attendee_consent_dialog_positive_button">Teilen</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog negative button -->
+    <string name="trace_location_attendee_consent_dialog_negative_button">Nicht teilen</string>
 </resources>
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 15aa30d87239bd87eb73419ef462a17123181184..26cc64fa9ff54a2200edb956e0e9bc0becf8b91f 100644
--- a/Corona-Warn-App/src/main/res/values-en/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/strings.xml
@@ -964,7 +964,7 @@
     <!-- XHED: Dialog title for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_title">"Do you want to cancel entering your symptoms?"</string>
     <!-- XMSG: Dialog body for keys submission process cancellation -->
-    <string name="submission_error_dialog_confirm_cancellation_body">"If you provide information about your symptoms, you can warn others more exactly."</string>
+    <string name="submission_error_dialog_confirm_cancellation_body">"If you provide information about your symptoms, you can warn others more accurately."</string>
     <!-- XBUT: Positive button for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_button_positive">"Yes"</string>
     <!-- XBUT: Negative button for keys submission process cancellation -->
@@ -1973,4 +1973,22 @@
     <string name="trace_location_organiser_poster_share">"Share"</string>
     <!-- XHED: Trace location poster title -->
     <string name="trace_location_organiser_poster_title">"Print Version"</string>
+    <!-- XHED: Trace location check-ins consent screen title -->
+    <string name="trace_location_attendee_consent_title">"Share Your Location Check-Ins?"</string>
+    <!-- XTXT: Trace location check-ins consent screen header description -->
+    <string name="trace_location_attendee_consent_header_description">"Warn others who checked in near you. Your personal data will not be shared."</string>
+    <!-- XBUT: Trace location check-ins consent screen header button -->
+    <string name="trace_location_attendee_consent_header_button">"Select all"</string>
+    <!-- XBUT: Trace location check-ins consent screen continue button -->
+    <string name="trace_location_attendee_consent_continue">"Submit"</string>
+    <!-- XBUT: Trace location check-ins consent screen skip button -->
+    <string name="trace_location_attendee_consent_skip">"Skip"</string>
+    <!-- XHED: Trace location check-ins consent screen dialog title -->
+    <string name="trace_location_attendee_consent_dialog_title">"Are you sure you don’t want to share your check-ins?"</string>
+    <!-- XTXT: Trace location check-ins consent screen dialog message -->
+    <string name="trace_location_attendee_consent_dialog_message">"Skipping will not warn any contacts who checked in near you."</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog positive button -->
+    <string name="trace_location_attendee_consent_dialog_positive_button">"Share"</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog negative button -->
+    <string name="trace_location_attendee_consent_dialog_negative_button">"Don’t Share"</string>
 </resources>
\ No newline at end of file
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 c0c4d62a5e992784ad81c673262b3cad75116089..f5836ab498ff0a42066e643ae89b6b57a0347c1c 100644
--- a/Corona-Warn-App/src/main/res/values-pl/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml
@@ -964,7 +964,7 @@
     <!-- XHED: Dialog title for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_title">"Czy chcesz anulować wprowadzanie objawów?"</string>
     <!-- XMSG: Dialog body for keys submission process cancellation -->
-    <string name="submission_error_dialog_confirm_cancellation_body">"Przekazanie informacji o objawach pozwoli Ci na wysyłanie innym bardziej szczegółowych ostrzeżeń."</string>
+    <string name="submission_error_dialog_confirm_cancellation_body">"Przekazanie informacji o objawach pozwoli Ci na wysyłanie innym dokładniejszych ostrzeżeń."</string>
     <!-- XBUT: Positive button for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_button_positive">"Tak"</string>
     <!-- XBUT: Negative button for keys submission process cancellation -->
@@ -1973,4 +1973,22 @@
     <string name="trace_location_organiser_poster_share">"Udostępnij"</string>
     <!-- XHED: Trace location poster title -->
     <string name="trace_location_organiser_poster_title">"Wersja do druku"</string>
+    <!-- XHED: Trace location check-ins consent screen title -->
+    <string name="trace_location_attendee_consent_title">"Udostępnić Twoje zameldowania w lokalizacji?"</string>
+    <!-- XTXT: Trace location check-ins consent screen header description -->
+    <string name="trace_location_attendee_consent_header_description">"Ostrzegaj inne osoby, które się zameldowały w pobliżu. Twoje dane osobowe nie będą udostępniane."</string>
+    <!-- XBUT: Trace location check-ins consent screen header button -->
+    <string name="trace_location_attendee_consent_header_button">"Wybierz wszystkie"</string>
+    <!-- XBUT: Trace location check-ins consent screen continue button -->
+    <string name="trace_location_attendee_consent_continue">"Prześlij"</string>
+    <!-- XBUT: Trace location check-ins consent screen skip button -->
+    <string name="trace_location_attendee_consent_skip">"Pomiń"</string>
+    <!-- XHED: Trace location check-ins consent screen dialog title -->
+    <string name="trace_location_attendee_consent_dialog_title">"Czy na pewno nie chcesz udostępniać swoich zameldowań?"</string>
+    <!-- XTXT: Trace location check-ins consent screen dialog message -->
+    <string name="trace_location_attendee_consent_dialog_message">"W przypadku pominięcia tej operacji żadne kontakty zameldowane w pobliżu nie otrzymają ostrzeżenia."</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog positive button -->
+    <string name="trace_location_attendee_consent_dialog_positive_button">"Udostępnij"</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog negative button -->
+    <string name="trace_location_attendee_consent_dialog_negative_button">"Nie udostępniaj"</string>
 </resources>
\ No newline at end of file
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 50d09ff8ae9d5fb6ce85e1264004498cf2e98d83..26877ff0620859f82c66a73889f988fa8d9c8b2d 100644
--- a/Corona-Warn-App/src/main/res/values-ro/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml
@@ -964,7 +964,7 @@
     <!-- XHED: Dialog title for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_title">"Doriți să anulați introducerea simptomelor dvs.?"</string>
     <!-- XMSG: Dialog body for keys submission process cancellation -->
-    <string name="submission_error_dialog_confirm_cancellation_body">"Dacă furnizați informații despre simptomele dvs., îi puteți avertiza pe ceilalți într-un mod mai exact."</string>
+    <string name="submission_error_dialog_confirm_cancellation_body">"Dacă furnizați informații despre simptomele dvs., îi puteți avertiza pe ceilalți într-un mod mai precis."</string>
     <!-- XBUT: Positive button for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_button_positive">"Da"</string>
     <!-- XBUT: Negative button for keys submission process cancellation -->
@@ -1973,4 +1973,22 @@
     <string name="trace_location_organiser_poster_share">"Partajare"</string>
     <!-- XHED: Trace location poster title -->
     <string name="trace_location_organiser_poster_title">"Versiune de tipărire"</string>
+    <!-- XHED: Trace location check-ins consent screen title -->
+    <string name="trace_location_attendee_consent_title">"Partajați check-inurile locației dvs.?"</string>
+    <!-- XTXT: Trace location check-ins consent screen header description -->
+    <string name="trace_location_attendee_consent_header_description">"Avertizați-i pe ceilalți care au făcut check-in în aproprierea dvs. Datele dvs. personale nu vor fi partajate."</string>
+    <!-- XBUT: Trace location check-ins consent screen header button -->
+    <string name="trace_location_attendee_consent_header_button">"Selectare tot"</string>
+    <!-- XBUT: Trace location check-ins consent screen continue button -->
+    <string name="trace_location_attendee_consent_continue">"Transmitere"</string>
+    <!-- XBUT: Trace location check-ins consent screen skip button -->
+    <string name="trace_location_attendee_consent_skip">"Omitere"</string>
+    <!-- XHED: Trace location check-ins consent screen dialog title -->
+    <string name="trace_location_attendee_consent_dialog_title">"Sigur nu doriți să partajați check-inurile dvs.?"</string>
+    <!-- XTXT: Trace location check-ins consent screen dialog message -->
+    <string name="trace_location_attendee_consent_dialog_message">"Omiterea nu va avertiza contactele care au făcut check-in în apropierea dvs."</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog positive button -->
+    <string name="trace_location_attendee_consent_dialog_positive_button">"Partajare"</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog negative button -->
+    <string name="trace_location_attendee_consent_dialog_negative_button">"Nu partajez"</string>
 </resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml
index 98fcec804ca472ed8f82a3c3cf4d08bdcaa46b65..47cf6a2bf11559775003354bd89b3e80499bf83f 100644
--- a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml
@@ -19,13 +19,15 @@
     <!-- YTXT: Body for consent sub section your consent subtext first point  -->
     <string name="submission_consent_your_consent_subsection_first_point">"<b>Uygulama, test sonucunuzu çağırır.</b>Daha sonra fikrinizi değiştirirseniz, söz konusu testi Uygulamadan kaldırabilirsiniz."</string>
     <!-- YTXT: Body for consent sub section your consent subtext second point  -->
-    <string name="submission_consent_your_consent_subsection_second_point">"<b>Korona testiniz pozitif çıkmışsa, Uygulama karşılaştığınız kullanıcıları uyarmak üzere test sonucunuzu paylaşır. Bu ise yukarıda adı geçen ülkelerdeki Korona uygulamalarının sizinle aynı olay veya konumda giriş denetimi yaptıran kullanıcılarını etkiler.\n\nAyrıca semptomlarınızın başlangıcı hakkında bilgi verirseniz, bu veri de paylaşılacaktır.</b>"</string>
+    <string name="submission_consent_your_consent_subsection_second_point">"<b>Korona testiniz pozitifse Uygulama test sonucunuzu karşılaştığınız kullanıcıları uyarmak için paylaşır. Bu kişiler yukarıda belirtilen ülkelerdeki Korona Uygulaması kullanıcılarla ve sizinle aynı zamanda aynı etkinlikte ya da yerde bulunan kullanıcılarla ilgilidir.</b>"</string>
     <!-- YTXT: Body for consent sub section your consent subtext third point  -->
-    <string name="submission_consent_your_consent_subsection_third_point">"Verdiğiniz rıza beyanını istediğiniz zaman geri alabilirsiniz. Bunun ayarını, “Testi Göster” altında bulabilirsiniz. Paylaşmadan önce, rıza beyanınıza ilişkin yeniden bilgilendirileceksiniz."</string>
+    <string name="submission_consent_your_consent_subsection_third_point">"<b>Ayrıca semptomlarınızın başlangıcı hakkında bilgi verirseniz, bu veri de paylaşılacaktır.</b>"</string>
+    <!-- YTXT: Body for consent sub section your consent subtext fourth point  -->
+    <string name="submission_consent_your_consent_subsection_fourth_point">"Onay beyanınızı istediğiniz zaman geri çekebilirsiniz. İlgili ayar “Testleri göster” altında yer alır. Bilgiler paylaşılmadan önce tekrar onayınız alınır."</string>
     <!-- YTXT: Body for your consent screen agreement share test results -->
-    <string name="submission_your_consent_agreement_share_test_results">"<b>Korona testiniz pozitif çıkmışsa, Uygulama karşılaştığınız kullanıcıları uyarmak üzere test sonucunuzu paylaşır.</b>\n\n<b>Bu uyarı, yukarıda belirtilen ülkelerdeki resmi Korona uygulamalarının kullanıcılarına ulaşabilir:</b>"</string>
+    <string name="submission_your_consent_agreement_share_test_results">"<b>Korona testiniz pozitif çıktıysa Uygulama test sonucunuzu, karşılaştığınız kullanıcıları yarmak için paylaşır.</b>\n\n<b>Uyarı sizinle aynı etkinlikteki ya da yerdeki kullanıcılara ve aşağıdaki ülkelerdeki Korona Uygulaması kullanıcılarına gönderilir:</b>"</string>
     <!-- YTXT: Body for your consent screen agreement share symptoms -->
-    <string name="submission_your_consent_agreement_share_symptoms">"<b>Ayrıca semptomlarınızın başlangıcı hakkında bilgi verirseniz, bu veri de paylaşılacaktır.</b>\n\nYukarıdaki “Diğer Kullanıcıları Uyarın” seçeneğini devre dışı bırakarak, verdiğiniz rıza beyanını geri alabilirsiniz."</string>
+    <string name="submission_your_consent_agreement_share_symptoms">"<b>Hızlı test durumunda sadece Korona Uyarı Uygulamasının kullanıcıları uyarılır. Semptomlarınızın başladığına ilişkin ek bilgiler verirseniz bu veriler de paylaşılır.</b>\n\nYukarıdaki “Diğer Kullanıcıları Uyarın” seçeneğini devre dışı bırakarak, verdiğiniz rıza beyanını geri alabilirsiniz."</string>
     <!-- YTXT: Body for your consent screen agreement share symptoms with additional hint for test result-->
     <string name="submission_your_consent_agreement_share_symptoms_2">"<b>Ayrıca semptomlarınızın başlangıcı hakkında bilgi verirseniz, bu veri de paylaşılacaktır.</b>\n\nYukarıdaki “Diğer Kullanıcıları Uyarın” seçeneğini devre dışı bırakarak, verdiğiniz rıza beyanını geri alabilirsiniz. Bunun ardından test sonucunuz size görüntülenir.</string>
     <!-- YTXT: Body for keys submission no consent text first part-->
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 d080eb6d94cba83a4cab05fbffa1ce71adb7ee8b..523c06ce04769693a14319f31900212b0cb8b1c6 100644
--- a/Corona-Warn-App/src/main/res/values-tr/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml
@@ -964,7 +964,7 @@
     <!-- XHED: Dialog title for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_title">"Belirtilerinizi girmeyi iptal etmek istiyor musunuz?"</string>
     <!-- XMSG: Dialog body for keys submission process cancellation -->
-    <string name="submission_error_dialog_confirm_cancellation_body">"Belirtilerinizle ilgili olarak bilgi verirseniz diğer kullanıcıları daha net bir biçimde uyarabilir."</string>
+    <string name="submission_error_dialog_confirm_cancellation_body">"Belirtilerinizle ilgili olarak bilgi verirseniz diğer kullanıcıları daha doğru bir şekilde uyarabilirsiniz."</string>
     <!-- XBUT: Positive button for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_button_positive">"Evet"</string>
     <!-- XBUT: Negative button for keys submission process cancellation -->
@@ -1973,4 +1973,22 @@
     <string name="trace_location_organiser_poster_share">"PaylaÅŸ"</string>
     <!-- XHED: Trace location poster title -->
     <string name="trace_location_organiser_poster_title">"Yazdırma Sürümü"</string>
+    <!-- XHED: Trace location check-ins consent screen title -->
+    <string name="trace_location_attendee_consent_title">"Konum Check-In’leriniz Paylaşılsın Mı?"</string>
+    <!-- XTXT: Trace location check-ins consent screen header description -->
+    <string name="trace_location_attendee_consent_header_description">"Sizin yakınınızda check in yapmış olan diğer kullanıcıları uyarın. Kişisel verileriniz paylaşılmayacaktır."</string>
+    <!-- XBUT: Trace location check-ins consent screen header button -->
+    <string name="trace_location_attendee_consent_header_button">"Tümünü seç"</string>
+    <!-- XBUT: Trace location check-ins consent screen continue button -->
+    <string name="trace_location_attendee_consent_continue">"Gönder"</string>
+    <!-- XBUT: Trace location check-ins consent screen skip button -->
+    <string name="trace_location_attendee_consent_skip">"Atla"</string>
+    <!-- XHED: Trace location check-ins consent screen dialog title -->
+    <string name="trace_location_attendee_consent_dialog_title">"Check-in’lerinizi paylaşmak istemediğinizden emin misiniz?"</string>
+    <!-- XTXT: Trace location check-ins consent screen dialog message -->
+    <string name="trace_location_attendee_consent_dialog_message">"Bu adımı atladığınızda sizin yakınınızda check in yapmış olan hiç kimseye uyarı gönderilmeyecektir."</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog positive button -->
+    <string name="trace_location_attendee_consent_dialog_positive_button">"PaylaÅŸ"</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog negative button -->
+    <string name="trace_location_attendee_consent_dialog_negative_button">"PaylaÅŸma"</string>
 </resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values/antigen_strings.xml b/Corona-Warn-App/src/main/res/values/antigen_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f31cf3f47e5e171b391924ccf15a33e4445f30c2
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/values/antigen_strings.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation">
+
+    <!-- ####################################
+        Homescreen cards - register test card
+    ###################################### -->
+    <!-- XHED: Register test homescreen card: title -->
+    <string name="ag_homescreen_card_test_register_title">"Test registrieren"</string>
+    <!-- XTXT: Register test homescreen card: body -->
+    <string name="ag_homescreen_card_test_register_body">"Nutzen Sie die App, um Ihre Testergebnisse abrufen und schneller warnen zu können."</string>
+
+    <!-- ####################################
+        Homescreen cards - common buttons
+    ###################################### -->
+    <!-- XBUT: Homescreen card: show test button -->
+    <string name="ag_homescreen_card_show_button">"Test anzeigen"</string>
+    <!-- XBUT: Homescreen card: warn others button -->
+    <string name="ag_homescreen_card_warn_others_button">"Andere warnen"</string>
+
+    <!-- ####################################
+        Homescreen cards - common status
+    ###################################### -->
+    <!-- XTXT: Homescreen card: status subtitle - no result -->
+    <string name="ag_homescreen_card_status_no_result">"Ergebnis liegt noch nicht vor"</string>
+    <!-- XTXT: Homescreen card: status subtitle - result available -->
+    <string name="ag_homescreen_card_status_result_available">"Testergebnis abrufen"</string>
+    <!-- XTXT: Homescreen card: status subtitle - error -->
+    <string name="ag_homescreen_card_status_error">"Fehlerhafter Test"</string>
+    <!-- XTXT: Homescreen card: status subtitle - invalid -->
+    <string name="ag_homescreen_card_status_invalid">"Nicht mehr gültig"</string>
+    <!-- XTXT: Homescreen card: status subtitle - findings -->
+    <string name="ag_homescreen_card_status_findings">"Befund"</string>
+    <!-- XTXT: Homescreen card: subtitle - corona official name -->
+    <string name="ag_homescreen_card_status_name_of_the_cause_of_this_app">"SARS-CoV-2"</string>
+    <!-- XTXT: Homescreen card: status - negative -->
+    <string name="ag_homescreen_card_status_negative">"Negativ"</string>
+    <!-- XTXT: Homescreen card: status - positiv -->
+    <string name="ag_homescreen_card_status_positiv">"Positiv"</string>
+
+    <!-- ####################################
+       Homescreen cards - common body
+   ###################################### -->
+    <!-- XTXT: homescreen card: body - result available -->
+    <string name="ag_homescreen_card_body_result_available">"Warnen Sie Ihre Mitmenschen, wenn Sie positiv auf Corona getestet wurden."</string>
+    <!-- XTXT: homescreen card: body - error -->
+    <string name="ag_homescreen_card_body_error">"Ihr Test konnte nicht ausgewertet werden."</string>
+    <!-- XTXT: homescreen card: body - not valid test -->
+    <string name="ag_homescreen_card_body_not_valid_test">"Ihr Test liegt länger als 21 Tage zurück und ist daher nicht länger relevant. Bitte löschen Sie den Test. Danach können Sie einen neuen Test hinzufügen. "</string>
+    <!-- XTXT: homescreen card: body - negative -->
+    <string name="ag_homescreen_card_body_result_negative">"Das Virus SARS-CoV-2 wurde bei Ihnen nicht nachgewiesen."</string>
+    <!-- XTXT: homescreen card: body - positive -->
+    <string name="ag_homescreen_card_body_result_positive">"Das Virus SARS-CoV-2 wurde bei Ihnen nachgewiesen."</string>
+
+    <!-- ####################################
+        Homescreen cards - rapid test
+    ###################################### -->
+    <!-- XHED: rapid test homescreen card: title -->
+    <string name="ag_homescreen_card_rapidtest_title">"Schnelltest"</string>
+    <!-- XBUT: rapid test homescreen card: dont show anymore button -->
+    <string name="ag_homescreen_card_rapidtest_dont_show_anymore_button">"Nicht mehr anzeigen"</string>
+    <!-- XTXT: rapid test homescreen card: status subtitle - outdated test -->
+    <string name="ag_homescreen_card_rapidtest_status_outdated_test">"Test nicht mehr aktuell"</string>
+    <!-- XTXT: rapid test homescreen card: result positive - check results with PCR test -->
+    <string name="ag_homescreen_card_rapidtest_body_result_positive_pcr_check">"Machen Sie einen PCR-Test um dieses Test-Ergebnis zu verifizieren."</string>
+
+    <!-- Body -->
+    <!-- XTXT: rapid test homescreen card: body - no result -->
+    <string name="ag_homescreen_card_rapidtest_body_no_result">"Die Auswertung Ihres Schnelltests ist noch nicht abgeschlossen."</string>
+    <!-- XTXT: rapid test homescreen card: body - outdated test -->
+    <string name="ag_homescreen_card_rapidtest_body_outdated_test">"Ihr Schnelltest ist älter als 48 Stunden und wird hier nicht mehr angezeigt."</string>
+    <!-- XTXT: homescreen card: body - negative -->
+    <string name="ag_homescreen_card_rapid_body_result_date">"Durchgeführt am %1$s"</string>
+
+    <!-- ####################################
+        Homescreen cards - PCR
+    ###################################### -->
+    <!-- XHED: PCR homescreen card: title -->
+    <string name="ag_homescreen_card_pcr_title">"PCR-Test"</string>
+    <!-- XBUT: PCR homescreen card: clear test button -->
+    <string name="ag_homescreen_card_pcr_clear_test_button">"TEST LÖSCHEN"</string>
+
+    <!-- Body -->
+    <!-- XTXT: PCR homescreen card: body - no result -->
+    <string name="ag_homescreen_card_pcr_body_no_result">"Die Auswertung Ihres PCR-Tests ist noch nicht abgeschlossen."</string>
+    <!-- XTXT: homescreen card: body - negative -->
+    <string name="ag_homescreen_card_pcr_body_result_date">"Test registriert am %1$s"</string>
+
+</resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values/legal_strings.xml b/Corona-Warn-App/src/main/res/values/legal_strings.xml
index 39847db49f32b5c3264721eb114a01d541a5c63f..07623e1a72136ddc1b563ae55fde9aad8a4f9d2b 100644
--- a/Corona-Warn-App/src/main/res/values/legal_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/legal_strings.xml
@@ -18,15 +18,17 @@
     <!-- YTXT: Body for consent sub section your consent subtext -->
     <string name="submission_consent_your_consent_subsection_tapping_agree" translatable="false">"By tapping on “Accept”, you consent to the following steps:"</string>
     <!-- YTXT: Body for consent sub section your consent subtext first point  -->
-    <string name="submission_consent_your_consent_subsection_first_point" translatable="false">"<b>The app will retrieve your test result.</b> If you change your mind later, you can delete the test in the app."</string>
+    <string name="submission_consent_your_consent_subsection_first_point" translatable="false">"<b>"The app will retrieve your test result.</b> If you change your mind later, you can delete the test in the app."</string>
     <!-- YTXT: Body for consent sub section your consent subtext second point  -->
-    <string name="submission_consent_your_consent_subsection_second_point" translatable="false">"<b>If you have tested positive for coronavirus, the app will share your test result in order to warn other users whom you have encountered. This applies to users of coronavirus apps from the above countries and users who were simultaneously checked in at the same event or place as you.\n\nIf you provide additional information about the onset of your symptoms, this will also be shared.</b>"</string>
+    <string name="submission_consent_your_consent_subsection_second_point" translatable="false">"<b>If you have tested positive for coronavirus, the app will share your test result in order to warn other users whom you have encountered. This applies to users of coronavirus apps from the above countries and users who were simultaneously checked in at the same event or place as you. In the case of rapid tests, only users of the Corona-Warn-App will be warned.</b>"</string>
     <!-- YTXT: Body for consent sub section your consent subtext third point  -->
-    <string name="submission_consent_your_consent_subsection_third_point" translatable="false">"You can withdraw your consent at any time. The setting for this can be found under “Display Test”. Before sharing your test result, you will be reminded of your consent."</string>
+    <string name="submission_consent_your_consent_subsection_third_point" translatable="false">"<b>"If you provide additional information about the onset of your symptoms, this will also be shared."</b>"</string>
+    <!-- YTXT: Body for consent sub section your consent subtext fourth point  -->
+    <string name="submission_consent_your_consent_subsection_fourth_point" translatable="false">"You can withdraw your consent at any time. The setting for this can be found under "Display Test". Before sharing your test result, you will be reminded of your consent."</string>
     <!-- YTXT: Body for your consent screen agreement share test results -->
-    <string name="submission_your_consent_agreement_share_test_results" translatable="false">"<b>If you have tested positive for coronavirus, the app will share your test result in order to warn other users whom you have encountered.</b>\n\n<b>The warning will reach users of the official coronavirus apps of the following participating countries:</b>"</string>
+    <string name="submission_your_consent_agreement_share_test_results" translatable="false">"<b>"If you have tested positive for coronavirus, the app will share your test result in order to warn other users whom you have encountered.</b>\n\n<b>This warning only affects users who were simultaneously checked in at the same event or place as you and users of coronavirus apps in the following countries:</b>"</string>
     <!-- YTXT: Body for your consent screen agreement share symptoms -->
-    <string name="submission_your_consent_agreement_share_symptoms" translatable="false">"<b>If you provide additional information about the onset of your symptoms, this will also be shared.</b>\n\nYou can withdraw your consent by disabling “Warn Others” above."</string>
+    <string name="submission_your_consent_agreement_share_symptoms" translatable="false">"<b>In the case of rapid tests, only users of the Corona-Warn-App will be warned. If you provide additional information about the onset of symptoms, this will be also shared.</b>\n\nYou can withdraw your consent by disabling “Warn Others” above."</string>
     <!-- YTXT: Body for your consent screen agreement share symptoms with additional hint for test result-->
     <string name="submission_your_consent_agreement_share_symptoms_2" translatable="false"><b>If you provide additional information about the onset of your symptoms, this will also be shared.</b>\n\nYou can withdraw your consent by disabling “Warn others” above. Your test result will then be shown to you.</string>
     <!-- YTXT: Body for keys submission no consent text first part-->
diff --git a/Corona-Warn-App/src/main/res/values/release_info_strings.xml b/Corona-Warn-App/src/main/res/values/release_info_strings.xml
index f480f402b927499db999d597b89dd70293fdc893..931baa1a0f23f04eea380f04b1788d0b9aab6c18 100644
--- a/Corona-Warn-App/src/main/res/values/release_info_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/release_info_strings.xml
@@ -17,22 +17,34 @@
 
     <!-- XHED: Titles for the release info screen bullet points -->
     <string-array name="new_release_title">
-        <item>"QR code check-in to effectively track chains of infection"</item>
+        <item>Registrierung von Antigen-Schnelltests</item>
+        <item>Anzeige von Nachweisen</item>
+        <item>Technische Hotline nun auch aus dem Ausland erreichbar</item>
+        <item>TAN-Hotline nun auch aus dem Ausland erreichbar</item>
     </string-array>
 
     <!-- XTXT: Text bodies for the release info screen bullet points -->
     <string-array name="new_release_body">
-        <item>"The CWA now enables you to check in to events and places. Event organizers and store owners can use the app to create a QR code. Guests can then scan this QR code when they arrive to register their presence. The app can also create a journal entry upon request. If someone who checked in to this event or place is diagnosed with coronavirus later, other people who were checked in at the same time can be warned automatically."</item>
+        <item>Sie können sich nun zusätzlich zu Ergebnissen von PCR-Tests auch Ergebnisse von Antigen-Schnelltests in der App anzeigen lassen und nutzen, um andere zu warnen.</item>
+        <item>Auf Wunsch können Sie über die App Ihren persönlichen Infektionsstatus nachweisen (z.B. negativer Schnelltest). Bitte beachten Sie, dass Sie grundsätzlich nicht zum Nachweis Ihres Infektionsstatus per App verpflichtet sind. Sie können Ihren Infektionsstatus im Rahmen der rechtlichen Bestimmungen an Ihrem Aufenthaltsort auch auf andere Weise nachweisen.</item>
+        <item>Sie können die technische Hotline nun auch aus dem Ausland unter der Nummer +49 30 498 75401 erreichen. Es fallen Gebühren des jeweiligen Telefonanbieters an.</item>
+        <item>Sie können bei einem positiven Befund nun auch aus dem Ausland eine TAN anfordern unter der Nummer +49 30 498 75402. Es fallen Gebühren des jeweiligen Telefonanbieters an.</item>
     </string-array>
 
     <!-- XTXT: Text labels that will be converted to Links -->
     <string-array name="new_release_linkified_labels">
-        <item/>
+        <item></item>
+        <item></item>
+        <item></item>
+        <item></item>
     </string-array>
 
     <!-- XTXT: URL destinations for the lables in new_release_linkified_labels -->
     <string-array name="new_release_target_urls">
-        <item/>
+        <item></item>
+        <item></item>
+        <item></item>
+        <item></item>
     </string-array>
 
 </resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index 530da0471d5301ce113b1209ffc9c87584c6f478..2dcf9f6df2e4985bf0e7b4a3c6472a5423051205 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -413,6 +413,16 @@
     <string name="onboarding_body">"Turn your smartphone into a coronavirus warning system. Get an overview of your risk status and find out whether you’ve had close contact with anyone diagnosed with coronavirus in the last 14 days."</string>
     <!-- YTXT: onboarding(together) - explain application -->
     <string name="onboarding_body_emphasized">"The app logs encounters between individuals by exchanging encrypted, random IDs between their smartphones, whereby no personal data whatsoever is accessed."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_1">"More protection for you and for us all. By using the Corona-Warn-App we can break chains of infection much quicker."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_2">"Turn your smartphone into a coronavirus warning system. Get an overview of your risk status and find out whether you’ve had close contact with anyone diagnosed with coronavirus in the last 14 days."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_3">"Lassen Sie sich Ihre Testergebnisse (PCR-Test oder Antigen-Schnelltest) in der App anzeigen und warnen Sie andere, wenn Sie ein positives Testergebnis erhalten."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_4">"Auf Wunsch können Sie mit der App Ihren persönlichen Infektionsstatus nachweisen (z.B. negativer Schnelltest). Bitte beachten Sie, dass Sie grundsätzlich nicht zum Nachweis Ihres Infektionsstatus per App verpflichtet sind. Sie können Ihren Infektionsstatus im Rahmen der rechtlichen Bestimmungen an Ihrem Aufenthaltsort auch auf andere Weise nachweisen."</string>
+    <!-- YTXT: onboarding(together) - paragraph -->
+    <string name="onboarding_body_5">"The app logs encounters between individuals by exchanging encrypted, random IDs between their smartphones, whereby no personal data whatsoever is accessed."</string>
     <!-- XACT: onboarding(together) - illustraction description, header image -->
     <string name="onboarding_illustration_description">"A group of persons use their smartphones around town."</string>
     <!-- XACT: Onboarding (privacy) page title -->
@@ -792,6 +802,14 @@
     <string name="information_contact_subtitle_phone">"Technical hotline:"</string>
     <!-- XLNK: Button / hyperlink to phone call for technical contact and hotline information page -->
     <string name="information_contact_button_phone">"0800 7540001"</string>
+    <!-- XLNK: Button / hyperlink to international phone call for technical contact and hotline information page -->
+    <string name="information_contact_button_international_phone">"+49 30 498 75401"</string>
+    <!-- XLNK: Button / hyperlink to international phone call for technical contact and hotline TAN info page -->
+    <string name="submission_contact_button_international_phone">"+49 30 498 75402"</string>
+    <!-- XLNK: Description for national technical contact and hotline information page -->
+    <string name="information_contact_button_phone_description">"Für Anrufe innerhalb Deutschlands. Der Anruf ist kostenfrei."</string>
+    <!-- XLNK: Description for international technical contact and hotline information page -->
+    <string name="information_contact_button_international_phone_description">"Für Anrufe aus dem Ausland. Es fallen die Gebühren des jeweiligen Telefonanbieters an."</string>
     <!-- XBUT: CAUTION - ONLY UPDATE THE NUMBER IF NEEDED, ONLY NUMBERS AND NO SPECIAL CHARACTERS EXCEPT "+" and "space" ALLOWED IN THIS FIELD; -->
     <string name="information_contact_phone_call_number">"0800 7540001"</string>
     <!-- XTXT: Body text for technical contact and hotline information page -->
@@ -973,7 +991,7 @@
     <!-- XHED: Dialog title for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_title">"Do you want to cancel entering your symptoms?"</string>
     <!-- XMSG: Dialog body for keys submission process cancellation -->
-    <string name="submission_error_dialog_confirm_cancellation_body">"If you provide information about your symptoms, you can warn others more exactly."</string>
+    <string name="submission_error_dialog_confirm_cancellation_body">"If you provide information about your symptoms, you can warn others more accurately."</string>
     <!-- XBUT: Positive button for keys submission process cancellation -->
     <string name="submission_error_dialog_confirm_cancellation_button_positive">"Yes"</string>
     <!-- XBUT: Negative button for keys submission process cancellation -->
@@ -1022,11 +1040,11 @@
     <!-- YTXT:  Body sub text 1 for Submission Consent call test result   -->
     <string name="submission_consent_call_test_result_scan_your_test_only">"Now scan the QR code for your test and retrieve your test result."</string>
     <!-- YTXT:  Body sub text 2 for Submission Consent call test result   -->
-    <string name="submission_consent_call_test_result_scan_test_only_once">"Your test can only be scanned once. The app cannot manage multiple tests at the same time."</string>
+    <string name="submission_consent_call_test_result_scan_test_only_once">"Jeder Test kann nur einmal gescannt werden. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten."</string>
     <!-- XHED: Page subheadline for consent help by warning others  -->
     <string name="submission_consent_help_by_warning_others_headline">"Please help others you have encountered by warning them!"</string>
     <!-- YTXT: Body for consent help by warning others -->
-    <string name="submission_consent_help_by_warning_others_body">"If you have been diagnosed with coronavirus, you can warn others through the app. The warning feature works in several countries. The following countries are currently participating:"</string>
+    <string name="submission_consent_help_by_warning_others_body">"Wenn Sie positiv auf Corona getestet wurden, können Sie Ihre Mitmenschen über die App warnen. Bei einem Schnelltest funktioniert die Warnung nur in Deutschland, im Falle eines PCR-Tests funktioniert die Warnung in folgenden Ländern:"</string>
     <!-- YTXT: Body for consent help by warning others - info about events -->
     <string name="submission_consent_help_by_warning_others_body_event">"People who were checked in to the same events or places at the same time as you were will also be warned."</string>
     <!-- YTXT: Page bottom text for consent screen -->
@@ -1999,4 +2017,22 @@
     <string name="trace_location_organiser_poster_share">"Share"</string>
     <!-- XHED: Trace location poster title -->
     <string name="trace_location_organiser_poster_title">"Print Version"</string>
+    <!-- XHED: Trace location check-ins consent screen title -->
+    <string name="trace_location_attendee_consent_title">"Share Your Location Check-Ins?"</string>
+    <!-- XTXT: Trace location check-ins consent screen header description -->
+    <string name="trace_location_attendee_consent_header_description">"Warn others who checked in near you. Your personal data will not be shared."</string>
+    <!-- XBUT: Trace location check-ins consent screen header button -->
+    <string name="trace_location_attendee_consent_header_button">"Select all"</string>
+    <!-- XBUT: Trace location check-ins consent screen continue button -->
+    <string name="trace_location_attendee_consent_continue">"Submit"</string>
+    <!-- XBUT: Trace location check-ins consent screen skip button -->
+    <string name="trace_location_attendee_consent_skip">"Skip"</string>
+    <!-- XHED: Trace location check-ins consent screen dialog title -->
+    <string name="trace_location_attendee_consent_dialog_title">"Are you sure you don’t want to share your check-ins?"</string>
+    <!-- XTXT: Trace location check-ins consent screen dialog message -->
+    <string name="trace_location_attendee_consent_dialog_message">"Skipping will not warn any contacts who checked in near you."</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog positive button -->
+    <string name="trace_location_attendee_consent_dialog_positive_button">"Share"</string>
+    <!-- XBUT: Trace location check-ins consent screen dialog negative button -->
+    <string name="trace_location_attendee_consent_dialog_negative_button">"Don’t Share"</string>
 </resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values/styles.xml b/Corona-Warn-App/src/main/res/values/styles.xml
index ecacc05a638093304b440e6405031fdf834af0ea..927a1ef0dba0d35470c54b1d435a62e8ae7f8b88 100644
--- a/Corona-Warn-App/src/main/res/values/styles.xml
+++ b/Corona-Warn-App/src/main/res/values/styles.xml
@@ -155,6 +155,10 @@
         <item name="android:textColor">@color/colorAccent</item>
     </style>
 
+    <style name="materialTextButton" parent="Widget.MaterialComponents.Button.TextButton.Dialog.Flush">
+        <item name="android:textColor">@color/colorAccent</item>
+    </style>
+
     <style name="buttonIcon">
         <item name="android:background">@drawable/circle_ripple</item>
         <item name="android:backgroundTint">@color/button_back</item>
@@ -188,6 +192,11 @@
         <item name="android:textColor">@color/colorTextPrimary1</item>
     </style>
 
+    <style name="phoneNumber">
+        <item name="android:textAllCaps">false</item>
+        <item name="android:background">?selectableItemBackground</item>
+    </style>
+
     <style name="rowSettings" parent="@style/row">
         <item name="android:paddingStart">0dp</item>
     </style>
@@ -289,9 +298,13 @@
         <item name="android:textColor">@color/colorTextPrimary1</item>
     </style>
 
-    <style name="subtitleBoldSixteen" parent="@style/TextAppearance.MaterialComponents.Subtitle1">
+
+    <style name="materialSubtitleSixteen" parent="@style/TextAppearance.MaterialComponents.Subtitle1">
         <item name="android:textColor">@color/colorTextPrimary1</item>
         <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="subtitleBoldSixteen" parent="materialSubtitleSixteen">
         <item name="android:textStyle">bold</item>
     </style>
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetectorTest.kt
index d8199e64261e221a682a98b6c2e65ffecadf4055..39f8b0fb3004295c5011fb9ef0dc11ab5345dd70 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetectorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetectorTest.kt
@@ -34,6 +34,7 @@ class ConfigChangeDetectorTest : BaseTest() {
         every { taskController.submit(any()) } just Runs
         every { appConfigProvider.currentConfig } returns currentConfigFake
         coEvery { riskLevelStorage.clear() } just Runs
+        coEvery { riskLevelStorage.clearResults() } just Runs
     }
 
     private fun mockConfigId(id: String): ConfigData {
@@ -59,7 +60,7 @@ class ConfigChangeDetectorTest : BaseTest() {
 
         coVerify(exactly = 0) {
             taskController.submit(any())
-            riskLevelStorage.clear()
+            riskLevelStorage.clearResults()
         }
     }
 
@@ -70,20 +71,25 @@ class ConfigChangeDetectorTest : BaseTest() {
         createInstance().launch()
 
         coVerifySequence {
-            riskLevelStorage.clear()
+            riskLevelStorage.clearResults()
             taskController.submit(any())
+            taskController.submit(any())
+        }
+
+        coVerify(exactly = 0) {
+            riskLevelStorage.clear()
         }
     }
 
     @Test
-    fun `same idetifier results in no op`() {
+    fun `same identifier results in no op`() {
         every { riskLevelSettings.lastUsedConfigIdentifier } returns "initial"
 
         createInstance().launch()
 
         coVerify(exactly = 0) {
             taskController.submit(any())
-            riskLevelStorage.clear()
+            riskLevelStorage.clearResults()
         }
     }
 
@@ -96,10 +102,16 @@ class ConfigChangeDetectorTest : BaseTest() {
         currentConfigFake.value = mockConfigId("berry")
 
         coVerifySequence {
-            riskLevelStorage.clear()
+            riskLevelStorage.clearResults()
+            taskController.submit(any())
+            taskController.submit(any())
+            riskLevelStorage.clearResults()
             taskController.submit(any())
-            riskLevelStorage.clear()
             taskController.submit(any())
         }
+
+        coVerify(exactly = 0) {
+            riskLevelStorage.clear()
+        }
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt
index 90bcddb7723145acd4131e3d4e6ba14343778a09..e6c3aa4a1df8bf4db40f25f4eb488cf3ce9150df 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt
@@ -5,6 +5,7 @@ import dagger.Module
 import dagger.Provides
 import de.rki.coronawarnapp.bugreporting.BugReportingSharedModule
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.submission.SubmissionSettings
 import io.github.classgraph.ClassGraph
 import io.kotest.matchers.collections.shouldContainAll
@@ -68,4 +69,8 @@ class MockProvider {
     @Singleton
     @Provides
     fun submissionSettings(): SubmissionSettings = mockk()
+
+    @Singleton
+    @Provides
+    fun coronaTestRepository(): CoronaTestRepository = mockk()
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt
index 284f8398badbfc1427c82fbfdaebc1bd6b488bc7..b3fde2e4884541ee4687eaa2afd73854c3e0d2bb 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt
@@ -1,26 +1,34 @@
 package de.rki.coronawarnapp.bugreporting.censors
 
 import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
-import de.rki.coronawarnapp.submission.SubmissionSettings
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.util.CWADebug
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
 import io.mockk.mockkObject
 import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import testhelpers.BaseTest
-import testhelpers.preferences.mockFlowPreference
 
 class RegistrationTokenCensorTest : BaseTest() {
-    @MockK lateinit var submissionSettings: SubmissionSettings
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
 
     private val testToken = "63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f"
 
-    private val regtokenPreference = mockFlowPreference<String?>(testToken)
+    private val coronaTests: MutableStateFlow<Set<CoronaTest>> = MutableStateFlow(
+        setOf(
+            mockk<CoronaTest>().apply {
+                every { registrationToken } returns testToken
+            }
+        )
+    )
 
     @BeforeEach
     fun setup() {
@@ -29,11 +37,11 @@ class RegistrationTokenCensorTest : BaseTest() {
         mockkObject(CWADebug)
         every { CWADebug.isDeviceForTestersBuild } returns false
 
-        every { submissionSettings.registrationToken } returns regtokenPreference
+        every { coronaTestRepository.coronaTests } returns coronaTests
     }
 
     private fun createInstance() = RegistrationTokenCensor(
-        submissionSettings = submissionSettings
+        coronaTestRepository = coronaTestRepository
     )
 
     @Test
@@ -55,12 +63,13 @@ class RegistrationTokenCensorTest : BaseTest() {
             message = "I'm a shy registration token: ########-e0de-4bd4-90c1-17c2bb683a2f"
         )
 
-        verify { regtokenPreference.value }
+        verify { coronaTestRepository.coronaTests }
     }
 
     @Test
     fun `censoring returns null if there is no token`() = runBlockingTest {
-        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
+        coronaTests.value = emptySet()
+
         val instance = createInstance()
         val filterMeNot = LogLine(
             timestamp = 1,
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQrCodeValidatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQrCodeValidatorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2d9478b797bb905bd6e88fab37518e52bd034c44
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQrCodeValidatorTest.kt
@@ -0,0 +1,32 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import io.kotest.matchers.shouldBe
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class CoronaTestQrCodeValidatorTest : BaseTest() {
+
+    @Test
+    fun `valid codes are extracted by corresponding extractor`() {
+        val instance = CoronaTestQrCodeValidator(RapidAntigenQrCodeExtractor(), PcrQrCodeExtractor())
+        instance.validate(pcrQrCode1).type shouldBe CoronaTest.Type.PCR
+        instance.validate(pcrQrCode2).type shouldBe CoronaTest.Type.PCR
+        instance.validate(pcrQrCode3).type shouldBe CoronaTest.Type.PCR
+        instance.validate(raQrCode1).type shouldBe CoronaTest.Type.RAPID_ANTIGEN
+        instance.validate(raQrCode2).type shouldBe CoronaTest.Type.RAPID_ANTIGEN
+        instance.validate(raQrCode3).type shouldBe CoronaTest.Type.RAPID_ANTIGEN
+    }
+
+    @Test
+    fun `invalid code throws exception`() {
+        val invalidCode = "HTTPS://somethingelse/?123456-12345678-1234-4DA7-B166-B86D85475064"
+        val instance = CoronaTestQrCodeValidator(RapidAntigenQrCodeExtractor(), PcrQrCodeExtractor())
+        return try {
+            instance.validate(invalidCode)
+            false
+        } catch (e: InvalidQRCodeException) {
+            true
+        } shouldBe true
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/ScanResultTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/PcrQrCodeExtractorTest.kt
similarity index 61%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/ScanResultTest.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/PcrQrCodeExtractorTest.kt
index 44bf60cc1340c0eaa3bfc20fd0484a0d7ce8ffd6..e4537ff5a6113f3d0c46a6691851bdf94a3a9a69 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/ScanResultTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/PcrQrCodeExtractorTest.kt
@@ -1,15 +1,10 @@
-package de.rki.coronawarnapp.service.submission
+package de.rki.coronawarnapp.coronatest.qrcode
 
 import io.kotest.matchers.shouldBe
-import io.mockk.MockKAnnotations
-import io.mockk.every
-import io.mockk.impl.annotations.MockK
-import io.mockk.mockkObject
-import org.junit.Before
 import org.junit.Test
 import testhelpers.BaseTest
 
-class ScanResultTest : BaseTest() {
+class PcrQrCodeExtractorTest : BaseTest() {
     private val guidUpperCase = "123456-12345678-1234-4DA7-B166-B86D85475064"
     private val guidLowerCase = "123456-12345678-1234-4da7-b166-b86d85475064"
     private val guidMixedCase = "123456-12345678-1234-4dA7-b166-B86d85475064"
@@ -17,19 +12,18 @@ class ScanResultTest : BaseTest() {
     private val localhostLowerCase = "https://localhost/?"
     private val localhostMixedCase = "https://LOCALHOST/?"
 
-    @MockK
-    private lateinit var scanResult: QRScanResult
-
-    @Before
-    fun setUp() {
-        MockKAnnotations.init(this)
-        mockkObject(scanResult)
-        every { scanResult.isValid } returns false
-    }
-
     private fun buildQRCodeCases(prefixString: String, guid: String, conditionToMatch: Boolean) {
-        scanResult = QRScanResult("$prefixString$guid")
-        scanResult.isValid shouldBe conditionToMatch
+        val extractor = PcrQrCodeExtractor()
+        try {
+            if (extractor.canHandle("$prefixString$guid")) {
+                extractor.extract("$prefixString$guid")
+                conditionToMatch shouldBe true
+            } else {
+                conditionToMatch shouldBe false
+            }
+        } catch (e: InvalidQRCodeException) {
+            conditionToMatch shouldBe false
+        }
     }
 
     @Test
@@ -83,16 +77,16 @@ class ScanResultTest : BaseTest() {
 
     @Test
     fun extractGUID() {
-        QRScanResult("$localhostUpperCase$guidUpperCase").guid shouldBe guidUpperCase
-        QRScanResult("$localhostUpperCase$guidLowerCase").guid shouldBe guidLowerCase
-        QRScanResult("$localhostUpperCase$guidMixedCase").guid shouldBe guidMixedCase
+        PcrQrCodeExtractor().extract("$localhostUpperCase$guidUpperCase").qrCodeGUID shouldBe guidUpperCase
+        PcrQrCodeExtractor().extract("$localhostUpperCase$guidLowerCase").qrCodeGUID shouldBe guidLowerCase
+        PcrQrCodeExtractor().extract("$localhostUpperCase$guidMixedCase").qrCodeGUID shouldBe guidMixedCase
 
-        QRScanResult("$localhostLowerCase$guidUpperCase").guid shouldBe guidUpperCase
-        QRScanResult("$localhostLowerCase$guidLowerCase").guid shouldBe guidLowerCase
-        QRScanResult("$localhostLowerCase$guidMixedCase").guid shouldBe guidMixedCase
+        PcrQrCodeExtractor().extract("$localhostLowerCase$guidUpperCase").qrCodeGUID shouldBe guidUpperCase
+        PcrQrCodeExtractor().extract("$localhostLowerCase$guidLowerCase").qrCodeGUID shouldBe guidLowerCase
+        PcrQrCodeExtractor().extract("$localhostLowerCase$guidMixedCase").qrCodeGUID shouldBe guidMixedCase
 
-        QRScanResult("$localhostMixedCase$guidUpperCase").guid shouldBe guidUpperCase
-        QRScanResult("$localhostMixedCase$guidLowerCase").guid shouldBe guidLowerCase
-        QRScanResult("$localhostMixedCase$guidMixedCase").guid shouldBe guidMixedCase
+        PcrQrCodeExtractor().extract("$localhostMixedCase$guidUpperCase").qrCodeGUID shouldBe guidUpperCase
+        PcrQrCodeExtractor().extract("$localhostMixedCase$guidLowerCase").qrCodeGUID shouldBe guidLowerCase
+        PcrQrCodeExtractor().extract("$localhostMixedCase$guidMixedCase").qrCodeGUID shouldBe guidMixedCase
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ac2fd0474a277dda83ceaeab4203437c593877d6
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractorTest.kt
@@ -0,0 +1,45 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import io.kotest.matchers.shouldBe
+import org.joda.time.Instant
+import org.joda.time.LocalDate
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class RapidAntigenQrCodeExtractorTest : BaseTest() {
+
+    private val instance = RapidAntigenQrCodeExtractor()
+
+    @Test
+    fun `valid codes are recognized`() {
+        listOf(raQrCode1, raQrCode2, raQrCode3, raQrCode4, raQrCode5, raQrCode6, raQrCode7, raQrCode8).forEach {
+            instance.canHandle(it) shouldBe true
+        }
+    }
+
+    @Test
+    fun `invalid codes are rejected`() {
+        listOf(pcrQrCode1, pcrQrCode2, pcrQrCode3).forEach {
+            instance.canHandle(it) shouldBe false
+        }
+    }
+
+    @Test
+    fun `extracting valid codes does not throw exception`() {
+        listOf(raQrCode1, raQrCode2, raQrCode3, raQrCode4, raQrCode5, raQrCode6, raQrCode7, raQrCode8).forEach {
+            instance.extract(it)
+        }
+    }
+
+    @Test
+    fun `personal data is extracted`() {
+        val data = instance.extract(raQrCode3)
+        data.type shouldBe CoronaTest.Type.RAPID_ANTIGEN
+        data.hash shouldBe "7b1c063e883063f8c33ffaa256aded506afd907f7446143b3da0f938a21967a9"
+        data.createdAt shouldBe Instant.ofEpochMilli(1618563782000)
+        data.dateOfBirth shouldBe LocalDate.parse("1962-01-08")
+        data.lastName shouldBe "Hayes"
+        data.firstName shouldBe "Alma"
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/TestQrCodes.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/TestQrCodes.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d8ebbe36ea3f19ae5f5931d8e1b31294c6338ce2
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/TestQrCodes.kt
@@ -0,0 +1,22 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+internal val pcrQrCode1 = "HTTPS://LOCALHOST/?123456-12345678-1234-4DA7-B166-B86D85475064"
+internal val pcrQrCode2 = "https://localhost/?123456-12345678-1234-4DA7-B166-B86D85475064"
+internal val pcrQrCode3 = "https://LOCALHOST/?123456-12345678-1234-4DA7-B166-B86D85475064"
+
+internal val raQrCode1 =
+    "https://s.coronawarn.app/?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjA5NjQsInNhbHQiOiIwQ0ZEMUJCQzI2Q0FCODVCNkZFNDE5MTJFQjFBQUU1QUNEN0QyNjA0RTQwNTQyRUVEQjZEQUYyQkRBMDQ5QzRGIiwidGVzdElkIjoiYjM2YzUzN2ItZWQ5NC00Njc3LTkzZmQtODUwMTY4NjlkYjEwIiwiaGFzaCI6IjJiNTc0NjhlN2Q4MTkyMWQzOGM4OGI1NjExOWE0Y2ViMzYyNmI1MDM4ZWI5Njk3ZjkxOTQ4NmJjMzg0Y2U2M2UifQ"
+internal val raQrCode2 =
+    "https://s.coronawarn.app?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjA5NjQsInNhbHQiOiIwQ0ZEMUJCQzI2Q0FCODVCNkZFNDE5MTJFQjFBQUU1QUNEN0QyNjA0RTQwNTQyRUVEQjZEQUYyQkRBMDQ5QzRGIiwidGVzdElkIjoiYjM2YzUzN2ItZWQ5NC00Njc3LTkzZmQtODUwMTY4NjlkYjEwIiwiaGFzaCI6IjJiNTc0NjhlN2Q4MTkyMWQzOGM4OGI1NjExOWE0Y2ViMzYyNmI1MDM4ZWI5Njk3ZjkxOTQ4NmJjMzg0Y2U2M2UifQ"
+internal val raQrCode3 =
+    "https://s.coronawarn.app/?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjM3ODIsInNhbHQiOiI1QTI3M0REREJCQTFEMkFDQUEzN0ExMDg4NjhGNkIwMjM3NjQzRjhBNjdCQTNENkQ3RUE3RkREQ0M0RDJGMjBEIiwidGVzdElkIjoiMGQ5ZTg0MzItZWI5MS00YzhmLTgyYWYtNWEwMWZiMWI2NzYwIiwiaGFzaCI6IjdiMWMwNjNlODgzMDYzZjhjMzNmZmFhMjU2YWRlZDUwNmFmZDkwN2Y3NDQ2MTQzYjNkYTBmOTM4YTIxOTY3YTkiLCJmbiI6IkFsbWEiLCJsbiI6IkhheWVzIiwiZG9iIjoiMTk2Mi0wMS0wOCJ9"
+internal val raQrCode4 =
+    "https://s.coronawarn.app?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjM3ODIsInNhbHQiOiI1QTI3M0REREJCQTFEMkFDQUEzN0ExMDg4NjhGNkIwMjM3NjQzRjhBNjdCQTNENkQ3RUE3RkREQ0M0RDJGMjBEIiwidGVzdElkIjoiMGQ5ZTg0MzItZWI5MS00YzhmLTgyYWYtNWEwMWZiMWI2NzYwIiwiaGFzaCI6IjdiMWMwNjNlODgzMDYzZjhjMzNmZmFhMjU2YWRlZDUwNmFmZDkwN2Y3NDQ2MTQzYjNkYTBmOTM4YTIxOTY3YTkiLCJmbiI6IkFsbWEiLCJsbiI6IkhheWVzIiwiZG9iIjoiMTk2Mi0wMS0wOCJ9"
+internal val raQrCode5 =
+    "https://s.coronawarn.app/?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjM4NDMsInNhbHQiOiJBNkVFRkZERDE2Qzk5RDFCODEyREE2NTc1NzgwMDM1QTQ0REI0OUM5NTBGODdCQkY0NDJBRkIwMDE5NkZCNDQ4IiwidGVzdElkIjoiNDhjOTc2ODgtY2U2ZC00MDFjLWEwMmMtMjU5MTE2YTRmODhmIiwiaGFzaCI6IjIyMTA4Y2FmNTQwNWM0MzI5Y2I3ZTEzNzg3MTMxMDJhMGNkNjY4OWM3YWFmMWJjOGViYzk3MDdiMjNjNTZhN2UifQ=="
+internal val raQrCode6 =
+    "https://s.coronawarn.app?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjM4NDMsInNhbHQiOiJBNkVFRkZERDE2Qzk5RDFCODEyREE2NTc1NzgwMDM1QTQ0REI0OUM5NTBGODdCQkY0NDJBRkIwMDE5NkZCNDQ4IiwidGVzdElkIjoiNDhjOTc2ODgtY2U2ZC00MDFjLWEwMmMtMjU5MTE2YTRmODhmIiwiaGFzaCI6IjIyMTA4Y2FmNTQwNWM0MzI5Y2I3ZTEzNzg3MTMxMDJhMGNkNjY4OWM3YWFmMWJjOGViYzk3MDdiMjNjNTZhN2UifQ=="
+internal val raQrCode7 =
+    "https://s.coronawarn.app/?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjM5MDIsInNhbHQiOiJGRkVERDZCMEM5MzZGRDVBQTg1M0NBNjgzOTY1QzYzMDI1NDRBMTIyNTY5MDAyQjg5OEI1NkM5OTY1NjRENTNGIiwidGVzdElkIjoiODYyNDcwYzItMzk3MS00M2Y0LWFjY2UtMjIzMzRlMjZiZTg3IiwiaGFzaCI6Ijc1ZmU5M2MyOWI1ZDI4NTU3NmJjZmM5NTZmYWIxMzllNTgxMWNhZWY1MTNiN2Y4MzhmNjY3NWJhYTU4MGM5YWUiLCJmbiI6IkJyeWFuIiwibG4iOiJNYXJzaCIsImRvYiI6IjE5OTAtMDQtMjgifQ=="
+internal val raQrCode8 =
+    "https://s.coronawarn.app?v=1#eyJ0aW1lc3RhbXAiOjE2MTg1NjM5MDIsInNhbHQiOiJGRkVERDZCMEM5MzZGRDVBQTg1M0NBNjgzOTY1QzYzMDI1NDRBMTIyNTY5MDAyQjg5OEI1NkM5OTY1NjRENTNGIiwidGVzdElkIjoiODYyNDcwYzItMzk3MS00M2Y0LWFjY2UtMjIzMzRlMjZiZTg3IiwiaGFzaCI6Ijc1ZmU5M2MyOWI1ZDI4NTU3NmJjZmM5NTZmYWIxMzllNTgxMWNhZWY1MTNiN2Y4MzhmNjY3NWJhYTU4MGM5YWUiLCJmbiI6IkJyeWFuIiwibG4iOiJNYXJzaCIsImRvYiI6IjE5OTAtMDQtMjgifQ=="
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/server/VerificationApiV1Test.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt
similarity index 98%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/server/VerificationApiV1Test.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt
index 74f8ea11fe2cdd3f9d43aa91ee3368c7fc707065..dd428bd56dace884e575ca41863a832297089bec 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/server/VerificationApiV1Test.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt
@@ -1,8 +1,7 @@
-package de.rki.coronawarnapp.verification.server
+package de.rki.coronawarnapp.coronatest.server
 
 import android.content.Context
 import de.rki.coronawarnapp.http.HttpModule
-import de.rki.coronawarnapp.verification.VerificationModule
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.every
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/VerificationModuleTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationModuleTest.kt
similarity index 97%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/VerificationModuleTest.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationModuleTest.kt
index 32eb9f446b80536621b2770298a932f9e8f932ff..1cc4d1f74f1e087776a206031615be33f35fd340 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/VerificationModuleTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationModuleTest.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.verification
+package de.rki.coronawarnapp.coronatest.server
 
 import android.content.Context
 import io.kotest.assertions.throwables.shouldNotThrowAny
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/server/VerificationServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt
similarity index 96%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/server/VerificationServerTest.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt
index 20fa57386a359a277ae4f2ee6f4dd88e69828a9b..a7f311ba6a18f1b0107130993b5912513bc0ad1c 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/verification/server/VerificationServerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt
@@ -1,9 +1,8 @@
-package de.rki.coronawarnapp.verification.server
+package de.rki.coronawarnapp.coronatest.server
 
 import android.content.Context
 import de.rki.coronawarnapp.http.HttpModule
 import de.rki.coronawarnapp.util.headerSizeIgnoringContentLength
-import de.rki.coronawarnapp.verification.VerificationModule
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.coEvery
@@ -115,7 +114,7 @@ class VerificationServerTest : BaseIOTest() {
             VerificationApiV1.TestResultResponse(testResult = 2)
         }
 
-        server.retrieveTestResults("testRegistrationToken") shouldBe 2
+        server.pollTestResult("testRegistrationToken") shouldBe CoronaTestResult.PCR_POSITIVE
 
         coVerify { verificationApi.getTestResult(any(), any(), any()) }
     }
@@ -189,7 +188,7 @@ class VerificationServerTest : BaseIOTest() {
         api.retrieveRegistrationToken(teletanExample, VerificationKeyType.TELETAN)
 
         webServer.enqueue(MockResponse().setBody("{}"))
-        api.retrieveTestResults(registrationTokenExample)
+        api.pollTestResult(registrationTokenExample)
 
         webServer.enqueue(MockResponse().setBody("{}"))
         api.retrieveTan(registrationTokenExample)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensionsTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f1ea759196b4ee7f44df7006f8f1825cc45a730f
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensionsTest.kt
@@ -0,0 +1,212 @@
+package de.rki.coronawarnapp.coronatest.type.pcr
+
+import io.kotest.matchers.shouldBe
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class PCRCoronaTestExtensionsTest : BaseTest() {
+
+    @Test
+    fun `state determination, unregistered test`() = runBlockingTest {
+        val test: PCRCoronaTest? = null
+        test.toSubmissionState() shouldBe SubmissionStatePCR.NoTest
+    }
+
+//     @Test
+//    fun removeTestFromDeviceSucceeds() = runBlockingTest {
+//        val submissionRepository = createInstance(scope = this)
+//
+//        val initialPollingForTestResultTimeStampSlot = slot<Long>()
+//        val isTestResultAvailableNotificationSent = slot<Boolean>()
+//        every {
+//            tracingSettings.initialPollingForTestResultTimeStamp = capture(initialPollingForTestResultTimeStampSlot)
+//        } answers {}
+//        every {
+//            tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent)
+//        } answers {}
+//
+//        every { submissionSettings.isAllowedToSubmitKeys = any() } just Runs
+//        every { submissionSettings.isSubmissionSuccessful = any() } just Runs
+//
+//        submissionRepository.removeTestFromDevice()
+//
+//        verify(exactly = 1) {
+//            testResultDataCollector.clear()
+//            registrationTokenPreference.update(any())
+//            submissionSettings.devicePairingSuccessfulAt = null
+//            submissionSettings.initialTestResultReceivedAt = null
+//            submissionSettings.isAllowedToSubmitKeys = false
+//            submissionSettings.isSubmissionSuccessful = false
+//        }
+//
+//        initialPollingForTestResultTimeStampSlot.captured shouldBe 0L
+//        isTestResultAvailableNotificationSent.captured shouldBe false
+//    }
+//
+//    @Test
+//    fun registrationWithGUIDSucceeds() = runBlockingTest {
+//        coEvery { submissionService.asyncRegisterDeviceViaGUID(guid) } returns registrationData
+//        coEvery { analyticsKeySubmissionCollector.reportTestRegistered() } just Runs
+//        every { analyticsKeySubmissionCollector.reset() } just Runs
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.asyncRegisterDeviceViaGUID(guid)
+//
+//        registrationTokenPreference.value shouldBe registrationToken
+//        submissionRepository.testResultReceivedDateFlow.first() shouldBe resultReceivedTimeStamp.toDate()
+//
+//        verify(exactly = 1) {
+//            registrationTokenPreference.update(any())
+//            submissionSettings.devicePairingSuccessfulAt = any()
+//            backgroundNoise.scheduleDummyPattern()
+//        }
+//
+//        coVerify { testResultDataCollector.saveTestResultAnalyticsSettings(any()) }
+//    }
+//
+//    @Test
+//    fun registrationWithTeleTANSucceeds() = runBlockingTest {
+//        coEvery { submissionService.asyncRegisterDeviceViaTAN(tan) } returns registrationData
+//        coEvery { analyticsKeySubmissionCollector.reportTestRegistered() } just Runs
+//        every { analyticsKeySubmissionCollector.reportRegisteredWithTeleTAN() } just Runs
+//        every { analyticsKeySubmissionCollector.reset() } just Runs
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.asyncRegisterDeviceViaTAN(tan)
+//
+//        registrationTokenPreference.value shouldBe registrationToken
+//        submissionRepository.testResultReceivedDateFlow.first() shouldBe resultReceivedTimeStamp.toDate()
+//
+//        verify(exactly = 1) {
+//            registrationTokenPreference.update(any())
+//            submissionSettings.devicePairingSuccessfulAt = any()
+//            backgroundNoise.scheduleDummyPattern()
+//        }
+//
+//        coVerify(exactly = 0) {
+//            testResultDataCollector.saveTestResultAnalyticsSettings(any())
+//        }
+//    }
+//
+//    @Test
+//    fun `reset clears tek history and settings`() = runBlockingTest {
+//        val instance = createInstance(this)
+//        instance.reset()
+//
+//        instance.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestIdle
+//
+//        coVerifyOrder {
+//            tekHistoryStorage.clear()
+//            submissionSettings.clear()
+//        }
+//    }
+//
+//    @Test
+//    fun `ui state is SUBMITTED_FINAL when submission was done`() = runBlockingTest {
+//        every { submissionSettings.isSubmissionSuccessful } returns true
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.refreshTest()
+//        submissionRepository.deviceUIStateFlow.first() shouldBe
+//            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL)
+//    }
+//
+//    @Test
+//    fun `ui state is UNPAIRED when no token is present`() = runBlockingTest {
+//        every { submissionSettings.isSubmissionSuccessful } returns false
+//        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.refreshTest()
+//        submissionRepository.deviceUIStateFlow.first() shouldBe
+//            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED)
+//    }
+//
+//    @Test
+//    fun `ui state is PAIRED_POSITIVE when allowed to submit`() = runBlockingTest {
+//        every { submissionSettings.isSubmissionSuccessful } returns false
+//        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
+//        coEvery { submissionSettings.isAllowedToSubmitKeys } returns true
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.refreshTest()
+//        submissionRepository.deviceUIStateFlow.first() shouldBe
+//            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE)
+//    }
+//
+//    @Test
+//    fun `refresh when state is PAIRED_NO_RESULT`() = runBlockingTest {
+//        every { submissionSettings.isSubmissionSuccessful } returns false
+//        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
+//        coEvery { submissionSettings.isAllowedToSubmitKeys } returns false
+//        coEvery { submissionService.asyncRequestTestResult(any()) } returns CoronaTestResult.PCR_OR_RAT_PENDING
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.refreshTest()
+//        submissionRepository.deviceUIStateFlow.first() shouldBe
+//            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT)
+//
+//        coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) }
+//    }
+//
+//    @Test
+//    fun `refresh when state is UNPAIRED`() = runBlockingTest {
+//        every { submissionSettings.isSubmissionSuccessful } returns false
+//        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
+//        coEvery { submissionSettings.isAllowedToSubmitKeys } returns false
+//        coEvery { submissionService.asyncRequestTestResult(any()) } returns CoronaTestResult.PCR_OR_RAT_PENDING
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.refreshTest()
+//        submissionRepository.deviceUIStateFlow.first() shouldBe
+//            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED)
+//
+//        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
+//
+//        submissionRepository.refreshTest()
+//
+//        coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) }
+//    }
+//
+//    @Test
+//    fun `no refresh when state is SUBMITTED_FINAL`() = runBlockingTest {
+//        every { submissionSettings.isSubmissionSuccessful } returns true
+//
+//        val submissionRepository = createInstance(scope = this)
+//
+//        submissionRepository.refreshTest()
+//
+//        submissionRepository.deviceUIStateFlow.first() shouldBe
+//            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL)
+//
+//        submissionRepository.refreshTest()
+//
+//        coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) }
+//    }
+//        @Test
+//    fun `updateTestResult updates test result donor data`() = runBlockingTest {
+//        val submissionRepository = createInstance(scope = this)
+//        submissionRepository.updateTestResult(CoronaTestResult.PCR_NEGATIVE)
+//
+//        verify { testResultDataCollector.updatePendingTestResultReceivedTime(any()) }
+//    }
+
+//    @Test
+//    fun `doDeviceRegistration calls TestResultDataCollector`() {
+//        val viewModel = createViewModel()
+//        val mockResult = mockk<QRScanResult>().apply {
+//            every { guid } returns "guid"
+//        }
+//
+//        coEvery { submissionRepository.asyncRegisterDeviceViaGUID(any()) } returns CoronaTestResult.PCR_POSITIVE
+//        viewModel.doDeviceRegistration(mockResult)
+//    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..39ee2b0fd03be3ed556a2059de6e0cc706a42e6b
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt
@@ -0,0 +1,12 @@
+package de.rki.coronawarnapp.coronatest.type.pcr
+
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class PCRProcessorTest : BaseTest() {
+
+    @Test
+    fun todo() {
+        // TODO
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensionsTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..48486b70cbcda1ec67315615d65c64bccc53fa88
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensionsTest.kt
@@ -0,0 +1,15 @@
+package de.rki.coronawarnapp.coronatest.type.rapidantigen
+
+import io.kotest.matchers.shouldBe
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class RapidAntigenCoronaTestExtensionsTest : BaseTest() {
+
+    @Test
+    fun `state determination, unregistered test`() = runBlockingTest {
+        val test: RACoronaTest? = null
+        test.toSubmissionState() shouldBe SubmissionStateRAT.NoTest
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2396194a96a5bc2fe009c291304880aad27afc56
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt
@@ -0,0 +1,12 @@
+package de.rki.coronawarnapp.coronatest.type.rapidantigen
+
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class RapidAntigenProcessorTest : BaseTest() {
+
+    @Test
+    fun todo() {
+        // TODO
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt
index fe6d8af0e238e196f949f8a58c52fff4485db9fb..c3146306200fc9ccb318127830add98f2c4c1ec2 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt
@@ -1,11 +1,15 @@
 package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest
 
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_NEGATIVE
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_OR_RAT_PENDING
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_POSITIVE
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_REDEEMED
 import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
 import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings
 import de.rki.coronawarnapp.risk.EwRiskLevelResult
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
 import de.rki.coronawarnapp.util.TimeStamper
-import de.rki.coronawarnapp.util.formatter.TestResult
 import io.mockk.Called
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
@@ -19,7 +23,6 @@ import kotlinx.coroutines.test.runBlockingTest
 import org.joda.time.Instant
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
-
 import testhelpers.BaseTest
 import testhelpers.preferences.mockFlowPreference
 
@@ -50,7 +53,7 @@ class TestResultDataCollectorTest : BaseTest() {
     fun `saveTestResultAnalyticsSettings does not save anything when no user consent`() =
         runBlockingTest {
             every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
-            testResultDataCollector.saveTestResultAnalyticsSettings(TestResult.POSITIVE)
+            testResultDataCollector.saveTestResultAnalyticsSettings(PCR_POSITIVE)
 
             verify(exactly = 0) {
                 testResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any())
@@ -66,9 +69,13 @@ class TestResultDataCollectorTest : BaseTest() {
                 every { calculatedAt } returns Instant.now()
                 every { wasSuccessfullyCalculated } returns true
             }
-            every { riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult } returns flowOf(listOf(mockRiskLevelResult))
+            every { riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult } returns flowOf(
+                listOf(
+                    mockRiskLevelResult
+                )
+            )
             every { testResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any()) } just Runs
-            testResultDataCollector.saveTestResultAnalyticsSettings(TestResult.POSITIVE)
+            testResultDataCollector.saveTestResultAnalyticsSettings(PCR_POSITIVE)
 
             verify(exactly = 1) {
                 testResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any())
@@ -79,7 +86,7 @@ class TestResultDataCollectorTest : BaseTest() {
     fun `saveTestResultAnalyticsSettings does not save data when TestResult is INVALID`() =
         runBlockingTest {
             every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
-            testResultDataCollector.saveTestResultAnalyticsSettings(TestResult.INVALID)
+            testResultDataCollector.saveTestResultAnalyticsSettings(PCR_INVALID)
 
             verify {
                 analyticsSettings.analyticsEnabled wasNot Called
@@ -90,7 +97,7 @@ class TestResultDataCollectorTest : BaseTest() {
     fun `saveTestResultAnalyticsSettings does not save data when TestResult is REDEEMED`() =
         runBlockingTest {
             every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
-            testResultDataCollector.saveTestResultAnalyticsSettings(TestResult.REDEEMED)
+            testResultDataCollector.saveTestResultAnalyticsSettings(PCR_REDEEMED)
 
             verify {
                 analyticsSettings.analyticsEnabled wasNot Called
@@ -100,10 +107,10 @@ class TestResultDataCollectorTest : BaseTest() {
     @Test
     fun `updatePendingTestResultReceivedTime doesn't update when TestResult isn't POS or NEG`() =
         runBlockingTest {
-            for (testResult in listOf(TestResult.REDEEMED, TestResult.INVALID, TestResult.PENDING)) {
+            for (testResult in listOf(PCR_REDEEMED, PCR_INVALID, PCR_OR_RAT_PENDING)) {
                 every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
                 every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.PENDING)
+                every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(PCR_OR_RAT_PENDING)
                 testResultDataCollector.updatePendingTestResultReceivedTime(testResult)
 
                 verify {
@@ -121,8 +128,8 @@ class TestResultDataCollectorTest : BaseTest() {
         runBlockingTest {
             every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
             every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(false)
-            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.PENDING)
-            testResultDataCollector.updatePendingTestResultReceivedTime(TestResult.NEGATIVE)
+            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(PCR_OR_RAT_PENDING)
+            testResultDataCollector.updatePendingTestResultReceivedTime(PCR_NEGATIVE)
 
             verify {
                 analyticsSettings.analyticsEnabled
@@ -136,10 +143,10 @@ class TestResultDataCollectorTest : BaseTest() {
     @Test
     fun `updatePendingTestResultReceivedTime update when TestResult is POS or NEG`() =
         runBlockingTest {
-            for (testResult in listOf(TestResult.NEGATIVE, TestResult.POSITIVE)) {
+            for (testResult in listOf(PCR_NEGATIVE, PCR_POSITIVE)) {
                 every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
                 every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.PENDING)
+                every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(PCR_OR_RAT_PENDING)
                 every { testResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(Instant.EPOCH)
                 testResultDataCollector.updatePendingTestResultReceivedTime(testResult)
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt
index 1b41fed797c94414bdfdaf339fca5ed88297401d..7ac52f2863e56274ceaa76cace64b9726d85efd8 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt
@@ -2,12 +2,14 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest
 
 import de.rki.coronawarnapp.appconfig.AnalyticsConfig
 import de.rki.coronawarnapp.appconfig.ConfigData
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule
 import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings
 import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.util.TimeStamper
-import de.rki.coronawarnapp.util.formatter.TestResult
 import io.kotest.matchers.shouldBe
 import io.kotest.matchers.types.shouldBeInstanceOf
 import io.mockk.MockKAnnotations
@@ -18,6 +20,7 @@ import io.mockk.just
 import io.mockk.mockk
 import io.mockk.unmockkAll
 import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runBlockingTest
 import org.joda.time.Duration
 import org.joda.time.Instant
@@ -30,12 +33,22 @@ import testhelpers.preferences.mockFlowPreference
 class TestResultDonorTest : BaseTest() {
     @MockK lateinit var testResultDonorSettings: TestResultDonorSettings
     @MockK lateinit var timeStamper: TimeStamper
-    @MockK lateinit var submissionSettings: SubmissionSettings
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
 
     private lateinit var testResultDonor: TestResultDonor
 
     private val baseTime = Instant.ofEpochMilli(101010101)
 
+    private val coronaTests: MutableStateFlow<Set<CoronaTest>> = MutableStateFlow(
+        setOf(
+            mockk<PCRCoronaTest>().apply {
+                every { registeredAt } returns baseTime
+                every { testResultReceivedAt } returns baseTime
+                every { type } returns CoronaTest.Type.PCR
+            }
+        )
+    )
+
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this, true)
@@ -45,12 +58,12 @@ class TestResultDonorTest : BaseTest() {
             every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
         }
         every { timeStamper.nowUTC } returns baseTime
-        every { submissionSettings.initialTestResultReceivedAt } returns baseTime
+        every { coronaTestRepository.coronaTests } returns coronaTests
 
         testResultDonor = TestResultDonor(
             testResultDonorSettings,
             timeStamper,
-            submissionSettings
+            coronaTestRepository = coronaTestRepository
         )
     }
 
@@ -68,21 +81,21 @@ class TestResultDonorTest : BaseTest() {
     @Test
     fun `No donation when timestamp at registration is missing`() = runBlockingTest {
         every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-        every { submissionSettings.initialTestResultReceivedAt } returns null
+        coronaTests.value = emptySet()
         testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution
     }
 
     @Test
     fun `No donation when test result is INVALID`() = runBlockingTest {
         every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-        every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.INVALID)
+        every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_INVALID)
         testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution
     }
 
     @Test
     fun `No donation when test result is REDEEMED`() = runBlockingTest {
         every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-        every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.REDEEMED)
+        every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_REDEEMED)
         testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution
     }
 
@@ -90,7 +103,7 @@ class TestResultDonorTest : BaseTest() {
     fun `No donation when test result is PENDING and hours isn't greater or equal to config hours`() {
         runBlockingTest {
             every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.PENDING)
+            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING)
 
             testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution
         }
@@ -100,10 +113,16 @@ class TestResultDonorTest : BaseTest() {
     fun `Donation is collected when test result is PENDING and hours is greater or equal to config hours`() {
         runBlockingTest {
             every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.PENDING)
+            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING)
 
             val timeDayBefore = baseTime.minus(Duration.standardDays(1))
-            every { submissionSettings.initialTestResultReceivedAt } returns timeDayBefore
+            coronaTests.value = setOf(
+                mockk<PCRCoronaTest>().apply {
+                    every { registeredAt } returns timeDayBefore
+                    every { testResultReceivedAt } returns baseTime
+                    every { type } returns CoronaTest.Type.PCR
+                }
+            )
             every { testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(
                 timeDayBefore
             )
@@ -124,7 +143,7 @@ class TestResultDonorTest : BaseTest() {
     fun `Donation is collected when test result is POSITIVE`() {
         runBlockingTest {
             every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE)
+            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
             every { testResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
 
             val donation = testResultDonor.beginDonation(TestRequest)
@@ -144,7 +163,7 @@ class TestResultDonorTest : BaseTest() {
         runBlockingTest {
             with(testResultDonorSettings) {
                 every { testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE)
+                every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
                 every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
                 every { riskLevelTurnedRedTime } returns mockFlowPreference(null)
                 every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH)
@@ -157,7 +176,7 @@ class TestResultDonorTest : BaseTest() {
         runBlockingTest {
             with(testResultDonorSettings) {
                 every { testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE)
+                every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
                 every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
                 every { riskLevelTurnedRedTime } returns mockFlowPreference(null)
                 every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH)
@@ -170,7 +189,7 @@ class TestResultDonorTest : BaseTest() {
         runBlockingTest {
             with(testResultDonorSettings) {
                 every { testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE)
+                every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
                 every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
                 every { riskLevelTurnedRedTime } returns mockFlowPreference(baseTime)
                 every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null)
@@ -189,7 +208,7 @@ class TestResultDonorTest : BaseTest() {
         runBlockingTest {
             with(testResultDonorSettings) {
                 every { testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE)
+                every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
                 every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
                 every { riskLevelTurnedRedTime } returns mockFlowPreference(baseTime)
                 every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null)
@@ -207,7 +226,7 @@ class TestResultDonorTest : BaseTest() {
         runBlockingTest {
             with(testResultDonorSettings) {
                 every { testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE)
+                every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
                 every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
                 every { riskLevelTurnedRedTime } returns mockFlowPreference(null)
                 every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null)
@@ -225,7 +244,7 @@ class TestResultDonorTest : BaseTest() {
         runBlockingTest {
             with(testResultDonorSettings) {
                 every { testScannedAfterConsent } returns mockFlowPreference(true)
-                every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE)
+                every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
                 every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
                 every { riskLevelTurnedRedTime } returns mockFlowPreference(null)
                 every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null)
@@ -241,7 +260,7 @@ class TestResultDonorTest : BaseTest() {
     fun `Donation is collected when test result is NEGATIVE`() {
         runBlockingTest {
             every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE)
+            every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
             every { testResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
 
             val donation = testResultDonor.beginDonation(TestRequest)
@@ -260,7 +279,7 @@ class TestResultDonorTest : BaseTest() {
     fun `Scenario 1 LowRisk`() = runBlockingTest {
         with(testResultDonorSettings) {
             every { testScannedAfterConsent } returns mockFlowPreference(true)
-            every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE)
+            every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
             every { finalTestResultReceivedAt } returns mockFlowPreference(
                 Instant.parse("2021-03-20T20:00:00Z")
             )
@@ -270,7 +289,13 @@ class TestResultDonorTest : BaseTest() {
             every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
         }
         every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z")
-        every { submissionSettings.initialTestResultReceivedAt } returns Instant.parse("2021-03-20T00:00:00Z")
+        coronaTests.value = setOf(
+            mockk<PCRCoronaTest>().apply {
+                every { testResultReceivedAt } returns baseTime
+                every { registeredAt } returns Instant.parse("2021-03-20T00:00:00Z")
+                every { type } returns CoronaTest.Type.PCR
+            }
+        )
 
         val donation = testResultDonor.beginDonation(TestRequest)
         donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>()
@@ -287,7 +312,7 @@ class TestResultDonorTest : BaseTest() {
     fun `Scenario 2 HighRisk`() = runBlockingTest {
         with(testResultDonorSettings) {
             every { testScannedAfterConsent } returns mockFlowPreference(true)
-            every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE)
+            every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
             every { finalTestResultReceivedAt } returns mockFlowPreference(
                 Instant.parse("2021-03-20T20:00:00Z")
             )
@@ -298,7 +323,13 @@ class TestResultDonorTest : BaseTest() {
         }
 
         every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z")
-        every { submissionSettings.initialTestResultReceivedAt } returns Instant.parse("2021-03-20T00:00:00Z")
+        coronaTests.value = setOf(
+            mockk<PCRCoronaTest>().apply {
+                every { testResultReceivedAt } returns baseTime
+                every { registeredAt } returns Instant.parse("2021-03-20T00:00:00Z")
+                every { type } returns CoronaTest.Type.PCR
+            }
+        )
 
         val donation = testResultDonor.beginDonation(TestRequest)
         donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>()
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt
index 3201a04a7c24b83bb2c573c35c9982a59ac3c585..3ad5c90bc8cf3913cd7ca78cc6faa48373a810c5 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt
@@ -3,12 +3,13 @@ package de.rki.coronawarnapp.diagnosiskeys.download
 import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.appconfig.ConfigData
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKey
 import de.rki.coronawarnapp.environment.BuildConfigWrap
 import de.rki.coronawarnapp.environment.EnvironmentSetup
 import de.rki.coronawarnapp.nearby.ENFClient
 import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.util.TimeStamper
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
@@ -18,8 +19,10 @@ import io.mockk.coVerifySequence
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.just
+import io.mockk.mockk
 import io.mockk.mockkObject
 import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runBlockingTest
 import org.joda.time.Duration
@@ -46,7 +49,13 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() {
     @MockK lateinit var newKey1: CachedKey
 
     @MockK lateinit var latestTrackedDetection: TrackedExposureDetection
-    @MockK lateinit var submissionSettings: SubmissionSettings
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
+
+    private val coronaTests: MutableStateFlow<Set<CoronaTest>> = MutableStateFlow(
+        setOf(
+            mockk<CoronaTest>().apply { every { isSubmissionAllowed } returns false }
+        )
+    )
 
     @BeforeEach
     fun setup() {
@@ -54,7 +63,8 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() {
 
         mockkObject(BuildConfigWrap)
         every { BuildConfigWrap.VERSION_CODE } returns 1080005
-        every { submissionSettings.isAllowedToSubmitKeys } returns false
+
+        every { coronaTestRepository.coronaTests } returns coronaTests
 
         availableKey1.apply {
             every { path } returns File("availableKey1")
@@ -102,7 +112,7 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() {
         keyPackageSyncTool = keyPackageSyncTool,
         timeStamper = timeStamper,
         settings = downloadSettings,
-        submissionSettings = submissionSettings
+        coronaTestRepository = coronaTestRepository,
     )
 
     @Test
@@ -230,7 +240,9 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() {
 
     @Test
     fun `we do not submit keys if user got positive test results`() = runBlockingTest {
-        every { submissionSettings.isAllowedToSubmitKeys } returns true
+        coronaTests.value = setOf(
+            mockk<CoronaTest>().apply { every { isSubmissionAllowed } returns true }
+        )
 
         createInstance().run(DownloadDiagnosisKeysTask.Arguments())
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt
index f8701565cac240c69b8fc9c2ce987e6c8a1978d6..cdd16544d8379ca907355ca249c0bf964f5b3875 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt
@@ -1,13 +1,13 @@
 package de.rki.coronawarnapp.http.playbook
 
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.server.VerificationKeyType
+import de.rki.coronawarnapp.coronatest.server.VerificationServer
 import de.rki.coronawarnapp.exception.TanPairingException
 import de.rki.coronawarnapp.exception.http.BadRequestException
 import de.rki.coronawarnapp.playbook.DefaultPlaybook
 import de.rki.coronawarnapp.playbook.Playbook
 import de.rki.coronawarnapp.submission.server.SubmissionServer
-import de.rki.coronawarnapp.util.formatter.TestResult
-import de.rki.coronawarnapp.verification.server.VerificationKeyType
-import de.rki.coronawarnapp.verification.server.VerificationServer
 import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.matchers.shouldBe
 import io.kotest.matchers.types.shouldBeInstanceOf
@@ -32,7 +32,7 @@ class DefaultPlaybookTest : BaseTest() {
         MockKAnnotations.init(this)
 
         coEvery { verificationServer.retrieveRegistrationToken(any(), any()) } returns "token"
-        coEvery { verificationServer.retrieveTestResults(any()) } returns 0
+        coEvery { verificationServer.pollTestResult(any()) } returns CoronaTestResult.PCR_OR_RAT_PENDING
         coEvery { verificationServer.retrieveTanFake() } returns mockk()
         coEvery { verificationServer.retrieveTan(any()) } returns "tan"
 
@@ -54,7 +54,7 @@ class DefaultPlaybookTest : BaseTest() {
         coVerifySequence {
             // ensure request order is 2x verification and 1x submission
             verificationServer.retrieveRegistrationToken(any(), any())
-            verificationServer.retrieveTestResults(any())
+            verificationServer.pollTestResult(any())
             submissionServer.submitFakePayload()
         }
     }
@@ -166,13 +166,11 @@ class DefaultPlaybookTest : BaseTest() {
 
     @Test
     fun `test result retrieval matches pattern`(): Unit = runBlocking {
-        coEvery { verificationServer.retrieveTestResults(any()) } returns 0
-
         createPlaybook().testResult("token")
 
         coVerifySequence {
             // ensure request order is 2x verification and 1x submission
-            verificationServer.retrieveTestResults(any())
+            verificationServer.pollTestResult(any())
             verificationServer.retrieveTanFake()
             submissionServer.submitFakePayload()
         }
@@ -194,8 +192,8 @@ class DefaultPlaybookTest : BaseTest() {
     fun `failures during dummy requests should be ignored`(): Unit = runBlocking {
         val expectedToken = "token"
         coEvery { verificationServer.retrieveRegistrationToken(any(), any()) } returns expectedToken
-        val expectedResult = TestResult.PENDING
-        coEvery { verificationServer.retrieveTestResults(expectedToken) } returns expectedResult.value
+        val expectedResult = CoronaTestResult.PCR_OR_RAT_PENDING
+        coEvery { verificationServer.pollTestResult(expectedToken) } returns expectedResult
         coEvery { submissionServer.submitFakePayload() } throws TestException()
 
         val (registrationToken, testResult) = createPlaybook()
@@ -224,7 +222,7 @@ class DefaultPlaybookTest : BaseTest() {
 
     @Test
     fun `registration pattern matches despite test result failure`(): Unit = runBlocking {
-        coEvery { verificationServer.retrieveTestResults(any()) } throws TestException()
+        coEvery { verificationServer.pollTestResult(any()) } throws TestException()
 
         shouldThrow<TestException> {
             createPlaybook().initialRegistration("9A3B578UMG", VerificationKeyType.TELETAN)
@@ -233,14 +231,14 @@ class DefaultPlaybookTest : BaseTest() {
         coVerifySequence {
             // ensure request order is 2x verification and 1x submission
             verificationServer.retrieveRegistrationToken(any(), any())
-            verificationServer.retrieveTestResults(any())
+            verificationServer.pollTestResult(any())
             submissionServer.submitFakePayload()
         }
     }
 
     @Test
     fun `test result pattern matches despite failure`(): Unit = runBlocking {
-        coEvery { verificationServer.retrieveTestResults(any()) } throws TestException()
+        coEvery { verificationServer.pollTestResult(any()) } throws TestException()
 
         shouldThrow<TestException> {
             createPlaybook().testResult("token")
@@ -248,7 +246,7 @@ class DefaultPlaybookTest : BaseTest() {
 
         coVerifySequence {
             // ensure request order is 2x verification and 1x submission
-            verificationServer.retrieveTestResults(any())
+            verificationServer.pollTestResult(any())
             verificationServer.retrieveTanFake()
             submissionServer.submitFakePayload()
         }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt
index 21548c0e148c7051f00e1e8987d3627fd0f19b5b..fa2a64983a5bdfba1a7fa9d4d8ed9197dcaa646c 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt
@@ -1,10 +1,12 @@
 package de.rki.coronawarnapp.main
 
 import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.environment.EnvironmentSetup
+import de.rki.coronawarnapp.playbook.BackgroundNoise
 import de.rki.coronawarnapp.presencetracing.TraceLocationSettings
 import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
-import de.rki.coronawarnapp.playbook.BackgroundNoise
 import de.rki.coronawarnapp.storage.OnboardingSettings
 import de.rki.coronawarnapp.ui.main.MainActivityViewModel
 import de.rki.coronawarnapp.util.CWADebug
@@ -33,6 +35,8 @@ class MainActivityViewModelTest : BaseTest() {
     @MockK lateinit var onboardingSettings: OnboardingSettings
     @MockK lateinit var traceLocationSettings: TraceLocationSettings
     @MockK lateinit var checkInRepository: CheckInRepository
+    @MockK lateinit var deadManScheduler: DeadmanNotificationScheduler
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
 
     @BeforeEach
     fun setup() {
@@ -55,7 +59,9 @@ class MainActivityViewModelTest : BaseTest() {
         backgroundNoise = backgroundNoise,
         onboardingSettings = onboardingSettings,
         checkInRepository = checkInRepository,
-        traceLocationSettings = traceLocationSettings
+        traceLocationSettings = traceLocationSettings,
+        deadmanScheduler = deadManScheduler,
+        coronaTestRepository = coronaTestRepository,
     )
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt
index 045f24aed0ab8ef9334aa47abe206b4b0a8ea703..a5a8653dd65751954d95be2c1057f26dd60f0a42 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt
@@ -2,6 +2,9 @@ package de.rki.coronawarnapp.main.home
 
 import android.content.Context
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
 import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.environment.BuildConfigWrap
 import de.rki.coronawarnapp.main.CWASettings
@@ -10,19 +13,14 @@ import de.rki.coronawarnapp.statistics.source.StatisticsProvider
 import de.rki.coronawarnapp.storage.TracingRepository
 import de.rki.coronawarnapp.storage.TracingSettings
 import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider
 import de.rki.coronawarnapp.tracing.GeneralTracingStatus
 import de.rki.coronawarnapp.tracing.GeneralTracingStatus.Status
 import de.rki.coronawarnapp.tracing.states.LowRisk
 import de.rki.coronawarnapp.tracing.states.TracingStateProvider
 import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState
-import de.rki.coronawarnapp.ui.presencetracing.organizer.TraceLocationOrganizerSettings
 import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents
 import de.rki.coronawarnapp.ui.main.home.HomeFragmentViewModel
-import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE
-import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE_TELETAN
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
+import de.rki.coronawarnapp.ui.presencetracing.organizer.TraceLocationOrganizerSettings
 import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool
 import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper
 import io.kotest.matchers.shouldBe
@@ -56,7 +54,7 @@ class HomeFragmentViewModelTest : BaseTest() {
     @MockK lateinit var errorResetTool: EncryptionErrorResetTool
     @MockK lateinit var tracingStateProvider: TracingStateProvider
     @MockK lateinit var tracingStateProviderFactory: TracingStateProvider.Factory
-    @MockK lateinit var submissionStateProvider: SubmissionStateProvider
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
     @MockK lateinit var tracingRepository: TracingRepository
     @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService
     @MockK lateinit var submissionRepository: SubmissionRepository
@@ -77,9 +75,7 @@ class HomeFragmentViewModelTest : BaseTest() {
         every { tracingStateProviderFactory.create(isDetailsMode = false) } returns tracingStateProvider
         every { tracingStateProvider.state } returns flowOf(mockk<LowRisk>())
 
-        every { submissionStateProvider.state } returns flowOf(mockk<SubmissionDone>())
-
-        every { submissionRepository.hasViewedTestResult } returns flowOf(true)
+        every { coronaTestRepository.coronaTests } returns emptyFlow()
 
         coEvery { appConfigProvider.currentConfig } returns emptyFlow()
         coEvery { statisticsProvider.current } returns emptyFlow()
@@ -92,7 +88,7 @@ class HomeFragmentViewModelTest : BaseTest() {
         tracingRepository = tracingRepository,
         shareTestResultNotificationService = shareTestResultNotificationService,
         submissionRepository = submissionRepository,
-        submissionStateProvider = submissionStateProvider,
+        coronaTestRepository = coronaTestRepository,
         tracingStateProviderFactory = tracingStateProviderFactory,
         cwaSettings = cwaSettings,
         appConfigProvider = appConfigProvider,
@@ -144,15 +140,21 @@ class HomeFragmentViewModelTest : BaseTest() {
             this.homeItems.observeForTesting { }
             coVerify {
                 tracingStateProvider.state
-                submissionStateProvider.state
+                coronaTestRepository.coronaTests
             }
         }
     }
 
     @Test
     fun `positive test result notification is triggered on positive QR code result`() {
-        every { submissionRepository.deviceUIStateFlow } returns flowOf(
-            NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE)
+//        every { submissionRepository.deviceUIStateFlow } returns flowOf(
+//            NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE)
+//        )
+        every { submissionRepository.pcrTest } returns flowOf(
+            mockk<PCRCoronaTest>().apply {
+                every { testResult } returns CoronaTestResult.PCR_POSITIVE
+                every { lastError } returns null
+            }
         )
         every { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } returns Unit
 
@@ -166,8 +168,14 @@ class HomeFragmentViewModelTest : BaseTest() {
 
     @Test
     fun `positive test result notification is triggered on positive TeleTan code result`() {
-        every { submissionRepository.deviceUIStateFlow } returns flowOf(
-            NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE_TELETAN)
+//        every { submissionRepository.deviceUIStateFlow } returns flowOf(
+//            NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE_TELETAN)
+//        )
+        every { submissionRepository.pcrTest } returns flowOf(
+            mockk<PCRCoronaTest>().apply {
+                every { testResult } returns CoronaTestResult.PCR_POSITIVE
+                every { lastError } returns null
+            }
         )
         every { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } returns Unit
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt
index b57db26b647333db5577520216f1e1b985999007..96a785177817e7ca7dfc02ada3aec020d859fb44 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt
@@ -6,9 +6,9 @@ import android.content.Context
 import androidx.navigation.NavDeepLinkBuilder
 import de.rki.coronawarnapp.CoronaWarnApplication
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.main.CWASettings
 import de.rki.coronawarnapp.util.device.ForegroundState
-import de.rki.coronawarnapp.util.formatter.TestResult
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
@@ -63,13 +63,13 @@ class TestResultAvailableNotificationServiceTest : BaseTest() {
 
     @Test
     fun `check destination`() {
-        val negative = createInstance().getNotificationDestination(TestResult.NEGATIVE)
+        val negative = createInstance().getNotificationDestination(CoronaTestResult.PCR_NEGATIVE)
         negative shouldBe (R.id.submissionTestResultPendingFragment)
 
-        val invalid = createInstance().getNotificationDestination(TestResult.INVALID)
+        val invalid = createInstance().getNotificationDestination(CoronaTestResult.PCR_INVALID)
         invalid shouldBe (R.id.submissionTestResultPendingFragment)
 
-        val positive = createInstance().getNotificationDestination(TestResult.POSITIVE)
+        val positive = createInstance().getNotificationDestination(CoronaTestResult.PCR_POSITIVE)
         positive shouldBe (R.id.submissionTestResultPendingFragment)
     }
 
@@ -77,7 +77,7 @@ class TestResultAvailableNotificationServiceTest : BaseTest() {
     fun `test notification in foreground`() = runBlockingTest {
         coEvery { foregroundState.isInForeground } returns flow { emit(true) }
 
-        createInstance().showTestResultAvailableNotification(TestResult.POSITIVE)
+        createInstance().showTestResultAvailableNotification(CoronaTestResult.PCR_POSITIVE)
 
         verify(exactly = 0) { navDeepLinkBuilderProvider.get() }
     }
@@ -94,11 +94,11 @@ class TestResultAvailableNotificationServiceTest : BaseTest() {
 
         val instance = createInstance()
 
-        instance.showTestResultAvailableNotification(TestResult.POSITIVE)
+        instance.showTestResultAvailableNotification(CoronaTestResult.PCR_POSITIVE)
 
         verifyOrder {
             navDeepLinkBuilderProvider.get()
-            instance.getNotificationDestination(TestResult.POSITIVE)
+            instance.getNotificationDestination(CoronaTestResult.PCR_POSITIVE)
             context.getString(R.string.notification_headline_test_result_ready)
             context.getString(R.string.notification_body_test_result_ready)
             notificationHelper.sendNotification(
@@ -114,7 +114,7 @@ class TestResultAvailableNotificationServiceTest : BaseTest() {
         every { cwaSettings.isNotificationsTestEnabled.value } returns false
 
         createInstance().apply {
-            showTestResultAvailableNotification(TestResult.POSITIVE)
+            showTestResultAvailableNotification(CoronaTestResult.PCR_POSITIVE)
 
             verify(exactly = 0) {
                 notificationHelper.sendNotification(
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/attendee/confirm/ConfirmCheckInViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/attendee/confirm/ConfirmCheckInViewModelTest.kt
index 51a12fe4489dfa0671daf297a37a49e60ab2751b..494ff184a0dfa3d16c230bf6788e39b39854cbae 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/attendee/confirm/ConfirmCheckInViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/attendee/confirm/ConfirmCheckInViewModelTest.kt
@@ -4,6 +4,7 @@ import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocation
 import de.rki.coronawarnapp.presencetracing.checkins.qrcode.VerifiedTraceLocation
 import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
+import de.rki.coronawarnapp.ui.presencetracing.attendee.TraceLocationAttendeeSettings
 import de.rki.coronawarnapp.ui.presencetracing.attendee.confirm.ConfirmCheckInNavigation
 import de.rki.coronawarnapp.ui.presencetracing.attendee.confirm.ConfirmCheckInViewModel
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.secondsToInstant
@@ -13,6 +14,9 @@ import io.mockk.MockKAnnotations
 import io.mockk.coEvery
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.runs
+import kotlinx.coroutines.flow.flowOf
 import okio.ByteString.Companion.decodeBase64
 import org.joda.time.Duration
 import org.joda.time.Instant
@@ -29,6 +33,7 @@ class ConfirmCheckInViewModelTest : BaseTest() {
     @MockK lateinit var verifiedTraceLocation: VerifiedTraceLocation
     @MockK lateinit var checkInRepository: CheckInRepository
     @MockK lateinit var timeStamper: TimeStamper
+    @MockK lateinit var traceLocationAttendeeSettings: TraceLocationAttendeeSettings
 
     private val traceLocation = TraceLocation(
         id = 1,
@@ -50,12 +55,15 @@ class ConfirmCheckInViewModelTest : BaseTest() {
         coEvery { checkInRepository.addCheckIn(any()) } returns 1L
         every { verifiedTraceLocation.traceLocation } returns traceLocation
         every { timeStamper.nowUTC } returns Instant.parse("2021-03-04T10:30:00Z")
+        every { traceLocationAttendeeSettings.createJournalEntryCheckedState } returns flowOf(true)
+        every { traceLocationAttendeeSettings.setCreateJournalEntryCheckedState(any()) } just runs
     }
 
     private fun createInstance() = ConfirmCheckInViewModel(
         verifiedTraceLocation = verifiedTraceLocation,
         checkInRepository = checkInRepository,
-        timeStamper = timeStamper
+        timeStamper = timeStamper,
+        traceLocationAttendeeSettings = traceLocationAttendeeSettings
     )
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepositoryTest.kt
index f5e3d4202e2587dad2ba7589ec22999e4529d99b..9763fe7788a5ea216e9901db0532c2483180eb48 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepositoryTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/CheckInRepositoryTest.kt
@@ -115,6 +115,7 @@ class CheckInRepositoryTest : BaseTest() {
                         completed = false,
                         createJournalEntry = false,
                         isSubmitted = true,
+                        hasSubmissionConsent = false,
                     )
                 )
             }
@@ -160,6 +161,7 @@ class CheckInRepositoryTest : BaseTest() {
                 completed = false,
                 createJournalEntry = false,
                 isSubmitted = true,
+                hasSubmissionConsent = true,
             )
         )
         runBlockingTest {
@@ -181,6 +183,7 @@ class CheckInRepositoryTest : BaseTest() {
                     completed = false,
                     createJournalEntry = false,
                     isSubmitted = true,
+                    hasSubmissionConsent = true,
                 )
             )
         }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapperTest.kt
index dd07536e331c18350214ed1ed23f8f70ca1898bf..341d5b9bb05c03111c75cb9573ab2c72df2f3af7 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapperTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapperTest.kt
@@ -8,6 +8,7 @@ import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParamete
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.coEvery
+import io.mockk.coVerify
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import kotlinx.coroutines.flow.flowOf
@@ -50,7 +51,7 @@ class PresenceTracingRiskMapperTest : BaseTest() {
             .setRiskLevel(RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH)
             .build()
 
-    val container = PresenceTracingRiskCalculationParamContainer(
+    private val container = PresenceTracingRiskCalculationParamContainer(
         transmissionRiskValueMapping = listOf(transmissionRiskValueMapping),
         normalizedTimePerCheckInToRiskLevelMapping = listOf(normalizedTimeMappingLow, normalizedTimeMappingHigh),
         normalizedTimePerDayToRiskLevelMapping = listOf(normalizedTimeMappingLow, normalizedTimeMappingHigh)
@@ -100,5 +101,26 @@ class PresenceTracingRiskMapperTest : BaseTest() {
         }
     }
 
+    @Test
+    fun `config is requested only once`() {
+        runBlockingTest {
+            val mapper = createInstance()
+            mapper.lookupRiskStatePerDay(30.0)
+            mapper.lookupRiskStatePerDay(60.0)
+            coVerify(exactly = 1) { configProvider.currentConfig }
+        }
+    }
+
+    @Test
+    fun `config is requested again after reset`() {
+        runBlockingTest {
+            val mapper = createInstance()
+            mapper.lookupRiskStatePerDay(30.0)
+            mapper.clearConfig()
+            mapper.lookupRiskStatePerDay(60.0)
+            coVerify(exactly = 2) { configProvider.currentConfig }
+        }
+    }
+
     private fun createInstance() = PresenceTracingRiskMapper(configProvider)
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt
index f9722d08a76a6568cd5d3b4782661a5b98b17637..57b4cfb409c03ec328048fc8b45bf0824cc33c0e 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.presencetracing.risk.execution
 
 import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import de.rki.coronawarnapp.presencetracing.risk.calculation.CheckInWarningMatcher
+import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingRiskMapper
 import de.rki.coronawarnapp.presencetracing.risk.calculation.createCheckIn
 import de.rki.coronawarnapp.presencetracing.risk.calculation.createWarning
 import de.rki.coronawarnapp.presencetracing.risk.storage.PresenceTracingRiskRepository
@@ -40,6 +41,7 @@ class PresenceTracingWarningTaskTest : BaseTest() {
     @MockK lateinit var presenceTracingRiskRepository: PresenceTracingRiskRepository
     @MockK lateinit var traceWarningRepository: TraceWarningRepository
     @MockK lateinit var checkInsRepository: CheckInRepository
+    @MockK lateinit var presenceTracingRiskMapper: PresenceTracingRiskMapper
 
     @BeforeEach
     fun setup() {
@@ -71,6 +73,8 @@ class PresenceTracingWarningTaskTest : BaseTest() {
             coEvery { deleteStaleData() } just Runs
             coEvery { reportCalculation(any(), any()) } just Runs
         }
+
+        coEvery { presenceTracingRiskMapper.clearConfig() } just Runs
     }
 
     private fun createInstance() = PresenceTracingWarningTask(
@@ -80,6 +84,7 @@ class PresenceTracingWarningTaskTest : BaseTest() {
         presenceTracingRiskRepository = presenceTracingRiskRepository,
         traceWarningRepository = traceWarningRepository,
         checkInsRepository = checkInsRepository,
+        presenceTracingRiskMapper = presenceTracingRiskMapper
     )
 
     @Test
@@ -102,6 +107,27 @@ class PresenceTracingWarningTaskTest : BaseTest() {
         }
     }
 
+    @Test
+    fun `happy path with config change`() = runBlockingTest {
+        createInstance().run(mockk()) shouldNotBe null
+
+        coVerifySequence {
+            presenceTracingRiskMapper.clearConfig()
+            syncTool.syncPackages()
+            presenceTracingRiskRepository.deleteStaleData()
+            checkInsRepository.checkInsWithinRetention
+            traceWarningRepository.unprocessedWarningPackages
+
+            checkInWarningMatcher.process(any(), any())
+
+            presenceTracingRiskRepository.reportCalculation(
+                successful = true,
+                overlaps = any()
+            )
+            traceWarningRepository.markPackagesProcessed(listOf(WARNING_PKG.packageId))
+        }
+    }
+
     @Test
     fun `overall task errors lead to a reported failed calculation`() = runBlockingTest {
         coEvery { syncTool.syncPackages() } throws IOException("Unexpected")
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt
index 225b325d98625085679e70b2392d65944b53f471..a88a3a777beda43d72e3800e7bbf588471b4a511 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt
@@ -5,6 +5,8 @@ import android.content.Context
 import androidx.core.app.NotificationCompat
 import androidx.core.app.NotificationManagerCompat
 import com.google.android.gms.nearby.exposurenotification.ExposureWindow
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings
 import de.rki.coronawarnapp.datadonation.survey.Surveys
 import de.rki.coronawarnapp.notification.GeneralNotifications
@@ -15,7 +17,6 @@ import de.rki.coronawarnapp.risk.RiskState.LOW_RISK
 import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
 import de.rki.coronawarnapp.storage.TracingSettings
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.device.ForegroundState
 import de.rki.coronawarnapp.util.notifications.setContentTextExpandable
@@ -30,6 +31,7 @@ import io.mockk.impl.annotations.MockK
 import io.mockk.just
 import io.mockk.mockk
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runBlockingTest
 import org.joda.time.Instant
@@ -47,19 +49,25 @@ class RiskLevelChangeDetectorTest : BaseTest() {
     @MockK lateinit var riskLevelSettings: RiskLevelSettings
     @MockK lateinit var notificationHelper: GeneralNotifications
     @MockK lateinit var surveys: Surveys
-    @MockK lateinit var submissionSettings: SubmissionSettings
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
     @MockK lateinit var tracingSettings: TracingSettings
     @MockK lateinit var testResultDonorSettings: TestResultDonorSettings
 
     @MockK lateinit var builder: NotificationCompat.Builder
     @MockK lateinit var notification: Notification
 
+    private val coronaTests: MutableStateFlow<Set<CoronaTest>> = MutableStateFlow(
+        setOf(
+            mockk<CoronaTest>().apply { every { isSubmitted } returns false }
+        )
+    )
+
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
 
         every { tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel } returns mockFlowPreference(false)
-        every { submissionSettings.isSubmissionSuccessful } returns false
+        every { coronaTestRepository.coronaTests } returns coronaTests
         every { foregroundState.isInForeground } returns flowOf(false)
         every { notificationManagerCompat.areNotificationsEnabled() } returns true
 
@@ -126,7 +134,7 @@ class RiskLevelChangeDetectorTest : BaseTest() {
         riskLevelSettings = riskLevelSettings,
         notificationHelper = notificationHelper,
         surveys = surveys,
-        submissionSettings = submissionSettings,
+        coronaTestRepository = coronaTestRepository,
         tracingSettings = tracingSettings,
         testResultDonorSettings = testResultDonorSettings
     )
@@ -204,7 +212,7 @@ class RiskLevelChangeDetectorTest : BaseTest() {
             advanceUntilIdle()
 
             coVerifySequence {
-                submissionSettings.isSubmissionSuccessful
+                coronaTestRepository.coronaTests
                 foregroundState.isInForeground
                 notificationHelper.newBaseBuilder()
                 notificationHelper.sendNotification(any(), any())
@@ -235,7 +243,7 @@ class RiskLevelChangeDetectorTest : BaseTest() {
             advanceUntilIdle()
 
             coVerifySequence {
-                submissionSettings.isSubmissionSuccessful
+                coronaTestRepository.coronaTests
                 foregroundState.isInForeground
                 notificationHelper.newBaseBuilder()
                 notificationHelper.sendNotification(any(), any())
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt
index ffc2478a2edc4d10d4e4d1265e78b59c0de45deb..77e65bd863c06f9f0ae662767e22adf81cd63c3a 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt
@@ -6,6 +6,8 @@ import android.net.Network
 import android.net.NetworkCapabilities
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.appconfig.ConfigData
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.exposurewindows.AnalyticsExposureWindowCollector
 import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKey
 import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKeyInfo
@@ -14,7 +16,6 @@ import de.rki.coronawarnapp.nearby.ENFClient
 import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut
 import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.task.Task
 import de.rki.coronawarnapp.task.TaskCancellationException
 import de.rki.coronawarnapp.util.TimeStamper
@@ -29,6 +30,7 @@ import io.mockk.just
 import io.mockk.mockk
 import io.mockk.mockkObject
 import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runBlockingTest
 import org.joda.time.DateTime
@@ -50,19 +52,28 @@ class RiskLevelTaskTest : BaseTest() {
     @MockK lateinit var appConfigProvider: AppConfigProvider
     @MockK lateinit var riskLevelStorage: RiskLevelStorage
     @MockK lateinit var keyCacheRepository: KeyCacheRepository
-    @MockK lateinit var submissionSettings: SubmissionSettings
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
     @MockK lateinit var analyticsExposureWindowCollector: AnalyticsExposureWindowCollector
     @MockK lateinit var autoCheckOut: AutoCheckOut
 
     private val arguments: Task.Arguments = object : Task.Arguments {}
 
+    private val coronaTests: MutableStateFlow<Set<CoronaTest>> = MutableStateFlow(
+        setOf(
+            mockk<CoronaTest>().apply {
+                every { isSubmissionAllowed } returns false
+                every { isViewed } returns false
+            }
+        )
+    )
+
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
 
         mockkObject(TimeVariables)
 
-        every { submissionSettings.isAllowedToSubmitKeys } returns false
+        every { coronaTestRepository.coronaTests } returns coronaTests
         every { configData.isDeviceTimeCorrect } returns true
         every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(true)
         coEvery { appConfigProvider.getAppConfig() } returns configData
@@ -99,7 +110,7 @@ class RiskLevelTaskTest : BaseTest() {
         appConfigProvider = appConfigProvider,
         riskLevelStorage = riskLevelStorage,
         keyCacheRepository = keyCacheRepository,
-        submissionSettings = submissionSettings,
+        coronaTestRepository = coronaTestRepository,
         analyticsExposureWindowCollector = analyticsExposureWindowCollector,
         autoCheckOut = autoCheckOut
     )
@@ -221,8 +232,13 @@ class RiskLevelTaskTest : BaseTest() {
         coEvery { keyCacheRepository.getAllCachedKeys() } returns listOf(cachedKey)
         every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(false)
         every { timeStamper.nowUTC } returns now
-        every { submissionSettings.isAllowedToSubmitKeys } returns true
-        every { submissionSettings.hasViewedTestResult.value } returns true
+
+        coronaTests.value = setOf(
+            mockk<CoronaTest>().apply {
+                every { isSubmissionAllowed } returns true
+                every { isViewed } returns true
+            }
+        )
 
         createTask().run(arguments) shouldBe EwRiskLevelTaskResult(
             calculatedAt = now,
@@ -248,8 +264,13 @@ class RiskLevelTaskTest : BaseTest() {
         every { riskLevels.aggregateResults(any(), any()) } returns aggregatedRiskResult
         every { timeStamper.nowUTC } returns now
         coEvery { analyticsExposureWindowCollector.reportRiskResultsPerWindow(any()) } just Runs
-        every { submissionSettings.isAllowedToSubmitKeys } returns true
-        every { submissionSettings.hasViewedTestResult.value } returns false
+
+        coronaTests.value = setOf(
+            mockk<CoronaTest>().apply {
+                every { isSubmissionAllowed } returns true
+                every { isViewed } returns false
+            }
+        )
 
         createTask().run(arguments) shouldBe EwRiskLevelTaskResult(
             calculatedAt = now,
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt
index e58c535688d11c39bbb8504d9c379c220060a544..3b74fd8ea4034bc728b7fd5ec7f1f77d479b2e82 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt
@@ -1,10 +1,12 @@
 package de.rki.coronawarnapp.service.submission
 
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.server.VerificationKeyType
+import de.rki.coronawarnapp.coronatest.type.CoronaTestService
+import de.rki.coronawarnapp.deniability.NoiseScheduler
 import de.rki.coronawarnapp.playbook.Playbook
 import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.util.di.ApplicationComponent
-import de.rki.coronawarnapp.util.formatter.TestResult
-import de.rki.coronawarnapp.verification.server.VerificationKeyType
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.coEvery
@@ -25,8 +27,9 @@ class SubmissionServiceTest : BaseTest() {
 
     @MockK lateinit var mockPlaybook: Playbook
     @MockK lateinit var appComponent: ApplicationComponent
+    @MockK lateinit var noiseScheduler: NoiseScheduler
 
-    lateinit var submissionService: SubmissionService
+    lateinit var submissionService: CoronaTestService
 
     @BeforeEach
     fun setUp() {
@@ -35,14 +38,17 @@ class SubmissionServiceTest : BaseTest() {
         every { AppInjector.component } returns appComponent
         every { appComponent.playbook } returns mockPlaybook
 
-        submissionService = SubmissionService(mockPlaybook)
+        submissionService = CoronaTestService(
+            playbook = mockPlaybook,
+            noiseScheduler = noiseScheduler
+        )
     }
 
     @Test
     fun registrationWithGUIDSucceeds() {
         coEvery {
             mockPlaybook.initialRegistration(guid, VerificationKeyType.GUID)
-        } returns (registrationToken to TestResult.PENDING)
+        } returns (registrationToken to CoronaTestResult.PCR_OR_RAT_PENDING)
 
         runBlocking {
             submissionService.asyncRegisterDeviceViaGUID(guid)
@@ -57,7 +63,7 @@ class SubmissionServiceTest : BaseTest() {
     fun registrationWithTeleTANSucceeds() {
         coEvery {
             mockPlaybook.initialRegistration(any(), VerificationKeyType.TELETAN)
-        } returns (registrationToken to TestResult.PENDING)
+        } returns (registrationToken to CoronaTestResult.PCR_OR_RAT_PENDING)
 
         runBlocking {
             submissionService.asyncRegisterDeviceViaTAN(tan)
@@ -70,10 +76,10 @@ class SubmissionServiceTest : BaseTest() {
 
     @Test
     fun requestTestResultSucceeds() {
-        coEvery { mockPlaybook.testResult(registrationToken) } returns TestResult.NEGATIVE
+        coEvery { mockPlaybook.testResult(registrationToken) } returns CoronaTestResult.PCR_NEGATIVE
 
         runBlocking {
-            submissionService.asyncRequestTestResult(registrationToken) shouldBe TestResult.NEGATIVE
+            submissionService.asyncRequestTestResult(registrationToken) shouldBe CoronaTestResult.PCR_NEGATIVE
         }
         coVerify(exactly = 1) {
             mockPlaybook.testResult(registrationToken)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt
index 38282aaf96e40847a257eaca7047f53b675770d1..ae77a7e3ca94f391438e9a5f0aedc71cbcc48fe8 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt
@@ -1,361 +1,36 @@
 package de.rki.coronawarnapp.storage
 
-import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
-import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.TestResultDataCollector
-import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
-import de.rki.coronawarnapp.playbook.BackgroundNoise
-import de.rki.coronawarnapp.service.submission.SubmissionService
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.SubmissionSettings
-import de.rki.coronawarnapp.submission.Symptoms
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage
-import de.rki.coronawarnapp.task.TaskController
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import de.rki.coronawarnapp.util.TimeStamper
-import de.rki.coronawarnapp.util.di.AppInjector
-import de.rki.coronawarnapp.util.di.ApplicationComponent
-import de.rki.coronawarnapp.util.encryptionmigration.EncryptedPreferencesFactory
-import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool
-import de.rki.coronawarnapp.util.formatter.TestResult
-import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
-import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
-import io.mockk.Runs
-import io.mockk.coEvery
-import io.mockk.coVerify
-import io.mockk.coVerifyOrder
-import io.mockk.every
 import io.mockk.impl.annotations.MockK
-import io.mockk.just
-import io.mockk.mockkObject
-import io.mockk.slot
-import io.mockk.verify
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.runBlockingTest
-import org.joda.time.Instant
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import testhelpers.BaseTest
-import testhelpers.preferences.mockFlowPreference
 
 class SubmissionRepositoryTest : BaseTest() {
 
     @MockK lateinit var submissionSettings: SubmissionSettings
-    @MockK lateinit var submissionService: SubmissionService
-
-    @MockK lateinit var backgroundNoise: BackgroundNoise
-    @MockK lateinit var appComponent: ApplicationComponent
-    @MockK lateinit var taskController: TaskController
     @MockK lateinit var tekHistoryStorage: TEKHistoryStorage
-    @MockK lateinit var timeStamper: TimeStamper
-
-    @MockK lateinit var encryptedPreferencesFactory: EncryptedPreferencesFactory
-    @MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool
-    @MockK lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler
-    @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
-    @MockK lateinit var tracingSettings: TracingSettings
-    @MockK lateinit var testResultDataCollector: TestResultDataCollector
-    @MockK lateinit var backgroundWorkScheduler: BackgroundWorkScheduler
-
-    private val guid = "123456-12345678-1234-4DA7-B166-B86D85475064"
-    private val tan = "123456-12345678-1234-4DA7-B166-B86D85475064"
-    private val registrationToken = "asdjnskjfdniuewbheboqudnsojdff"
-    private val testResult = TestResult.PENDING
-    private val registrationData = SubmissionService.RegistrationData(registrationToken, testResult)
-
-    private val registrationTokenPreference = mockFlowPreference<String?>(null)
-    private val resultReceivedTimeStamp = Instant.ofEpochMilli(101010101)
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
 
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this)
-
-        mockkObject(AppInjector)
-        every { AppInjector.component } returns appComponent
-        every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory
-        every { appComponent.errorResetTool } returns encryptionErrorResetTool
-
-        every { backgroundNoise.scheduleDummyPattern() } just Runs
-
-        every { submissionSettings.registrationToken } returns registrationTokenPreference
-
-        every { submissionSettings.devicePairingSuccessfulAt = any() } just Runs
-        every { submissionSettings.initialTestResultReceivedAt } returns resultReceivedTimeStamp
-        every { submissionSettings.initialTestResultReceivedAt = any() } just Runs
-
-        every { submissionSettings.hasGivenConsent } returns mockFlowPreference(false)
-        every { submissionSettings.hasViewedTestResult } returns mockFlowPreference(false)
-        every { submissionSettings.symptoms } returns mockFlowPreference(Symptoms.NO_INFO_GIVEN)
-        every { submissionSettings.clear() } just Runs
-
-        every { submissionSettings.devicePairingSuccessfulAt } returns null
-
-        every { taskController.tasks } returns emptyFlow()
-
-        coEvery { tekHistoryStorage.clear() } just Runs
-
-        every { timeStamper.nowUTC } returns Instant.EPOCH
-        every { testResultDataCollector.updatePendingTestResultReceivedTime(any()) } just Runs
-        coEvery { testResultDataCollector.saveTestResultAnalyticsSettings(any()) } just Runs
-        every { testResultDataCollector.clear() } just Runs
-
-        backgroundWorkScheduler.apply {
-            every { startWorkScheduler() } just Runs
-        }
     }
 
     fun createInstance(scope: CoroutineScope) = SubmissionRepository(
         scope = scope,
         submissionSettings = submissionSettings,
-        submissionService = submissionService,
-        timeStamper = timeStamper,
         tekHistoryStorage = tekHistoryStorage,
-        deadmanNotificationScheduler = deadmanNotificationScheduler,
-        backgroundNoise = backgroundNoise,
-        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector,
-        tracingSettings = tracingSettings,
-        testResultDataCollector = testResultDataCollector,
-        backgroundWorkScheduler = backgroundWorkScheduler
+        coronaTestRepository = coronaTestRepository,
     )
 
     @Test
-    fun removeTestFromDeviceSucceeds() = runBlockingTest {
-        val submissionRepository = createInstance(scope = this)
-
-        val initialPollingForTestResultTimeStampSlot = slot<Long>()
-        val isTestResultAvailableNotificationSent = slot<Boolean>()
-        every {
-            tracingSettings.initialPollingForTestResultTimeStamp = capture(initialPollingForTestResultTimeStampSlot)
-        } answers {}
-        every {
-            tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent)
-        } answers {}
-
-        every { submissionSettings.isAllowedToSubmitKeys = any() } just Runs
-        every { submissionSettings.isSubmissionSuccessful = any() } just Runs
-
-        submissionRepository.removeTestFromDevice()
-
-        verify(exactly = 1) {
-            testResultDataCollector.clear()
-            registrationTokenPreference.update(any())
-            submissionSettings.devicePairingSuccessfulAt = null
-            submissionSettings.initialTestResultReceivedAt = null
-            submissionSettings.isAllowedToSubmitKeys = false
-            submissionSettings.isSubmissionSuccessful = false
-        }
-
-        initialPollingForTestResultTimeStampSlot.captured shouldBe 0L
-        isTestResultAvailableNotificationSent.captured shouldBe false
-    }
-
-    @Test
-    fun registrationWithGUIDSucceeds() = runBlockingTest {
-        coEvery { submissionService.asyncRegisterDeviceViaGUID(guid) } returns registrationData
-        coEvery { analyticsKeySubmissionCollector.reportTestRegistered() } just Runs
-        every { analyticsKeySubmissionCollector.reset() } just Runs
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.asyncRegisterDeviceViaGUID(guid)
-
-        registrationTokenPreference.value shouldBe registrationToken
-        submissionRepository.testResultReceivedDateFlow.first() shouldBe resultReceivedTimeStamp.toDate()
-
-        verify(exactly = 1) {
-            registrationTokenPreference.update(any())
-            submissionSettings.devicePairingSuccessfulAt = any()
-            backgroundNoise.scheduleDummyPattern()
-        }
-
-        coVerify { testResultDataCollector.saveTestResultAnalyticsSettings(any()) }
-    }
-
-    @Test
-    fun registrationWithTeleTANSucceeds() = runBlockingTest {
-        coEvery { submissionService.asyncRegisterDeviceViaTAN(tan) } returns registrationData
-        coEvery { analyticsKeySubmissionCollector.reportTestRegistered() } just Runs
-        every { analyticsKeySubmissionCollector.reportRegisteredWithTeleTAN() } just Runs
-        every { analyticsKeySubmissionCollector.reset() } just Runs
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.asyncRegisterDeviceViaTAN(tan)
-
-        registrationTokenPreference.value shouldBe registrationToken
-        submissionRepository.testResultReceivedDateFlow.first() shouldBe resultReceivedTimeStamp.toDate()
-
-        verify(exactly = 1) {
-            registrationTokenPreference.update(any())
-            submissionSettings.devicePairingSuccessfulAt = any()
-            backgroundNoise.scheduleDummyPattern()
-        }
-
-        coVerify(exactly = 0) {
-            testResultDataCollector.saveTestResultAnalyticsSettings(any())
-        }
-    }
-
-    @Test
-    fun `reset clears tek history and settings`() = runBlockingTest {
-        val instance = createInstance(this)
-        instance.reset()
-
-        instance.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestIdle
-
-        coVerifyOrder {
-            tekHistoryStorage.clear()
-            submissionSettings.clear()
-        }
-    }
-
-    @Test
-    fun `ui state is SUBMITTED_FINAL when submission was done`() = runBlockingTest {
-        every { submissionSettings.isSubmissionSuccessful } returns true
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.refreshDeviceUIState()
-        submissionRepository.deviceUIStateFlow.first() shouldBe
-            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL)
-    }
-
-    @Test
-    fun `ui state is UNPAIRED when no token is present`() = runBlockingTest {
-        every { submissionSettings.isSubmissionSuccessful } returns false
-        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.refreshDeviceUIState()
-        submissionRepository.deviceUIStateFlow.first() shouldBe
-            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED)
-    }
-
-    @Test
-    fun `ui state is PAIRED_POSITIVE when allowed to submit`() = runBlockingTest {
-        every { submissionSettings.isSubmissionSuccessful } returns false
-        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
-        coEvery { submissionSettings.isAllowedToSubmitKeys } returns true
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.refreshDeviceUIState()
-        submissionRepository.deviceUIStateFlow.first() shouldBe
-            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE)
-    }
-
-    @Test
-    fun `refresh when state is PAIRED_NO_RESULT`() = runBlockingTest {
-        every { submissionSettings.isSubmissionSuccessful } returns false
-        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
-        coEvery { submissionSettings.isAllowedToSubmitKeys } returns false
-        coEvery { submissionService.asyncRequestTestResult(any()) } returns TestResult.PENDING
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.refreshDeviceUIState()
-        submissionRepository.deviceUIStateFlow.first() shouldBe
-            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT)
-
-        coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) }
-    }
-
-    @Test
-    fun `refresh when state is UNPAIRED`() = runBlockingTest {
-        every { submissionSettings.isSubmissionSuccessful } returns false
-        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
-        coEvery { submissionSettings.isAllowedToSubmitKeys } returns false
-        coEvery { submissionService.asyncRequestTestResult(any()) } returns TestResult.PENDING
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.refreshDeviceUIState()
-        submissionRepository.deviceUIStateFlow.first() shouldBe
-            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED)
-
-        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
-
-        submissionRepository.refreshDeviceUIState()
-
-        coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) }
-    }
-
-    @Test
-    fun `no refresh when state is SUBMITTED_FINAL`() = runBlockingTest {
-        every { submissionSettings.isSubmissionSuccessful } returns true
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.refreshDeviceUIState()
-
-        submissionRepository.deviceUIStateFlow.first() shouldBe
-            NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL)
-
-        submissionRepository.refreshDeviceUIState()
-
-        coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) }
-    }
-
-    @Test
-    fun `EXPOSUREAPP-4484 is fixed`() = runBlockingTest {
-        every { timeStamper.nowUTC } returns Instant.EPOCH
-
-        var initialTimeStamp = Instant.EPOCH.plus(9999)
-        every { submissionSettings.initialTestResultReceivedAt } answers { initialTimeStamp }
-        every { submissionSettings.initialTestResultReceivedAt = any() } answers { initialTimeStamp = arg(0) }
-
-        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
-        every { submissionSettings.devicePairingSuccessfulAt } returns null
-
-        val submissionRepository = createInstance(scope = this)
-
-        submissionRepository.updateTestResult(TestResult.NEGATIVE)
-
-        verify {
-            submissionSettings.initialTestResultReceivedAt = null
-            submissionSettings.initialTestResultReceivedAt = Instant.EPOCH
-        }
-
-        initialTimeStamp shouldBe Instant.EPOCH
-    }
-
-    @Test
-    fun `EXPOSUREAPP-4484 has specific conditions`() = runBlockingTest {
-        val submissionRepository = createInstance(scope = this)
-
-        every { submissionSettings.initialTestResultReceivedAt } returns Instant.ofEpochMilli(1234)
-        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
-        // This needs to be null to trigger the fix
-        every { submissionSettings.devicePairingSuccessfulAt } returns Instant.ofEpochMilli(5678)
-
-        submissionRepository.updateTestResult(TestResult.NEGATIVE)
-
-        every { submissionSettings.initialTestResultReceivedAt } returns Instant.ofEpochMilli(1234)
-        // This needs to be non null to trigger the fix
-        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
-        every { submissionSettings.devicePairingSuccessfulAt } returns null
-
-        submissionRepository.updateTestResult(TestResult.NEGATIVE)
-
-        // This needs to be non null to trigger the fix
-        every { submissionSettings.initialTestResultReceivedAt } returns null
-        every { submissionSettings.registrationToken } returns mockFlowPreference("token")
-        every { submissionSettings.devicePairingSuccessfulAt } returns null
-
-        submissionRepository.updateTestResult(TestResult.NEGATIVE)
-
-        verify(exactly = 0) { submissionSettings.initialTestResultReceivedAt = null }
-    }
-
-    @Test
-    fun `updateTestResult updates test result donor data`() = runBlockingTest {
-        val submissionRepository = createInstance(scope = this)
-        submissionRepository.updateTestResult(TestResult.NEGATIVE)
-
-        verify { testResultDataCollector.updatePendingTestResultReceivedTime(any()) }
+    fun todo() {
+        // TODO
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/SubmissionSettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/SubmissionSettingsTest.kt
index b1b1f7c5df7e50acbf4f1de90f1619f596a93a1a..51f0d5706abcbe23e682f94a644ad3a5369b6335 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/SubmissionSettingsTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/SubmissionSettingsTest.kt
@@ -39,15 +39,6 @@ class SubmissionSettingsTest {
         baseGson = baseGson
     )
 
-    @Test
-    fun consentIsPersisted() {
-        createInstance().apply {
-            hasGivenConsent.value shouldBe false
-            hasGivenConsent.update { true }
-            hasGivenConsent.value shouldBe true
-        }
-    }
-
     @Test
     fun `persist symptoms`() {
         createInstance().apply {
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt
index 1ebc52af489fd60fa81b8470ff6b12cfb78af9a2..9a10b45206fd8216eb1325d4da42275e83f3341f 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt
@@ -3,14 +3,15 @@ package de.rki.coronawarnapp.submission.task
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.appconfig.ConfigData
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
-import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
-import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
-import de.rki.coronawarnapp.presencetracing.checkins.CheckInsTransformer
-import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
 import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.playbook.Playbook
+import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInsTransformer
 import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass
 import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.submission.Symptoms
@@ -32,6 +33,7 @@ import io.mockk.impl.annotations.MockK
 import io.mockk.just
 import io.mockk.mockk
 import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runBlockingTest
@@ -64,17 +66,28 @@ class SubmissionTaskTest : BaseTest() {
     @MockK lateinit var checkInsTransformer: CheckInsTransformer
     @MockK lateinit var checkInRepository: CheckInRepository
     @MockK lateinit var backgroundWorkScheduler: BackgroundWorkScheduler
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
 
     private lateinit var settingSymptomsPreference: FlowPreference<Symptoms?>
 
-    private val registrationToken: FlowPreference<String?> = mockFlowPreference("regtoken")
-    private val settingHasGivenConsent: FlowPreference<Boolean> = mockFlowPreference(true)
     private val settingAutoSubmissionAttemptsCount: FlowPreference<Int> = mockFlowPreference(0)
     private val settingAutoSubmissionAttemptsLast: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH)
 
     private val settingLastUserActivityUTC: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH.plus(1))
 
-    private val testCheckIn1 = CheckIn(
+    private val coronaTestsFlow = MutableStateFlow(
+        setOf(
+            mockk<CoronaTest>().apply {
+                every { isAdvancedConsentGiven } returns true
+                every { isSubmissionAllowed } returns true
+                every { isSubmitted } returns false
+                every { registrationToken } returns "regtoken"
+                every { identifier } returns "coronatest-identifier"
+            }
+        )
+    )
+
+    private val validCheckIn = CheckIn(
         id = 1L,
         traceLocationId = mockk(),
         version = 1,
@@ -88,17 +101,24 @@ class SubmissionTaskTest : BaseTest() {
         cnPublicKey = "cnPublicKey",
         checkInStart = Instant.EPOCH,
         checkInEnd = Instant.EPOCH.plus(9000),
-        completed = false,
+        completed = true,
         createJournalEntry = false,
-        isSubmitted = true
+        isSubmitted = false,
+        hasSubmissionConsent = true
     )
 
+    private val invalidCheckIn1 = validCheckIn.copy(id = 2L, completed = false)
+    private val invalidCheckIn2 = validCheckIn.copy(id = 3L, isSubmitted = true)
+    private val invalidCheckIn3 = validCheckIn.copy(id = 4L, hasSubmissionConsent = false)
+
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
 
-        every { submissionSettings.registrationToken } returns registrationToken
-        every { submissionSettings.isSubmissionSuccessful = any() } just Runs
+        coronaTestRepository.apply {
+            every { coronaTests } returns coronaTestsFlow
+            coEvery { markAsSubmitted("coronatest-identifier") } just Runs
+        }
 
         every { backgroundWorkScheduler.stopWorkScheduler() } just Runs
         every { backgroundWorkScheduler.startWorkScheduler() } just Runs
@@ -113,7 +133,6 @@ class SubmissionTaskTest : BaseTest() {
 
         settingSymptomsPreference = mockFlowPreference(userSymptoms)
         every { submissionSettings.symptoms } returns settingSymptomsPreference
-        every { submissionSettings.hasGivenConsent } returns settingHasGivenConsent
         every { submissionSettings.lastSubmissionUserActivityUTC } returns settingLastUserActivityUTC
         every { submissionSettings.autoSubmissionAttemptsCount } returns settingAutoSubmissionAttemptsCount
         every { submissionSettings.autoSubmissionAttemptsLast } returns settingAutoSubmissionAttemptsLast
@@ -133,7 +152,18 @@ class SubmissionTaskTest : BaseTest() {
 
         every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardHours(1))
 
-        every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(testCheckIn1))
+        checkInRepository.apply {
+            every { checkInsWithinRetention } returns flowOf(
+                listOf(
+                    validCheckIn,
+                    invalidCheckIn1,
+                    invalidCheckIn2,
+                    invalidCheckIn3
+                )
+            )
+            coEvery { updatePostSubmissionFlags(any()) } just Runs
+        }
+
         coEvery { checkInsTransformer.transform(any(), any()) } returns emptyList()
     }
 
@@ -151,6 +181,7 @@ class SubmissionTaskTest : BaseTest() {
         checkInsRepository = checkInRepository,
         checkInsTransformer = checkInsTransformer,
         backgroundWorkScheduler = backgroundWorkScheduler,
+        coronaTestRepository = coronaTestRepository,
     )
 
     @Test
@@ -163,16 +194,14 @@ class SubmissionTaskTest : BaseTest() {
         coVerifySequence {
             submissionSettings.lastSubmissionUserActivityUTC
             settingLastUserActivityUTC.value
-            submissionSettings.hasGivenConsent
-            settingHasGivenConsent.value
+            coronaTestRepository.coronaTests
 
             submissionSettings.autoSubmissionAttemptsCount
             submissionSettings.autoSubmissionAttemptsLast
             submissionSettings.autoSubmissionAttemptsCount
             submissionSettings.autoSubmissionAttemptsLast
-            submissionSettings.registrationToken
 
-            registrationToken.value
+            coronaTestRepository.coronaTests
             tekHistoryStorage.tekData
             submissionSettings.symptoms
             settingSymptomsPreference.value
@@ -199,17 +228,23 @@ class SubmissionTaskTest : BaseTest() {
             submissionSettings.symptoms
             settingSymptomsPreference.update(match { it.invoke(mockk()) == null })
 
-            checkInRepository.markCheckInAsSubmitted(testCheckIn1.id)
+            checkInRepository.updatePostSubmissionFlags(validCheckIn.id)
 
             autoSubmission.updateMode(AutoSubmission.Mode.DISABLED)
 
             backgroundWorkScheduler.stopWorkScheduler()
-            submissionSettings.isSubmissionSuccessful = true
+            coronaTestRepository.markAsSubmitted(any())
             backgroundWorkScheduler.startWorkScheduler()
 
             shareTestResultNotificationService.cancelSharePositiveTestResultNotification()
             testResultAvailableNotificationService.cancelTestResultAvailableNotification()
         }
+
+        coVerify(exactly = 0) {
+            checkInRepository.updatePostSubmissionFlags(invalidCheckIn1.id)
+            checkInRepository.updatePostSubmissionFlags(invalidCheckIn2.id)
+            checkInRepository.updatePostSubmissionFlags(invalidCheckIn3.id)
+        }
     }
 
     @Test
@@ -236,9 +271,8 @@ class SubmissionTaskTest : BaseTest() {
         }
 
         coVerifySequence {
-            settingHasGivenConsent.value
-
-            registrationToken.value
+            coronaTestRepository.coronaTests // Consent
+            coronaTestRepository.coronaTests // regToken
             tekHistoryStorage.tekData
             settingSymptomsPreference.value
 
@@ -267,10 +301,16 @@ class SubmissionTaskTest : BaseTest() {
 
     @Test
     fun `task throws if no registration token is available`() = runBlockingTest {
-        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
+        coronaTestsFlow.value = setOf(
+            mockk<CoronaTest>().apply {
+                every { isAdvancedConsentGiven } returns true
+                every { isSubmissionAllowed } returns false
+                every { isSubmitted } returns false
+            }
+        )
 
         val task = createTask()
-        shouldThrow<NoRegistrationTokenSetException> {
+        shouldThrow<IllegalStateException> {
             task.run(SubmissionTask.Arguments())
         }
     }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt
index 7898f97ead95c935255b9b58703bfb809607c1fc..c8430ea2a07a0a8ee28b306d979c7157397ab775 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt
@@ -1,18 +1,19 @@
 package de.rki.coronawarnapp.submission.testresult.pending
 
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingViewModel
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
+import io.mockk.Runs
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.mockk
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestCoroutineScope
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
@@ -27,14 +28,15 @@ class SubmissionTestResultPendingViewModelTest : BaseTest() {
     @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService
     @MockK lateinit var submissionRepository: SubmissionRepository
 
+    private val testFlow = MutableStateFlow<CoronaTest?>(null)
+
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
 
         submissionRepository.apply {
-            every { hasGivenConsentToSubmission } returns emptyFlow()
-            every { deviceUIStateFlow } returns emptyFlow()
-            every { testResultReceivedDateFlow } returns emptyFlow()
+            every { testForType(any()) } returns testFlow
+            every { setViewedTestResult(any()) } just Runs
         }
     }
 
@@ -46,19 +48,17 @@ class SubmissionTestResultPendingViewModelTest : BaseTest() {
 
     @Test
     fun `web exception handling`() {
-        val expectedType = NetworkRequestWrapper.RequestFailed<DeviceUIState, CwaWebException>(
-            CwaWebException(statusCode = 1, message = "message")
-        )
-        val unexpectedType =
-            NetworkRequestWrapper.RequestFailed<DeviceUIState, Throwable>(UnsupportedOperationException())
-        val deviceUI = MutableStateFlow<NetworkRequestWrapper<DeviceUIState, Throwable>>(expectedType)
-        every { submissionRepository.deviceUIStateFlow } returns deviceUI
+        val expectedError = CwaWebException(statusCode = 1, message = "message")
+        val unexpectedError = UnsupportedOperationException()
+
+        testFlow.value = mockk<CoronaTest>().apply { every { lastError } returns expectedError }
+
         createInstance().apply {
             cwaWebExceptionLiveData.observeForever {}
-            cwaWebExceptionLiveData.value shouldBe expectedType.error
+            cwaWebExceptionLiveData.value shouldBe expectedError
 
-            deviceUI.value = unexpectedType
-            cwaWebExceptionLiveData.value shouldBe unexpectedType.error
+            testFlow.value = mockk<CoronaTest>().apply { every { lastError } returns unexpectedError }
+            cwaWebExceptionLiveData.value shouldBe unexpectedError
         }
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt
deleted file mode 100644
index dee497b2ccdd3f88fa5a838b3480616fd2940ce5..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package de.rki.coronawarnapp.tracing.ui.homecards
-
-import android.content.Context
-import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.submission.SubmissionSettings
-import de.rki.coronawarnapp.submission.ui.homecards.NoTest
-import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
-import io.kotest.matchers.shouldBe
-import io.mockk.MockKAnnotations
-import io.mockk.every
-import io.mockk.impl.annotations.MockK
-import io.mockk.verifySequence
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.extension.ExtendWith
-import testhelpers.BaseTest
-import testhelpers.extensions.CoroutinesTestExtension
-import testhelpers.extensions.InstantExecutorExtension
-import testhelpers.preferences.mockFlowPreference
-import java.util.Date
-
-@ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class)
-class SubmissionStateProviderTest : BaseTest() {
-
-    @MockK lateinit var context: Context
-    @MockK lateinit var submissionRepository: SubmissionRepository
-    @MockK lateinit var submissionSettings: SubmissionSettings
-
-    @BeforeEach
-    fun setup() {
-        MockKAnnotations.init(this)
-
-        every { submissionRepository.hasViewedTestResult } returns flow { emit(true) }
-        every { submissionRepository.deviceUIStateFlow } returns flow {
-            emit(NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>(DeviceUIState.PAIRED_POSITIVE))
-        }
-        every { submissionRepository.testResultReceivedDateFlow } returns flow { emit(Date()) }
-        every { submissionSettings.registrationToken } returns mockFlowPreference(null)
-    }
-
-    private fun createInstance() = SubmissionStateProvider(
-        submissionRepository = submissionRepository,
-        submissionSettings = submissionSettings
-    )
-
-    @Test
-    fun `state determination, unregistered test`() = runBlockingTest {
-        createInstance().apply {
-            state.first() shouldBe NoTest
-
-            verifySequence {
-                submissionRepository.deviceUIStateFlow
-                submissionRepository.hasViewedTestResult
-                submissionRepository.testResultReceivedDateFlow
-                submissionSettings.registrationToken
-            }
-        }
-    }
-}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentViewModelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0012a6ae7af09003d77c24227dd99432abb9d0ac
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/consent/CheckInsConsentViewModelTest.kt
@@ -0,0 +1,345 @@
+package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.consent
+
+import androidx.lifecycle.SavedStateHandle
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
+import de.rki.coronawarnapp.submission.SubmissionRepository
+import de.rki.coronawarnapp.submission.auto.AutoSubmission
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.mockk
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import okio.ByteString.Companion.decodeBase64
+import okio.ByteString.Companion.encode
+import org.joda.time.Instant
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.BaseTest
+import testhelpers.TestDispatcherProvider
+import testhelpers.extensions.InstantExecutorExtension
+import testhelpers.extensions.getOrAwaitValue
+
+@ExtendWith(InstantExecutorExtension::class)
+class CheckInsConsentViewModelTest : BaseTest() {
+
+    @MockK lateinit var savedState: SavedStateHandle
+    @MockK lateinit var checkInRepository: CheckInRepository
+    @MockK lateinit var submissionRepository: SubmissionRepository
+    @MockK lateinit var autoSubmission: AutoSubmission
+
+    private val checkIn1 = CheckIn(
+        id = 1L,
+        traceLocationId = "41da2115-eba2-49bd-bf17-adb3d635ddaf".decodeBase64()!!,
+        version = 1,
+        type = 2,
+        description = "brothers birthday",
+        address = "Malibu",
+        traceLocationStart = Instant.EPOCH,
+        traceLocationEnd = null,
+        defaultCheckInLengthInMinutes = null,
+        cryptographicSeed = "cryptographicSeed".encode(),
+        cnPublicKey = "cnPublicKey",
+        checkInStart = Instant.EPOCH,
+        checkInEnd = Instant.EPOCH,
+        completed = true,
+        createJournalEntry = false
+    )
+
+    private val checkIn2 = CheckIn(
+        id = 2L,
+        traceLocationId = "41da2115-eba2-49bd-bf17-adb3d635ddaf".decodeBase64()!!,
+        version = 1,
+        type = 2,
+        description = "brothers birthday",
+        address = "Malibu",
+        traceLocationStart = Instant.EPOCH,
+        traceLocationEnd = null,
+        defaultCheckInLengthInMinutes = null,
+        cryptographicSeed = "cryptographicSeed".encode(),
+        cnPublicKey = "cnPublicKey",
+        checkInStart = Instant.EPOCH,
+        checkInEnd = Instant.EPOCH,
+        completed = true,
+        createJournalEntry = false
+    )
+
+    private val checkIn3 = CheckIn(
+        id = 3L,
+        traceLocationId = "41da2115-eba2-49bd-bf17-adb3d635ddaf".decodeBase64()!!,
+        version = 1,
+        type = 2,
+        description = "brothers birthday",
+        address = "Malibu",
+        traceLocationStart = Instant.EPOCH,
+        traceLocationEnd = null,
+        defaultCheckInLengthInMinutes = null,
+        cryptographicSeed = "cryptographicSeed".encode(),
+        cnPublicKey = "cnPublicKey",
+        checkInStart = Instant.EPOCH,
+        checkInEnd = Instant.EPOCH,
+        completed = false,
+        createJournalEntry = false
+    )
+
+    private val coronaTestFlow = MutableStateFlow(
+        mockk<CoronaTest>().apply { every { isViewed } returns false }
+    )
+
+    @BeforeEach
+    fun setUp() {
+        MockKAnnotations.init(this)
+
+        every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(checkIn1, checkIn2, checkIn3))
+        coEvery { checkInRepository.updateSubmissionConsents(any(), true) } just Runs
+        coEvery { checkInRepository.updateSubmissionConsents(any(), false) } just Runs
+        every { savedState.set(any(), any<Set<Long>>()) } just Runs
+        every { autoSubmission.updateMode(any()) } just Runs
+        every { submissionRepository.testForType(any()) } returns coronaTestFlow
+        every { savedState.get<Set<Long>>(any()) } returns emptySet()
+    }
+
+    @Test
+    fun `Nothing is selected initially`() {
+        every { savedState.get<Set<Long>>(any()) } returns emptySet()
+
+        val viewModel = createViewModel()
+        viewModel.checkIns.getOrAwaitValue().apply {
+            size shouldBe 3
+            get(0).apply {
+                this is HeaderCheckInsVH.Item
+            }
+
+            get(1).apply {
+                this as SelectableCheckInVH.Item
+                this.checkIn.hasSubmissionConsent shouldBe false
+            }
+
+            get(2).apply {
+                this as SelectableCheckInVH.Item
+                this.checkIn.hasSubmissionConsent shouldBe false
+            }
+        }
+    }
+
+    @Test
+    fun `Saved state is restored`() {
+        every { savedState.get<Set<Long>>(any()) } returns setOf(1L)
+        val viewModel = createViewModel()
+        viewModel.checkIns.getOrAwaitValue().apply {
+            size shouldBe 3
+            get(0).apply {
+                this is HeaderCheckInsVH.Item
+            }
+
+            get(1).apply {
+                this as SelectableCheckInVH.Item
+                this.checkIn.hasSubmissionConsent shouldBe true
+            }
+
+            get(2).apply {
+                this as SelectableCheckInVH.Item
+                this.checkIn.hasSubmissionConsent shouldBe false
+            }
+        }
+    }
+
+    @Test
+    fun `Select all`() {
+        every { savedState.get<Set<Long>>(any()) } returns emptySet()
+        val viewModel = createViewModel()
+        viewModel.checkIns.getOrAwaitValue().apply {
+            size shouldBe 3
+            get(0).apply {
+                this as HeaderCheckInsVH.Item
+                (get(1) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe false
+                (get(2) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe false
+
+                this.selectAll()
+
+                viewModel.checkIns.getOrAwaitValue().apply {
+                    (get(1) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe true
+                    (get(2) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe true
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `Select all does not un-select all`() {
+        every { savedState.get<Set<Long>>(any()) } returns emptySet()
+        val viewModel = createViewModel()
+        viewModel.checkIns.getOrAwaitValue().apply {
+            size shouldBe 3
+            get(0).apply {
+                this as HeaderCheckInsVH.Item
+                (get(1) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe false
+                (get(2) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe false
+
+                this.selectAll()
+
+                viewModel.checkIns.getOrAwaitValue().apply {
+                    (get(1) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe true
+                    (get(2) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe true
+                }
+
+                this.selectAll()
+
+                viewModel.checkIns.getOrAwaitValue().apply {
+                    (get(1) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe true
+                    (get(2) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe true
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `Single selection`() {
+        every { savedState.get<Set<Long>>(any()) } returns emptySet()
+        val viewModel = createViewModel()
+        viewModel.checkIns.getOrAwaitValue().apply {
+
+            (get(1) as SelectableCheckInVH.Item).apply {
+                checkIn.hasSubmissionConsent shouldBe false
+                onItemSelected(checkIn)
+            }
+
+            viewModel.checkIns.getOrAwaitValue().apply {
+                (get(1) as SelectableCheckInVH.Item).checkIn.hasSubmissionConsent shouldBe true
+            }
+        }
+    }
+
+    @Test
+    fun `Single deselection`() {
+        every { savedState.get<Set<Long>>(any()) } returns emptySet()
+        val viewModel = createViewModel()
+        viewModel.checkIns.getOrAwaitValue().apply {
+            (get(1) as SelectableCheckInVH.Item).apply {
+                checkIn.hasSubmissionConsent shouldBe false
+                onItemSelected(checkIn)
+            }
+
+            viewModel.checkIns.getOrAwaitValue().apply {
+                (get(1) as SelectableCheckInVH.Item).apply {
+                    checkIn.hasSubmissionConsent shouldBe true
+                    onItemSelected(checkIn)
+                }
+            }
+
+            viewModel.checkIns.getOrAwaitValue().apply {
+                (get(1) as SelectableCheckInVH.Item).apply {
+                    checkIn.hasSubmissionConsent shouldBe false
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `Confirming cancel goes to home screen`() {
+        createViewModel().apply {
+            onCancelConfirmed()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.ToHomeFragment
+        }
+    }
+
+    @Test
+    fun `Skip opens skipDialog`() {
+        createViewModel().apply {
+            onSkipClick()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.OpenSkipDialog
+        }
+    }
+
+    @Test
+    fun `Close opens skipDialog when test result has been shown`() {
+        coronaTestFlow.value = mockk<CoronaTest>().apply { every { isViewed } returns true }
+        createViewModel().apply {
+            onCloseClick()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.OpenSkipDialog
+        }
+    }
+
+    @Test
+    fun `Close opens closeDialog when test result has not been shown`() {
+        coronaTestFlow.value = mockk<CoronaTest>().apply { every { isViewed } returns false }
+        createViewModel().apply {
+            onCloseClick()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.OpenCloseDialog
+        }
+    }
+
+    @Test
+    fun `shareSelectedCheckIns when test result has been shown`() {
+        coronaTestFlow.value = mockk<CoronaTest>().apply { every { isViewed } returns true }
+        createViewModel().apply {
+            shareSelectedCheckIns()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.ToSubmissionResultReadyFragment
+        }
+
+        coVerify {
+            checkInRepository.updateSubmissionConsents(any(), false)
+            autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+            checkInRepository.updateSubmissionConsents(any(), true)
+        }
+    }
+
+    @Test
+    fun `shareSelectedCheckIns when test result has not been shown`() {
+        coronaTestFlow.value = mockk<CoronaTest>().apply { every { isViewed } returns false }
+        createViewModel().apply {
+            shareSelectedCheckIns()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.ToSubmissionTestResultConsentGivenFragment
+        }
+
+        coVerify {
+            checkInRepository.updateSubmissionConsents(any(), false)
+            autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+            checkInRepository.updateSubmissionConsents(any(), true)
+        }
+    }
+
+    @Test
+    fun `doNotShareCheckIns when test result has been shown`() {
+        coronaTestFlow.value = mockk<CoronaTest>().apply { every { isViewed } returns true }
+        createViewModel().apply {
+            doNotShareCheckIns()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.ToSubmissionResultReadyFragment
+        }
+
+        coVerify {
+            checkInRepository.updateSubmissionConsents(any(), false)
+            autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+        }
+    }
+
+    @Test
+    fun `doNotShareCheckIns when test result has not been shown`() {
+        coronaTestFlow.value = mockk<CoronaTest>().apply { every { isViewed } returns false }
+        createViewModel().apply {
+            doNotShareCheckIns()
+            events.getOrAwaitValue() shouldBe CheckInsConsentNavigation.ToSubmissionTestResultConsentGivenFragment
+        }
+
+        coVerify {
+            checkInRepository.updateSubmissionConsents(any(), false)
+            autoSubmission.updateMode(AutoSubmission.Mode.MONITOR)
+        }
+    }
+
+    private fun createViewModel() = CheckInsConsentViewModel(
+        savedState = savedState,
+        dispatcherProvider = TestDispatcherProvider(),
+        checkInRepository = checkInRepository,
+        submissionRepository = submissionRepository,
+        autoSubmission = autoSubmission
+    )
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt
index 5e1f894d73b3e3296a5664e021376872cef265b7..8a3ef38c00f8fc402969356ee5b861921c3d73bc 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt
@@ -16,7 +16,6 @@ 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.flow.MutableStateFlow
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
@@ -41,7 +40,7 @@ class SubmissionConsentViewModelTest {
     fun setUp() {
         MockKAnnotations.init(this)
         every { interoperabilityRepository.countryList } returns MutableStateFlow(countryList)
-        every { submissionRepository.giveConsentToSubmission() } just Runs
+        every { submissionRepository.giveConsentToSubmission(any()) } just Runs
         every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven() } just Runs
         viewModel = SubmissionConsentViewModel(
             submissionRepository,
@@ -55,7 +54,8 @@ class SubmissionConsentViewModelTest {
     @Test
     fun testOnConsentButtonClick() {
         viewModel.onConsentButtonClick()
-        verify(exactly = 1) { submissionRepository.giveConsentToSubmission() }
+        // TODO doesn't happen here anymore, we don't have a CoronaTest instance to store it with, see QR Code VM
+//        verify(exactly = 1) { submissionRepository.giveConsentToSubmission(any()) }
     }
 
     @Test
@@ -92,7 +92,8 @@ class SubmissionConsentViewModelTest {
     @Test
     fun `onConsentButtonClick sets normal consent and request new Google consent Api`() {
         viewModel.onConsentButtonClick()
-        verify(exactly = 1) { submissionRepository.giveConsentToSubmission() }
+        // TODO doesn't happen here anymore, we don't have a CoronaTest instance to store it with, see QR Code VM
+//        verify(exactly = 1) { submissionRepository.giveConsentToSubmission(any()) }
         coVerify(exactly = 1) { tekHistoryProvider.preAuthorizeExposureKeyHistory() }
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
index a20bc57af4ef729bcd28897e8c3c6c18bedb6cf9..02ed331db2b6e0bea748693db91616d7b058694b 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
@@ -1,10 +1,12 @@
 package de.rki.coronawarnapp.ui.submission.qrcode.scan
 
 import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor
-import de.rki.coronawarnapp.service.submission.QRScanResult
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator
+import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.ScanStatus
-import de.rki.coronawarnapp.util.formatter.TestResult
 import de.rki.coronawarnapp.util.permission.CameraSettings
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
@@ -26,6 +28,7 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
 
     @MockK lateinit var submissionRepository: SubmissionRepository
     @MockK lateinit var cameraSettings: CameraSettings
+    @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator
 
     @BeforeEach
     fun setUp() {
@@ -34,11 +37,23 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
 
     private fun createViewModel() = SubmissionQRCodeScanViewModel(
         submissionRepository,
-        cameraSettings
+        cameraSettings,
+        qrCodeValidator
     )
 
     @Test
     fun scanStatusValid() {
+        // valid guid
+        val guid = "123456-12345678-1234-4DA7-B166-B86D85475064"
+        val coronaTestQRCode = CoronaTestQRCode.PCR(
+            qrCodeGUID = guid
+        )
+
+        val validQrCode = "https://localhost/?$guid"
+        val invalidQrCode = "https://no-guid-here"
+
+        every { qrCodeValidator.validate(validQrCode) } returns coronaTestQRCode
+        every { qrCodeValidator.validate(invalidQrCode) } throws InvalidQRCodeException()
         val viewModel = createViewModel()
 
         // start
@@ -48,25 +63,23 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
 
         QRCodeCensor.lastGUID = null
 
-        // valid guid
-        val guid = "123456-12345678-1234-4DA7-B166-B86D85475064"
-        viewModel.validateTestGUID("https://localhost/?$guid")
+        viewModel.validateTestGUID(validQrCode)
         viewModel.scanStatusValue.let { Assert.assertEquals(ScanStatus.SUCCESS, it.value) }
         QRCodeCensor.lastGUID = guid
 
         // invalid guid
-        viewModel.validateTestGUID("https://no-guid-here")
+        viewModel.validateTestGUID(invalidQrCode)
         viewModel.scanStatusValue.let { Assert.assertEquals(ScanStatus.INVALID, it.value) }
     }
 
     @Test
     fun `doDeviceRegistration calls TestResultDataCollector`() {
         val viewModel = createViewModel()
-        val mockResult = mockk<QRScanResult>().apply {
-            every { guid } returns "guid"
+        val mockResult = mockk<CoronaTestQRCode>().apply {
+            every { registrationIdentifier } returns "guid"
         }
-
-        coEvery { submissionRepository.asyncRegisterDeviceViaGUID(any()) } returns TestResult.POSITIVE
+        val mockTest = mockk<CoronaTest>()
+        coEvery { submissionRepository.registerTest(any()) } returns mockTest
         viewModel.doDeviceRegistration(mockResult)
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt
index f06833355235b74b2ecc8746f09cd475bf9fa929..d9c1e6ea258382161a12a535ae48bb3b39a78e79 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt
@@ -1,6 +1,9 @@
 package de.rki.coronawarnapp.ui.submission.testavailable
 
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.auto.AutoSubmission
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater
@@ -12,9 +15,9 @@ import io.mockk.Runs
 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.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
@@ -31,17 +34,27 @@ class SubmissionTestResultAvailableViewModelTest : BaseTest() {
     @MockK lateinit var tekHistoryUpdater: TEKHistoryUpdater
     @MockK lateinit var tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory
     @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
+    @MockK lateinit var checkInRepository: CheckInRepository
+
+    private val coronaTestFlow = MutableStateFlow(
+        mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns true
+        }
+    )
 
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this)
-        every { submissionRepository.hasGivenConsentToSubmission } returns flowOf(true)
 
         every { tekHistoryUpdaterFactory.create(any()) } returns tekHistoryUpdater
         every { tekHistoryUpdater.updateTEKHistoryOrRequestPermission() } just Runs
 
         // TODO Check specific behavior
-        every { submissionRepository.refreshDeviceUIState(any()) } just Runs
+        submissionRepository.apply {
+            every { refreshTest(any()) } just Runs
+            every { testForType(type = any()) } returns coronaTestFlow
+        }
     }
 
     private fun createViewModel(): SubmissionTestResultAvailableViewModel = SubmissionTestResultAvailableViewModel(
@@ -49,20 +62,24 @@ class SubmissionTestResultAvailableViewModelTest : BaseTest() {
         dispatcherProvider = TestDispatcherProvider(),
         tekHistoryUpdaterFactory = tekHistoryUpdaterFactory,
         autoSubmission = autoSubmission,
-        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector
+        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector,
+        checkInRepository = checkInRepository
     )
 
     @Test
     fun `consent repository changed`() {
-        val consentMutable = MutableStateFlow(false)
-        every { submissionRepository.hasGivenConsentToSubmission } returns consentMutable
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns false
+        }
 
         val viewModel = createViewModel()
 
         viewModel.consent.observeForever { }
         viewModel.consent.value shouldBe false
 
-        consentMutable.value = true
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns true
+        }
         viewModel.consent.value shouldBe true
     }
 
@@ -96,8 +113,11 @@ class SubmissionTestResultAvailableViewModelTest : BaseTest() {
 
     @Test
     fun `go to test result without updating TEK history if NO consent is given`() {
-        every { submissionRepository.hasGivenConsentToSubmission } returns flowOf(false)
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns false
+        }
         every { analyticsKeySubmissionCollector.reportConsentWithdrawn() } just Runs
+
         val viewModel = createViewModel()
 
         viewModel.proceed()
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModelTest.kt
index 6b3e2fdc42a2b7829f254c762b2bf1e682637b0d..40080f1f514b7f5af1865eb5243bc9c837b0b365 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModelTest.kt
@@ -1,7 +1,9 @@
 package de.rki.coronawarnapp.ui.submission.warnothers
 
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.auto.AutoSubmission
@@ -11,6 +13,7 @@ import io.mockk.Runs
 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.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
@@ -33,6 +36,13 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModelTest : BaseTest() {
     @MockK lateinit var interoperabilityRepository: InteroperabilityRepository
     @MockK lateinit var enfClient: ENFClient
     @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
+    @MockK lateinit var checkInRepository: CheckInRepository
+
+    private val coronaTestFlow = MutableStateFlow(
+        mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns true
+        }
+    )
 
     @BeforeEach
     fun setUp() {
@@ -41,7 +51,11 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModelTest : BaseTest() {
         every { tekHistoryUpdater.updateTEKHistoryOrRequestPermission() } just Runs
 
         every { interoperabilityRepository.countryList } returns emptyFlow()
-        every { submissionRepository.giveConsentToSubmission() } just Runs
+
+        submissionRepository.apply {
+            every { giveConsentToSubmission(any()) } just Runs
+            every { testForType(any()) } returns coronaTestFlow
+        }
 
         every { enfClient.isTracingEnabled } returns flowOf(true)
     }
@@ -53,19 +67,21 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModelTest : BaseTest() {
         enfClient = enfClient,
         interoperabilityRepository = interoperabilityRepository,
         submissionRepository = submissionRepository,
-        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector
+        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector,
+        checkInRepository = checkInRepository
     )
 
     @Test
     fun `consent is stored and tek history updated`() {
-        val consentMutable = MutableStateFlow(false)
-        every { submissionRepository.hasGivenConsentToSubmission } returns consentMutable
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns false
+        }
 
         val viewModel = createViewModel()
 
         viewModel.onConsentButtonClicked()
 
-        verify { submissionRepository.giveConsentToSubmission() }
+        verify { submissionRepository.giveConsentToSubmission(any()) }
         verify { tekHistoryUpdater.updateTEKHistoryOrRequestPermission() }
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt
index 30797a6da0077966f6444c60504b416bb256dabc..e11349903be08c2f7d039efc891d1b868519aa7f 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt
@@ -1,18 +1,18 @@
 package de.rki.coronawarnapp.ui.submission.yourconsent
 
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.ui.Country
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
-import io.mockk.coEvery
 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.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
@@ -29,13 +29,19 @@ class SubmissionYourConsentViewModelTest : BaseTest() {
 
     private val countryList = Country.values().toList()
 
+    private val coronaTestFlow = MutableStateFlow(
+        mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns true
+        }
+    )
+
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this)
+        every { submissionRepository.testForType(any()) } returns coronaTestFlow
         every { interoperabilityRepository.countryList } returns MutableStateFlow(countryList)
-        every { submissionRepository.hasGivenConsentToSubmission } returns flowOf(true)
-        every { submissionRepository.giveConsentToSubmission() } just Runs
-        every { submissionRepository.revokeConsentToSubmission() } just Runs
+        every { submissionRepository.giveConsentToSubmission(any()) } just Runs
+        every { submissionRepository.revokeConsentToSubmission(any()) } just Runs
     }
 
     private fun createViewModel(): SubmissionYourConsentViewModel = SubmissionYourConsentViewModel(
@@ -62,33 +68,38 @@ class SubmissionYourConsentViewModelTest : BaseTest() {
 
     @Test
     fun `consent removed`() {
-        val viewModel = createViewModel()
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns true
+        }
 
-        coEvery { submissionRepository.hasGivenConsentToSubmission } returns flowOf(true)
-        viewModel.switchConsent()
-        verify(exactly = 1) { submissionRepository.revokeConsentToSubmission() }
+        createViewModel().switchConsent()
+        verify(exactly = 1) { submissionRepository.revokeConsentToSubmission(any()) }
     }
 
     @Test
     fun `consent given`() {
-        val viewModel = createViewModel()
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns false
+        }
 
-        coEvery { submissionRepository.hasGivenConsentToSubmission } returns flowOf(false)
-        viewModel.switchConsent()
-        verify(exactly = 1) { submissionRepository.giveConsentToSubmission() }
+        createViewModel().switchConsent()
+        verify(exactly = 1) { submissionRepository.giveConsentToSubmission(any()) }
     }
 
     @Test
     fun `consent repository changed`() {
-        val consentMutable = MutableStateFlow(false)
-        every { submissionRepository.hasGivenConsentToSubmission } returns consentMutable
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns false
+        }
 
         val viewModel = createViewModel()
 
         viewModel.consent.observeForever { }
         viewModel.consent.value shouldBe false
 
-        consentMutable.value = true
+        coronaTestFlow.value = mockk<CoronaTest>().apply {
+            every { isAdvancedConsentGiven } returns true
+        }
         viewModel.consent.value shouldBe true
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt
index 38c405c76885fa2c6c6c7927832ef032bcd8269b..b78a603a9f5ecca262d3ec469086c2a4848f998a 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt
@@ -5,6 +5,7 @@ import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.bugreporting.BugReportingSettings
 import de.rki.coronawarnapp.contactdiary.storage.ContactDiaryPreferences
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.datadonation.analytics.Analytics
 import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
 import de.rki.coronawarnapp.datadonation.survey.SurveySettings
@@ -56,6 +57,7 @@ internal class DataResetTest : BaseTest() {
     @MockK lateinit var traceWarningRepository: TraceWarningRepository
     @MockK lateinit var checkInRepository: CheckInRepository
     @MockK lateinit var traceLocationSettings: TraceLocationSettings
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
 
     @BeforeEach
     fun setUp() {
@@ -85,7 +87,8 @@ internal class DataResetTest : BaseTest() {
         traceLocationRepository = traceLocationRepository,
         checkInRepository = checkInRepository,
         traceLocationSettings = traceLocationSettings,
-        traceWarningRepository = traceWarningRepository
+        traceWarningRepository = traceWarningRepository,
+        coronaTestRepository = coronaTestRepository
     )
 
     @Test
@@ -118,5 +121,6 @@ internal class DataResetTest : BaseTest() {
         coVerify(exactly = 1) { traceWarningRepository.clear() }
         coVerify(exactly = 1) { traceLocationRepository.deleteAllTraceLocations() }
         coVerify(exactly = 1) { checkInRepository.clear() }
+        coVerify(exactly = 1) { coronaTestRepository.clear() }
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt
index 4c7411bca6a60422e7bdd71bb58d47e0c792f4e1..876272834a33adb138e10e30d164f15c5dc3614b 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt
@@ -2,7 +2,6 @@ package de.rki.coronawarnapp.util
 
 import de.rki.coronawarnapp.CoronaWarnApplication
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.ageInDays
-import de.rki.coronawarnapp.util.TimeAndDateExtensions.calculateDays
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.derive10MinutesInterval
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.deriveHourInterval
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.getCurrentHourUTC
@@ -20,7 +19,6 @@ import org.joda.time.LocalDate
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import testhelpers.BaseTest
-import java.util.concurrent.TimeUnit
 
 /**
  * TimeAndDateExtensions test.
@@ -40,15 +38,6 @@ class TimeAndDateExtensionsTest : BaseTest() {
         MatcherAssert.assertThat(result, CoreMatchers.`is`(DateTime(Instant.now(), DateTimeZone.UTC).hourOfDay().get()))
     }
 
-    @Test
-    fun calculateDaysTest() {
-        val lFirstDate = DateTime(2019, 1, 1, 1, 1).millis
-        val lSecondDate = DateTime(2020, 1, 1, 1, 1).millis
-
-        val result = calculateDays(firstDate = lFirstDate, secondDate = lSecondDate)
-        MatcherAssert.assertThat(result, CoreMatchers.`is`(TimeUnit.MILLISECONDS.toDays(lSecondDate - lFirstDate)))
-    }
-
     @Test
     fun test_daysAgo() {
         LocalDate(2012, 3, 4).ageInDays(
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt
index bc18a0cf95c56e371fc70394f66f767ce40f307d..f3585f5d8fe1a1cab0d9a359b7d6b780daf782dc 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt
@@ -15,6 +15,7 @@ import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.just
 import io.mockk.mockk
+import io.mockk.verify
 import org.joda.time.Instant
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
@@ -24,6 +25,7 @@ import testhelpers.preferences.MockSharedPreferences
 import testhelpers.preferences.mockFlowPreference
 import java.io.File
 
+@Suppress("DEPRECATION")
 class EncryptedPreferencesMigrationTest : BaseIOTest() {
     @MockK lateinit var context: Context
     @MockK lateinit var encryptedPreferencesHelper: EncryptedPreferencesHelper
@@ -111,19 +113,18 @@ class EncryptedPreferencesMigrationTest : BaseIOTest() {
         every { onboardingSettings.isBackgroundCheckDone = true } just Runs
 
         // TracingLocalData
-        every { tracingSettings.initialPollingForTestResultTimeStamp = 10101010L } just Runs
-        every { tracingSettings.isTestResultAvailableNotificationSent = true } just Runs
+        every { tracingSettings.initialPollingForTestResultTimeStampMigration = 10101010L } just Runs
+        every { tracingSettings.isTestResultAvailableNotificationSentMigration = true } just Runs
         val mockNotificationPreference = mockFlowPreference(false)
         every { tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel } returns mockNotificationPreference
         every { tracingSettings.isConsentGiven = true } just Runs
 
         // SubmissionLocalData
-        val mockRegtokenPreference = mockFlowPreference<String?>(null)
-        every { submissionSettings.registrationToken } returns mockRegtokenPreference
-        every { submissionSettings.initialTestResultReceivedAt = Instant.ofEpochMilli(10101010L) } just Runs
-        every { submissionSettings.devicePairingSuccessfulAt = Instant.ofEpochMilli(10101010L) } just Runs
-        every { submissionSettings.isSubmissionSuccessful = true } just Runs
-        every { submissionSettings.isAllowedToSubmitKeys = true } just Runs
+        every { submissionSettings.registrationTokenMigration = any() } just Runs
+        every { submissionSettings.initialTestResultReceivedAtMigration = Instant.ofEpochMilli(10101010L) } just Runs
+        every { submissionSettings.devicePairingSuccessfulAtMigration = Instant.ofEpochMilli(10101010L) } just Runs
+        every { submissionSettings.isSubmissionSuccessfulMigration = true } just Runs
+        every { submissionSettings.isAllowedToSubmitKeysMigration = true } just Runs
 
         val migrationInstance = createInstance()
 
@@ -137,7 +138,7 @@ class EncryptedPreferencesMigrationTest : BaseIOTest() {
         mockNotificationPreference.value shouldBe true
 
         // SubmissionLocalData
-        mockRegtokenPreference.value shouldBe "super_secret_token"
+        verify { submissionSettings.registrationTokenMigration = "super_secret_token" }
     }
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt
index ec822d947365799b5a2a69c65924eb56d926e457..54d8d584e583125d432175bc799c7845a1a6e17f 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt
@@ -5,14 +5,12 @@ import android.graphics.drawable.Drawable
 import android.text.Spannable
 import android.text.SpannableStringBuilder
 import android.text.style.ForegroundColorSpan
-import android.view.View
 import de.rki.coronawarnapp.CoronaWarnApplication
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.util.ContextExtensions
 import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat
 import de.rki.coronawarnapp.util.ContextExtensions.getDrawableCompat
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
@@ -78,7 +76,7 @@ class FormatterSubmissionHelperTest : BaseTest() {
     }
 
     private fun formatTestResultStatusTextBase(
-        oUiState: NetworkRequestWrapper<DeviceUIState, Throwable>?,
+        oUiState: DeviceUIState,
         iResult: String
     ) {
         val result = formatTestResultStatusText(context = context, uiState = oUiState)
@@ -86,35 +84,19 @@ class FormatterSubmissionHelperTest : BaseTest() {
     }
 
     private fun formatTestResultStatusColorBase(
-        oUiState: NetworkRequestWrapper<DeviceUIState, Throwable>?,
+        oUiState: DeviceUIState,
         iResult: Int
     ) {
         val result = formatTestResultStatusColor(context = context, uiState = oUiState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestStatusIconBase(oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?) {
+    private fun formatTestStatusIconBase(oUiState: DeviceUIState) {
         val result = formatTestStatusIcon(context = context, uiState = oUiState)
         assertThat(result, `is`(drawable))
     }
 
-    private fun formatTestResultPendingStepsVisibleBase(
-        oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?,
-        iResult: Int
-    ) {
-        val result = formatTestResultPendingStepsVisible(uiState = oUiState)
-        assertThat(result, `is`(iResult))
-    }
-
-    private fun formatTestResultInvalidStepsVisibleBase(
-        oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?,
-        iResult: Int
-    ) {
-        val result = formatTestResultInvalidStepsVisible(uiState = oUiState)
-        assertThat(result, `is`(iResult))
-    }
-
-    private fun formatTestResultBase(oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?) {
+    private fun formatTestResultBase(oUiState: DeviceUIState) {
         mockkConstructor(SpannableStringBuilder::class)
 
         val spannableStringBuilder1 =
@@ -141,39 +123,35 @@ class FormatterSubmissionHelperTest : BaseTest() {
     @Test
     fun formatTestResultStatusText() {
         formatTestResultStatusTextBase(
-            oUiState = null,
-            iResult = context.getString(R.string.test_result_card_status_invalid)
-        )
-        formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
+            oUiState = DeviceUIState.PAIRED_NEGATIVE,
             iResult = context.getString(R.string.test_result_card_status_negative)
         )
         formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
+            oUiState = DeviceUIState.PAIRED_ERROR,
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
+            oUiState = DeviceUIState.PAIRED_NO_RESULT,
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
+            oUiState = DeviceUIState.PAIRED_POSITIVE,
             iResult = context.getString(R.string.test_result_card_status_positive)
         )
         formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
+            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
             iResult = context.getString(R.string.test_result_card_status_positive)
         )
         formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
+            oUiState = DeviceUIState.SUBMITTED_FINAL,
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
+            oUiState = DeviceUIState.SUBMITTED_INITIAL,
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
+            oUiState = DeviceUIState.UNPAIRED,
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
     }
@@ -181,140 +159,60 @@ class FormatterSubmissionHelperTest : BaseTest() {
     @Test
     fun formatTestResultStatusColor() {
         formatTestResultStatusColorBase(
-            oUiState = null,
-            iResult = context.getColorCompat(R.color.colorTextSemanticRed)
-        )
-        formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
+            oUiState = DeviceUIState.PAIRED_NEGATIVE,
             iResult = context.getColorCompat(R.color.colorTextSemanticGreen)
         )
         formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
+            oUiState = DeviceUIState.PAIRED_ERROR,
             iResult = context.getColorCompat(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
+            oUiState = DeviceUIState.PAIRED_NO_RESULT,
             iResult = context.getColorCompat(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
+            oUiState = DeviceUIState.PAIRED_POSITIVE,
             iResult = context.getColorCompat(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
+            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
             iResult = context.getColorCompat(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
+            oUiState = DeviceUIState.SUBMITTED_FINAL,
             iResult = context.getColorCompat(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
+            oUiState = DeviceUIState.SUBMITTED_INITIAL,
             iResult = context.getColorCompat(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
+            oUiState = DeviceUIState.UNPAIRED,
             iResult = context.getColorCompat(R.color.colorTextSemanticRed)
         )
     }
 
     @Test
     fun formatTestStatusIcon() {
-        formatTestStatusIconBase(oUiState = null)
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE))
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR))
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT))
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE))
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN))
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL))
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL))
-        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED))
-    }
-
-    @Test
-    fun formatTestResultPendingStepsVisible() {
-        formatTestResultPendingStepsVisibleBase(oUiState = null, iResult = View.GONE)
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
-            iResult = View.GONE
-        )
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
-            iResult = View.GONE
-        )
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
-            iResult = View.VISIBLE
-        )
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
-            iResult = View.GONE
-        )
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
-            iResult = View.GONE
-        )
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
-            iResult = View.GONE
-        )
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
-            iResult = View.GONE
-        )
-        formatTestResultPendingStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
-            iResult = View.GONE
-        )
-    }
-
-    @Test
-    fun formatTestResultInvalidStepsVisible() {
-        formatTestResultInvalidStepsVisibleBase(oUiState = null, iResult = View.GONE)
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
-            iResult = View.GONE
-        )
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
-            iResult = View.VISIBLE
-        )
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
-            iResult = View.GONE
-        )
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
-            iResult = View.GONE
-        )
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
-            iResult = View.GONE
-        )
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
-            iResult = View.GONE
-        )
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
-            iResult = View.GONE
-        )
-        formatTestResultInvalidStepsVisibleBase(
-            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
-            iResult = View.GONE
-        )
+        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_NEGATIVE)
+        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_ERROR)
+        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_NO_RESULT)
+        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_POSITIVE)
+        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN)
+        formatTestStatusIconBase(oUiState = DeviceUIState.SUBMITTED_FINAL)
+        formatTestStatusIconBase(oUiState = DeviceUIState.SUBMITTED_INITIAL)
+        formatTestStatusIconBase(oUiState = DeviceUIState.UNPAIRED)
     }
 
     @Test
     fun formatTestResult() {
-        formatTestResultBase(oUiState = null)
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE))
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR))
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT))
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE))
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN))
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL))
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL))
-        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED))
+        formatTestResultBase(oUiState = DeviceUIState.PAIRED_NEGATIVE)
+        formatTestResultBase(oUiState = DeviceUIState.PAIRED_ERROR)
+        formatTestResultBase(oUiState = DeviceUIState.PAIRED_NO_RESULT)
+        formatTestResultBase(oUiState = DeviceUIState.PAIRED_POSITIVE)
+        formatTestResultBase(oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN)
+        formatTestResultBase(oUiState = DeviceUIState.SUBMITTED_FINAL)
+        formatTestResultBase(oUiState = DeviceUIState.SUBMITTED_INITIAL)
+        formatTestResultBase(oUiState = DeviceUIState.UNPAIRED)
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt
index 5f94538cd6fd726a3bc983a0dde5227b696bfe0e..da4f8b70e31ad2b531ee73da732b52d8b1d83dc8 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt
@@ -6,20 +6,24 @@ import com.google.gson.Gson
 import dagger.Component
 import dagger.Module
 import dagger.Provides
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler
 import de.rki.coronawarnapp.datadonation.analytics.Analytics
 import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler
 import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.deadman.DeadmanNotificationSender
-import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository
-import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpScheduler
+import de.rki.coronawarnapp.deniability.NoiseScheduler
 import de.rki.coronawarnapp.nearby.ENFClient
 import de.rki.coronawarnapp.notification.GeneralNotifications
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
 import de.rki.coronawarnapp.playbook.Playbook
 import de.rki.coronawarnapp.presencetracing.checkins.checkout.CheckOutNotification
 import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut
+import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository
+import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpScheduler
 import de.rki.coronawarnapp.risk.execution.RiskWorkScheduler
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
+import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.task.TaskController
 import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.serialization.BaseGson
@@ -147,4 +151,16 @@ class MockProvider {
 
     @Provides
     fun riskWorkScheduler(): RiskWorkScheduler = mockk()
+
+    @Provides
+    fun submissionRepository(): SubmissionRepository = mockk()
+
+    @Provides
+    fun coronaTestRepository(): CoronaTestRepository = mockk()
+
+    @Provides
+    fun noiseScheduler(): NoiseScheduler = mockk()
+
+    @Provides
+    fun testResultScheduler(): TestResultScheduler = mockk()
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundConstantsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundConstantsTest.kt
index a2247300dd4871e0a949b7bfaba310237c2862b3..899fc46235d9c8adbb16c326d5238e873c61211e 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundConstantsTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundConstantsTest.kt
@@ -10,7 +10,6 @@ class BackgroundConstantsTest {
         Assert.assertEquals(BackgroundConstants.MINUTES_IN_DAY, 1440)
         Assert.assertEquals(BackgroundConstants.DIAGNOSIS_TEST_RESULT_RETRIEVAL_TRIES_PER_DAY, 12)
         Assert.assertEquals(BackgroundConstants.KIND_DELAY, 1L)
-        Assert.assertEquals(BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY, 10L)
         Assert.assertEquals(BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD, 2)
         Assert.assertEquals(BackgroundConstants.POLLING_VALIDITY_MAX_DAYS, 21)
         Assert.assertEquals(BackgroundConstants.BACKOFF_INITIAL_DELAY, 8L)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt
index a59702bd5d771dbc90c8fe80cf09ec523e6f83bb..aa99fa078ddeaf064e3f154ba7e717bbdc8b2398 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt
@@ -4,19 +4,20 @@ import android.content.Context
 import androidx.work.ListenableWorker
 import androidx.work.WorkRequest
 import androidx.work.WorkerParameters
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
 import de.rki.coronawarnapp.notification.GeneralNotifications
 import de.rki.coronawarnapp.notification.NotificationConstants
 import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
-import de.rki.coronawarnapp.service.submission.SubmissionService
-import de.rki.coronawarnapp.storage.TracingSettings
-import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.daysToMilliseconds
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.util.di.ApplicationComponent
 import de.rki.coronawarnapp.util.encryptionmigration.EncryptedPreferencesFactory
 import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool
-import de.rki.coronawarnapp.util.formatter.TestResult
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
@@ -26,232 +27,230 @@ import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.impl.annotations.RelaxedMockK
 import io.mockk.just
+import io.mockk.mockk
 import io.mockk.mockkObject
-import io.mockk.slot
 import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.test.runBlockingTest
 import org.joda.time.Instant
 import org.junit.Before
 import org.junit.Test
 import testhelpers.BaseTest
-import testhelpers.preferences.mockFlowPreference
 
 class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
     @MockK lateinit var context: Context
     @MockK lateinit var request: WorkRequest
-    @MockK lateinit var submissionSettings: SubmissionSettings
-    @MockK lateinit var submissionService: SubmissionService
     @MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService
     @MockK lateinit var notificationHelper: GeneralNotifications
     @MockK lateinit var appComponent: ApplicationComponent
     @MockK lateinit var encryptedPreferencesFactory: EncryptedPreferencesFactory
     @MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool
     @MockK lateinit var timeStamper: TimeStamper
-    @MockK lateinit var tracingSettings: TracingSettings
-    @MockK lateinit var backgroundWorkScheduler: BackgroundWorkScheduler
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
+    @MockK lateinit var testResultScheduler: TestResultScheduler
 
     @RelaxedMockK lateinit var workerParams: WorkerParameters
     private val currentInstant = Instant.ofEpochSecond(1611764225)
-    private val registrationToken = "test token"
+    private val testToken = "test token"
+
+    private val coronaTestFlow = MutableStateFlow(emptySet<CoronaTest>())
 
     @Before
     fun setUp() {
         MockKAnnotations.init(this)
-        every { submissionSettings.hasViewedTestResult.value } returns false
         every { timeStamper.nowUTC } returns currentInstant
-        every { tracingSettings.initialPollingForTestResultTimeStamp } returns currentInstant.millis
-        every { tracingSettings.isTestResultAvailableNotificationSent } returns false
-        every { tracingSettings.initialPollingForTestResultTimeStamp = capture(slot()) } answers {}
-        every { tracingSettings.isTestResultAvailableNotificationSent = capture(slot()) } answers {}
 
         mockkObject(AppInjector)
         every { AppInjector.component } returns appComponent
         every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory
         every { appComponent.errorResetTool } returns encryptionErrorResetTool
 
-        every { submissionSettings.registrationToken } returns mockFlowPreference(registrationToken)
+        every { testResultScheduler.setPeriodicTestPolling(enabled = any()) } just Runs
+
+        every { notificationHelper.cancelCurrentNotification(any()) } just Runs
 
-        every { backgroundWorkScheduler.stopDiagnosisTestResultPeriodicWork() } just Runs
+        coronaTestRepository.apply {
+            every { coronaTests } answers { coronaTestFlow }
+            coEvery { refresh(any()) } coAnswers { coronaTestFlow.first() }
+            coEvery { updateResultNotification(identifier = any(), sent = any()) } just Runs
+        }
     }
 
-    @Test
-    fun testStopWorkerWhenResultHasBeenViewed() {
-        runBlockingTest {
-            every { submissionSettings.hasViewedTestResult.value } returns true
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) }
-            verify(exactly = 1) { backgroundWorkScheduler.stopDiagnosisTestResultPeriodicWork() }
-            result shouldBe ListenableWorker.Result.success()
+    private fun newCoronaTest(
+        registered: Instant = currentInstant,
+        viewed: Boolean = false,
+        result: CoronaTestResult = CoronaTestResult.PCR_POSITIVE,
+        isNotificationSent: Boolean = false,
+    ): CoronaTest {
+        return mockk<PCRCoronaTest>().apply {
+            every { identifier } returns ""
+            every { type } returns CoronaTest.Type.PCR
+            every { registeredAt } returns registered
+            every { isViewed } returns viewed
+            every { testResult } returns result
+            every { registrationToken } returns testToken
+            every { isResultAvailableNotificationSent } returns isNotificationSent
         }
     }
 
+    private fun createWorker() = DiagnosisTestResultRetrievalPeriodicWorker(
+        context = context,
+        workerParams = workerParams,
+        testResultAvailableNotificationService = testResultAvailableNotificationService,
+        notificationHelper = notificationHelper,
+        coronaTestRepository = coronaTestRepository,
+        testResultScheduler = testResultScheduler,
+        timeStamper = timeStamper,
+    )
+
     @Test
-    fun testStopWorkerWhenNotificationSent() {
-        runBlockingTest {
-            every { tracingSettings.isTestResultAvailableNotificationSent } returns true
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) }
-            verify(exactly = 1) { backgroundWorkScheduler.stopDiagnosisTestResultPeriodicWork() }
-            result shouldBe ListenableWorker.Result.success()
-        }
+    fun testStopWorkerWhenResultHasBeenViewed() = runBlockingTest {
+        coronaTestFlow.value = setOf(newCoronaTest(viewed = true))
+
+        val result = createWorker().doWork()
+
+        coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) }
+        verify(exactly = 1) { testResultScheduler.setPeriodicTestPolling(enabled = false) }
+        result shouldBe ListenableWorker.Result.success()
     }
 
     @Test
-    fun testStopWorkerWhenMaxDaysExceeded() {
-        runBlockingTest {
-            val past =
-                currentInstant - (BackgroundConstants.POLLING_VALIDITY_MAX_DAYS.toLong() + 1).daysToMilliseconds()
-            every { tracingSettings.initialPollingForTestResultTimeStamp } returns past.millis
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) }
-            verify(exactly = 1) { backgroundWorkScheduler.stopDiagnosisTestResultPeriodicWork() }
-            result shouldBe ListenableWorker.Result.success()
-        }
+    fun testStopWorkerWhenNotificationSent() = runBlockingTest {
+        coronaTestFlow.value = setOf(newCoronaTest(isNotificationSent = true))
+
+        val result = createWorker().doWork()
+
+        coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) }
+        verify(exactly = 1) { testResultScheduler.setPeriodicTestPolling(enabled = false) }
+        result shouldBe ListenableWorker.Result.success()
     }
 
     @Test
-    fun testSendNotificationWhenPositive() {
-        val isTestResultAvailableNotificationSent = slot<Boolean>()
-        every {
-            tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent)
-        } answers {}
-
-        runBlockingTest {
-            val testResult = TestResult.POSITIVE
-            coEvery { submissionService.asyncRequestTestResult(registrationToken) } returns testResult
-            coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
-            coEvery {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            } just Runs
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify { submissionService.asyncRequestTestResult(registrationToken) }
-            coVerify { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) }
-            coVerify {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            }
-            result shouldBe ListenableWorker.Result.success()
-            isTestResultAvailableNotificationSent.captured shouldBe true
-        }
+    fun testStopWorkerWhenMaxDaysExceeded() = runBlockingTest {
+        val past =
+            currentInstant - (BackgroundConstants.POLLING_VALIDITY_MAX_DAYS.toLong() + 1).daysToMilliseconds()
+        coronaTestFlow.value = setOf(newCoronaTest(registered = past))
+
+        val result = createWorker().doWork()
+
+        coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) }
+        verify(exactly = 1) { testResultScheduler.setPeriodicTestPolling(enabled = false) }
+        result shouldBe ListenableWorker.Result.success()
     }
 
     @Test
-    fun testSendNotificationWhenNegative() {
-        val isTestResultAvailableNotificationSent = slot<Boolean>()
-        every {
-            tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent)
-        } answers {}
-
-        runBlockingTest {
-            val testResult = TestResult.NEGATIVE
-            coEvery { submissionService.asyncRequestTestResult(registrationToken) } returns testResult
-            coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
-            coEvery {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            } just Runs
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify { submissionService.asyncRequestTestResult(registrationToken) }
-            coVerify { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) }
-            coVerify {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            }
-            result shouldBe ListenableWorker.Result.success()
-            isTestResultAvailableNotificationSent.captured shouldBe true
+    fun testSendNotificationWhenPositive() = runBlockingTest {
+        val testResult = CoronaTestResult.PCR_POSITIVE
+        coronaTestFlow.value = setOf(newCoronaTest(result = testResult))
+
+        coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
+        coEvery {
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+        } just Runs
+
+        val result = createWorker().doWork()
+
+        coVerify {
+            coronaTestRepository.refresh(type = CoronaTest.Type.PCR)
+            testResultAvailableNotificationService.showTestResultAvailableNotification(testResult)
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+            coronaTestRepository.updateResultNotification(any(), sent = true)
         }
+
+        result shouldBe ListenableWorker.Result.success()
     }
 
     @Test
-    fun testSendNotificationWhenInvalid() {
-        val isTestResultAvailableNotificationSent = slot<Boolean>()
-        every {
-            tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent)
-        } answers {}
-
-        runBlockingTest {
-            val testResult = TestResult.INVALID
-            coEvery { submissionService.asyncRequestTestResult(registrationToken) } returns testResult
-            coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
-            coEvery {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            } just Runs
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify { submissionService.asyncRequestTestResult(registrationToken) }
-            coVerify { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) }
-            coVerify {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            }
-            result shouldBe ListenableWorker.Result.success()
-            isTestResultAvailableNotificationSent.captured shouldBe true
+    fun testSendNotificationWhenNegative() = runBlockingTest {
+        val testResult = CoronaTestResult.PCR_NEGATIVE
+        coronaTestFlow.value = setOf(newCoronaTest(result = testResult))
+        coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
+        coEvery {
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+        } just Runs
+
+        val result = createWorker().doWork()
+
+        coVerify {
+            coronaTestRepository.refresh(type = CoronaTest.Type.PCR)
+            testResultAvailableNotificationService.showTestResultAvailableNotification(testResult)
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+            coronaTestRepository.updateResultNotification(any(), sent = true)
         }
+
+        result shouldBe ListenableWorker.Result.success()
     }
 
     @Test
-    fun testSendNoNotificationWhenPending() {
-        runBlockingTest {
-            val testResult = TestResult.PENDING
-            coEvery { submissionService.asyncRequestTestResult(registrationToken) } returns testResult
-            coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
-            coEvery {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            } just Runs
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify { submissionService.asyncRequestTestResult(registrationToken) }
-            coVerify(exactly = 0) {
-                testResultAvailableNotificationService.showTestResultAvailableNotification(
-                    testResult
-                )
-            }
-            coVerify(exactly = 0) {
-                notificationHelper.cancelCurrentNotification(
-                    NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-                )
-            }
-            coVerify(exactly = 0) { backgroundWorkScheduler.stopDiagnosisTestResultPeriodicWork() }
-            result shouldBe ListenableWorker.Result.success()
+    fun testSendNotificationWhenInvalid() = runBlockingTest {
+        val testResult = CoronaTestResult.PCR_INVALID
+        coronaTestFlow.value = setOf(newCoronaTest(result = testResult))
+        coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
+        coEvery {
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+        } just Runs
+
+        val result = createWorker().doWork()
+
+        coVerify {
+            coronaTestRepository.refresh(type = CoronaTest.Type.PCR)
+            testResultAvailableNotificationService.showTestResultAvailableNotification(testResult)
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+            coronaTestRepository.updateResultNotification(any(), sent = true)
         }
+
+        result shouldBe ListenableWorker.Result.success()
     }
 
     @Test
-    fun testRetryWhenExceptionIsThrown() {
-        runBlockingTest {
-            coEvery { submissionService.asyncRequestTestResult(registrationToken) } throws Exception()
-            val worker = createWorker()
-            val result = worker.doWork()
-            coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) }
-            coVerify(exactly = 0) { backgroundWorkScheduler.stopDiagnosisTestResultPeriodicWork() }
-            result shouldBe ListenableWorker.Result.retry()
+    fun testSendNoNotificationWhenPending() = runBlockingTest {
+        val testResult = CoronaTestResult.PCR_OR_RAT_PENDING
+        coronaTestFlow.value = setOf(newCoronaTest(result = testResult))
+        coEvery { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } just Runs
+        coEvery {
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+        } just Runs
+
+        val result = createWorker().doWork()
+
+        coVerify { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) }
+        coVerify(exactly = 0) {
+            testResultAvailableNotificationService.showTestResultAvailableNotification(
+                testResult
+            )
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
+            )
+            testResultScheduler.setPeriodicTestPolling(enabled = false)
         }
+
+        result shouldBe ListenableWorker.Result.success()
     }
 
-    private fun createWorker() = DiagnosisTestResultRetrievalPeriodicWorker(
-        context,
-        workerParams,
-        testResultAvailableNotificationService,
-        notificationHelper,
-        submissionSettings,
-        submissionService,
-        timeStamper,
-        tracingSettings,
-        backgroundWorkScheduler,
-    )
+    @Test
+    fun testRetryWhenExceptionIsThrown() = runBlockingTest {
+        coronaTestFlow.value = setOf(newCoronaTest())
+        coEvery { coronaTestRepository.refresh(any()) } throws Exception()
+
+        val result = createWorker().doWork()
+
+        coVerify(exactly = 1) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) }
+        coVerify(exactly = 0) { testResultScheduler.setPeriodicTestPolling(any()) }
+        result shouldBe ListenableWorker.Result.retry()
+    }
 }