diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt
index 39be22363ad93e8911be994071f4b7e9591ce19d..a1865e350a2cf081bc25d2949059ab328264e63b 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt
@@ -42,7 +42,7 @@ class CertificatesFragmentTest : BaseUITest() {
     @MockK lateinit var vaccinatedPerson: VaccinatedPerson
 
     private val formatter = DateTimeFormat.forPattern("dd.MM.yyyy HH:mm")
-    private val testDate = DateTime.parse("12.05.2021 19:00", formatter).toInstant()
+    private val testDate = DateTime.parse("12.05.2021 19:00", formatter)
 
     @Before
     fun setup() {
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt
index 4b7f88440901b41d38e243161bfd088e54e4ac6e..7ea2e16565672eb475f993f182df41a03fd66880 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt
@@ -7,7 +7,7 @@ import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator
 import de.rki.coronawarnapp.nearby.modules.tekhistory.TEKHistoryProvider
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragment
 import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragmentArgs
 import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentViewModel
@@ -31,7 +31,7 @@ class SubmissionConsentFragmentTest : BaseUITest() {
     @MockK lateinit var submissionRepository: SubmissionRepository
     @MockK lateinit var interoperabilityRepository: InteroperabilityRepository
     @MockK lateinit var tekHistoryProvider: TEKHistoryProvider
-    @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor
+    @MockK lateinit var testRegistrationStateProcessor: TestRegistrationStateProcessor
     @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator
 
     private lateinit var viewModel: SubmissionConsentViewModel
@@ -48,7 +48,7 @@ class SubmissionConsentFragmentTest : BaseUITest() {
             interoperabilityRepository,
             TestDispatcherProvider(),
             tekHistoryProvider,
-            qrCodeRegistrationStateProcessor,
+            testRegistrationStateProcessor,
             submissionRepository,
             qrCodeValidator
         )
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 9efe83450ff7438a21e8d19d08f852e8543a79c3..628b6cd792cc25f91aba21a62d3320e1b69dd7fc 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
@@ -7,8 +7,8 @@ import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.coronatest.type.pcr.notification.PCRTestResultAvailableNotificationService
+import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository
 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.ui.submission.testresult.positive.SubmissionTestResultConsentGivenFragmentArgs
@@ -34,6 +34,7 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() {
 
     lateinit var viewModel: SubmissionTestResultNegativeViewModel
     @MockK lateinit var submissionRepository: SubmissionRepository
+    @MockK lateinit var certificateRepository: TestCertificateRepository
     @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService
     @MockK lateinit var testType: CoronaTest.Type
     private val resultNegativeFragmentArgs =
@@ -44,11 +45,13 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() {
         MockKAnnotations.init(this, relaxed = true)
 
         every { submissionRepository.testForType(any()) } returns flowOf()
+        every { certificateRepository.certificates } returns flowOf()
 
         viewModel = spyk(
             SubmissionTestResultNegativeViewModel(
                 TestDispatcherProvider(),
                 submissionRepository,
+                certificateRepository,
                 testResultAvailableNotificationService,
                 testType
             )
@@ -70,12 +73,13 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() {
     @Screenshot
     fun capture_fragment() {
         every { viewModel.testResult } returns MutableLiveData(
-            TestResultUIState(
+            SubmissionTestResultNegativeViewModel.UIState(
                 coronaTest = mockk<CoronaTest>().apply {
                     every { testResult } returns CoronaTestResult.PCR_NEGATIVE
                     every { registeredAt } returns Instant.now()
                     every { type } returns CoronaTest.Type.PCR
-                }
+                },
+                certificateState = SubmissionTestResultNegativeViewModel.CertificateState.AVAILABLE
             )
         )
         launchFragmentInContainer2<SubmissionTestResultNegativeFragment>(fragmentArgs = resultNegativeFragmentArgs)
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt
index 68b8a1b80a3d8ae83fad80d050cced8549a2e6b0..1f1b0dac59fa9341c3a57ea7c9c2362955e1b324 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt
@@ -9,9 +9,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
@@ -36,13 +36,13 @@ class RequestCovidCertificateFragmentTest : BaseUITest() {
 
         every { viewModel.birthDate } returns MutableLiveData(null)
         every { viewModel.registrationState } returns MutableLiveData(
-            QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE)
+            TestRegistrationStateProcessor.State.Idle
         )
 
         setupMockViewModel(
             object : RequestCovidCertificateViewModel.Factory {
                 override fun create(
-                    coronaTestQrCode: CoronaTestQRCode,
+                    testRegistrationRequest: TestRegistrationRequest,
                     coronaTestConsent: Boolean,
                     deleteOldTest: Boolean
                 ): RequestCovidCertificateViewModel = viewModel
diff --git a/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml b/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..88a2064d0a9ce8cd0aeff6f6f8bbf950930d744f
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <queries>
+        <!-- See DebugLogger.isAutoLoggingEnabled -->
+        <package android:name="de.rki.coronawarnapp.els.autologger" />
+    </queries>
+
+</manifest>
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
index 4eaae484d689e3f167712fb188037a219e6ddaf3..c744a067c8a4c9463485f31d30ddd7b9bacaafcd 100644
--- 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
@@ -65,16 +65,16 @@ class CoronaTestTestFragment : Fragment(R.layout.fragment_test_coronatest), Auto
             qrcodeScanViewfinder.setCameraPreview(binding.qrcodeScanPreview)
         }
 
-        viewModel.pcrtState.observe2(this) {
-            binding.pcrtData.text = it.getNiceTextForHumans()
+        viewModel.pcrtState.observe2(this) { state ->
+            binding.pcrtData.text = state.getNiceTextForHumans()
         }
         binding.apply {
             pcrtDeleteAction.setOnClickListener { viewModel.deletePCRT() }
             pcrtRefreshAction.setOnClickListener { viewModel.refreshPCRT() }
         }
 
-        viewModel.ratState.observe2(this) {
-            binding.ratData.text = it.getNiceTextForHumans()
+        viewModel.ratState.observe2(this) { state ->
+            binding.ratData.text = state.getNiceTextForHumans()
         }
         binding.apply {
             ratDeleteAction.setOnClickListener { viewModel.deleteRAT() }
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
index 544158248957931dfca9e3df2390b194c2ff719f..79c8f84ce15a08b94c5843b4461ae055b17d398a 100644
--- 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
@@ -6,12 +6,8 @@ import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 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
@@ -28,17 +24,17 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor(
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
     val errorEvents = SingleLiveEvent<Throwable>()
-    val pcrtState = coronaTestRepository.latestPCRT.map {
-        PCRTState(
-            coronaTest = it
-        )
-    }.asLiveData(context = dispatcherProvider.Default)
+    val pcrtState = coronaTestRepository.coronaTests
+        .map { tests -> tests.filter { it.type == CoronaTest.Type.PCR } }
+        .map { pcrTests ->
+            PCRTState(coronaTests = pcrTests)
+        }.asLiveData(context = dispatcherProvider.Default)
 
-    val ratState = coronaTestRepository.latestRAT.map {
-        RATState(
-            coronaTest = it
-        )
-    }.asLiveData(context = dispatcherProvider.Default)
+    val ratState = coronaTestRepository.coronaTests
+        .map { tests -> tests.filter { it.type == CoronaTest.Type.RAPID_ANTIGEN } }
+        .map { raTests ->
+            RATState(coronaTests = raTests)
+        }.asLiveData(context = dispatcherProvider.Default)
 
     val testsInContactDiary = contactDiaryRepository.testResults.map {
         it.foldIndexed(StringBuilder()) { id, buffer, item ->
@@ -58,12 +54,12 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor(
 
     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)
+            Timber.i("Deleting PCR tests.")
+            coronaTestRepository.coronaTests.first()
+                .filter { it.type == CoronaTest.Type.PCR }
+                .forEach { test ->
+                    coronaTestRepository.removeTest(test.identifier)
+                }
         } catch (e: Exception) {
             Timber.e(e, "Failed to delete PCR test.")
             errorEvents.postValue(e)
@@ -80,14 +76,14 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor(
         }
     }
 
-    fun deleteRAT() = launch {
+    fun deleteRAT(): Unit = launch {
         try {
-            val raTest = coronaTestRepository.latestRAT.first()
-            if (raTest == null) {
-                Timber.d("No RA test to delete")
-                return@launch
-            }
-            coronaTestRepository.removeTest(raTest.identifier)
+            Timber.i("Deleting RA tests.")
+            coronaTestRepository.coronaTests.first()
+                .filter { it.type == CoronaTest.Type.RAPID_ANTIGEN }
+                .forEach { test ->
+                    coronaTestRepository.removeTest(test.identifier)
+                }
         } catch (e: Exception) {
             Timber.e(e, "Failed to delete RA test.")
             errorEvents.postValue(e)
@@ -105,28 +101,34 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor(
     }
 
     data class PCRTState(
-        val coronaTest: PCRCoronaTest?
+        val coronaTests: Collection<CoronaTest>
     ) {
         fun getNiceTextForHumans(): String {
-            return coronaTest
-                ?.toString()
-                ?.replace("PCRCoronaTest(", "")
-                ?.replace(",", ",\n")
-                ?.trimEnd { it == ')' }
-                ?: "No PCR test registered."
+            if (coronaTests.isEmpty()) {
+                return "No PCR test registered."
+            }
+            return coronaTests.joinToString("\n") { test ->
+                test.toString()
+                    .replace("PCRCoronaTest(", "")
+                    .replace(",", ",\n")
+                    .trimEnd { it == ')' }
+            }
         }
     }
 
     data class RATState(
-        val coronaTest: RACoronaTest?
+        val coronaTests: Collection<CoronaTest>
     ) {
         fun getNiceTextForHumans(): String {
-            return coronaTest
-                ?.toString()
-                ?.replace("RACoronaTest(", "")
-                ?.replace(",", ",\n")
-                ?.trimEnd { it == ')' }
-                ?: "No rapid antigen test registered."
+            if (coronaTests.isEmpty()) {
+                return "No rapid antigen test registered."
+            }
+            return coronaTests.joinToString("\n") { test ->
+                test.toString()
+                    .replace("RACoronaTest(", "")
+                    .replace(",", ",\n")
+                    .trimEnd { it == ')' }
+            }
         }
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
index ab7f0f1f689ab045fca88abbefb6e70b9cdc0e1f..3860af4b3168b998dd46e7f56eb9d9c0bfbfca8d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.bugreporting.debuglog
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.content.pm.PackageManager
 import android.util.Log
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogStorageCheck
@@ -62,13 +63,30 @@ class DebugLogger(
         )
     }
 
+    private fun isAutoLoggingEnabled(): Boolean {
+        if (!CWADebug.isDeviceForTestersBuild) return false
+
+        return try {
+            val autoLoggerPkg = "de.rki.coronawarnapp.els.autologger"
+            context.packageManager.getPackageInfo(autoLoggerPkg, 0)
+            Timber.tag(TAG).i("Autologger package is installed (%s).", autoLoggerPkg)
+            true
+        } catch (e: PackageManager.NameNotFoundException) {
+            Timber.tag(TAG).i("DeviceForTester build, but no autologger package installed.")
+            false
+        } catch (e: Exception) {
+            Timber.tag(TAG).e(e, "Failed to determiner if autologger package is installed.")
+            false
+        }
+    }
+
     fun init() = try {
         val startLogger = when {
             triggerFile.exists() -> {
                 Timber.tag(TAG).i("Trigger file exists, starting debug log.")
                 true
             }
-            CWADebug.isDeviceForTestersBuild -> {
+            isAutoLoggingEnabled() -> {
                 Timber.tag(TAG).i("Trigger file does not exist, but it's a tester build, starting debug log.")
                 true
             }
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 06648265c95838ea0cafb2b40594bbfe850cd725..6ef130cb08c18a8fa7c27ab940a6a1a9027b80a7 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
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.coronatest
 
 import de.rki.coronawarnapp.bugreporting.reportProblem
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
+import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException
 import de.rki.coronawarnapp.coronatest.errors.CoronaTestNotFoundException
 import de.rki.coronawarnapp.coronatest.errors.DuplicateCoronaTestException
 import de.rki.coronawarnapp.coronatest.migration.PCRTestMigration
@@ -10,6 +11,8 @@ import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage
 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.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.util.coroutine.AppScope
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.flow.HotDataFlow
@@ -72,22 +75,65 @@ class CoronaTestRepository @Inject constructor(
 
     private fun getProcessor(type: CoronaTest.Type) = processors.single { it.type == type }
 
-    suspend fun registerTest(request: TestRegistrationRequest): CoronaTest {
-        Timber.tag(TAG).i("registerTest(request=%s)", request)
+    /**
+     * Default preconditions prevent duplicate test registration,
+     * and registration of an already redeemed test.
+     * If pre and post-condition are not met an [IllegalStateException] is thrown.
+     *
+     * @return the new test that was registered (or an exception is thrown)
+     */
+    suspend fun registerTest(
+        request: TestRegistrationRequest,
+        preCondition: ((Collection<CoronaTest>) -> Boolean) = { currentTests ->
+            if (currentTests.any { it.type == request.type }) {
+                throw DuplicateCoronaTestException("There is already a test of this type: ${request.type}.")
+            }
+            true
+        },
+        postCondition: ((CoronaTest) -> Boolean) = { newTest ->
+            if (newTest.isRedeemed) {
+                Timber.w("Replacement test was already redeemed, removing it, will not use.")
+                throw AlreadyRedeemedException(newTest)
+            }
+            true
+        }
+    ): CoronaTest {
+        Timber.tag(TAG).i(
+            "registerTest(request=%s, preCondition=%s, postCondition=%s)",
+            request, preCondition, postCondition
+        )
 
         // 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 DuplicateCoronaTestException("There is already a test of this type: ${request.type}.")
+            if (!preCondition(values)) {
+                throw IllegalStateException("PreCondition for current tests not fullfilled.")
             }
 
-            val test = processor.create(request).also {
+            val existing = values.singleOrNull { it.type == request.type }
+
+            val newTest = processor.create(request).also {
                 Timber.tag(TAG).i("New test created: %s", it)
             }
 
-            toMutableMap().apply { this[test.identifier] = test }
+            if (!postCondition(newTest)) {
+                throw IllegalStateException("PostCondition for new tests not fullfilled.")
+            }
+
+            if (existing != null) {
+                Timber.tag(TAG).w("We already have a test of this type, removing old test: %s", request)
+                try {
+                    getProcessor(existing.type).onRemove(existing)
+                } catch (e: Exception) {
+                    e.report(ExceptionCategory.INTERNAL)
+                }
+            }
+
+            toMutableMap().apply {
+                existing?.let { remove(it.identifier) }
+                this[newTest.identifier] = newTest
+            }
         }
 
         return currentTests[request.identifier]!!
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
index 99ab06763c99b475ee7f25a631f09bac318dac55..5e0ec73e6dbf2d1dda8d64bc83c15bf05faf8bca 100644
--- 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
@@ -1,9 +1,10 @@
 package de.rki.coronawarnapp.coronatest
 
+import android.os.Parcelable
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import org.joda.time.LocalDate
 
-interface TestRegistrationRequest {
+interface TestRegistrationRequest : Parcelable {
     val type: CoronaTest.Type
     val identifier: String
     val isDccSupportedByPoc: Boolean
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/errors/AlreadyRedeemedException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/errors/AlreadyRedeemedException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..40ceeef924d23b21513c4e4ccede47ba3b86cf52
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/errors/AlreadyRedeemedException.kt
@@ -0,0 +1,7 @@
+package de.rki.coronawarnapp.coronatest.errors
+
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+
+class AlreadyRedeemedException(
+    coronaTest: CoronaTest
+) : IllegalArgumentException("Test was already redeemed ${coronaTest.identifier}")
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
index 0fdb395a62ec5d5ca519f8d009b3ee5b9f8514d2..09c931f12a74f13ca57de95b32e7eecf41136844 100644
--- 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
@@ -8,13 +8,15 @@ import org.json.JSONObject
 
 data class CoronaTestResultResponse(
     val coronaTestResult: CoronaTestResult,
-    val sampleCollectedAt: Instant?
+    val sampleCollectedAt: Instant?,
+    val labId: String?
 ) {
     companion object {
         fun fromResponse(response: VerificationApiV1.TestResultResponse) =
             CoronaTestResultResponse(
                 coronaTestResult = CoronaTestResult.fromInt(response.testResult),
-                sampleCollectedAt = response.sampleCollectedAt?.toLong()?.let { Instant.ofEpochSecond(it) }
+                sampleCollectedAt = response.sampleCollectedAt?.toLong()?.let { Instant.ofEpochSecond(it) },
+                labId = response.labId
             )
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt
index 4f46627679001d32028ec1307cdb8a281e3cca00..b7c4efd790ac9045d3345a4d6e753ee6a98ce332 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt
@@ -32,7 +32,8 @@ interface VerificationApiV1 {
 
     data class TestResultResponse(
         @SerializedName("testResult") val testResult: Int,
-        @SerializedName("sc") val sampleCollectedAt: Int?
+        @SerializedName("sc") val sampleCollectedAt: Int?,
+        @SerializedName("labId") val labId: String?
     )
 
     @POST("version/v1/testresult")
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 824a468b746938f0b30c3c532cc7f00800ad8cd4..d1fc0813d294254adcc85f57aac5b327e5731b3f 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
@@ -49,6 +49,9 @@ interface CoronaTest {
     // Has the corresponding entry been created in the test certificate storage
     val isDccDataSetCreated: Boolean
 
+    //  The ID of the lab that uploaded the test result
+    val labId: String?
+
     enum class Type(val raw: String) {
         @SerializedName("PCR")
         PCR("PCR"),
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 fd28cb44782cf18837dda25c487667d006a4c34a..0fc8c26a31eae2e78ddccfc1a0240b522eb0da17 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
@@ -46,6 +46,9 @@ data class PCRCoronaTest(
 
     @SerializedName("isDccDataSetCreated")
     override val isDccDataSetCreated: Boolean = false,
+
+    @SerializedName("labId")
+    override val labId: String? = null,
 ) : CoronaTest {
 
     override val type: CoronaTest.Type
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt
index 58bfe8a28d16fd55ab3d07efd251fd7f4458e288..ce35035b404f73f9d96b48bdbdde63b0166a9ea3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt
@@ -130,6 +130,7 @@ class PCRTestProcessor @Inject constructor(
             testResult = testResult,
             testResultReceivedAt = determineReceivedDate(null, testResult),
             isDccConsentGiven = request.isDccConsentGiven,
+            labId = response.testResultResponse.labId
         )
     }
 
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
index 6dfa9ad11f4179a805596e85dee86c89e613b510..fa0933bc2da91e216d9278ea1565ba9f41f2a839 100644
--- 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
@@ -70,6 +70,9 @@ data class RACoronaTest(
     override val isDccConsentGiven: Boolean = false,
     @SerializedName("isDccDataSetCreated")
     override val isDccDataSetCreated: Boolean = false,
+
+    @SerializedName("labId")
+    override val labId: String? = null,
 ) : CoronaTest {
 
     override val type: CoronaTest.Type
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt
index 075881497a8447e524264062768309f02867f60d..e119cba0f64720b70a39a218182be317879ac02e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt
@@ -98,6 +98,7 @@ class RATestProcessor @Inject constructor(
             sampleCollectedAt = sampleCollectedAt,
             isDccSupportedByPoc = request.isDccSupportedByPoc,
             isDccConsentGiven = request.isDccConsentGiven,
+            labId = registrationData.testResultResponse.labId
         )
     }
 
@@ -137,7 +138,8 @@ class RATestProcessor @Inject constructor(
                     Timber.tag(TAG).w("HTTP 400 error after 21 days, remapping to RAT_REDEEMED.")
                     CoronaTestResultResponse(
                         coronaTestResult = RAT_REDEEMED,
-                        sampleCollectedAt = null
+                        sampleCollectedAt = null,
+                        labId = null
                     )
                 } else {
                     Timber.tag(TAG).v("Unexpected HTTP 400 error, rethrowing...")
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt
index c5770640218d238c5fe2dcc65f58be6a63ab5518..647d26a192d6d5f8ab1ced05d3b633f62b0a9026 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt
@@ -62,6 +62,10 @@ class TestCertificateServerException(
             "DCC Components failed with error 500: Signing server error",
             ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE
         ),
+        DCC_NOT_SUPPORTED_BY_LAB(
+            "DCC is not supported by the lab",
+            ERROR_MESSAGE_DCC_NOT_SUPPORTED_BY_LAB
+        ),
         DCC_COMP_NO_NETWORK(
             "DCC Test Certificate Components failed due to no network connection.",
             ERROR_MESSAGE_NO_NETWORK
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt
index 9bcbf87936cc1dd3c6187082729203a7bf7e6282..0565eec8d468af286024b667bc8bf9e1b7dab2b8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt
@@ -7,16 +7,16 @@ import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificate
 import de.rki.coronawarnapp.covidcertificate.test.ui.CertificatesAdapter
 import de.rki.coronawarnapp.databinding.CovidTestErrorCardBinding
-import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortDayFormat
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortTimeFormat
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+import org.joda.time.DateTime
 
 class CovidTestCertificatePendingCard(parent: ViewGroup) :
     CertificatesAdapter.CertificatesItemVH<CovidTestCertificatePendingCard.Item, CovidTestErrorCardBinding>(
         R.layout.home_card_container_layout,
         parent
     ) {
-
     override val viewBinding = lazy {
         CovidTestErrorCardBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
     }
@@ -28,11 +28,10 @@ class CovidTestCertificatePendingCard(parent: ViewGroup) :
 
         val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
 
-        val registeredAt = curItem.certificate.registeredAt
         testTime.text = context.getString(
             R.string.test_certificate_time,
-            registeredAt.toShortDayFormat(),
-            registeredAt.toShortTimeFormat(),
+            curItem.testDate.toDayFormat(),
+            curItem.testDate.toShortTimeFormat()
         )
 
         retryButton.setOnClickListener {
@@ -56,6 +55,7 @@ class CovidTestCertificatePendingCard(parent: ViewGroup) :
     }
 
     data class Item(
+        override val testDate: DateTime,
         val certificate: TestCertificate,
         val onRetryAction: (Item) -> Unit,
         val onDeleteAction: (Item) -> Unit
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt
index ed383c7d1fa83bb75448b86cb217054caeea4445..b632cea276213c1ca43a3f1a7046921588cf1046 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.covidcertificate.test.core
 
 import de.rki.coronawarnapp.bugreporting.reportProblem
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException
 import de.rki.coronawarnapp.covidcertificate.test.core.qrcode.TestCertificateQRCodeExtractor
 import de.rki.coronawarnapp.covidcertificate.test.core.storage.PCRCertificateData
 import de.rki.coronawarnapp.covidcertificate.test.core.storage.RACertificateData
@@ -87,7 +88,7 @@ class TestCertificateRepository @Inject constructor(
 
     /**
      * Will create a new test certificate entry.
-     * Automation via [de.rki.coronawarnapp.coronatest.type.common.TestCertificateRetrievalScheduler] will kick in.
+     * Automation via [de.rki.coronawarnapp.covidcertificate.test.core.execution.TestCertificateRetrievalScheduler] will kick in.
      *
      * Throws an exception if there already is a test certificate entry for this test
      * or this is not a valid test (no consent, not supported by PoC).
@@ -114,11 +115,13 @@ class TestCertificateRepository @Inject constructor(
                     identifier = identifier,
                     registeredAt = test.registeredAt,
                     registrationToken = test.registrationToken,
+                    labId = test.labId
                 )
                 CoronaTest.Type.RAPID_ANTIGEN -> RACertificateData(
                     identifier = identifier,
                     registeredAt = test.registeredAt,
                     registrationToken = test.registrationToken,
+                    labId = test.labId
                 )
             }
             val container = TestCertificateContainer(
@@ -172,12 +175,42 @@ class TestCertificateRepository @Inject constructor(
             }
         }
 
+        // Not sure i really like this
+        internalData.updateBlocking {
+            Timber.tag(TAG).d("Checking for invalid lab id.")
+
+            val refreshedCerts = values
+                .filter { workedOnIds.contains(it.identifier) } // Refresh targets
+                .filter { it.labId == null } // Targets of this step
+                .map { cert ->
+                    Timber.tag(TAG).d("%s is missing a lab id returning exception", cert)
+                    RefreshResult(
+                        cert,
+                        TestCertificateServerException(
+                            TestCertificateServerException.ErrorCode.DCC_NOT_SUPPORTED_BY_LAB
+                        )
+                    )
+                }
+
+            refreshedCerts.forEach {
+                refreshCallResults[it.certificateContainer.identifier] = it
+            }
+
+            mutate {
+                refreshedCerts
+                    .filter { it.error == null }
+                    .map { it.certificateContainer }
+                    .forEach { this[it.identifier] = it }
+            }
+        }
+
         internalData.updateBlocking {
             Timber.tag(TAG).d("Checking for unregistered public keys.")
 
             val refreshedCerts = values
                 .filter { workedOnIds.contains(it.identifier) } // Refresh targets
                 .filter { !it.isPublicKeyRegistered } // Targets of this step
+                .filter { it.labId != null }
                 .map { cert ->
                     withContext(dispatcherProvider.IO) {
                         try {
@@ -208,6 +241,7 @@ class TestCertificateRepository @Inject constructor(
             val refreshedCerts = values
                 .filter { workedOnIds.contains(it.identifier) } // Refresh targets
                 .filter { it.isPublicKeyRegistered && it.isCertificateRetrievalPending } // Targets of this step
+                .filter { it.labId != null }
                 .map { cert ->
                     withContext(dispatcherProvider.IO) {
                         try {
@@ -262,6 +296,29 @@ class TestCertificateRepository @Inject constructor(
         internalData.updateBlocking { emptyMap() }
     }
 
+    suspend fun markCertificateAsSeenByUser(identifier: TestCertificateIdentifier) {
+        Timber.tag(TAG).d("markCertificateSeenByUser(identifier=%s)", identifier)
+
+        internalData.updateBlocking {
+            val current = this[identifier]
+            if (current == null) {
+                Timber.tag(TAG).w("Can't mark %s as seen, it doesn't exist, racecondition?", identifier)
+                return@updateBlocking this
+            }
+
+            if (current.isCertificateRetrievalPending) {
+                Timber.tag(TAG).w("Can't mark %s as seen, certificate has not been retrieved yet.", identifier)
+                return@updateBlocking this
+            }
+
+            val updated = current.copy(
+                data = processor.updateSeenByUser(current.data, true)
+            )
+
+            mutate { this[identifier] = updated }
+        }
+    }
+
     companion object {
         private val TAG = TestCertificateRepository::class.simpleName!!
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt
index 2be76074c81765443b9c52167bf4d028cceadaab..0df7c8164ead0b32c0651a94157746965feeeab7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt
@@ -17,7 +17,11 @@ data class TestCertificateWrapper(
 
     val registeredAt = container.registeredAt
 
+    val seenByUser = container.certificateSeenByUser
+
     val testCertificate: TestCertificate? by lazy {
         container.toTestCertificate(valueSets)
     }
+
+    val registrationToken = container.registrationToken
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt
index 2ad3ee6454b7fa8be7038f7c5b0fd0bb5654db43..02e1e616f3c5bce3fbb2826c0a19ca37381227df 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt
@@ -14,7 +14,6 @@ import de.rki.coronawarnapp.util.device.ForegroundState
 import de.rki.coronawarnapp.util.flow.combine
 import de.rki.coronawarnapp.worker.BackgroundConstants
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -30,7 +29,7 @@ class TestCertificateRetrievalScheduler @Inject constructor(
     private val workManager: WorkManager,
     private val certificateRepo: TestCertificateRepository,
     private val testRepo: CoronaTestRepository,
-    private val foregroundState: ForegroundState,
+    foregroundState: ForegroundState,
 ) : ResultScheduler(
     workManager = workManager
 ) {
@@ -69,21 +68,27 @@ class TestCertificateRetrievalScheduler @Inject constructor(
             .onEach { testsWithoutCert ->
                 Timber.tag(TAG).d("State change: testsWithoutCert=$testsWithoutCert")
                 testsWithoutCert.forEach { test ->
-                    val cert = certificateRepo.requestCertificate(test)
-                    Timber.tag(TAG).v("Certificate was created: %s", cert)
-                    testRepo.markDccAsCreated(test.identifier, created = true)
+                    try {
+                        val cert = certificateRepo.requestCertificate(test)
+                        Timber.tag(TAG).v("Certificate was created: %s", cert)
+                        testRepo.markDccAsCreated(test.identifier, created = true)
+                    } catch (e: Exception) {
+                        Timber.tag(TAG).e(e, "Creation trigger failed.")
+                    }
                 }
             }
-            .catch { Timber.tag(TAG).e(it, "Creation trigger failed.") }
             .launchIn(appScope)
 
         // For each change to the set of existing certificates, check if we need to refresh/load data
         refreshTrigger
             .onEach { checkCerts ->
-                Timber.tag(TAG).d("State change: checkCerts=$checkCerts")
-                if (checkCerts) scheduleWorker()
+                try {
+                    Timber.tag(TAG).d("State change: checkCerts=$checkCerts")
+                    if (checkCerts) scheduleWorker()
+                } catch (e: Exception) {
+                    Timber.tag(TAG).e(e, "Refresh trigger failed.")
+                }
             }
-            .catch { Timber.tag(TAG).e(it, "Refresh trigger failed.") }
             .launchIn(appScope)
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt
index 118bf5bc112a3fc4399a49210542b1f6069c7256..d53a1d54b8bd82a026349e87f191443e7564fb4f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt
@@ -37,6 +37,12 @@ data class PCRCertificateData internal constructor(
 
     @SerializedName("testCertificateQrCode")
     override val testCertificateQrCode: String? = null,
+
+    @SerializedName("labId")
+    override val labId: String? = null,
+
+    @SerializedName("certificateSeenByUser")
+    override val certificateSeenByUser: Boolean = false,
 ) : StoredTestCertificateData {
 
     // Otherwise GSON unsafes reflection to create this class, and sets the LAZY to null
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt
index dfcb82bad80c8f2000c505ce3929fb62dc6f1f64..fe10fabb6604501b72a8f73f6482095e834db3dc 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt
@@ -37,6 +37,12 @@ data class RACertificateData(
 
     @SerializedName("testCertificateQrCode")
     override val testCertificateQrCode: String? = null,
+
+    @SerializedName("labId")
+    override val labId: String? = null,
+
+    @SerializedName("certificateSeenByUser")
+    override val certificateSeenByUser: Boolean = false,
 ) : StoredTestCertificateData {
 
     // Otherwise GSON unsafes reflection to create this class, and sets the LAZY to null
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt
index 131c18e26608fb530f939f6cccc0cbb49128133d..9ce2281d176f35a686386dcea9203446f856edea 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt
@@ -18,4 +18,6 @@ interface StoredTestCertificateData {
     val encryptedDataEncryptionkey: ByteString?
     val encryptedDccCose: ByteString?
     val testCertificateQrCode: String?
+    val labId: String?
+    val certificateSeenByUser: Boolean
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt
index 181d3f8aee6eabbcf13aee81c29806dae2b39966..f929e89fbcf5ddb9d9d112c870f838ede70705d1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt
@@ -150,6 +150,17 @@ class TestCertificateProcessor @Inject constructor(
         }
     }
 
+    internal suspend fun updateSeenByUser(
+        data: StoredTestCertificateData,
+        seenByUser: Boolean,
+    ): StoredTestCertificateData {
+        Timber.tag(TAG).d("updateSeenByUser(data=%s, seenByUser=%b)", data, seenByUser)
+        return when (data.type) {
+            CoronaTest.Type.PCR -> (data as PCRCertificateData).copy(certificateSeenByUser = seenByUser)
+            CoronaTest.Type.RAPID_ANTIGEN -> (data as RACertificateData).copy(certificateSeenByUser = seenByUser)
+        }
+    }
+
     companion object {
         private val TAG = TestCertificateProcessor::class.simpleName!!
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt
index 9a83e0d986a23848e5fae91f6c3f29c77fc5a5a2..d287cc6255231aab51e41ab159e634e977f4f30b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt
@@ -1,9 +1,10 @@
 package de.rki.coronawarnapp.covidcertificate.test.ui
 
+import android.content.Context
 import androidx.lifecycle.LiveData
-import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.contactdiary.util.getLocale
 import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository
 import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.CertificatesItem
 import de.rki.coronawarnapp.covidcertificate.vaccination.core.VaccinatedPerson
@@ -14,17 +15,30 @@ import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.HeaderInfoVacc
 import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.ImmuneVaccinationCard
 import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.NoCovidTestCertificatesCard
 import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.VaccinationCard
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone
+import de.rki.coronawarnapp.covidcertificate.valueset.ValueSetsRepository
+import de.rki.coronawarnapp.util.di.AppContext
 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.catch
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import timber.log.Timber
 
 class CertificatesViewModel @AssistedInject constructor(
     vaccinationRepository: VaccinationRepository,
+    valueSetsRepository: ValueSetsRepository,
+    @AppContext context: Context,
     private val vaccinationSettings: VaccinationSettings,
     private val testCertificateRepository: TestCertificateRepository
 ) : CWAViewModel() {
 
+    init {
+        valueSetsRepository.triggerUpdateValueSet(languageCode = context.getLocale())
+    }
+
     val events = SingleLiveEvent<CertificatesFragmentEvents>()
 
     val screenItems: LiveData<List<CertificatesItem>> =
@@ -50,7 +64,8 @@ class CertificatesViewModel @AssistedInject constructor(
                         add(NoCovidTestCertificatesCard.Item)
                     }
                 }
-            }.asLiveData()
+            }
+            .asLiveData2()
 
     private fun Set<VaccinatedPerson>.toCertificateItems(): List<CertificatesItem> = map { vaccinatedPerson ->
         when (vaccinatedPerson.getVaccinationStatus()) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt
index 85ddac6dcb9ddf4fded87e296b67482f52b934c1..5e241cdd80a234e30c69954139a9cd9de46b9ae9 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt
@@ -1,10 +1,10 @@
 package de.rki.coronawarnapp.covidcertificate.test.ui.cards
 
 import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.CertificatesItem
-import org.joda.time.Instant
+import org.joda.time.DateTime
 
 interface CovidCertificateTestItem : CertificatesItem {
-    val testDate: Instant
+    val testDate: DateTime
 
     override val stableId: Long
         get() = testDate.hashCode().toLong()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt
index 791baaf5ab8d8625f9aa814583dd4d9975107aad..a5eb74819dfab46b7c0260eb167b255b6c6f9161 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt
@@ -4,10 +4,10 @@ import android.view.ViewGroup
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.covidcertificate.test.ui.CertificatesAdapter
 import de.rki.coronawarnapp.databinding.CovidTestSuccessCardBinding
-import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortDayFormat
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortTimeFormat
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
-import org.joda.time.Instant
+import org.joda.time.DateTime
 
 class CovidTestCertificateCard(parent: ViewGroup) :
     CertificatesAdapter.CertificatesItemVH<CovidTestCertificateCard.Item, CovidTestSuccessCardBinding>(
@@ -26,8 +26,8 @@ class CovidTestCertificateCard(parent: ViewGroup) :
         val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
         testTime.text = context.getString(
             R.string.test_certificate_time,
-            curItem.testDate.toShortDayFormat(),
-            curItem.testDate.toShortTimeFormat(),
+            item.testDate.toDayFormat(),
+            item.testDate.toShortTimeFormat()
         )
 
         personName.text = curItem.testPerson
@@ -36,7 +36,7 @@ class CovidTestCertificateCard(parent: ViewGroup) :
     }
 
     data class Item(
-        override val testDate: Instant,
+        override val testDate: DateTime,
         val testPerson: String,
         val onClickAction: (Item) -> Unit,
     ) : CovidCertificateTestItem, HasPayloadDiffer {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt
index 670da564f0f1f45af487c4cdca23c5ab9a2fa4f9..671bcc4865983b5330bb63ea2fd54bde29ea30ce 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt
@@ -5,6 +5,7 @@ import android.os.Bundle
 import android.view.View
 import android.widget.LinearLayout
 import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.isGone
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.FragmentNavigatorExtras
 import androidx.navigation.fragment.findNavController
@@ -43,6 +44,8 @@ class CovidCertificateDetailsFragment : Fragment(R.layout.fragment_covid_certifi
     )
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) {
+        qrCodeCard.title.isGone = true
+        qrCodeCard.subtitle.isGone = true
         appBarLayout.onOffsetChange { titleAlpha, subtitleAlpha ->
             title.alpha = titleAlpha
             subtitle.alpha = subtitleAlpha
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt
index 942fe20bd7096d0eb936facb7a8e867abfc99c8c..9b751fc6ae731ab6ed5ebc1be8e89469830ebca3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt
@@ -39,7 +39,7 @@ data class VaccinatedPerson(
         val daysToImmunity = getTimeUntilImmunity(nowUTC)?.standardDays ?: return Status.INCOMPLETE
 
         return when {
-            daysToImmunity <= 0 -> Status.IMMUNITY
+            daysToImmunity < 0 -> Status.IMMUNITY
             else -> Status.COMPLETE
         }
     }
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 9215b9f36c135c5fa7d96017bd02f82c05932c8c..8d75bf3d4be2413a59a57b76081bdb7152996d8c 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
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.submission
 
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
+import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException
 import de.rki.coronawarnapp.coronatest.errors.CoronaTestNotFoundException
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
@@ -20,7 +21,6 @@ import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
-@Suppress("LongParameterList")
 @Singleton
 class SubmissionRepository @Inject constructor(
     @AppScope private val scope: CoroutineScope,
@@ -92,6 +92,32 @@ class SubmissionRepository @Inject constructor(
         return coronaTest
     }
 
+    /**
+     * Attempt to register a new test, but if it is already redeemed, keep the previous test.
+     */
+    suspend fun tryReplaceTest(request: TestRegistrationRequest): CoronaTest {
+        Timber.tag(TAG).v("tryReplaceTest(request=%s)", request)
+
+        val coronaTest = coronaTestRepository.registerTest(
+            request = request,
+            preCondition = { currentTests ->
+                if (currentTests.any { it.type == request.type }) {
+                    Timber.tag(TAG).i("Test type already exists, will try to replace.")
+                }
+                true
+            },
+            postCondition = { newTest ->
+                if (newTest.isRedeemed) {
+                    Timber.w("Replacement test was already redeemed, removing it, will not use.")
+                    throw AlreadyRedeemedException(newTest)
+                }
+                true
+            }
+        )
+        Timber.d("Registered test %s -> %s", request, coronaTest)
+        return coronaTest
+    }
+
     suspend fun reset() {
         Timber.tag(TAG).v("reset()")
         tekHistoryStorage.clear()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..817591e968fdb28dca40793b92f3ceabf977d489
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessor.kt
@@ -0,0 +1,111 @@
+package de.rki.coronawarnapp.submission
+
+import android.content.Context
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
+import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+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.http.BadRequestException
+import de.rki.coronawarnapp.exception.http.CwaClientError
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.exception.reporting.report
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import javax.inject.Inject
+
+class TestRegistrationStateProcessor @Inject constructor(
+    private val submissionRepository: SubmissionRepository,
+    private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
+) {
+
+    private val mutex = Mutex()
+
+    sealed class State {
+        object Idle : State()
+        object Working : State()
+        data class TestRegistered(val test: CoronaTest) : State()
+
+        data class Error(val exception: Exception) : State() {
+            fun getDialogBuilder(context: Context): MaterialAlertDialogBuilder {
+                val builder = MaterialAlertDialogBuilder(context).apply {
+                    setCancelable(true)
+                }
+
+                return when (exception) {
+                    is AlreadyRedeemedException -> builder.apply {
+                        setTitle(R.string.submission_error_dialog_web_tan_redeemed_title)
+                        setMessage(R.string.submission_error_dialog_web_tan_redeemed_body)
+                        setPositiveButton(android.R.string.ok) { _, _ ->
+                            /* dismiss */
+                        }
+                    }
+                    is BadRequestException -> builder.apply {
+                        setTitle(R.string.submission_qr_code_scan_invalid_dialog_headline)
+                        setMessage(R.string.submission_qr_code_scan_invalid_dialog_body)
+                        setPositiveButton(android.R.string.ok) { _, _ ->
+                            /* dismiss */
+                        }
+                    }
+                    is CwaClientError, is CwaServerError -> builder.apply {
+                        setTitle(R.string.submission_error_dialog_web_generic_error_title)
+                        setMessage(R.string.submission_error_dialog_web_generic_network_error_body)
+                        setPositiveButton(android.R.string.ok) { _, _ ->
+                            /* dismiss */
+                        }
+                    }
+                    is CwaWebException -> builder.apply {
+                        setTitle(R.string.submission_error_dialog_web_generic_error_title)
+                        setMessage(R.string.submission_error_dialog_web_generic_error_body)
+                        setPositiveButton(android.R.string.ok) { _, _ ->
+                            /* dismiss */
+                        }
+                    }
+                    else -> exception.toErrorDialogBuilder(context)
+                }
+            }
+        }
+    }
+
+    private val stateInternal = MutableStateFlow<State>(State.Idle)
+    val state: Flow<State> = stateInternal
+
+    suspend fun startRegistration(
+        request: TestRegistrationRequest,
+        isSubmissionConsentGiven: Boolean,
+        allowReplacement: Boolean,
+    ): CoronaTest? = mutex.withLock {
+        return try {
+            stateInternal.value = State.Working
+
+            val coronaTest = if (allowReplacement) {
+                submissionRepository.tryReplaceTest(request)
+            } else {
+                submissionRepository.registerTest(request)
+            }
+
+            if (isSubmissionConsentGiven) {
+                submissionRepository.giveConsentToSubmission(type = coronaTest.type)
+                if (request is CoronaTestQRCode) {
+                    analyticsKeySubmissionCollector.reportAdvancedConsentGiven(request.type)
+                }
+            }
+
+            stateInternal.value = State.TestRegistered(test = coronaTest)
+            coronaTest
+        } catch (err: Exception) {
+            stateInternal.value = State.Error(exception = err)
+            if (err !is CwaWebException) {
+                err.report(ExceptionCategory.INTERNAL)
+            }
+            null
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt
index 790a2cbca56872f2a4ef0643fdde97b27979eda2..c9c72866e42bf329a6ba89256bc5477369c3fa51 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.submission.ui.testresults.negative
 
 import android.os.Bundle
 import android.view.View
+import androidx.appcompat.content.res.AppCompatResources.getDrawable
 import androidx.core.text.bold
 import androidx.core.text.buildSpannedString
 import androidx.core.view.isGone
@@ -9,13 +10,14 @@ import androidx.fragment.app.Fragment
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionAntigenTestResultNegativeBinding
 import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortTimeFormat
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.popBackStack
 import de.rki.coronawarnapp.util.ui.viewBinding
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
-import org.joda.time.format.DateTimeFormat
 import javax.inject.Inject
 
 class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_test_result_negative), AutoInject {
@@ -24,8 +26,6 @@ class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_
 
     private val binding: FragmentSubmissionAntigenTestResultNegativeBinding by viewBinding()
 
-    private val shortTime = DateTimeFormat.shortTime()
-
     private val deleteRatTestConfirmationDialog by lazy {
         DialogHelper.DialogInstance(
             requireActivity(),
@@ -58,38 +58,38 @@ class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_
         }
 
     private fun FragmentSubmissionAntigenTestResultNegativeBinding.bindView(
-        testAge: RATResultNegativeViewModel.TestAge
+        uiState: RATResultNegativeViewModel.UIState
     ) {
-        resultReceivedCounter.chronometer.text = testAge.ageText
+        resultReceivedCounter.chronometer.text = uiState.ageText
 
         val patientName = getString(
             R.string.submission_test_result_antigen_patient_name_placeholder,
-            testAge.test.firstName ?: "",
-            testAge.test.lastName ?: ""
+            uiState.test.firstName ?: "",
+            uiState.test.lastName ?: ""
         )
 
         rapidTestCardPatientInfo.text = buildSpannedString {
             bold {
                 if (patientName.isNotBlank()) append(patientName)
             }
-            testAge.test.dateOfBirth?.let {
+            uiState.test.dateOfBirth?.let {
                 val birthDate = getString(
                     R.string.submission_test_result_antigen_patient_birth_date_placeholder,
-                    it.toString(DATE_FORMAT)
+                    it.toDayFormat()
                 )
                 if (this.isNotBlank()) append(", ")
                 append(birthDate)
             }
         }
 
-        val localTime = testAge.test.testTakenAt.toUserTimeZone()
+        val localTime = uiState.test.testTakenAt.toUserTimeZone()
         resultReceivedTimeAndDate.text = getString(
             R.string.coronatest_negative_antigen_result_time_date_placeholder,
-            localTime.toString(DATE_FORMAT),
-            localTime.toString(shortTime)
+            localTime.toDayFormat(),
+            localTime.toShortTimeFormat()
         )
 
-        val isAnonymousTest = with(testAge.test) {
+        val isAnonymousTest = with(uiState.test) {
             firstName == null && lastName == null && dateOfBirth == null
         }
 
@@ -108,9 +108,32 @@ class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_
         negativeTestProofBody.text = getString(proofBodyString)
 
         negativeTestProofAdditionalInformation.isGone = isAnonymousTest
-    }
 
-    companion object {
-        private const val DATE_FORMAT = "dd.MM.yyyy"
+        when (uiState.certificateState) {
+            RATResultNegativeViewModel.CertificateState.NOT_REQUESTED -> {
+                coronatestNegativeAntigenResultThirdInfo.setIsFinal(true)
+                coronatestNegativeAntigenResultFourthInfo.isGone = true
+            }
+            RATResultNegativeViewModel.CertificateState.PENDING -> {
+                coronatestNegativeAntigenResultThirdInfo.setIsFinal(false)
+                coronatestNegativeAntigenResultFourthInfo.isGone = false
+                coronatestNegativeAntigenResultFourthInfo.setEntryText(
+                    getText(R.string.submission_test_result_pending_steps_test_certificate_not_available_yet_body)
+                )
+                coronatestNegativeAntigenResultFourthInfo.setIcon(
+                    getDrawable(requireContext(), R.drawable.ic_result_pending_certificate_info)
+                )
+            }
+            RATResultNegativeViewModel.CertificateState.AVAILABLE -> {
+                coronatestNegativeAntigenResultThirdInfo.setIsFinal(false)
+                coronatestNegativeAntigenResultFourthInfo.isGone = false
+                coronatestNegativeAntigenResultFourthInfo.setEntryText(
+                    getText(R.string.coronatest_negative_result_certificate_info_body)
+                )
+                coronatestNegativeAntigenResultFourthInfo.setIcon(
+                    getDrawable(requireContext(), R.drawable.ic_qr_code_illustration)
+                )
+            }
+        }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt
index 70990f8e12ca4f2f5b95082d58f6fa46701c4a23..763476722f855d1523fe01a5f1a9b26047e2b443 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt
@@ -6,6 +6,8 @@ import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
+import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository
+import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateWrapper
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.submission.SubmissionRepository
@@ -25,22 +27,28 @@ class RATResultNegativeViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     private val timeStamper: TimeStamper,
     private val submissionRepository: SubmissionRepository,
-    coronaTestRepository: CoronaTestRepository
+    coronaTestRepository: CoronaTestRepository,
+    certificateRepository: TestCertificateRepository
 ) : CWAViewModel(dispatcherProvider) {
 
     val events = SingleLiveEvent<RATResultNegativeNavigation>()
     val testAge = combine(
         intervalFlow(1),
-        coronaTestRepository.coronaTests
-    ) { _, tests ->
+        coronaTestRepository.coronaTests,
+        certificateRepository.certificates
+    ) { _, tests, certs ->
         val rapidTest = tests.firstOrNull {
             it.type == CoronaTest.Type.RAPID_ANTIGEN
         }
 
-        rapidTest?.testAge()
+        val certificate = certs.firstOrNull {
+            it.registrationToken == rapidTest?.registrationToken
+        }
+
+        rapidTest?.uiState(certificate)
     }.asLiveData(context = dispatcherProvider.Default)
 
-    private fun CoronaTest.testAge(): TestAge? {
+    private fun CoronaTest.uiState(certificate: TestCertificateWrapper?): UIState? {
         if (this !is RACoronaTest) {
             Timber.d("Rapid test is missing")
             return null
@@ -50,7 +58,17 @@ class RATResultNegativeViewModel @AssistedInject constructor(
         val age = nowUTC.millis - testTakenAt.millis
         val ageText = formatter.print(Duration(age).toPeriod())
 
-        return TestAge(test = this, ageText)
+        val certificateState: CertificateState = when (certificate?.isCertificateRetrievalPending) {
+            true -> CertificateState.PENDING
+            false -> CertificateState.AVAILABLE
+            else -> CertificateState.NOT_REQUESTED
+        }
+
+        return UIState(
+            test = this,
+            ageText = ageText,
+            certificateState = certificateState
+        )
     }
 
     fun onDeleteTestConfirmed() {
@@ -75,9 +93,16 @@ class RATResultNegativeViewModel @AssistedInject constructor(
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<RATResultNegativeViewModel>
 
-    data class TestAge(
+    enum class CertificateState {
+        NOT_REQUESTED,
+        PENDING,
+        AVAILABLE
+    }
+
+    data class UIState(
         val test: RACoronaTest,
         val ageText: String,
+        val certificateState: CertificateState
     )
 
     companion object {
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 0313c13a83921b33d5a9b037821f821d2fbe771d..cf227d6ca7bc01a71f2f79b93020b1795e5e071a 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
@@ -26,13 +26,13 @@ import de.rki.coronawarnapp.ui.setupWithNavController2
 import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragment
 import de.rki.coronawarnapp.util.AppShortcuts
 import de.rki.coronawarnapp.util.CWADebug
-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
 import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper.Companion.getShortcutExtra
 import de.rki.coronawarnapp.util.ui.findNavController
 import de.rki.coronawarnapp.util.ui.findNestedGraph
+import de.rki.coronawarnapp.util.ui.updateCountBadge
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
 import org.joda.time.LocalDate
@@ -103,16 +103,11 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
         }
 
         vm.activeCheckIns.observe(this) { count ->
-            val targetId = R.id.trace_location_attendee_nav_graph
-            binding.mainBottomNavigation.apply {
-                if (count > 0) {
-                    val badge = getOrCreateBadge(targetId)
-                    badge.number = count
-                    badge.badgeTextColor = getColorCompat(android.R.color.white)
-                } else {
-                    removeBadge(targetId)
-                }
-            }
+            binding.mainBottomNavigation.updateCountBadge(R.id.trace_location_attendee_nav_graph, count)
+        }
+
+        vm.newCertificates.observe(this) { count ->
+            binding.mainBottomNavigation.updateCountBadge(R.id.green_certificate_graph, count)
         }
 
         if (savedInstanceState == null) {
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 6fe194da45ba54ed22a245032879f0a93aeb8556..6890d05c4e7cbfbb73d9a5dfdcded9e7b0238435 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
@@ -2,10 +2,10 @@ package de.rki.coronawarnapp.ui.main
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings
+import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository
 import de.rki.coronawarnapp.covidcertificate.vaccination.core.VaccinationSettings
 import de.rki.coronawarnapp.environment.EnvironmentSetup
 import de.rki.coronawarnapp.playbook.BackgroundNoise
@@ -31,7 +31,8 @@ class MainActivityViewModel @AssistedInject constructor(
     private val onboardingSettings: OnboardingSettings,
     private val traceLocationSettings: TraceLocationSettings,
     private val vaccinationSettings: VaccinationSettings,
-    checkInRepository: CheckInRepository
+    checkInRepository: CheckInRepository,
+    testCertificateRepository: TestCertificateRepository,
 ) : CWAViewModel(
     dispatcherProvider = dispatcherProvider
 ) {
@@ -49,7 +50,13 @@ class MainActivityViewModel @AssistedInject constructor(
 
     val activeCheckIns = checkInRepository.checkInsWithinRetention
         .map { checkins -> checkins.filter { !it.completed }.size }
-        .asLiveData(context = dispatcherProvider.Default)
+        .asLiveData2()
+
+    val newCertificates = testCertificateRepository.certificates
+        .map { certs ->
+            certs.filter { !it.seenByUser && !it.isCertificateRetrievalPending }.size
+        }
+        .asLiveData2()
 
     init {
         if (CWADebug.isDeviceForTestersBuild) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt
index 2635a01a5d5cdcfec0d8b5f48f392d1616777c46..b3e85a78717130c063c3fb68ad51207cdfcd56aa 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt
@@ -1,30 +1,22 @@
 package de.rki.coronawarnapp.ui.submission.covidcertificate
 
 import android.os.Bundle
-import androidx.fragment.app.Fragment
 import android.view.View
+import androidx.activity.addCallback
 import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
 import androidx.navigation.fragment.navArgs
 import com.google.android.material.datepicker.MaterialDatePicker
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import de.rki.coronawarnapp.NavGraphDirections
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
-import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR
-import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.databinding.FragmentRequestCovidCertificateBinding
 import de.rki.coronawarnapp.exception.http.BadRequestException
-import de.rki.coronawarnapp.exception.http.CwaClientError
-import de.rki.coronawarnapp.exception.http.CwaServerError
-import de.rki.coronawarnapp.exception.http.CwaWebException
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
-import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor.State
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.doNavigate
@@ -33,7 +25,6 @@ import de.rki.coronawarnapp.util.ui.viewBinding
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
 import org.joda.time.LocalDate
-import timber.log.Timber
 import javax.inject.Inject
 
 class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid_certificate), AutoInject {
@@ -43,7 +34,7 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid
         factoryProducer = { viewModelFactory },
         constructorCall = { factory, _ ->
             factory as RequestCovidCertificateViewModel.Factory
-            factory.create(args.coronaTestQrCode, args.coronaTestConsent, args.deleteOldTest)
+            factory.create(args.testRegistrationRequest, args.coronaTestConsent, args.deleteOldTest)
         }
     )
     private val binding by viewBinding<FragmentRequestCovidCertificateBinding>()
@@ -51,7 +42,7 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) =
         with(binding) {
-            val isPCR = args.coronaTestQrCode is CoronaTestQRCode.PCR
+            val isPCR = args.testRegistrationRequest.type == CoronaTest.Type.PCR
             birthDateGroup.isVisible = isPCR
             privacyCard.pcrExtraBullet.isVisible = isPCR
 
@@ -59,7 +50,9 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid
                 if (text.toString().isEmpty()) viewModel.birthDateChanged(null)
             }
 
+            requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { showCloseDialog() }
             toolbar.setNavigationOnClickListener { showCloseDialog() }
+
             agreeButton.setOnClickListener { viewModel.onAgreeGC() }
             disagreeButton.setOnClickListener { viewModel.onDisagreeGC() }
             dateInputEdit.setOnClickListener { openDatePicker() }
@@ -68,74 +61,64 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid
             viewModel.events.observe(viewLifecycleOwner) { event ->
                 when (event) {
                     Back -> popBackStack()
-                    ToDispatcherScreen -> doNavigate(
+
+                    ToDispatcherScreen ->
                         RequestCovidCertificateFragmentDirections
                             .actionRequestCovidCertificateFragmentToDispatcherFragment()
-                    )
-                    ToHomeScreen -> doNavigate(
+                            .run { doNavigate(this) }
+
+                    ToHomeScreen ->
                         RequestCovidCertificateFragmentDirections.actionRequestCovidCertificateFragmentToHomeFragment()
-                    )
+                            .run { doNavigate(this) }
                 }
             }
             viewModel.birthDate.observe(viewLifecycleOwner) { date -> agreeButton.isEnabled = !isPCR || date != null }
-            viewModel.registrationError.observe(viewLifecycleOwner) { DialogHelper.showDialog(buildErrorDialog(it)) }
             viewModel.registrationState.observe(viewLifecycleOwner) { state -> handleRegistrationState(state) }
-            viewModel.showRedeemedTokenWarning.observe(viewLifecycleOwner) { DialogHelper.showDialog(redeemDialog()) }
-            viewModel.removalError.observe(viewLifecycleOwner) { it.toErrorDialogBuilder(requireContext()).show() }
         }
 
-    private fun handleRegistrationState(state: QrCodeRegistrationStateProcessor.RegistrationState) {
-        when (state.apiRequestState) {
-            ApiRequestState.STARTED -> binding.apply {
-                progressBar.show()
-                agreeButton.isInvisible = true
-                disagreeButton.isInvisible = true
+    private fun handleRegistrationState(state: State) {
+        val isWorking = state is State.Working
+        binding.apply {
+            if (isWorking) progressBar.show() else progressBar.hide()
+            agreeButton.isInvisible = isWorking
+            disagreeButton.isInvisible = isWorking
+        }
+        when (state) {
+            State.Idle,
+            State.Working -> {
+                // Handled above
             }
-            else -> binding.apply {
-                progressBar.hide()
-                agreeButton.isInvisible = false
-                disagreeButton.isInvisible = false
+            is State.Error -> {
+                state.getDialogBuilder(requireContext()).apply {
+                    if (state.exception is BadRequestException) {
+                        setPositiveButton(R.string.submission_qr_code_scan_invalid_dialog_button_positive) { _, _ ->
+                            viewModel.navigateBack()
+                        }
+                        setNegativeButton(R.string.submission_qr_code_scan_invalid_dialog_button_negative) { _, _ ->
+                            viewModel.navigateToDispatcherScreen()
+                        }
+                        setOnCancelListener { viewModel.navigateToDispatcherScreen() }
+                    } else {
+                        setOnDismissListener { viewModel.navigateToDispatcherScreen() }
+                    }
+                }.show()
             }
-        }
-
-        when (state.test?.testResult) {
-            CoronaTestResult.PCR_POSITIVE ->
-                NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = PCR)
-
-            CoronaTestResult.PCR_OR_RAT_PENDING ->
-                NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type)
-
-            CoronaTestResult.PCR_NEGATIVE,
-            CoronaTestResult.PCR_INVALID,
-            CoronaTestResult.PCR_REDEEMED ->
-                NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = PCR)
-
-            CoronaTestResult.RAT_POSITIVE ->
-                NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = RAPID_ANTIGEN)
-
-            CoronaTestResult.RAT_NEGATIVE,
-            CoronaTestResult.RAT_INVALID,
-            CoronaTestResult.RAT_PENDING,
-            CoronaTestResult.RAT_REDEEMED ->
-                NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = RAPID_ANTIGEN)
-            null -> {
-                Timber.w("Successful API request, but test was null?")
-                return
+            is State.TestRegistered -> when {
+                state.test.isPositive ->
+                    NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type)
+                        .run { doNavigate(this) }
+
+                else ->
+                    NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type)
+                        .run { doNavigate(this) }
             }
-        }.run { doNavigate(this) }
+        }
     }
 
-    private fun redeemDialog(): DialogHelper.DialogInstance = DialogHelper.DialogInstance(
-        requireActivity(),
-        R.string.submission_error_dialog_web_tan_redeemed_title,
-        R.string.submission_error_dialog_web_tan_redeemed_body,
-        R.string.submission_error_dialog_web_tan_redeemed_button_positive
-    )
-
     private fun showCloseDialog() = MaterialAlertDialogBuilder(requireContext())
         .setTitle(R.string.request_gc_dialog_title)
         .setMessage(R.string.request_gc_dialog_message)
-        .setNegativeButton(R.string.request_gc_dialog_negative_button) { _, _ -> viewModel.navigateBack() }
+        .setNegativeButton(R.string.request_gc_dialog_negative_button) { _, _ -> }
         .setPositiveButton(R.string.request_gc_dialog_positive_button) { _, _ -> viewModel.navigateToHomeScreen() }
         .create()
         .show()
@@ -151,39 +134,4 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid
             }
         }
         .show(childFragmentManager, "RequestGreenCertificateFragment.MaterialDatePicker")
-
-    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance =
-        when (exception) {
-            is BadRequestException -> createInvalidScanDialog()
-            is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_generic_error_title,
-                R.string.submission_error_dialog_web_generic_network_error_body,
-                R.string.submission_error_dialog_web_generic_error_button_positive,
-                null,
-                true,
-                { viewModel.navigateToDispatcherScreen() }
-            )
-            else -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_generic_error_title,
-                R.string.submission_error_dialog_web_generic_error_body,
-                R.string.submission_error_dialog_web_generic_error_button_positive,
-                null,
-                true,
-                { viewModel.navigateToDispatcherScreen() }
-            )
-        }
-
-    private fun createInvalidScanDialog() = DialogHelper.DialogInstance(
-        requireActivity(),
-        R.string.submission_qr_code_scan_invalid_dialog_headline,
-        R.string.submission_qr_code_scan_invalid_dialog_body,
-        R.string.submission_qr_code_scan_invalid_dialog_button_positive,
-        R.string.submission_qr_code_scan_invalid_dialog_button_negative,
-        true,
-        { viewModel.navigateBack() },
-        { viewModel.navigateToDispatcherScreen() },
-        { viewModel.navigateToDispatcherScreen() }
-    )
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModel.kt
index ff0e272e71fbba2a1b6de47be1defa317be6f66c..cdbf4b7dcaac3a95a57f758e388f3e8be7102b7c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModel.kt
@@ -5,33 +5,22 @@ import androidx.lifecycle.MutableLiveData
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
-import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
-import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
-import kotlinx.coroutines.flow.first
 import org.joda.time.LocalDate
-import timber.log.Timber
 
 class RequestCovidCertificateViewModel @AssistedInject constructor(
-    @Assisted private val coronaTestQrCode: CoronaTestQRCode,
+    @Assisted private val testRegistrationRequest: TestRegistrationRequest,
     @Assisted("coronaTestConsent") private val coronaTestConsent: Boolean,
     @Assisted("deleteOldTest") private val deleteOldTest: Boolean,
-    private val qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor,
-    private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
-    private val submissionRepository: SubmissionRepository,
-    private val coronaTestRepository: CoronaTestRepository,
+    private val registrationStateProcessor: TestRegistrationStateProcessor,
 ) : CWAViewModel() {
 
-    // Test registration LiveData
-    val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning
-    val registrationState = qrCodeRegistrationStateProcessor.registrationState
-    val registrationError = qrCodeRegistrationStateProcessor.registrationError
-    val removalError = SingleLiveEvent<Throwable>()
+    val registrationState = registrationStateProcessor.state.asLiveData2()
 
     private val birthDateData = MutableLiveData<LocalDate>(null)
     val birthDate: LiveData<LocalDate> = birthDateData
@@ -58,38 +47,26 @@ class RequestCovidCertificateViewModel @AssistedInject constructor(
     }
 
     private fun registerAndMaybeDelete(dccConsent: Boolean) = launch {
-        if (deleteOldTest) removeOldTest()
-        registerWithDccConsent(dccConsent)
-    }
-
-    private suspend fun registerWithDccConsent(dccConsent: Boolean) {
-        val consentedQrCode = when (coronaTestQrCode) {
-            is CoronaTestQRCode.PCR -> coronaTestQrCode.copy(
+        val consentedQrCode = when (testRegistrationRequest) {
+            is CoronaTestQRCode.PCR -> testRegistrationRequest.copy(
                 dateOfBirth = birthDateData.value,
                 isDccConsentGiven = dccConsent
             )
-            is CoronaTestQRCode.RapidAntigen -> coronaTestQrCode.copy(isDccConsentGiven = dccConsent)
+            is CoronaTestQRCode.RapidAntigen -> testRegistrationRequest.copy(isDccConsentGiven = dccConsent)
+            else -> testRegistrationRequest
         }
 
-        qrCodeRegistrationStateProcessor.startQrCodeRegistration(consentedQrCode, coronaTestConsent)
-        if (coronaTestConsent) analyticsKeySubmissionCollector.reportAdvancedConsentGiven(consentedQrCode.type)
-    }
-
-    private suspend fun removeOldTest() {
-        try {
-            submissionRepository.testForType(coronaTestQrCode.type).first()?.let {
-                coronaTestRepository.removeTest(it.identifier)
-            } ?: Timber.e("Test for type ${coronaTestQrCode.type} is not found")
-        } catch (e: Exception) {
-            Timber.d(e, "removeOldTest failed")
-            removalError.postValue(e)
-        }
+        registrationStateProcessor.startRegistration(
+            request = consentedQrCode,
+            isSubmissionConsentGiven = coronaTestConsent,
+            allowReplacement = deleteOldTest
+        )
     }
 
     @AssistedFactory
     interface Factory : CWAViewModelFactory<RequestCovidCertificateViewModel> {
         fun create(
-            coronaTestQrCode: CoronaTestQRCode,
+            testRegistrationRequest: TestRegistrationRequest,
             @Assisted("coronaTestConsent") coronaTestConsent: Boolean,
             @Assisted("deleteOldTest") deleteOldTest: Boolean
         ): RequestCovidCertificateViewModel
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt
index 623f30a36ff938ee643c382133385a2ebe348b8c..f2b01f53a3c88b9ad1c6fb223465843dc8910274 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt
@@ -6,17 +6,12 @@ import android.view.accessibility.AccessibilityEvent
 import androidx.core.view.isVisible
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.navArgs
+import de.rki.coronawarnapp.NavGraphDirections
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder
-import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.databinding.FragmentSubmissionDeletionWarningBinding
-import de.rki.coronawarnapp.exception.http.BadRequestException
-import de.rki.coronawarnapp.exception.http.CwaClientError
-import de.rki.coronawarnapp.exception.http.CwaServerError
-import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor.State
 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.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.ui.doNavigate
@@ -38,7 +33,7 @@ class SubmissionDeletionWarningFragment : Fragment(R.layout.fragment_submission_
         factoryProducer = { viewModelFactory },
         constructorCall = { factory, _ ->
             factory as SubmissionDeletionWarningViewModel.Factory
-            factory.create(args.coronaTestQrCode, args.coronaTestTan, args.isConsentGiven)
+            factory.create(args.testRegistrationRequest, args.isConsentGiven)
         }
     )
     private val binding: FragmentSubmissionDeletionWarningBinding by viewBinding()
@@ -65,57 +60,38 @@ class SubmissionDeletionWarningFragment : Fragment(R.layout.fragment_submission_
         }
 
         viewModel.registrationState.observe2(this) { state ->
-            binding.submissionQrCodeScanSpinner.isVisible = state.isFetching
-            binding.continueButton.isVisible = !state.isFetching && state.coronaTest == null
-        }
-        viewModel.registrationError.observe2(this) {
-            showErrorDialog(it)
-            doNavigate(
-                SubmissionDeletionWarningFragmentDirections
-                    .actionSubmissionDeletionWarningFragmentToSubmissionDispatcherFragment()
-            )
-        }
+            val isWorking = state is State.Working
+            binding.apply {
+                submissionQrCodeScanSpinner.isVisible = isWorking
+                continueButton.isVisible = !isWorking
+            }
+            when (state) {
+                State.Idle,
+                State.Working -> {
+                    // Handled above
+                }
+                is State.Error -> {
+                    state.getDialogBuilder(requireContext()).show()
+                    SubmissionDeletionWarningFragmentDirections
+                        .actionSubmissionDeletionWarningFragmentToSubmissionDispatcherFragment()
+                        .run { doNavigate(this) }
+                }
+                is State.TestRegistered -> when {
+                    state.test.isPositive ->
+                        NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type)
+                            .run { doNavigate(this) }
 
-        viewModel.routeToScreen.observe2(this) {
-            Timber.d("Navigating to %s", it)
-            doNavigate(it)
-        }
-    }
+                    else ->
+                        NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type)
+                            .run { doNavigate(this) }
+                }
+            }
 
-    private fun showErrorDialog(exception: Throwable) = when (exception) {
-        is InvalidQRCodeException -> DialogHelper.DialogInstance(
-            context = requireActivity(),
-            title = R.string.submission_error_dialog_web_tan_redeemed_title,
-            message = R.string.submission_error_dialog_web_tan_redeemed_body,
-            cancelable = true,
-            positiveButton = R.string.submission_error_dialog_web_tan_redeemed_button_positive,
-            positiveButtonFunction = { /* dismiss */ },
-        ).run { DialogHelper.showDialog(this) }
-        is BadRequestException -> DialogHelper.DialogInstance(
-            context = requireActivity(),
-            title = R.string.submission_qr_code_scan_invalid_dialog_headline,
-            message = R.string.submission_qr_code_scan_invalid_dialog_body,
-            cancelable = true,
-            positiveButton = R.string.submission_qr_code_scan_invalid_dialog_button_positive,
-            positiveButtonFunction = { /* dismiss */ },
-        ).run { DialogHelper.showDialog(this) }
-        is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance(
-            context = requireActivity(),
-            title = R.string.submission_error_dialog_web_generic_error_title,
-            message = R.string.submission_error_dialog_web_generic_network_error_body,
-            cancelable = true,
-            positiveButton = R.string.submission_error_dialog_web_generic_error_button_positive,
-            positiveButtonFunction = { /* dismiss */ },
-        ).run { DialogHelper.showDialog(this) }
-        is CwaWebException -> DialogHelper.DialogInstance(
-            context = requireActivity(),
-            title = R.string.submission_error_dialog_web_generic_error_title,
-            message = R.string.submission_error_dialog_web_generic_error_body,
-            cancelable = true,
-            positiveButton = R.string.submission_error_dialog_web_generic_error_button_positive,
-            positiveButtonFunction = { /* dismiss */ },
-        ).run { DialogHelper.showDialog(this) }
-        else -> exception.toErrorDialogBuilder(requireContext()).show()
+            viewModel.routeToScreen.observe2(this) {
+                Timber.d("Navigating to %s", it)
+                doNavigate(it)
+            }
+        }
     }
 
     override fun onResume() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt
index 5db57987c07498586d0921592400b48e558619e8..7f479939cbfc28be5c9039e69e2abaf997bc222d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt
@@ -1,153 +1,81 @@
 package de.rki.coronawarnapp.ui.submission.deletionwarning
 
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
 import androidx.navigation.NavDirections
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.coronatest.CoronaTestRepository
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
-import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
 import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
-import de.rki.coronawarnapp.submission.SubmissionRepository
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
-import kotlinx.coroutines.flow.first
 import timber.log.Timber
 
 class SubmissionDeletionWarningViewModel @AssistedInject constructor(
-    @Assisted private val coronaTestQrCode: CoronaTestQRCode?,
-    @Assisted private val coronaTestQrTan: CoronaTestTAN?,
+    @Assisted private val testRegistrationRequest: TestRegistrationRequest,
     @Assisted private val isConsentGiven: Boolean,
-    private val submissionRepository: SubmissionRepository,
-    private val coronaTestRepository: CoronaTestRepository,
+    private val registrationStateProcessor: TestRegistrationStateProcessor,
 ) : CWAViewModel() {
 
     val routeToScreen = SingleLiveEvent<NavDirections>()
-    private val mutableRegistrationState = MutableLiveData(RegistrationState())
-    val registrationState: LiveData<RegistrationState> = mutableRegistrationState
-    val registrationError = SingleLiveEvent<Throwable>()
+    val registrationState = registrationStateProcessor.state.asLiveData2()
 
-    private fun getRegistrationType(): RegistrationType = if (coronaTestQrCode != null) {
-        RegistrationType.QR
-    } else {
-        RegistrationType.TAN
-    }
-
-    // If there is no qrCode, it must be a TAN, and TANs are always PCR
-    internal fun getTestType(): CoronaTest.Type = coronaTestQrCode?.type ?: CoronaTest.Type.PCR
+    internal fun getTestType(): CoronaTest.Type = testRegistrationRequest.type
 
     fun deleteExistingAndRegisterNewTest() = launch {
-        when {
-            coronaTestQrTan != null -> deleteExistingAndRegisterNewTestWitTAN()
-            else -> deleteExistingAndRegisterNewTestWithQrCode()
-        }
-    }
-
-    private suspend fun deleteExistingAndRegisterNewTestWithQrCode() = try {
-        requireNotNull(coronaTestQrCode) { "QR Code was unavailable" }
-        if (coronaTestQrCode.isDccSupportedByPoc) {
+        if (testRegistrationRequest.isDccSupportedByPoc) {
             SubmissionDeletionWarningFragmentDirections
                 .actionSubmissionDeletionWarningFragmentToRequestCovidCertificateFragment(
-                    coronaTestQrCode = coronaTestQrCode,
+                    testRegistrationRequest = testRegistrationRequest,
                     coronaTestConsent = isConsentGiven,
                     deleteOldTest = true
                 ).run { routeToScreen.postValue(this) }
         } else {
-            removeAndRegisterNew(coronaTestQrCode)
+            removeAndRegisterNew(testRegistrationRequest)
         }
-    } catch (e: Exception) {
-        Timber.e(e, "Error during test registration via QR code")
-        mutableRegistrationState.postValue(RegistrationState(isFetching = false))
-        registrationError.postValue(e)
     }
 
-    private suspend fun removeAndRegisterNew(
-        coronaTestQrCode: CoronaTestQRCode
-    ) {
-        // Remove existing test and wait until that is done
-        submissionRepository.testForType(coronaTestQrCode.type).first()?.let {
-            coronaTestRepository.removeTest(it.identifier)
-        } ?: Timber.w("Test we will replace with QR was already removed?")
-
-        mutableRegistrationState.postValue(RegistrationState(isFetching = true))
-        val coronaTest = submissionRepository.registerTest(coronaTestQrCode)
+    private suspend fun removeAndRegisterNew(request: TestRegistrationRequest) {
+        val newTest = registrationStateProcessor.startRegistration(
+            request = request,
+            isSubmissionConsentGiven = isConsentGiven,
+            allowReplacement = true
+        )
 
-        if (coronaTest.isRedeemed) {
-            Timber.d("New test was already redeemed, removing it again: %s", coronaTest)
-            // This does not wait until the test is removed,
-            // the exception handling should navigate the user to a new screen anyways
-            submissionRepository.removeTestFromDevice(type = coronaTest.type)
-            throw InvalidQRCodeException("Test is already redeemed")
+        if (newTest == null) {
+            Timber.w("Test registration failed.")
+            return
+        } else {
+            Timber.d("Continuing with our new CoronaTest: %s", newTest)
         }
 
-        if (isConsentGiven) submissionRepository.giveConsentToSubmission(type = coronaTestQrCode.type)
-
-        continueWithNewTest(coronaTest)
-        mutableRegistrationState.postValue(RegistrationState(coronaTest = coronaTest))
-    }
-
-    private suspend fun deleteExistingAndRegisterNewTestWitTAN() = try {
-        requireNotNull(coronaTestQrTan) { "TAN was unavailable" }
-
-        submissionRepository.testForType(CoronaTest.Type.PCR).first()?.let {
-            coronaTestRepository.removeTest(it.identifier)
-        } ?: Timber.w("Test we will replace with TAN was already removed?")
-
-        mutableRegistrationState.postValue(RegistrationState(isFetching = true))
-
-        val coronaTest = submissionRepository.registerTest(coronaTestQrTan)
-        continueWithNewTest(coronaTest)
-
-        mutableRegistrationState.postValue(RegistrationState(coronaTest = coronaTest))
-    } catch (e: Exception) {
-        Timber.e(e, "Error during test registration via TAN")
-        mutableRegistrationState.postValue(RegistrationState(isFetching = false))
-        registrationError.postValue(e)
-    }
-
-    fun onCancelButtonClick() {
-        SubmissionDeletionWarningFragmentDirections
-            .actionSubmissionDeletionWarningFragmentToSubmissionConsentFragment()
-            .run { routeToScreen.postValue(this) }
-    }
+        when (request) {
+            is CoronaTestTAN ->
+                SubmissionDeletionWarningFragmentDirections
+                    .actionSubmissionDeletionFragmentToSubmissionTestResultNoConsentFragment(newTest.type)
 
-    private fun continueWithNewTest(coronaTest: CoronaTest) {
-        Timber.d("Continuing with our new CoronaTest: %s", coronaTest)
-        val testType = coronaTestQrCode!!.type
-        when (getRegistrationType()) {
-            RegistrationType.QR -> if (coronaTest.isPositive) {
+            else -> if (newTest.isPositive) {
                 SubmissionDeletionWarningFragmentDirections
-                    .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment(testType)
+                    .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment(newTest.type)
             } else {
                 SubmissionDeletionWarningFragmentDirections
-                    .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment(testType)
+                    .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment(newTest.type)
             }
-
-            RegistrationType.TAN ->
-                SubmissionDeletionWarningFragmentDirections
-                    .actionSubmissionDeletionFragmentToSubmissionTestResultNoConsentFragment(getTestType())
         }.run { routeToScreen.postValue(this) }
     }
 
-    data class RegistrationState(
-        val isFetching: Boolean = false,
-        val coronaTest: CoronaTest? = null,
-    )
-
-    sealed class RegistrationType {
-        object TAN : RegistrationType()
-        object QR : RegistrationType()
+    fun onCancelButtonClick() {
+        SubmissionDeletionWarningFragmentDirections
+            .actionSubmissionDeletionWarningFragmentToSubmissionConsentFragment()
+            .run { routeToScreen.postValue(this) }
     }
 
     @AssistedFactory
     interface Factory : CWAViewModelFactory<SubmissionDeletionWarningViewModel> {
         fun create(
-            coronaTestQrCode: CoronaTestQRCode?,
-            coronaTestTan: CoronaTestTAN?,
+            testRegistrationRequest: TestRegistrationRequest,
             isConsentGiven: Boolean
         ): SubmissionDeletionWarningViewModel
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt
deleted file mode 100644
index 19ec160f5228e0ec0336abb76c5afdf7f7c9a180..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.qrcode
-
-import androidx.lifecycle.MutableLiveData
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
-import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException
-import de.rki.coronawarnapp.coronatest.type.CoronaTest
-import de.rki.coronawarnapp.exception.ExceptionCategory
-import de.rki.coronawarnapp.exception.http.CwaWebException
-import de.rki.coronawarnapp.exception.reporting.report
-import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
-import timber.log.Timber
-import javax.inject.Inject
-
-class QrCodeRegistrationStateProcessor @Inject constructor(
-    private val submissionRepository: SubmissionRepository
-) {
-
-    data class RegistrationState(
-        val apiRequestState: ApiRequestState,
-        val test: CoronaTest? = null
-    )
-
-    val showRedeemedTokenWarning = SingleLiveEvent<Unit>()
-    val registrationState = MutableLiveData(RegistrationState(ApiRequestState.IDLE))
-    val registrationError = SingleLiveEvent<CwaWebException>()
-
-    suspend fun startQrCodeRegistration(coronaTestQRCode: CoronaTestQRCode, isConsentGiven: Boolean) =
-        try {
-            registrationState.postValue(RegistrationState(ApiRequestState.STARTED))
-            val coronaTest = submissionRepository.registerTest(coronaTestQRCode)
-            if (isConsentGiven) {
-                submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type)
-            }
-            checkTestResult(coronaTestQRCode, coronaTest)
-            registrationState.postValue(RegistrationState(ApiRequestState.SUCCESS, coronaTest))
-        } catch (err: CwaWebException) {
-            registrationState.postValue(RegistrationState(ApiRequestState.FAILED))
-            registrationError.postValue(err)
-        } catch (err: InvalidQRCodeException) {
-            registrationState.postValue(RegistrationState(ApiRequestState.FAILED))
-            Timber.d("deregisterTestFromDevice()")
-            submissionRepository.removeTestFromDevice(type = coronaTestQRCode.type)
-            showRedeemedTokenWarning.postValue(Unit)
-        } catch (err: Exception) {
-            registrationState.postValue(RegistrationState(ApiRequestState.FAILED))
-            err.report(ExceptionCategory.INTERNAL)
-        }
-
-    private fun checkTestResult(request: CoronaTestQRCode, test: CoronaTest) {
-        if (test.isRedeemed) {
-            throw InvalidQRCodeException("CoronaTestResult already redeemed ${request.registrationIdentifier}")
-        }
-    }
-
-    enum class ValidationState {
-        STARTED, INVALID, SUCCESS
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt
index e5fd022f5390e06bdbdb6a4a1c91e76dcb3429e4..6c5384b49dc6b328b67a05eb293109ce9f05cbdd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt
@@ -10,14 +10,8 @@ import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.navArgs
 import de.rki.coronawarnapp.NavGraphDirections
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.databinding.FragmentSubmissionConsentBinding
-import de.rki.coronawarnapp.exception.http.BadRequestException
-import de.rki.coronawarnapp.exception.http.CwaClientError
-import de.rki.coronawarnapp.exception.http.CwaServerError
-import de.rki.coronawarnapp.exception.http.CwaWebException
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor.ValidationState
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor.State
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
@@ -61,71 +55,54 @@ class SubmissionConsentFragment : Fragment(R.layout.fragment_submission_consent)
                         requireActivity(),
                         REQUEST_USER_RESOLUTION
                     )
-                is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> {
-                    doNavigate(
-                        NavGraphDirections
-                            .actionToSubmissionDeletionWarningFragment(
-                                it.consentGiven,
-                                it.coronaTestQRCode
-                            )
+                is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> doNavigate(
+                    NavGraphDirections.actionToSubmissionDeletionWarningFragment(
+                        testRegistrationRequest = it.coronaTestQRCode,
+                        isConsentGiven = it.consentGiven,
                     )
-                }
+                )
+                is SubmissionNavigationEvents.NavigateToRequestDccFragment -> doNavigate(
+                    NavGraphDirections.actionRequestCovidCertificateFragment(
+                        testRegistrationRequest = it.coronaTestQRCode,
+                        coronaTestConsent = it.consentGiven
+                    )
+                )
             }
         }
         viewModel.countries.observe2(this) {
             binding.countries = it
         }
 
-        viewModel.showRedeemedTokenWarning.observe2(this) {
-            val dialog = DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_tan_redeemed_title,
-                R.string.submission_error_dialog_web_tan_redeemed_body,
-                R.string.submission_error_dialog_web_tan_redeemed_button_positive
-            )
-
-            DialogHelper.showDialog(dialog)
-            popBackStack()
-        }
-
-        viewModel.qrCodeValidationState.observe2(this) {
-            if (ValidationState.INVALID == it) {
-                showInvalidQrCodeDialog()
-            }
+        viewModel.qrCodeError.observe2(this) {
+            showInvalidQrCodeDialog()
         }
 
         viewModel.registrationState.observe2(this) { state ->
-            binding.progressSpinner.isVisible = state.apiRequestState == ApiRequestState.STARTED
-            binding.submissionConsentButton.isEnabled = when (state.apiRequestState) {
-                ApiRequestState.STARTED -> false
-                else -> true
+            val isWorking = state is State.Working
+            binding.apply {
+                progressSpinner.isVisible = isWorking
+                submissionConsentButton.isEnabled = !isWorking
             }
-
-            if (ApiRequestState.SUCCESS == state.apiRequestState) {
-                when (state.test?.type) {
-                    CoronaTest.Type.PCR -> throw UnsupportedOperationException()
-                    CoronaTest.Type.RAPID_ANTIGEN -> {
-                        when {
-                            state.test.isPositive ->
-                                doNavigate(
-                                    NavGraphDirections.actionToSubmissionTestResultAvailableFragment(
-                                        CoronaTest.Type.RAPID_ANTIGEN
-                                    )
-                                )
-                            else -> doNavigate(
-                                NavGraphDirections.actionSubmissionTestResultPendingFragment(
-                                    testType = CoronaTest.Type.RAPID_ANTIGEN
-                                )
-                            )
-                        }
-                    }
+            when (state) {
+                State.Idle,
+                State.Working -> {
+                    // Handled above
+                }
+                is State.Error -> {
+                    state.getDialogBuilder(requireContext()).show()
+                    popBackStack()
+                }
+                is State.TestRegistered -> when {
+                    state.test.isPositive ->
+                        NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type)
+                            .run { doNavigate(this) }
+
+                    else ->
+                        NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type)
+                            .run { doNavigate(this) }
                 }
             }
         }
-
-        viewModel.registrationError.observe2(this) {
-            DialogHelper.showDialog(buildErrorDialog(it))
-        }
     }
 
     override fun onResume() {
@@ -149,49 +126,15 @@ class SubmissionConsentFragment : Fragment(R.layout.fragment_submission_consent)
             R.string.submission_qr_code_scan_invalid_dialog_button_negative,
             true,
             positiveButtonFunction = {},
-            negativeButtonFunction = ::navigateHome
+            negativeButtonFunction = {
+                popBackStack()
+                Unit
+            }
         )
 
         DialogHelper.showDialog(invalidScanDialogInstance)
     }
 
-    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
-        return when (exception) {
-            is BadRequestException -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_qr_code_scan_invalid_dialog_headline,
-                R.string.submission_qr_code_scan_invalid_dialog_body,
-                R.string.submission_qr_code_scan_invalid_dialog_button_positive,
-                R.string.submission_qr_code_scan_invalid_dialog_button_negative,
-                true,
-                { },
-                ::navigateHome
-            )
-            is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_generic_error_title,
-                R.string.submission_error_dialog_web_generic_network_error_body,
-                R.string.submission_error_dialog_web_generic_error_button_positive,
-                null,
-                true,
-                ::navigateHome
-            )
-            else -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_generic_error_title,
-                R.string.submission_error_dialog_web_generic_error_body,
-                R.string.submission_error_dialog_web_generic_error_button_positive,
-                null,
-                true,
-                ::navigateHome
-            )
-        }
-    }
-
-    private fun navigateHome() {
-        popBackStack()
-    }
-
     companion object {
         private const val REQUEST_USER_RESOLUTION = 3000
         fun canHandle(rootUri: String): Boolean = rootUri.startsWith("https://s.coronawarn.app")
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 f6876b62aeebd0f5662569e1a26073a0fd7e8e66..643028c08ebfdd80dd2992c326020d243adc3031 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
@@ -9,7 +9,7 @@ import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException
 import de.rki.coronawarnapp.nearby.modules.tekhistory.TEKHistoryProvider
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
@@ -22,17 +22,14 @@ class SubmissionConsentViewModel @AssistedInject constructor(
     interoperabilityRepository: InteroperabilityRepository,
     dispatcherProvider: DispatcherProvider,
     private val tekHistoryProvider: TEKHistoryProvider,
-    private val qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor,
+    private val registrationStateProcessor: TestRegistrationStateProcessor,
     private val submissionRepository: SubmissionRepository,
     private val qrCodeValidator: CoronaTestQrCodeValidator
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
     val routeToScreen = SingleLiveEvent<SubmissionNavigationEvents>()
-    val qrCodeValidationState = SingleLiveEvent<QrCodeRegistrationStateProcessor.ValidationState>()
-
-    val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning
-    val registrationState = qrCodeRegistrationStateProcessor.registrationState
-    val registrationError = qrCodeRegistrationStateProcessor.registrationError
+    val qrCodeError = SingleLiveEvent<Exception>()
+    val registrationState = registrationStateProcessor.state.asLiveData2()
 
     val countries = interoperabilityRepository.countryList
         .asLiveData(context = dispatcherProvider.Default)
@@ -76,24 +73,36 @@ class SubmissionConsentViewModel @AssistedInject constructor(
     }
 
     private suspend fun validateAndRegister(qrCodeString: String) {
-        try {
-            val coronaTestQRCode = qrCodeValidator.validate(qrCodeString)
-            qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.SUCCESS)
-            val coronaTest = submissionRepository.testForType(coronaTestQRCode.type).first()
+        val coronaTestQRCode = try {
+            qrCodeValidator.validate(qrCodeString)
+        } catch (err: InvalidQRCodeException) {
+            Timber.i(err, "Failed to validate QRCode")
+            qrCodeError.postValue(err)
+            return
+        }
+
+        val coronaTest = submissionRepository.testForType(coronaTestQRCode.type).first()
 
-            if (coronaTest != null) {
-                routeToScreen.postValue(
-                    SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode(
-                        coronaTestQRCode,
-                        consentGiven = true
-                    )
+        when {
+            coronaTest != null -> {
+                SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode(
+                    coronaTestQRCode,
+                    consentGiven = true
+                ).run { routeToScreen.postValue(this) }
+            }
+            coronaTestQRCode.isDccSupportedByPoc && !coronaTestQRCode.isDccConsentGiven -> {
+                SubmissionNavigationEvents.NavigateToRequestDccFragment(
+                    coronaTestQRCode = coronaTestQRCode,
+                    consentGiven = true,
+                ).run { routeToScreen.postValue(this) }
+            }
+            else -> {
+                registrationStateProcessor.startRegistration(
+                    request = coronaTestQRCode,
+                    isSubmissionConsentGiven = true,
+                    allowReplacement = false
                 )
-            } else {
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(coronaTestQRCode, true)
             }
-        } catch (err: InvalidQRCodeException) {
-            Timber.i(err)
-            qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.INVALID)
         }
     }
 
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 6839584b08883c9d03fcb3e5b107c8123a8bf331..6484865f7cb2706c9559ad71214401bec7d2b5fc 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
@@ -7,19 +7,15 @@ import android.view.View
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.navArgs
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.zxing.BarcodeFormat
 import com.journeyapps.barcodescanner.DefaultDecoderFactory
 import de.rki.coronawarnapp.NavGraphDirections
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
-import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type
+import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException
 import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding
 import de.rki.coronawarnapp.exception.http.BadRequestException
-import de.rki.coronawarnapp.exception.http.CwaClientError
-import de.rki.coronawarnapp.exception.http.CwaServerError
-import de.rki.coronawarnapp.exception.http.CwaWebException
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor.State
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
@@ -30,7 +26,6 @@ import de.rki.coronawarnapp.util.ui.popBackStack
 import de.rki.coronawarnapp.util.ui.viewBinding
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
-import timber.log.Timber
 import javax.inject.Inject
 
 /**
@@ -72,7 +67,10 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
             when (it) {
                 is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> {
                     NavGraphDirections
-                        .actionToSubmissionDeletionWarningFragment(it.consentGiven, it.coronaTestQRCode)
+                        .actionToSubmissionDeletionWarningFragment(
+                            testRegistrationRequest = it.coronaTestQRCode,
+                            isConsentGiven = it.consentGiven,
+                        )
                         .run { doNavigate(this) }
                 }
                 is SubmissionNavigationEvents.NavigateToDispatcher -> navigateToDispatchScreen()
@@ -83,57 +81,44 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
             }
         }
 
-        viewModel.qrCodeValidationState.observe2(this) {
-            if (QrCodeRegistrationStateProcessor.ValidationState.INVALID == it) {
-                DialogHelper.showDialog(createInvalidScanDialog())
-            }
-        }
-
-        viewModel.registrationError.observe2(this) {
-            DialogHelper.showDialog(buildErrorDialog(it))
-        }
-        viewModel.showRedeemedTokenWarning.observe2(this) {
-            val dialog = DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_tan_redeemed_title,
-                R.string.submission_error_dialog_web_tan_redeemed_body,
-                R.string.submission_error_dialog_web_tan_redeemed_button_positive
-            )
-
-            DialogHelper.showDialog(dialog)
-            goBack()
+        viewModel.qrCodeErrorEvent.observe2(this) {
+            showInvalidQrCodeDialog()
         }
 
         viewModel.registrationState.observe2(this) { state ->
-            when (state.apiRequestState) {
-                ApiRequestState.STARTED -> binding.submissionQrCodeScanSpinner.show()
-                else -> binding.submissionQrCodeScanSpinner.hide()
+            if (state is State.Working) {
+                binding.submissionQrCodeScanSpinner.show()
+            } else {
+                binding.submissionQrCodeScanSpinner.hide()
             }
-            when (state.test?.testResult) {
-                CoronaTestResult.PCR_POSITIVE ->
-                    NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = Type.PCR)
-
-                CoronaTestResult.PCR_OR_RAT_PENDING ->
-                    NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type)
-
-                CoronaTestResult.PCR_NEGATIVE,
-                CoronaTestResult.PCR_INVALID,
-                CoronaTestResult.PCR_REDEEMED ->
-                    NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = Type.PCR)
-
-                CoronaTestResult.RAT_POSITIVE ->
-                    NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = Type.RAPID_ANTIGEN)
-
-                CoronaTestResult.RAT_NEGATIVE,
-                CoronaTestResult.RAT_INVALID,
-                CoronaTestResult.RAT_PENDING,
-                CoronaTestResult.RAT_REDEEMED ->
-                    NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = Type.RAPID_ANTIGEN)
-                null -> {
-                    Timber.w("Successful API request, but test was null?")
-                    return@observe2
+            when (state) {
+                State.Idle,
+                State.Working -> {
+                    // Handled above
+                }
+                is State.Error -> {
+                    when (state.exception) {
+                        is BadRequestException -> showInvalidQrCodeDialog()
+                        else -> {
+                            state.getDialogBuilder(requireContext()).apply {
+                                when (state.exception) {
+                                    is AlreadyRedeemedException -> setOnDismissListener { goBack() }
+                                    else -> setOnDismissListener { navigateToDispatchScreen() }
+                                }
+                            }.show()
+                        }
+                    }
                 }
-            }.run { doNavigate(this) }
+                is State.TestRegistered -> when {
+                    state.test.isPositive ->
+                        NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type)
+                            .run { doNavigate(this) }
+
+                    else ->
+                        NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type)
+                            .run { doNavigate(this) }
+                }
+            }
         }
     }
 
@@ -143,45 +128,23 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
         }
     }
 
-    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
-        return when (exception) {
-            is BadRequestException -> createInvalidScanDialog()
-            is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_generic_error_title,
-                R.string.submission_error_dialog_web_generic_network_error_body,
-                R.string.submission_error_dialog_web_generic_error_button_positive,
-                null,
-                true,
-                ::navigateToDispatchScreen
-            )
-            else -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_generic_error_title,
-                R.string.submission_error_dialog_web_generic_error_body,
-                R.string.submission_error_dialog_web_generic_error_button_positive,
-                null,
-                true,
-                ::navigateToDispatchScreen
-            )
-        }
-    }
-
     private fun navigateToDispatchScreen() = doNavigate(
         SubmissionQRCodeScanFragmentDirections.actionSubmissionQRCodeScanFragmentToSubmissionDispatcherFragment()
     )
 
-    private fun createInvalidScanDialog() = DialogHelper.DialogInstance(
-        requireActivity(),
-        R.string.submission_qr_code_scan_invalid_dialog_headline,
-        R.string.submission_qr_code_scan_invalid_dialog_body,
-        R.string.submission_qr_code_scan_invalid_dialog_button_positive,
-        R.string.submission_qr_code_scan_invalid_dialog_button_negative,
-        true,
-        { startDecode() },
-        { viewModel.onBackPressed() },
-        { viewModel.onBackPressed() }
-    )
+    private fun showInvalidQrCodeDialog() {
+        MaterialAlertDialogBuilder(requireContext()).apply {
+            setTitle(R.string.submission_qr_code_scan_invalid_dialog_headline)
+            setMessage(R.string.submission_qr_code_scan_invalid_dialog_body)
+            setPositiveButton(R.string.submission_qr_code_scan_invalid_dialog_button_positive) { _, _ ->
+                startDecode()
+            }
+            setNegativeButton(R.string.submission_qr_code_scan_invalid_dialog_button_negative) { _, _ ->
+                viewModel.onBackPressed()
+            }
+            setOnCancelListener { viewModel.onBackPressed() }
+        }.show()
+    }
 
     override fun onRequestPermissionsResult(
         requestCode: Int,
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 b1e0a834805da9dffe95c759703f5ef3752ce5f9..7cc747194e11cccf1cfc0319a132d1dbad705fa6 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,9 +5,8 @@ import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator
 import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException
-import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.permission.CameraSettings
@@ -21,21 +20,19 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     @Assisted private val isConsentGiven: Boolean,
     private val cameraSettings: CameraSettings,
-    private val qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor,
+    private val registrationStateProcessor: TestRegistrationStateProcessor,
     private val submissionRepository: SubmissionRepository,
     private val qrCodeValidator: CoronaTestQrCodeValidator,
-    private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
     val events = SingleLiveEvent<SubmissionNavigationEvents>()
-    val qrCodeValidationState = SingleLiveEvent<QrCodeRegistrationStateProcessor.ValidationState>()
-    val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning
-    val registrationState = qrCodeRegistrationStateProcessor.registrationState
-    val registrationError = qrCodeRegistrationStateProcessor.registrationError
+    val qrCodeErrorEvent = SingleLiveEvent<Exception>()
+    val registrationState = registrationStateProcessor.state.asLiveData2()
 
     fun registerCoronaTest(rawResult: String) = launch {
         try {
             val ctQrCode = qrCodeValidator.validate(rawResult)
-            qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.SUCCESS)
+
             val coronaTest = submissionRepository.testForType(ctQrCode.type).first()
             when {
                 coronaTest != null -> events.postValue(
@@ -46,8 +43,11 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
                 )
 
                 else -> if (!ctQrCode.isDccSupportedByPoc) {
-                    qrCodeRegistrationStateProcessor.startQrCodeRegistration(ctQrCode, isConsentGiven)
-                    if (isConsentGiven) analyticsKeySubmissionCollector.reportAdvancedConsentGiven(ctQrCode.type)
+                    registrationStateProcessor.startRegistration(
+                        request = ctQrCode,
+                        isSubmissionConsentGiven = isConsentGiven,
+                        allowReplacement = false
+                    )
                 } else {
                     events.postValue(
                         SubmissionNavigationEvents.NavigateToRequestDccFragment(ctQrCode, isConsentGiven)
@@ -56,7 +56,7 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
             }
         } catch (err: InvalidQRCodeException) {
             Timber.d(err, "Invalid QrCode")
-            qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.INVALID)
+            qrCodeErrorEvent.postValue(err)
         }
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt
index 97ea51f53dcb69910aeda363e1097fbcf3f10aff..f669290e7550a91070a4e39dff6d96c0224d0207 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt
@@ -55,8 +55,8 @@ class SubmissionTanFragment : Fragment(R.layout.fragment_submission_tan), AutoIn
                 is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromTan ->
                     doNavigate(
                         SubmissionTanFragmentDirections.actionSubmissionTanFragmentToSubmissionDeletionWarningFragment(
+                            testRegistrationRequest = it.coronaTestTan,
                             isConsentGiven = it.consentGiven,
-                            coronaTestTan = it.coronaTestTan
                         )
                     )
             }
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 b3eb8f9bb14043d99eab30fe568aa671ef9f4a2f..b3e8bdd3c47256a29473af30801a6eae25b390aa 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
@@ -4,6 +4,8 @@ import android.content.DialogInterface
 import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityEvent
+import androidx.appcompat.content.res.AppCompatResources.getDrawable
+import androidx.core.view.isGone
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.navArgs
 import de.rki.coronawarnapp.R
@@ -45,7 +47,38 @@ class SubmissionTestResultNegativeFragment : Fragment(R.layout.fragment_submissi
         }
 
         viewModel.testResult.observe2(this) {
-            binding.submissionTestResultSection.setTestResultSection(it.coronaTest)
+            binding.apply {
+                submissionTestResultSection.setTestResultSection(it.coronaTest)
+
+                when (it.certificateState) {
+                    SubmissionTestResultNegativeViewModel.CertificateState.NOT_REQUESTED -> {
+                        testResultNegativeStepsNegativeResult.setIsFinal(true)
+                        testResultNegativeStepsCertificate.isGone = true
+                    }
+                    SubmissionTestResultNegativeViewModel.CertificateState.PENDING -> {
+                        testResultNegativeStepsNegativeResult.setIsFinal(false)
+                        testResultNegativeStepsCertificate.isGone = false
+                        testResultNegativeStepsCertificate.setEntryText(
+                            getText(
+                                R.string.submission_test_result_pending_steps_test_certificate_not_available_yet_body
+                            )
+                        )
+                        testResultNegativeStepsCertificate.setIcon(
+                            getDrawable(requireContext(), R.drawable.ic_result_pending_certificate_info)
+                        )
+                    }
+                    SubmissionTestResultNegativeViewModel.CertificateState.AVAILABLE -> {
+                        testResultNegativeStepsNegativeResult.setIsFinal(false)
+                        testResultNegativeStepsCertificate.isGone = false
+                        testResultNegativeStepsCertificate.setEntryText(
+                            getText(R.string.coronatest_negative_result_certificate_info_body)
+                        )
+                        testResultNegativeStepsCertificate.setIcon(
+                            getDrawable(requireContext(), R.drawable.ic_qr_code_illustration)
+                        )
+                    }
+                }
+            }
         }
 
         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 98ed23aeeba648f2888d14b4eddd749a73c2f73e..b9f77425471d4a5524286cfa86f1fcdcaeeed465 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
@@ -1,6 +1,5 @@
 package de.rki.coronawarnapp.ui.submission.testresult.negative
 
-import androidx.lifecycle.LiveData
 import androidx.lifecycle.asLiveData
 import androidx.navigation.NavDirections
 import dagger.assisted.Assisted
@@ -8,19 +7,20 @@ import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.coronatest.type.pcr.notification.PCRTestResultAvailableNotificationService
+import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository
 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.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
 import timber.log.Timber
 
 class SubmissionTestResultNegativeViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     private val submissionRepository: SubmissionRepository,
+    certificateRepository: TestCertificateRepository,
     private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService,
     @Assisted private val testType: CoronaTest.Type
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
@@ -30,11 +30,25 @@ class SubmissionTestResultNegativeViewModel @AssistedInject constructor(
     }
 
     val routeToScreen = SingleLiveEvent<NavDirections?>()
-    val testResult: LiveData<TestResultUIState> = submissionRepository.testForType(type = testType)
-        .filterNotNull()
-        .map { test ->
-            TestResultUIState(coronaTest = test)
-        }.asLiveData(context = dispatcherProvider.Default)
+    val testResult = combine(
+        submissionRepository.testForType(type = testType).filterNotNull(),
+        certificateRepository.certificates
+    ) { test, certs ->
+        val cert = certs.firstOrNull {
+            it.registrationToken == test.registrationToken
+        }
+
+        val certificateState: CertificateState = when (cert?.isCertificateRetrievalPending) {
+            true -> CertificateState.PENDING
+            false -> CertificateState.AVAILABLE
+            else -> CertificateState.NOT_REQUESTED
+        }
+
+        UIState(
+            coronaTest = test,
+            certificateState = certificateState
+        )
+    }.asLiveData(context = dispatcherProvider.Default)
 
     fun deregisterTestFromDevice() = launch {
         Timber.tag(TAG).d("deregisterTestFromDevice()")
@@ -49,6 +63,17 @@ class SubmissionTestResultNegativeViewModel @AssistedInject constructor(
         testResultAvailableNotificationService.cancelTestResultAvailableNotification()
     }
 
+    enum class CertificateState {
+        NOT_REQUESTED,
+        PENDING,
+        AVAILABLE
+    }
+
+    data class UIState(
+        val coronaTest: CoronaTest,
+        val certificateState: CertificateState
+    )
+
     @AssistedFactory
     interface Factory : CWAViewModelFactory<SubmissionTestResultNegativeViewModel> {
         fun create(testType: CoronaTest.Type): SubmissionTestResultNegativeViewModel
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 0a7ea7fe96df788be96c9b00af8de2dc9ff166ac..599af6b26539326859a360ec7374b093dc7e373b 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
@@ -106,7 +106,7 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
                     R.string.submission_test_result_pending_steps_test_certificate_not_supported_body
                 }
                 else -> {
-                    if (it.coronaTest.isAdvancedConsentGiven) {
+                    if (it.coronaTest.isDccConsentGiven) {
                         R.string.submission_test_result_pending_steps_test_certificate_not_available_yet_body
                     } else {
                         R.string.submission_test_result_pending_steps_test_certificate_not_desired_by_user_body
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt
index f8c117a56350b2d839999f3f3d1c45a3d090513b..d6f50ad585e4ede14cbc4d64433f8912901f26f8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt
@@ -1,6 +1,7 @@
 package de.rki.coronawarnapp.ui.view
 
 import android.content.Context
+import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewGroup
@@ -20,6 +21,9 @@ open class StepEntry @JvmOverloads constructor(
 
     val body: FrameLayout?
 
+    private lateinit var entryLine: View
+    private lateinit var entryIcon: ImageView
+
     init {
         inflate(context, R.layout.view_step_entry, this)
 
@@ -27,14 +31,24 @@ open class StepEntry @JvmOverloads constructor(
 
         context.withStyledAttributes(attrs, R.styleable.StepEntry) {
             val icon = getDrawable(R.styleable.StepEntry_step_entry_icon)
-            findViewById<ImageView>(R.id.step_entry_icon).setImageDrawable(icon)
+            entryIcon = findViewById(R.id.step_entry_icon)
+            setIcon(icon)
 
             val isFinal = getBoolean(R.styleable.StepEntry_step_entry_final, false)
-            findViewById<View>(R.id.step_entry_line).visibility = if (isFinal) {
-                View.INVISIBLE
-            } else {
-                View.VISIBLE
-            }
+            entryLine = findViewById(R.id.step_entry_line)
+            setIsFinal(isFinal)
+        }
+    }
+
+    fun setIcon(icon: Drawable?) {
+        entryIcon.setImageDrawable(icon)
+    }
+
+    fun setIsFinal(isFinal: Boolean) {
+        entryLine.visibility = if (isFinal) {
+            View.INVISIBLE
+        } else {
+            View.VISIBLE
         }
     }
 
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 5175efcfa2e55acfd56450775bef96df63f6de12..401f895a839666f8542a2477d1f34003b96f60f3 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
@@ -121,16 +121,26 @@ object TimeAndDateExtensions {
      */
     fun LocalDate.toDayFormat(): String = toString(dayFormatter)
 
+    /**
+     * Returns a readable date String with the format "dd.MM.yyyy" like 23.05.1989 of a DateTime
+     */
+    fun DateTime.toDayFormat(): String = toString(dayFormatter)
+
     /**
      * Returns a readable time String with the format "hh:mm" like 12:00 of a LocalDate
      */
     fun LocalDate.toShortTimeFormat(): String = toString(shortTime)
 
     /**
-     * Returns a readable time String with the format "hh:mm" like 12:00 of a LocalDate
+     * Returns a readable time String with the format "hh:mm" like 12:00 of a Instant
      */
     fun Instant.toShortTimeFormat(): String = toString(shortTime)
 
+    /**
+     * Returns a readable time String with the format "hh:mm" like 12:00 of a DateTime
+     */
+    fun DateTime.toShortTimeFormat(): String = toString(shortTime)
+
     /**
      * Returns a readable date String with the format "dd.MM.yy" like 23.05.89 of an Instant
      */
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/BottomNavigationViewExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/BottomNavigationViewExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e118feb598543f94085928d719c1850abbf3b246
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/BottomNavigationViewExtensions.kt
@@ -0,0 +1,15 @@
+package de.rki.coronawarnapp.util.ui
+
+import androidx.annotation.IdRes
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat
+
+fun BottomNavigationView.updateCountBadge(@IdRes badgeId: Int, count: Int) {
+    if (count > 0) {
+        val badge = getOrCreateBadge(badgeId)
+        badge.number = count
+        badge.badgeTextColor = context.getColorCompat(android.R.color.white)
+    } else {
+        removeBadge(badgeId)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
index eca763ca8350351466411c1cdbf9051ac9d5bde8..d132186293c7d25e07426910920b187be39ec3b6 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.util.viewmodel
 
 import androidx.annotation.CallSuper
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
 import androidx.lifecycle.viewModelScope
 import de.rki.coronawarnapp.util.coroutine.DefaultDispatcherProvider
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
@@ -27,6 +28,8 @@ abstract class CWAViewModel constructor(
         Timber.tag(tag).v("Initialized")
     }
 
+    fun <T> Flow<T>.asLiveData2() = asLiveData(context = dispatcherProvider.Default)
+
     /**
      * This launches a coroutine on another thread
      * Remember to switch to the main thread if you want to update the UI directly
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml
index 0a623944ccacd4ba886906078827ffa1bdbf0095..1f89e65894908f1bfe8cf3896ccd01875d31ea00 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml
@@ -226,9 +226,22 @@
                 app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_negative_result"
                 app:simple_step_entry_text="@string/coronatest_negative_antigen_restul_third_info_body"
                 app:simple_step_entry_title="@string/coronatest_negative_antigen_result_third_info_title"
-                app:step_entry_final="true"
+                app:step_entry_final="false"
                 app:step_entry_icon="@drawable/ic_test_result_delete_test" />
 
+            <de.rki.coronawarnapp.ui.view.SimpleStepEntry
+                android:id="@+id/coronatest_negative_antigen_result_fourth_info"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="@dimen/spacing_normal"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="@id/coronatest_negative_antigen_result_third_info"
+                app:layout_constraintTop_toBottomOf="@+id/coronatest_negative_antigen_result_third_info"
+                app:simple_step_entry_text="@string/coronatest_negative_result_certificate_info_body"
+                app:simple_step_entry_title="@string/coronatest_negative_result_certificate_info_title"
+                app:step_entry_final="true"
+                app:step_entry_icon="@drawable/ic_qr_code_illustration" />
+
             <LinearLayout
                 android:id="@+id/further_info"
                 android:layout_width="match_parent"
@@ -236,7 +249,7 @@
                 android:background="@color/colorSurface2"
                 android:orientation="vertical"
                 android:padding="@dimen/spacing_normal"
-                app:layout_constraintTop_toBottomOf="@id/coronatest_negative_antigen_result_third_info">
+                app:layout_constraintTop_toBottomOf="@id/coronatest_negative_antigen_result_fourth_info">
 
                 <TextView
                     android:id="@+id/further_info_title"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml
index 49d5aab4c16290b5dbfae8b3908674b1f6f69173..bba45502a73051f0722591844122fe8cc7529596 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml
@@ -88,9 +88,22 @@
                     app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_added"
                     app:simple_step_entry_text="@string/submission_test_result_negative_steps_negative_body"
                     app:simple_step_entry_title="@string/submission_test_result_negative_steps_negative_heading"
-                    app:step_entry_final="true"
+                    app:step_entry_final="false"
                     app:step_entry_icon="@drawable/ic_test_result_step_done" />
 
+                <de.rki.coronawarnapp.ui.view.SimpleStepEntry
+                    android:id="@+id/test_result_negative_steps_certificate"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginEnd="@dimen/spacing_normal"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="@+id/test_result_negative_steps_negative_result"
+                    app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_negative_result"
+                    app:simple_step_entry_text="@string/coronatest_negative_result_certificate_info_body"
+                    app:simple_step_entry_title="@string/coronatest_negative_result_certificate_info_title"
+                    app:step_entry_final="true"
+                    app:step_entry_icon="@drawable/ic_qr_code_illustration" />
+
                 <LinearLayout
                     android:id="@+id/further_info"
                     android:layout_width="match_parent"
@@ -98,7 +111,7 @@
                     android:background="@color/colorSurface2"
                     android:orientation="vertical"
                     android:padding="@dimen/spacing_normal"
-                    app:layout_constraintTop_toBottomOf="@id/test_result_negative_steps_negative_result">
+                    app:layout_constraintTop_toBottomOf="@id/test_result_negative_steps_certificate">
 
                     <TextView
                         android:id="@+id/further_info_title"
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 217906dc87c006ef5b5aba2b0eca4c7bf84c34dc..1d171160791470a43dca36467ad62968b6508433 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -709,20 +709,13 @@
         android:name="de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningFragment"
         android:label="SubmissionDeletionWarningFragment"
         tools:layout="@layout/fragment_submission_deletion_warning">
+        <argument
+            android:name="testRegistrationRequest"
+            app:argType="de.rki.coronawarnapp.coronatest.TestRegistrationRequest" />
         <argument
             android:name="isConsentGiven"
             android:defaultValue="false"
             app:argType="boolean" />
-        <argument
-            android:name="coronaTestQrCode"
-            android:defaultValue="@null"
-            app:argType="de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode"
-            app:nullable="true" />
-        <argument
-            android:name="coronaTestTan"
-            android:defaultValue="@null"
-            app:argType="de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN"
-            app:nullable="true" />
         <action
             android:id="@+id/action_submissionDeletionWarningFragment_to_submissionDispatcherFragment"
             app:popUpTo="@id/submissionDispatcherFragment"
@@ -828,8 +821,8 @@
         android:label="fragment_request_covid_certificate"
         tools:layout="@layout/fragment_request_covid_certificate">
         <argument
-            android:name="coronaTestQrCode"
-            app:argType="de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode" />
+            android:name="testRegistrationRequest"
+            app:argType="de.rki.coronawarnapp.coronatest.TestRegistrationRequest" />
         <argument
             android:name="coronaTestConsent"
             android:defaultValue="false"
@@ -842,7 +835,8 @@
 
         <action
             android:id="@+id/action_requestCovidCertificateFragment_to_dispatcherFragment"
-            app:popUpTo="@id/submissionDispatcherFragment"
+            app:destination="@id/submissionDispatcherFragment"
+            app:popUpTo="@id/mainFragment"
             app:popUpToInclusive="false" />
 
         <action
diff --git a/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml
index 100652ed537682e1f90c188696f927b0ad0de261..ff9f329d4d311627cc34144a8a4f812d17ffc2cc 100644
--- a/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml
@@ -140,6 +140,10 @@
     <string name="coronatest_negative_antigen_result_third_info_title">"Премахване на теста"</string>
     <!-- XTXT: coronatest negative antigen result third info body -->
     <string name="coronatest_negative_antigen_restul_third_info_body">"Моля, изтрийте теста от приложението Corona-Warn-App, за да можете да запазите нов код на тест, ако е необходимо."</string>
+    <!-- XTXT: coronatest negative result certificate info title -->
+    <string name="coronatest_negative_result_certificate_info_title">"Сертификат за тестване"</string>
+    <!-- XTXT: coronatest negative result certificate info body -->
+    <string name="coronatest_negative_result_certificate_info_body">"Сертификатът за направен тест е наличен на таба „Сертификати“."</string>
 
     <!-- ####################################
      Rapid Antigen Test Profile
diff --git a/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml
index ddae0a81127cfe8a2349f50c4d572b3b68e19b95..fa89d6092649084bd11e75f81ffd1d159ab31e15 100644
--- a/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?><resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
     <!-- XTXT: Request green certificate title -->
-    <string name="request_green_certificate_title">"Сертификат за тест за COVID"</string>
+    <string name="request_green_certificate_title">"COVID сертификат за тестване"</string>
     <!-- XTXT: Request green certificate subtitle -->
-    <string name="request_green_certificate_subtitle">"Можете да използвате приложението, за да заявите официален електронен сертификат за тестване, който ще бъде добавен в приложението."</string>
+    <string name="request_green_certificate_subtitle">"Можете да използвате приложението, за да заявите официален цифров сертификат за тестване, който ще бъде добавен вътре."</string>
     <!-- XTXT: Request green certificate body section 1 -->
-    <string name="request_green_certificate_body_1">"Сертификатът за тестване се издава само ако получите отрицателен резултат от теста."</string>
+    <string name="request_green_certificate_body_1">"Сертификат за тестване се издава само ако резултатът от теста е отрицателен."</string>
     <!-- XTXT: Request green certificate body section 2 -->
     <string name="request_green_certificate_body_2">"Сертификатът за тестване е валидно доказателство за отрицателен резултат от тест в рамките на ЕС (например при пътуване)."</string>
     <!-- XTXT: Request green certificate body section 3 -->
-    <string name="request_green_certificate_body_3">"Сертификатът за тестване съдържа валидни данни, които позволяват на приложението за проверка да валидира сертификата ви."</string>
+    <string name="request_green_certificate_body_3">"Сертификатът за тестване съдържа данни, които позволяват на приложението за проверка да валидира сертификата Ви."</string>
     <!-- XBUT: Request green certificate agree button -->
-    <string name="request_green_certificate_agree_button">"Заявете тестов сертификат"</string>
+    <string name="request_green_certificate_agree_button">"Заявете сертификат за тест"</string>
     <!-- XBUT: Request green certificate disagree button -->
     <string name="request_green_certificate_disagree_button">"Не, благодаря!"</string>
     <!-- XTXT: Request green certificate birth date description -->
@@ -26,25 +26,77 @@
     <!-- XBUT: Request green certificate exit dialog negative button -->
     <string name="request_gc_dialog_negative_button">"Отказ"</string>
     <!-- XTXT: Detail green certificate title -->
-    <string name="detail_green_certificate_title">"Електронен сертификат на ЕС за тестване за COVID"</string>
+    <string name="detail_green_certificate_title">"Цифров COVID сертификат на ЕС за тестване"</string>
     <!-- XTXT: Detail green certificate test type -->
     <string name="detail_green_certificate_test_type">"Тест за SARS-CoV-2"</string>
     <!-- XTXT: Detail green certificate card title -->
     <string name="detail_green_certificate_card_title">"Сертификат за тестване"</string>
+    <!-- XTXT: Detail green certificate card subtitle -->
+    <string name="detail_green_certificate_card_subtitle">"Тестът е извършен на %1$s %2$s"</string>
     <!-- XTXT: Detail green certificate travel notice link en -->
     <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string>
     <!-- XTXT: Detail green certificate travel notice link de -->
     <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string>
+    <!-- XTXT: Detail green certificate menu item: delete -->
+    <string name="green_certificate_details_menu_item_delete">"Изтриване"</string>
+    <!-- XTXT: Detail green certificate diaglog title -->
+    <string name="green_certificate_details_dialog_remove_test_title">"Желаете ли да изтриете сертификата за тестване?"</string>
+    <!-- XTXT: Detail green certificate diaglog message -->
+    <string name="green_certificate_details_dialog_remove_test_message">"Ако изтриете този сертификат, повече няма да можете да го използвате за доказателство чрез приложението."</string>
+    <!-- XTXT: Detail green certificate diaglog positive -->
+    <string name="green_certificate_details_dialog_remove_test_button_positive">"Изтриване"</string>
+    <!-- XTXT: Detail green certificate diaglog negative -->
+    <string name="green_certificate_details_dialog_remove_test_button_negative">"Отказ"</string>
+
     <!-- XTXT: Green certificate main screen title -->
     <string name="certification_screen_title">"Сертификати"</string>
     <!-- XTXT: Green certificate menu item -->
     <string name="menu_certification_information">"Информация"</string>
     <!-- XTXT: Green certificate main screen header text -->
-    <string name="green_certification_header_info">"Тук се показват електронните ви сертификати за ваксиниране и за тествания."</string>
+    <string name="green_certification_header_info">"Тук се показват цифровите Ви сертификати за ваксиниране и за тествания."</string>
     <!-- XTXT: Green certificate info card title 1st line -->
     <string name="info_banner_title_1">"Електронен COVID-19"</string>
     <!-- XTXT: Green certificate info card title 2nd line  -->
-    <string name="info_banner_title_2">"Сертификат за тест за COVID"</string>
+    <string name="info_banner_title_2">"COVID сертификат за тестване"</string>
     <!-- XTXT: Green certificate info card body  -->
-    <string name="info_banner_body">"Регистрирайте тест на началния екран и дайте съгласие да получите електронен сертификат за тестване. Той ще се покаже тук веднага щом е наличен."</string>
+    <string name="info_banner_body">"Регистрирайте тест от началния екран и дайте съгласие да получите цифров сертификат за тестване. Той ще се покаже тук веднага щом стане наличен."</string>
+
+    <!-- XTXT: Test certificate time -->
+    <string name="test_certificate_time">"Тестът е извършен на %1$s, %2$s"</string>
+    <!-- XTXT: Test error label -->
+    <string name="test_certificate_error_label">"Грешка при извличането на сертификата"</string>
+    <!-- XBUT: Test error retry button -->
+    <string name="test_certificate_error_retry_button">"Опитайте пак"</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again">"Не беше установена връзка. Опитайте отново."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_not_supported_by_lab">"Не можете да заявите сертификат за направен тест, тъй като пунктът не поддържа издаването на такива. Моля, изтрийте сертификата или се свържете с горещата линия за технически проблеми, посочена в „Информация за приложението“ -&gt; „Технически въпроси“."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_no_network">"Няма връзка с интернет. Моля, проверете я и опитайте отново."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_e2e_error_call_hotline">"Възникна грешка. Моля, опитайте по-късно или се свържете с горещата линия за технически проблеми, посочена в „Информация за приложението“ -&gt; „Технически въпроси“."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again_dcc_not_available_yet">"Вашият сертификат още не е наличен. Моля, опитайте отново. Ако грешката продължи да възниква, се обърнете към горещата линия за технически проблеми, посочена в „Информация за приложението“ -&gt; „Технически въпроси“."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_client_error_call_hotline">"Възникна грешка. Моля, опитайте по-късно или се свържете с горещата линия за технически проблеми, посочена в „Информация за приложението“ -&gt; „Технически въпроси“."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_expired">"Сертификатът вече не е актуален. Можете да го изтриете от Corona-Warn-App."</string>
+    <!-- XBUT: Test error delete button -->
+    <string name="test_certificate_error_delete_button">"Изтриване на сертификата за тестване"</string>
+    <!-- XTXT: Test error refresh dialog title -->
+    <string name="test_certificate_refresh_dialog_title">"Все още има проблем със заявката."</string>
+    <!-- XBUT: Test error refresh dialog confirm button -->
+    <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string>
+    <!-- XTXT: Test error delete dialog title -->
+    <string name="test_certificate_delete_dialog_title">"Желаете ли да изтриете сертификата?"</string>
+    <!-- XTXT: Test error delete dialog body -->
+    <string name="test_certificate_delete_dialog_body">"Ако изтриете този сертификат, не можете да го заявите отново."</string>
+    <!-- XBUT: Test error delete dialog confirm button -->
+    <string name="test_certificate_delete_dialog_confirm_button">"Изтриване"</string>
+    <!-- XBUT: Test error delete dialog cancel button -->
+    <string name="test_certificate_delete_dialog_cancel_button">"Отказ"</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_label_refreshing">"Вашият сертификат се създава..."</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_refreshing_status">"Вашият сертификат се заявява. Това може да отнеме няколко минути..."</string>
 </resources>
\ No newline at end of file
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 9d754260bca4c26af7a54b40655eac90a195aa73..45c8bcb75e157cb31e3b3d37fc4314d9f7dd47ab 100644
--- a/Corona-Warn-App/src/main/res/values-bg/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml
@@ -308,7 +308,7 @@
     <!-- XHED: risk details - infection period logged headling, below behaviors -->
     <string name="risk_details_subtitle_period_logged">"Този период се включва в изчислението."</string>
     <!-- XHED: risk details - infection period logged information body, below behaviors -->
-<!--    Dialog part 1-->
+    <!--    Dialog part 1-->
     <string name="risk_details_information_body_period_logged">"Вашият риск от заразяване може да се изчисли само за периодите, в които регистрирането на излаганията на риск е било активно. Ето защо тази функция трябва да бъде активирана постоянно. Регистрирането на контактите Ви покрива последните 14 дни."</string>
     <!-- XTXT: risk details - infection period logged information body, under 14 days -->
     <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Приложението Corona-Warn-App е инсталирано преди %s дни. Рискът да сте се заразили се изчислява за периодите, през които е било активно регистрирането на контактите Ви. Ако сте били в близост до други хора и регистрирането на излаганията е било активно, рискът да сте се заразили ще бъде изчислен."</string>
@@ -1143,8 +1143,12 @@
     <string name="submission_test_result_dialog_tracing_required_button">"OK"</string>
     <!-- XHED: Dialog title for test removal  -->
     <string name="submission_test_result_dialog_remove_test_title">"Тестът може да се сканира само веднъж."</string>
+    <!-- XHED: Dialog title for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_title_no_submission">"Наистина ли искате да изтриете теста?"</string>
     <!-- YTXT: Dialog text for test removal -->
     <string name="submission_test_result_dialog_remove_test_message">"Ако изтриете теста, повече няма да можете да извлечете данните за резултата си. Ще получите резултата си от център за тестване или лаборатория, независимо от валидността на QR кода. Ако Ви бъде поставена диагноза „коронавирус“, службата за обществено здравеопазване ще бъде уведомена за това по установения от правните норми канал и ще се свърже с Вас."</string>
+    <!-- YTXT: Dialog text for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_message_no_submission">"Ако го направите, другите няма да бъдат предупредени."</string>
     <!-- XBUT: Positive button for test removal -->
     <string name="submission_test_result_dialog_remove_test_button_positive">"Изтриване"</string>
     <!-- XBUT: Negative button for test removal -->
@@ -1233,6 +1237,8 @@
     <string name="submission_test_result_positive_no_consent_text_3">"Моля, следвайте инструкциите на Вашия лекар и си останете вкъщи, за да не заразявате други хора."</string>
     <!-- XBUT: Button for giving consent for key sharing -->
     <string name="submission_test_result_positive_no_consent_button_warn_others">"Предупредете другите"</string>
+    <!-- XBUT: Button for test removal -->
+    <string name="submission_test_result_positive_no_consent_button_remove_test">"Премахване на теста"</string>
 
     <!-- Submission Country Selector -->
     <!-- XHED: Page title for the submission country selection page -->
diff --git a/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml
index fe8bd5d56c8839e6420023b221c98576bb25c0d9..384be191b9e1bf252f265e196ef4963d4ab79370 100644
--- a/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml
@@ -36,21 +36,21 @@
     <string name="vaccination_list_vaccination_card_subtitle">"Извършено на %1$s"</string>
     <!-- XTXT: Vaccination List immunity information card body-->
     <plurals name="vaccination_list_immunity_card_body">
-        <item quantity="one">"Получихте всички планирани за момента ваксинации, но защитата ви от ваксиниране ще е пълна след %1$d ден."</item>
-        <item quantity="other">"Получихте всички планирани за момента ваксинации, но защитата ви от ваксиниране ще е пълна едва след %1$d дни."</item>
-        <item quantity="zero">"Получихте всички планирани за момента ваксинации, но защитата ви от ваксиниране ще е пълна едва след %1$d дни."</item>
-        <item quantity="two">"Получихте всички планирани за момента ваксинации, но защитата ви от ваксиниране ще е пълна едва след %1$d дни."</item>
-        <item quantity="few">"Получихте всички планирани за момента ваксинации, но защитата ви от ваксиниране ще е пълна едва след %1$d дни."</item>
-        <item quantity="many">"Получихте всички планирани за момента ваксинации, но защитата ви от ваксиниране ще е пълна едва след %1$d дни."</item>
+        <item quantity="one">"Получихте всички планирани за момента ваксинации, но ваксинационната Ви защита ще е пълна след %1$d ден."</item>
+        <item quantity="other">"Получихте всички планирани за момента ваксинации, но ваксинационната Ви защита ще е пълна едва след %1$d дни."</item>
+        <item quantity="zero">"Получихте всички планирани за момента ваксинации, но ваксинационната Ви защита ще е пълна едва след %1$d дни."</item>
+        <item quantity="two">"Получихте всички планирани за момента ваксинации, но ваксинационната Ви защита ще е пълна едва след %1$d дни."</item>
+        <item quantity="few">"Получихте всички планирани за момента ваксинации, но ваксинационната Ви защита ще е пълна едва след %1$d дни."</item>
+        <item quantity="many">"Получихте всички планирани за момента ваксинации, но ваксинационната Ви защита ще е пълна едва след %1$d дни."</item>
     </plurals>
     <!-- XBUT: Vaccination List register additional vaccination button -->
     <string name="vaccination_list_register_new_vaccination_button">"Регистрирайте друго ваксиниране"</string>
     <!-- XBUT: Vaccination List delete button -->
     <string name="vaccination_list_delete_button">"Изтриване"</string>
     <!-- XTXT: Vaccination List deletion dialog title-->
-    <string name="vaccination_list_deletion_dialog_title">"Желаете ли да изтриете ваксинационния сертификат?"</string>
+    <string name="vaccination_list_deletion_dialog_title">"Желаете ли да изтриете сертификата за ваксиниране?"</string>
     <!-- XTXT: Vaccination List deletion dialog message-->
-    <string name="vaccination_list_deletion_dialog_message">"Ако изтриете сертификата, приложението вече няма да може да използва тази ваксинация за удостоверяване на вашия ваксинационен статус."</string>
+    <string name="vaccination_list_deletion_dialog_message">"Ако изтриете сертификата за ваксиниране, приложението вече няма да може да използва ваксинацията за удостоверяване на вашия ваксинационен статус."</string>
     <!-- XBUT: Vaccination List deletion dialog positive button-->
     <string name="vaccination_list_deletion_dialog_positive_button">"Изтриване"</string>
     <!-- XBUT: Vaccination List deletion dialog negative button-->
@@ -66,11 +66,13 @@
     <!-- XHED: Title for Vaccination Certificate Registration Home Card -->
     <string name="vaccination_card_registration_title_line_2">"за ваксиниране"</string>
     <!-- YTXT: Body text for Vaccination Certificate Registration Home Card -->
-    <string name="vaccination_card_registration_body">"Добавете ваксинационните си сертификати в приложението, за да са винаги с вас. За да направите това, сканирайте QR кода от вашия документ."</string>
+    <string name="vaccination_card_registration_body">"Добавете ваксинационните си сертификати в приложението, за да са винаги с Вас. За целта сканирайте QR кода от Вашия документ."</string>
     <!-- XBUT: button for Vaccination Certificate Registration Home Card -->
     <string name="vaccination_card_register">"Добавяне"</string>
     <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title">"Електронно доказателство за ваксиниране"</string>
+    <string name="vaccination_card_status_title_line_1">"Цифрово"</string>
+    <!-- XHED: Homescreen vaccination status card title -->
+    <string name="vaccination_card_status_title_line_2">"Доказателство за ваксинация"</string>
     <!-- XHED: Homescreen vaccination status card vaccination name -->
     <string name="vaccination_card_status_vaccination_name">"Ваксиниране срещу SARS-CoV-2"</string>
     <!-- XTXT: Homescreen card incomplete vaccination status label -->
@@ -84,17 +86,19 @@
         <item quantity="few">"Пълна защита от ваксиниране след %1$d дни"</item>
         <item quantity="many">"Пълна защита от ваксиниране след %1$d дни"</item>
     </plurals>
+    <!-- XTXT: Homescreen card complete vaccination status label -->
+    <string name="vaccination_card_status_vaccination_complete">"Валидно до %s"</string>
 
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_invalid">"Този QR не е валиден ваксинационен сертификат.\n\nЗа да разберете как можете да се сдобиете със сертификат за извършена ваксинация, вижте страницата ни с често задавани въпроси (ЧЗВ)."</string>
     <!-- XTXT: Vaccination QR code scan error message-->
-    <string name="error_vc_not_yet_supported">"Този ваксинационен сертификат все още не се поддържа във версията на Вашето приложение. Моля, актуализирайте приложението си или се свържете с горещата линия за технически проблеми, посочена в „Информация за приложението“."</string>
+    <string name="error_vc_not_yet_supported">"Този сертификат за ваксиниране все още не се поддържа във версията на вашето приложение. Моля, актуализирайте приложението си или се свържете с горещата линия за технически проблеми от “Информация за приложението”."</string>
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_scan_again">"Ваксинационният сертификат не може да бъде запазен на смартфона Ви. Моля, опитайте отново по-късно или се свържете с горещата линия за технически проблеми, посочена в „Информация за приложението“."</string>
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_already_registered">"Ваксинационният сертификат вече е регистриран в приложението Ви."</string>
     <!-- XTXT: Vaccination QR code scan error message-->
-    <string name="error_vc_different_person">"Личната информация в този ваксинационен сертификат не съответства на тази във вече регистрираните сертификати. В приложението можете да регистрирате сертификати само за едно лице."</string>
+    <string name="error_vc_different_person">"Личната информация в този сертификат за ваксиниране не съответства на тази във вече регистрираните сертификати. В приложението можете да регистрирате сертификати само за едно лице."</string>
 
     <!-- XTXT: Vaccination Consent title-->
     <string name="vaccination_consent_title">"Вашето съгласие"</string>
@@ -107,7 +111,7 @@
     <!-- XTXT: Vaccination Consent qr code text -->
     <string name="vaccination_consent_qr_info_qr_code_text">"Тези сертификати се считат за валидно доказателство в рамките на ЕС (например при пътуване)."</string>
     <!-- XTXT: Vaccination Consent time text  -->
-    <string name="vaccination_consent_qr_info_time_text">"След като добавите сертификат, той ще се запази в смартфона Ви. Ще се споделя с останалите само ако представите сертификат за ваксиниране."</string>
+    <string name="vaccination_consent_qr_info_time_text">"След като добавите сертификат, той ще се запази в смартфона Ви. Ще се споделя с останалите само ако представите ваксинационен сертификат."</string>
     <!-- XTXT: Text for vaccination consent legal information button -->
     <string name="vaccination_consent_onboarding_legal_information">"За повече информация относно обработката на данни, прочетете декларацията за поверителност."</string>
     <!-- XBUT: Text for vaccination consent accept button -->
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
index 3eb70a980ce8286f10c6cb13bcb86e4971d746de..d43011a41eb89212c632f17a2e6d6ceca3e1e2d0 100644
--- a/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml
@@ -141,6 +141,10 @@
     <string name="coronatest_negative_antigen_result_third_info_title">"Test entfernen"</string>
     <!-- XTXT: coronatest negative antigen result third info body -->
     <string name="coronatest_negative_antigen_restul_third_info_body">"Bitte entfernen Sie den Test wieder aus der Corona-Warn-App, damit Sie bei Bedarf einen neuen Test hinterlegen können."</string>
+    <!-- XTXT: coronatest negative result certificate info title -->
+    <string name="coronatest_negative_result_certificate_info_title">"Testzertifikat"</string>
+    <!-- XTXT: coronatest negative result certificate info body -->
+    <string name="coronatest_negative_result_certificate_info_body">"Das Testzertifikat liegt im Tab „Zertifikate“ vor."</string>
 
     <!-- ####################################
      Rapid Antigen Test Profile
diff --git a/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml
index 43963ce719e701a30573a977f1a8aadf03262801..efb49c76369609b6cf22be32af4b232e569094e6 100644
--- a/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml
@@ -22,10 +22,10 @@
     <string name="request_gc_dialog_title">Registrierung abbrechen</string>
     <!-- XTXT: Request green certificate exit dialog message -->
     <string name="request_gc_dialog_message">Wenn Sie die Test-Registrierung abbrechen, können Sie Ihr Testergebnis nicht in der App erhalten.</string>
+    <!-- XBUT: Request green certificate exit dialog positive button -->
+    <string name="request_gc_dialog_positive_button">Registrierung abbrechen</string>
     <!-- XBUT: Request green certificate exit dialog negative button -->
-    <string name="request_gc_dialog_positive_button">OK</string>
-    <!-- XBUT: Request green certificate exit dialog negative button -->
-    <string name="request_gc_dialog_negative_button">Abbrechen</string>
+    <string name="request_gc_dialog_negative_button">Registrierung fortsetzen</string>
     <!-- XTXT: Detail green certificate title -->
     <string name="detail_green_certificate_title">EU Digitales COVID-Testzertifikat</string>
     <!-- XTXT: Detail green certificate test type -->
@@ -100,4 +100,8 @@
     <string name="test_certificate_error_label_refreshing">"Ihr Zertifikat wird gerade erstellt…"</string>
     <!-- XTXT: Test error card body refreshing -->
     <string name="test_certificate_error_refreshing_status">"Ihr Zertifikat wird gerade angefragt, dies kann einige Minuten dauern…"</string>
+    <!-- XBUT: Text for invalid test certificate error button, linking to FAQ-->
+    <string name="test_certificate_error_invalid_labid_faq">"FAQ zu Testzertifikaten"</string>
+    <!-- XTXT: Explains user about test certificate: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#vac_cert_invalid -->
+    <string name="test_certificate_error_invalid_labid_faq_link">"https://www.coronawarn.app/de/faq/#test_cert"</string>
 </resources>
diff --git a/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml
index 566c614eb2cf7ad4f67557ea834c5a065deca493..c9f84fba0b2d487a61bca413ea73d7fc0b546915 100644
--- a/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml
@@ -140,6 +140,10 @@
     <string name="coronatest_negative_antigen_result_third_info_title">"Remove Test"</string>
     <!-- XTXT: coronatest negative antigen result third info body -->
     <string name="coronatest_negative_antigen_restul_third_info_body">"Please delete the test from the Corona-Warn-App, so that you can save a new test code here if necessary."</string>
+    <!-- XTXT: coronatest negative result certificate info title -->
+    <string name="coronatest_negative_result_certificate_info_title">"Test Certificate"</string>
+    <!-- XTXT: coronatest negative result certificate info body -->
+    <string name="coronatest_negative_result_certificate_info_body">"The test certificate is available on the “Certificates” tab."</string>
 
     <!-- ####################################
      Rapid Antigen Test Profile
diff --git a/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml
index 4d945372179f5d78a3cd8891514823e8e52f5377..f0daa94caf21542f19635794aae920a0adea4add 100644
--- a/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml
@@ -8,7 +8,7 @@
     <!-- XTXT: Request green certificate body section 2 -->
     <string name="request_green_certificate_body_2">"The test certificate is valid proof of a negative test result within the EU (e.g. for travel)."</string>
     <!-- XTXT: Request green certificate body section 3 -->
-    <string name="request_green_certificate_body_3">"The test certificate contains valid that enables the verification app to validate your certificate."</string>
+    <string name="request_green_certificate_body_3">"The test certificate contains data that enables the verification app to validate your certificate."</string>
     <!-- XBUT: Request green certificate agree button -->
     <string name="request_green_certificate_agree_button">"Request Test Certificate"</string>
     <!-- XBUT: Request green certificate disagree button -->
@@ -31,10 +31,23 @@
     <string name="detail_green_certificate_test_type">"SARS-CoV-2 Test"</string>
     <!-- XTXT: Detail green certificate card title -->
     <string name="detail_green_certificate_card_title">"Test Certificate"</string>
+    <!-- XTXT: Detail green certificate card subtitle -->
+    <string name="detail_green_certificate_card_subtitle">"Test performed on %1$s %2$s"</string>
     <!-- XTXT: Detail green certificate travel notice link en -->
     <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string>
     <!-- XTXT: Detail green certificate travel notice link de -->
     <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string>
+    <!-- XTXT: Detail green certificate menu item: delete -->
+    <string name="green_certificate_details_menu_item_delete">"Remove"</string>
+    <!-- XTXT: Detail green certificate diaglog title -->
+    <string name="green_certificate_details_dialog_remove_test_title">"Do you want to remove the test certificate?"</string>
+    <!-- XTXT: Detail green certificate diaglog message -->
+    <string name="green_certificate_details_dialog_remove_test_message">"If you remove the test certificate, you can no longer use it as proof in the app."</string>
+    <!-- XTXT: Detail green certificate diaglog positive -->
+    <string name="green_certificate_details_dialog_remove_test_button_positive">"Remove"</string>
+    <!-- XTXT: Detail green certificate diaglog negative -->
+    <string name="green_certificate_details_dialog_remove_test_button_negative">"Cancel"</string>
+
     <!-- XTXT: Green certificate main screen title -->
     <string name="certification_screen_title">"Certificates"</string>
     <!-- XTXT: Green certificate menu item -->
@@ -47,4 +60,43 @@
     <string name="info_banner_title_2">"COVID Test Certificate"</string>
     <!-- XTXT: Green certificate info card body  -->
     <string name="info_banner_body">"Register a test on the home screen and agree to receive a digital test certificate. As soon as the certificate is available, it is displayed here."</string>
+
+    <!-- XTXT: Test certificate time -->
+    <string name="test_certificate_time">"Test performed on %1$s, %2$s"</string>
+    <!-- XTXT: Test error label -->
+    <string name="test_certificate_error_label">"Error during certificate retrieval"</string>
+    <!-- XBUT: Test error retry button -->
+    <string name="test_certificate_error_retry_button">"Try Again"</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again">"Could not establish a connection. Please try again."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_not_supported_by_lab">"You cannot request a test certificate because this testing point does not support the issuing of test certificates. Please remove the certificate or contact the technical hotline via App Information -&gt; Technical Hotline."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_no_network">"Your Internet connection was lost. Please check the connection and try again."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_e2e_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -&gt; Technical Hotline."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again_dcc_not_available_yet">"Your certificate it not available yet. Please try again. If the error persists, please contact the technical hotline via App Information -&gt; Technical Hotline."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_client_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -&gt; Technical Hotline."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_expired">"This certificate is no longer current. You can remove it from the Corona-Warn-App."</string>
+    <!-- XBUT: Test error delete button -->
+    <string name="test_certificate_error_delete_button">"Remove Test Certificate"</string>
+    <!-- XTXT: Test error refresh dialog title -->
+    <string name="test_certificate_refresh_dialog_title">"There are still problems with the request."</string>
+    <!-- XBUT: Test error refresh dialog confirm button -->
+    <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string>
+    <!-- XTXT: Test error delete dialog title -->
+    <string name="test_certificate_delete_dialog_title">"Do you want to remove the certificate?"</string>
+    <!-- XTXT: Test error delete dialog body -->
+    <string name="test_certificate_delete_dialog_body">"If you remove the certificate, you cannot request it again."</string>
+    <!-- XBUT: Test error delete dialog confirm button -->
+    <string name="test_certificate_delete_dialog_confirm_button">"Remove"</string>
+    <!-- XBUT: Test error delete dialog cancel button -->
+    <string name="test_certificate_delete_dialog_cancel_button">"Cancel"</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_label_refreshing">"Your certificate is being created..."</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_refreshing_status">"Your certificate is being requested. This may take a few minutes..."</string>
 </resources>
\ No newline at end of file
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 d4eeb5af5155c76c7800fd3ce6fc77badebc2c26..edcb69236fd34be8dcf03ab7bfa8c8edf5354fad 100644
--- a/Corona-Warn-App/src/main/res/values-en/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/strings.xml
@@ -308,7 +308,7 @@
     <!-- XHED: risk details - infection period logged headling, below behaviors -->
     <string name="risk_details_subtitle_period_logged">"This period is included in the calculation."</string>
     <!-- XHED: risk details - infection period logged information body, below behaviors -->
-<!--    Dialog part 1-->
+    <!--    Dialog part 1-->
     <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently. Exposure logging covers the last 14 days."</string>
     <!-- XTXT: risk details - infection period logged information body, under 14 days -->
     <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s days ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string>
@@ -1143,8 +1143,12 @@
     <string name="submission_test_result_dialog_tracing_required_button">"OK"</string>
     <!-- XHED: Dialog title for test removal  -->
     <string name="submission_test_result_dialog_remove_test_title">"The test can only be scanned once."</string>
+    <!-- XHED: Dialog title for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_title_no_submission">"Are you sure you want to remove your test?"</string>
     <!-- YTXT: Dialog text for test removal -->
     <string name="submission_test_result_dialog_remove_test_message">"If you delete the test, you can no longer retrieve your test result. You will receive your test result from the test center or laboratory regardless of the validity of the QR code. If you are diagnosed with coronavirus, the public health authority will be notified through the legally prescribed channel and will contact you."</string>
+    <!-- YTXT: Dialog text for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_message_no_submission">"If you do, others will not be warned."</string>
     <!-- XBUT: Positive button for test removal -->
     <string name="submission_test_result_dialog_remove_test_button_positive">"Delete"</string>
     <!-- XBUT: Negative button for test removal -->
@@ -1233,6 +1237,8 @@
     <string name="submission_test_result_positive_no_consent_text_3">"Please be sure to follow the instructions from your public health authority and stay home, so you don’t infect others."</string>
     <!-- XBUT: Button for giving consent for key sharing -->
     <string name="submission_test_result_positive_no_consent_button_warn_others">"Warn Others"</string>
+    <!-- XBUT: Button for test removal -->
+    <string name="submission_test_result_positive_no_consent_button_remove_test">"Remove Test"</string>
 
     <!-- Submission Country Selector -->
     <!-- XHED: Page title for the submission country selection page -->
diff --git a/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml
index e9f2c33cfe132f9ed57c4cc8c892449411360a5d..fa39d43df4de775da395fd08bf2c95b527a0c7b0 100644
--- a/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml
@@ -70,7 +70,9 @@
     <!-- XBUT: button for Vaccination Certificate Registration Home Card -->
     <string name="vaccination_card_register">"Add"</string>
     <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title">"Digital Proof of Vaccination"</string>
+    <string name="vaccination_card_status_title_line_1">"Digital"</string>
+    <!-- XHED: Homescreen vaccination status card title -->
+    <string name="vaccination_card_status_title_line_2">"Proof of Vaccination"</string>
     <!-- XHED: Homescreen vaccination status card vaccination name -->
     <string name="vaccination_card_status_vaccination_name">"SARS-CoV-2 Vaccination"</string>
     <!-- XTXT: Homescreen card incomplete vaccination status label -->
@@ -84,6 +86,8 @@
         <item quantity="few">"Full vaccination protection in %1$d days"</item>
         <item quantity="many">"Full vaccination protection in %1$d days"</item>
     </plurals>
+    <!-- XTXT: Homescreen card complete vaccination status label -->
+    <string name="vaccination_card_status_vaccination_complete">"Valid through %s"</string>
 
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_invalid">"This QR code is not a valid vaccination certificate.\n\nFor more information about how to receive your vaccination certificate, please see our FAQ page."</string>
diff --git a/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml
index 076c006f193cb06bc1c01751e391bf4f5bb3e6b2..008da831547ec3b3e3b3a3f5c72875e3ac8e8957 100644
--- a/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml
@@ -43,7 +43,7 @@
     <!-- XTXT: homescreen card: body - error -->
     <string name="ag_homescreen_card_body_error">"Ustalenie wyniku Twojego testu było niemożliwe."</string>
     <!-- XTXT: homescreen card: body - not valid test -->
-    <string name="ag_homescreen_card_body_not_valid_test">"Twój test ma więcej niż 21 dni i stracił ważność. Usuń test. Będziesz mieć wówczas możliwość dodania kolejnego. "</string>
+    <string name="ag_homescreen_card_body_not_valid_test">"Twój test ma więcej niż 21 dni i stracił ważność. Usuń test. Będziesz mieć wówczas możliwość dodania kolejnego."</string>
     <!-- XTXT: homescreen card: body - negative -->
     <string name="ag_homescreen_card_body_result_negative">"Nie zdiagnozowano u Ciebie wirusa SARS-CoV-2."</string>
     <!-- XTXT: homescreen card: body - positive -->
@@ -140,6 +140,10 @@
     <string name="coronatest_negative_antigen_result_third_info_title">"Usuń test"</string>
     <!-- XTXT: coronatest negative antigen result third info body -->
     <string name="coronatest_negative_antigen_restul_third_info_body">"Usuń test z aplikacji Corona-Warn-App, aby w razie potrzeby można było zapisać w niej kod nowego testu."</string>
+    <!-- XTXT: coronatest negative result certificate info title -->
+    <string name="coronatest_negative_result_certificate_info_title">"Certyfikat testu"</string>
+    <!-- XTXT: coronatest negative result certificate info body -->
+    <string name="coronatest_negative_result_certificate_info_body">"Certyfikat testu dostępny jest w karcie „Certyfikaty”."</string>
 
     <!-- ####################################
      Rapid Antigen Test Profile
diff --git a/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml
index 0ab72d844ebb07cf949604ed985321f1a6f6bdb7..1c42258b5bd01afb2a222eb2d78d5eabc754be71 100644
--- a/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml
@@ -8,7 +8,7 @@
     <!-- XTXT: Request green certificate body section 2 -->
     <string name="request_green_certificate_body_2">"Certyfikat testu jest ważnym dowodem negatywnego wyniku testu na terenie UE (np. dla celów podróży)."</string>
     <!-- XTXT: Request green certificate body section 3 -->
-    <string name="request_green_certificate_body_3">"Certyfikat zawiera ważne dane, które umożliwiają aplikacji weryfikacyjnej jego walidację."</string>
+    <string name="request_green_certificate_body_3">"Certyfikat testu zawiera dane, które umożliwiają aplikacji weryfikacyjnej jego walidację."</string>
     <!-- XBUT: Request green certificate agree button -->
     <string name="request_green_certificate_agree_button">"PoproÅ› o certyfikat testu"</string>
     <!-- XBUT: Request green certificate disagree button -->
@@ -31,10 +31,23 @@
     <string name="detail_green_certificate_test_type">"Test SARS-CoV-2"</string>
     <!-- XTXT: Detail green certificate card title -->
     <string name="detail_green_certificate_card_title">"Certyfikat testu"</string>
+    <!-- XTXT: Detail green certificate card subtitle -->
+    <string name="detail_green_certificate_card_subtitle">"Test wykonany dnia %1$s %2$s"</string>
     <!-- XTXT: Detail green certificate travel notice link en -->
     <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string>
     <!-- XTXT: Detail green certificate travel notice link de -->
     <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string>
+    <!-- XTXT: Detail green certificate menu item: delete -->
+    <string name="green_certificate_details_menu_item_delete">"Usuń"</string>
+    <!-- XTXT: Detail green certificate diaglog title -->
+    <string name="green_certificate_details_dialog_remove_test_title">"Czy chcesz usunąć certyfikat testu?"</string>
+    <!-- XTXT: Detail green certificate diaglog message -->
+    <string name="green_certificate_details_dialog_remove_test_message">"Po usunięciu certyfikatu testu jego użycie jako dowodu w aplikacji nie będzie już możliwe."</string>
+    <!-- XTXT: Detail green certificate diaglog positive -->
+    <string name="green_certificate_details_dialog_remove_test_button_positive">"Usuń"</string>
+    <!-- XTXT: Detail green certificate diaglog negative -->
+    <string name="green_certificate_details_dialog_remove_test_button_negative">"Anuluj"</string>
+
     <!-- XTXT: Green certificate main screen title -->
     <string name="certification_screen_title">"Certyfikaty"</string>
     <!-- XTXT: Green certificate menu item -->
@@ -47,4 +60,43 @@
     <string name="info_banner_title_2">"Certyfikat testu na COVID"</string>
     <!-- XTXT: Green certificate info card body  -->
     <string name="info_banner_body">"Zarejestruj test na ekranie głównym i wyraź zgodę na otrzymanie cyfrowego certyfikatu testu. Gdy tylko certyfikat będzie dostępny, pojawi się w tym miejscu."</string>
+
+    <!-- XTXT: Test certificate time -->
+    <string name="test_certificate_time">"Test wykonany dnia %1$s, %2$s"</string>
+    <!-- XTXT: Test error label -->
+    <string name="test_certificate_error_label">"BÅ‚Ä…d podczas pobierania certyfikatu"</string>
+    <!-- XBUT: Test error retry button -->
+    <string name="test_certificate_error_retry_button">"Spróbuj ponownie"</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again">"Nie udało się nawiązać połączenia. Spróbuj ponownie."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_not_supported_by_lab">"Nie możesz zażądać wydania certyfikatu testu, ponieważ ten punkt testowania nie wydaje certyfikatów testów. Usuń certyfikat lub skontaktuj się z infolinią techniczną, korzystając z opcji: Informacje o aplikacji -&gt; Infolinia techniczna."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_no_network">"Połączenie internetowe zostało utracone. Sprawdź połączenie i spróbuj ponownie."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_e2e_error_call_hotline">"Wystąpił błąd. Spróbuj ponownie później lub skontaktuj się z infolinią techniczną, korzystając z opcji: Informacje o aplikacji -&gt; Infolinia techniczna."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again_dcc_not_available_yet">"Twój certyfikat nie jest jeszcze dostępny. Spróbuj ponownie. Jeśli błąd będzie nadal występował, skontaktuj się z infolinią techniczną, korzystając z opcji: Informacje o aplikacji -&gt; Infolinia techniczna."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_client_error_call_hotline">"Wystąpił błąd. Spróbuj ponownie później lub skontaktuj się z infolinią techniczną, korzystając z opcji: Informacje o aplikacji -&gt; Infolinia techniczna."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_expired">"Ten certyfikat nie jest już aktualny. Możesz go usunąć z aplikacji Corona-Warn-App."</string>
+    <!-- XBUT: Test error delete button -->
+    <string name="test_certificate_error_delete_button">"Usuń certyfikat testu"</string>
+    <!-- XTXT: Test error refresh dialog title -->
+    <string name="test_certificate_refresh_dialog_title">"Nadal występują problemy z żądaniem."</string>
+    <!-- XBUT: Test error refresh dialog confirm button -->
+    <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string>
+    <!-- XTXT: Test error delete dialog title -->
+    <string name="test_certificate_delete_dialog_title">"Czy chcesz usunąć certyfikat?"</string>
+    <!-- XTXT: Test error delete dialog body -->
+    <string name="test_certificate_delete_dialog_body">"Po usunięciu certyfikatu nie można zażądać go ponownie."</string>
+    <!-- XBUT: Test error delete dialog confirm button -->
+    <string name="test_certificate_delete_dialog_confirm_button">"Usuń"</string>
+    <!-- XBUT: Test error delete dialog cancel button -->
+    <string name="test_certificate_delete_dialog_cancel_button">"Anuluj"</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_label_refreshing">"Certyfikat jest tworzony..."</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_refreshing_status">"Żądanie dotyczące certyfikatu jest przetwarzane. Może to potrwać kilka minut..."</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 e7ebf5e3ecead984375f3a3f95010e35aaed009c..61ae08c0fcf495641a399ceb395b930066856a14 100644
--- a/Corona-Warn-App/src/main/res/values-pl/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml
@@ -308,7 +308,7 @@
     <!-- XHED: risk details - infection period logged headling, below behaviors -->
     <string name="risk_details_subtitle_period_logged">"Ten okres jest uwzględniony w obliczeniu."</string>
     <!-- XHED: risk details - infection period logged information body, below behaviors -->
-<!--    Dialog part 1-->
+    <!--    Dialog part 1-->
     <string name="risk_details_information_body_period_logged">"Ryzyko zakażenia można obliczyć tylko dla okresów, w których rejestrowanie narażenia było aktywne. Dlatego też funkcja rejestrowania powinna być stale aktywna. Rejestrowanie narażenia obejmuje 14 ostatnich dni."</string>
     <!-- XTXT: risk details - infection period logged information body, under 14 days -->
     <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Aplikacja Corona-Warn-App została zainstalowana %s dni temu. Ryzyko zakażenia jest obliczane dla okresów, w których aktywne było rejestrowanie narażenia. Oblicza się je w przypadku kontaktowania się z innymi ludźmi przy aktywnej funkcji rejestrowania narażenia."</string>
@@ -1143,8 +1143,12 @@
     <string name="submission_test_result_dialog_tracing_required_button">"OK"</string>
     <!-- XHED: Dialog title for test removal  -->
     <string name="submission_test_result_dialog_remove_test_title">"Test można zeskanować tylko raz."</string>
+    <!-- XHED: Dialog title for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_title_no_submission">"Czy na pewno chcesz usunąć swój test?"</string>
     <!-- YTXT: Dialog text for test removal -->
     <string name="submission_test_result_dialog_remove_test_message">"Jeśli usuniesz test, nie będziesz mieć możliwości pobrania wyniku swojego testu. Otrzymasz swój wynik testu z ośrodka wykonującego testy lub laboratorium niezależnie od ważności kodu QR. W przypadku zdiagnozowania u Ciebie koronawirusa organ ds. zdrowia publicznego zostanie powiadomiony przez ustanowiony prawnie kanał komunikacyjny i skontaktuje się z Tobą."</string>
+    <!-- YTXT: Dialog text for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_message_no_submission">"Jeśli to zrobisz, inne osoby nie otrzymają ostrzeżeń."</string>
     <!-- XBUT: Positive button for test removal -->
     <string name="submission_test_result_dialog_remove_test_button_positive">"Usuń"</string>
     <!-- XBUT: Negative button for test removal -->
@@ -1233,6 +1237,8 @@
     <string name="submission_test_result_positive_no_consent_text_3">"Postępuj zgodnie z poleceniami organu ds. zdrowia publicznego i zostań w domu, aby nie zarażać innych."</string>
     <!-- XBUT: Button for giving consent for key sharing -->
     <string name="submission_test_result_positive_no_consent_button_warn_others">"Ostrzegaj innych"</string>
+    <!-- XBUT: Button for test removal -->
+    <string name="submission_test_result_positive_no_consent_button_remove_test">"Usuń test"</string>
 
     <!-- Submission Country Selector -->
     <!-- XHED: Page title for the submission country selection page -->
diff --git a/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml
index fbf8ad38370692383771ad67cd0f82eaf5a0b287..58bcf8c675e9fa32b8d0c9a27d5841a2e89a2931 100644
--- a/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml
@@ -70,7 +70,9 @@
     <!-- XBUT: button for Vaccination Certificate Registration Home Card -->
     <string name="vaccination_card_register">"Dodaj"</string>
     <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title">"Cyfrowy dowód szczepienia"</string>
+    <string name="vaccination_card_status_title_line_1">"Cyfrowy"</string>
+    <!-- XHED: Homescreen vaccination status card title -->
+    <string name="vaccination_card_status_title_line_2">"Dowód szczepienia"</string>
     <!-- XHED: Homescreen vaccination status card vaccination name -->
     <string name="vaccination_card_status_vaccination_name">"Szczepienie przeciwko SARS-CoV-2"</string>
     <!-- XTXT: Homescreen card incomplete vaccination status label -->
@@ -84,6 +86,8 @@
         <item quantity="few">"Pełna ochrona poszczepienna za %1$d dni"</item>
         <item quantity="many">"Pełna ochrona poszczepienna za %1$d dni"</item>
     </plurals>
+    <!-- XTXT: Homescreen card complete vaccination status label -->
+    <string name="vaccination_card_status_vaccination_complete">"Ważne do %s"</string>
 
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_invalid">"Ten kod QR nie jest ważnym świadectwem szczepienia.\n\nWięcej informacji na temat tego, jak otrzymać swoje świadectwo szczepienia, znajduje się na naszej stronie „Często zadawane pytania”."</string>
diff --git a/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml
index 245102cd46f431beef2ed97554a2d35b0bf68f95..e048a39527f46d269b62167e6cdc35846330a164 100644
--- a/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml
@@ -140,6 +140,10 @@
     <string name="coronatest_negative_antigen_result_third_info_title">"Eliminare test"</string>
     <!-- XTXT: coronatest negative antigen result third info body -->
     <string name="coronatest_negative_antigen_restul_third_info_body">"Ștergeți testul din Corona-Warn-App pentru a salva un nou cod de test aici dacă este necesar."</string>
+    <!-- XTXT: coronatest negative result certificate info title -->
+    <string name="coronatest_negative_result_certificate_info_title">"Certificat de test"</string>
+    <!-- XTXT: coronatest negative result certificate info body -->
+    <string name="coronatest_negative_result_certificate_info_body">"Certificatul de test este disponibil pe tabul „Certificate”."</string>
 
     <!-- ####################################
      Rapid Antigen Test Profile
diff --git a/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml
index 348c6a1a29591723d6f8606f85c57b92eb9308d2..ac9cc94ace505c368451612aa96704a73282b1f4 100644
--- a/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml
@@ -31,10 +31,23 @@
     <string name="detail_green_certificate_test_type">"Test SARS-CoV-2"</string>
     <!-- XTXT: Detail green certificate card title -->
     <string name="detail_green_certificate_card_title">"Certificat de test"</string>
+    <!-- XTXT: Detail green certificate card subtitle -->
+    <string name="detail_green_certificate_card_subtitle">"Test efectuat pe %1$s %2$s"</string>
     <!-- XTXT: Detail green certificate travel notice link en -->
     <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string>
     <!-- XTXT: Detail green certificate travel notice link de -->
     <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string>
+    <!-- XTXT: Detail green certificate menu item: delete -->
+    <string name="green_certificate_details_menu_item_delete">"Eliminare"</string>
+    <!-- XTXT: Detail green certificate diaglog title -->
+    <string name="green_certificate_details_dialog_remove_test_title">"Doriți să eliminați certificatul de test?"</string>
+    <!-- XTXT: Detail green certificate diaglog message -->
+    <string name="green_certificate_details_dialog_remove_test_message">"Dacă eliminați certificatul de test, nu îl mai puteți utiliza ca dovadă în aplicație."</string>
+    <!-- XTXT: Detail green certificate diaglog positive -->
+    <string name="green_certificate_details_dialog_remove_test_button_positive">"Eliminare"</string>
+    <!-- XTXT: Detail green certificate diaglog negative -->
+    <string name="green_certificate_details_dialog_remove_test_button_negative">"Anulare"</string>
+
     <!-- XTXT: Green certificate main screen title -->
     <string name="certification_screen_title">"Certificate"</string>
     <!-- XTXT: Green certificate menu item -->
@@ -47,4 +60,43 @@
     <string name="info_banner_title_2">"Certificat de test COVID"</string>
     <!-- XTXT: Green certificate info card body  -->
     <string name="info_banner_body">"Înregistrați un test pe ecranul inițial și consimțiți să primiți un certificat de test digital. De îndată ce este disponibil certificatul, acesta este afișat aici."</string>
+
+    <!-- XTXT: Test certificate time -->
+    <string name="test_certificate_time">"Test efectuat pe %1$s, %2$s"</string>
+    <!-- XTXT: Test error label -->
+    <string name="test_certificate_error_label">"Eroare la obținerea certificatului"</string>
+    <!-- XBUT: Test error retry button -->
+    <string name="test_certificate_error_retry_button">"Încercați din nou"</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again">"Nu a putut fi stabilită o conexiune. Încercați din nou."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_not_supported_by_lab">"Nu puteți solicita un certificat de test deoarece acest punct de testare nu suportă emiterea de certificate de test. Eliminați certificatul sau contactați hotline-ul tehnic prin Informații aplicație -&gt; Hotline tehnic."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_no_network">"Conexiunea dvs. la internet a fost pierdută. Verificați conexiunea și încercați din nou."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_e2e_error_call_hotline">"A apărut o eroare. Încercați din nou mai târziu sau contactați hotline-ul tehnic prin Informații aplicație -&gt; Hotline tehnic."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again_dcc_not_available_yet">"Certificatul dvs. nu este disponibil încă. Încercați din nou. Dacă eroarea persistă, contactați hotline-ul tehnic prin Informații aplicație -&gt; Hotline tehnic."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_client_error_call_hotline">"A apărut o eroare. Încercați din nou mai târziu sau contactați hotline-ul tehnic prin Informații aplicație -&gt; Hotline tehnic."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_expired">"Acest certificat nu mai este de actualitate. Îl puteți elimina din Corona-Warn-App."</string>
+    <!-- XBUT: Test error delete button -->
+    <string name="test_certificate_error_delete_button">"Eliminare certificat de test"</string>
+    <!-- XTXT: Test error refresh dialog title -->
+    <string name="test_certificate_refresh_dialog_title">"Încă există probleme cu cererea dvs."</string>
+    <!-- XBUT: Test error refresh dialog confirm button -->
+    <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string>
+    <!-- XTXT: Test error delete dialog title -->
+    <string name="test_certificate_delete_dialog_title">"Doriți să eliminați certificatul?"</string>
+    <!-- XTXT: Test error delete dialog body -->
+    <string name="test_certificate_delete_dialog_body">"Dacă eliminați certificatul, nu îl puteți solicita din nou."</string>
+    <!-- XBUT: Test error delete dialog confirm button -->
+    <string name="test_certificate_delete_dialog_confirm_button">"Eliminare"</string>
+    <!-- XBUT: Test error delete dialog cancel button -->
+    <string name="test_certificate_delete_dialog_cancel_button">"Anulare"</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_label_refreshing">"Certificatul dvs. este în curs de creare..."</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_refreshing_status">"Certificatul dvs. este în curs de solicitare. Acest lucru poate dura câteva minute..."</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 fc3960ab897d37077eb075fc61d29a13f771909b..d7b4f42d22aa2686d3695be314c00cf4a06cc7a5 100644
--- a/Corona-Warn-App/src/main/res/values-ro/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml
@@ -308,7 +308,7 @@
     <!-- XHED: risk details - infection period logged headling, below behaviors -->
     <string name="risk_details_subtitle_period_logged">"Această perioadă este inclusă în calcul."</string>
     <!-- XHED: risk details - infection period logged information body, below behaviors -->
-<!--    Dialog part 1-->
+    <!--    Dialog part 1-->
     <string name="risk_details_information_body_period_logged">"Riscul dvs. de infectare poate fi calculat doar pentru perioadele în care a fost activă înregistrarea în jurnal a expunerilor. Prin urmare, caracteristica de înregistrare în jurnal trebuie să rămână permanent activă. Înregistrarea în jurnal a expunerilor acoperă ultimele 14 zile."</string>
     <!-- XTXT: risk details - infection period logged information body, under 14 days -->
     <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Aplicația Corona-Warn a fost instalată acum %s zi(le). Riscul dvs. de infectare este calculat pentru perioadele în care înregistrarea în jurnal a expunerilor a fost activă. Dacă v-ați întâlnit cu alte persoane și înregistrarea în jurnal a expunerilor a fost activă, este calculat riscul dvs. de infectare."</string>
@@ -1143,8 +1143,12 @@
     <string name="submission_test_result_dialog_tracing_required_button">"OK"</string>
     <!-- XHED: Dialog title for test removal  -->
     <string name="submission_test_result_dialog_remove_test_title">"Testul poate fi scanat o singură dată."</string>
+    <!-- XHED: Dialog title for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_title_no_submission">"Sigur doriți să eliminați testul dvs.?"</string>
     <!-- YTXT: Dialog text for test removal -->
     <string name="submission_test_result_dialog_remove_test_message">"Dacă ștergeți testul, nu veți mai putea afla rezultatul testului. Veți primi rezultatul testului dvs. de la centrul sau laboratorul de testare, indiferent de valabilitatea codului QR. Dacă sunteți diagnosticat cu coronavirus, autoritatea de sănătate publică va fi notificată prin canalul de comunicare prevăzut în mod legal și vă va contacta."</string>
+    <!-- YTXT: Dialog text for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_message_no_submission">"ÃŽn caz afirmativ, alte persoane nu vor fi avertizate."</string>
     <!-- XBUT: Positive button for test removal -->
     <string name="submission_test_result_dialog_remove_test_button_positive">"Ștergere"</string>
     <!-- XBUT: Negative button for test removal -->
@@ -1233,6 +1237,8 @@
     <string name="submission_test_result_positive_no_consent_text_3">"Asigurați-vă că urmați instrucțiunile autorității dvs. de sănătate publică și că stați acasă, pentru a nu-i infecta pe ceilalți."</string>
     <!-- XBUT: Button for giving consent for key sharing -->
     <string name="submission_test_result_positive_no_consent_button_warn_others">"Avertizați-i pe ceilalți"</string>
+    <!-- XBUT: Button for test removal -->
+    <string name="submission_test_result_positive_no_consent_button_remove_test">"Eliminare test"</string>
 
     <!-- Submission Country Selector -->
     <!-- XHED: Page title for the submission country selection page -->
diff --git a/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml
index 276ff68ee28120c6141de936946719d1c965f212..a6fefa697ef42368e6dcbaaa13d9d0c75db14272 100644
--- a/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml
@@ -70,7 +70,9 @@
     <!-- XBUT: button for Vaccination Certificate Registration Home Card -->
     <string name="vaccination_card_register">"Adăugare"</string>
     <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title">"Dovada digitală a vaccinării"</string>
+    <string name="vaccination_card_status_title_line_1">"Dovada vaccinării"</string>
+    <!-- XHED: Homescreen vaccination status card title -->
+    <string name="vaccination_card_status_title_line_2">"digitale"</string>
     <!-- XHED: Homescreen vaccination status card vaccination name -->
     <string name="vaccination_card_status_vaccination_name">"Vaccinare SARS-CoV-2"</string>
     <!-- XTXT: Homescreen card incomplete vaccination status label -->
@@ -84,6 +86,8 @@
         <item quantity="few">"Protecția prin vaccinare este completă în %1$d zile"</item>
         <item quantity="many">"Protecția prin vaccinare este completă în %1$d zile"</item>
     </plurals>
+    <!-- XTXT: Homescreen card complete vaccination status label -->
+    <string name="vaccination_card_status_vaccination_complete">"Valabil până la %s"</string>
 
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_invalid">"Acest cod QR nu este un certificat de vaccinare valabil.\n\nPentru mai multe informații despre modul în care puteți primi certificatul dvs. de vaccinare, consultați pagina noastră de întrebări frecvente."</string>
diff --git a/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml
index 3a2d0403eb184b10f202d8ae22e5c541653714b1..056defb948e5e397527e1c5325cee02c28086d8e 100644
--- a/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml
@@ -140,6 +140,10 @@
     <string name="coronatest_negative_antigen_result_third_info_title">"Testi Kaldır"</string>
     <!-- XTXT: coronatest negative antigen result third info body -->
     <string name="coronatest_negative_antigen_restul_third_info_body">"Gerekirse yeni bir test kodu kaydedebilmeniz için lütfen testi Corona-Warn-App\'ten silin."</string>
+    <!-- XTXT: coronatest negative result certificate info title -->
+    <string name="coronatest_negative_result_certificate_info_title">"Test Sertifikası"</string>
+    <!-- XTXT: coronatest negative result certificate info body -->
+    <string name="coronatest_negative_result_certificate_info_body">"Test sertifikasını \"Sertifikalar\" sekmesinde bulabilirsiniz."</string>
 
     <!-- ####################################
      Rapid Antigen Test Profile
diff --git a/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml
index fc5992cfe8200a789105192fd25303a52d7474e0..2fdfd113ef26b3330a2f44859b3bd25faa9f0bc0 100644
--- a/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml
@@ -8,7 +8,7 @@
     <!-- XTXT: Request green certificate body section 2 -->
     <string name="request_green_certificate_body_2">"Test sertifikası, AB’de geçerli bir negatif test sonucu kanıtıdır (ör. seyahat için)."</string>
     <!-- XTXT: Request green certificate body section 3 -->
-    <string name="request_green_certificate_body_3">"Test sertifikası, doğrulama uygulamasının sertifikanızı doğrulamasını sağlayan geçerli değer içerir."</string>
+    <string name="request_green_certificate_body_3">"Test sertifikası, doğrulama uygulamasının sertifikanızı doğrulamasını sağlayan verileri içerir."</string>
     <!-- XBUT: Request green certificate agree button -->
     <string name="request_green_certificate_agree_button">"Test Sertifikası Talep Et"</string>
     <!-- XBUT: Request green certificate disagree button -->
@@ -31,10 +31,23 @@
     <string name="detail_green_certificate_test_type">"SARS-CoV-2 Testi"</string>
     <!-- XTXT: Detail green certificate card title -->
     <string name="detail_green_certificate_card_title">"Test Sertifikası"</string>
+    <!-- XTXT: Detail green certificate card subtitle -->
+    <string name="detail_green_certificate_card_subtitle">"Testin gerçekleştirildiği tarih: %1$s %2$s"</string>
     <!-- XTXT: Detail green certificate travel notice link en -->
     <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string>
     <!-- XTXT: Detail green certificate travel notice link de -->
     <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string>
+    <!-- XTXT: Detail green certificate menu item: delete -->
+    <string name="green_certificate_details_menu_item_delete">"Kaldır"</string>
+    <!-- XTXT: Detail green certificate diaglog title -->
+    <string name="green_certificate_details_dialog_remove_test_title">"Test sertifikasını kaldırmak istiyor musunuz?"</string>
+    <!-- XTXT: Detail green certificate diaglog message -->
+    <string name="green_certificate_details_dialog_remove_test_message">"Test sertifikasını kaldırırsanız artık bunu uygulamada kanıt olarak kullanamazsınız."</string>
+    <!-- XTXT: Detail green certificate diaglog positive -->
+    <string name="green_certificate_details_dialog_remove_test_button_positive">"Kaldır"</string>
+    <!-- XTXT: Detail green certificate diaglog negative -->
+    <string name="green_certificate_details_dialog_remove_test_button_negative">"Ä°ptal Et"</string>
+
     <!-- XTXT: Green certificate main screen title -->
     <string name="certification_screen_title">"Sertifikalar"</string>
     <!-- XTXT: Green certificate menu item -->
@@ -47,4 +60,43 @@
     <string name="info_banner_title_2">"COVID Test Sertifikası"</string>
     <!-- XTXT: Green certificate info card body  -->
     <string name="info_banner_body">"Ana ekranda bir testi kaydedin ve dijital test sertifikası almayı kabul edin. Sertifika düzenlenir düzenlenmez burada görüntülenir."</string>
+
+    <!-- XTXT: Test certificate time -->
+    <string name="test_certificate_time">"Testin gerçekleştirildiği tarih: %1$s, %2$s"</string>
+    <!-- XTXT: Test error label -->
+    <string name="test_certificate_error_label">"Sertifika alınırken hata oluştu"</string>
+    <!-- XBUT: Test error retry button -->
+    <string name="test_certificate_error_retry_button">"Tekrar Dene"</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again">"Bağlantı kurulamadı. Lütfen tekrar deneyin."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_not_supported_by_lab">"Bu test noktasında test sertifikalarının düzenlenmesi desteklenmediğinden bir test sertifikası talep edemezsiniz. Lütfen sertifikayı kaldırın veya Uygulama Bilgileri -&gt; Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletişime geçin."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_no_network">"İnternet bağlantınız kesildi. Lütfen bağlantıyı kontrol edin ve tekrar deneyin."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_e2e_error_call_hotline">"Bir hata oluştu. Lütfen daha sonra tekrar deneyin veya Uygulama Bilgileri -&gt; Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletişime geçin."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_try_again_dcc_not_available_yet">"Sertifikanız henüz çıkmadı. Lütfen tekrar deneyin. Hata devam ederse lütfen Uygulama Bilgileri -&gt; Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletişime geçin."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_client_error_call_hotline">"Bir hata oluştu. Lütfen daha sonra tekrar deneyin veya Uygulama Bilgileri -&gt; Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletişime geçin."</string>
+    <!-- XTXT: Error text -->
+    <string name="error_tc_dcc_expired">"Bu sertifika artık güncel değil. Sertifikayı Corona-Warn-App\'ten kaldırabilirsiniz."</string>
+    <!-- XBUT: Test error delete button -->
+    <string name="test_certificate_error_delete_button">"Test Sertifikasını Kaldır"</string>
+    <!-- XTXT: Test error refresh dialog title -->
+    <string name="test_certificate_refresh_dialog_title">"Yine de talebinizle ilgili problemler var."</string>
+    <!-- XBUT: Test error refresh dialog confirm button -->
+    <string name="test_certificate_refresh_dialog_confirm_button">"Tamam"</string>
+    <!-- XTXT: Test error delete dialog title -->
+    <string name="test_certificate_delete_dialog_title">"Sertifikayı kaldırmak istiyor musunuz?"</string>
+    <!-- XTXT: Test error delete dialog body -->
+    <string name="test_certificate_delete_dialog_body">"Sertifikayı kaldırırsanız tekrar talep edemezsiniz."</string>
+    <!-- XBUT: Test error delete dialog confirm button -->
+    <string name="test_certificate_delete_dialog_confirm_button">"Kaldır"</string>
+    <!-- XBUT: Test error delete dialog cancel button -->
+    <string name="test_certificate_delete_dialog_cancel_button">"Ä°ptal Et"</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_label_refreshing">"Sertifikanız oluşturuluyor..."</string>
+    <!-- XTXT: Test error card body refreshing -->
+    <string name="test_certificate_error_refreshing_status">"Sertifikanız talep ediliyor. Bu işlem birkaç dakika sürebilir..."</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 1c074fcc7a663cedc3220ff7ef59d5ccf87cf57a..ae2bd972f7fefc5434b605d4895adea28e209db1 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
@@ -170,13 +170,13 @@
     <!-- XHED: Title for privacy card -->
     <string name="vaccination_privacy_card_title_text" translatable="false">"Veri gizliliği ve veri güvenliği"</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Dijital aşı sertifikasının kullanımı isteğe bağlıdır. Tam aşı korumasının gerçekleştirildiğine ilişkin kanıt, diğer yöntemlerle de sağlanabilir (örn. sarı aşı kartı ile)."</string>
+    <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Dijital COVID sertifikasının kullanımı isteğe bağlıdır. Tam aşı korumasının gerçekleştirildiğine veya test sonucunun negatif çıktığına ilişkin kanıt, diğer yöntemlerle de sağlanabilir."</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"Aşı sertifikası, yaptırdığınız Korona aşılarınıza ilişkin veriler içerir. Yasal açıdan zorunlu durumlarda aşı koruması yaptırdığınızı kanıtlamak için Uygulamada QR kodu göstermeniz yeterli olacaktır. Söz konusu verilerin okunmasını istemiyorsanız, aşı sertifikasını ve QR kodu hiç kimseye göstermeyin."</string>
+    <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"COVID sertifikaları (aşılar veya test sonuçlarınıza ilişkin) kişisel verilerinizi içerir. Yasal açıdan zorunlu durumlarda, Uygulamadaki QR kodu göstermeniz yeterli olacaktır. Söz konusu verilerin okunmasını istemiyorsanız, sertifikalarınızı ve QR kodu hiç kimseye göstermeyin."</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"QR kodu Uygulamada gösterirseniz ve bu kod, kontrol uygulamasıyla taranırsa, diğer kişiler sizin tam aşı koruması aldığınızı anlayabilir. Kontrol işlemi sırasında adınız ve doğum tarihiniz de resmi kontrol uygulamasında görüntülenecektir."</string>
+    <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"Bir kontrol sırasında Uygulamanızdaki bir sertifikanın QR kodunu gösterirseniz, orada bulunan diğer kişiler test sonucunuzu veya aşı korumanızın tamamlanıp tamamlanmadığını öğrenebilir. Kontrol işlemi sırasında adınız ve doğum tarihiniz de resmi kontrol uygulamasında görüntülenecektir."</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"Aşı sertifikasını istediğiniz zaman Uygulamadan kaldırma seçeneğiniz vardır. Bunu yapıncaya kadar aşı sertifikaları akıllı telefonunuzda kayıtlı kalacaktır."</string>
+    <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"COVID sertifikalarınızı istediğiniz zaman Uygulamadan kaldırma seçeneğiniz vardır. Bunu yapıncaya kadar bu sertifikalar akıllı telefonunuzda kayıtlı kalacaktır."</string>
 
     <!--    Green Certificate -->
     <!-- XTXT: Request GC title for privacy card -->
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 f8752dd07863f43e4e9e0343df0acac4d73b68ff..8e6480e07b70290fd577ddc015940808f82e19a8 100644
--- a/Corona-Warn-App/src/main/res/values-tr/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml
@@ -308,7 +308,7 @@
     <!-- XHED: risk details - infection period logged headling, below behaviors -->
     <string name="risk_details_subtitle_period_logged">"Bu dönem hesaplamaya dahil edildi."</string>
     <!-- XHED: risk details - infection period logged information body, below behaviors -->
-<!--    Dialog part 1-->
+    <!--    Dialog part 1-->
     <string name="risk_details_information_body_period_logged">"Enfeksiyon riskiniz yalnızca maruz kalma günlüğünün etkin olduğu dönemler için hesaplanabilir. Bu nedenle günlüğe kaydetme özelliğinin sürekli etkin kalması gerekir. Maruz kalma günlüğü son 14 günü kapsar."</string>
     <!-- XTXT: risk details - infection period logged information body, under 14 days -->
     <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Corona-Warn-App %s gün önce yüklendi. Enfeksiyon riskiniz, maruz kalma günlüğünün etkin olduğu dönemler için hesaplanır. Başka insanlarla karşılaşmışsanız ve bu sırada maruz kalma günlüğü etkindiyse enfeksiyon riskiniz hesaplanır."</string>
@@ -1143,8 +1143,12 @@
     <string name="submission_test_result_dialog_tracing_required_button">"Tamam"</string>
     <!-- XHED: Dialog title for test removal  -->
     <string name="submission_test_result_dialog_remove_test_title">"Test yalnızca bir kez taranabilir."</string>
+    <!-- XHED: Dialog title for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_title_no_submission">"Testi kaldırmak istediğinizden emin misiniz?"</string>
     <!-- YTXT: Dialog text for test removal -->
     <string name="submission_test_result_dialog_remove_test_message">"Testi silerseniz test sonucunuzu alamazsınız. QR kod geçerli olsun veya olmasın test sonucunuzu test merkezinden veya laboratuvardan alacaksınız. Koronavirüs tanısı alırsanız kamu sağlığı yetkilisi yasal olarak belirlenen kanal üzerinden bilgilendirilecektir ve kamu sağlığı yetkilisi sizinle iletişime geçecektir."</string>
+    <!-- YTXT: Dialog text for test removal no submission -->
+    <string name="submission_test_result_dialog_remove_test_message_no_submission">"Bu işlemi gerçekleştirirseniz diğer kullanıcılar uyarılmaz."</string>
     <!-- XBUT: Positive button for test removal -->
     <string name="submission_test_result_dialog_remove_test_button_positive">"Sil"</string>
     <!-- XBUT: Negative button for test removal -->
@@ -1233,6 +1237,8 @@
     <string name="submission_test_result_positive_no_consent_text_3">"Başka insanların enfeksiyona yakalanmaması için lütfen kamu sağlığı yetkilinizin talimatlarına uyun ve evde kalın."</string>
     <!-- XBUT: Button for giving consent for key sharing -->
     <string name="submission_test_result_positive_no_consent_button_warn_others">"Diğer Kullanıcıları Uyarın"</string>
+    <!-- XBUT: Button for test removal -->
+    <string name="submission_test_result_positive_no_consent_button_remove_test">"Testi Kaldır"</string>
 
     <!-- Submission Country Selector -->
     <!-- XHED: Page title for the submission country selection page -->
diff --git a/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml
index b0657b9bd66120e133e6c702353adb9a6e66e791..111e60df3030ac0922886621b24f2229c52ceb8f 100644
--- a/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml
@@ -70,7 +70,9 @@
     <!-- XBUT: button for Vaccination Certificate Registration Home Card -->
     <string name="vaccination_card_register">"Ekle"</string>
     <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title">"Dijital Aşı Kanıtı"</string>
+    <string name="vaccination_card_status_title_line_1">"Dijital"</string>
+    <!-- XHED: Homescreen vaccination status card title -->
+    <string name="vaccination_card_status_title_line_2">"Aşı Kanıtı"</string>
     <!-- XHED: Homescreen vaccination status card vaccination name -->
     <string name="vaccination_card_status_vaccination_name">"SARS-CoV-2 Aşısı"</string>
     <!-- XTXT: Homescreen card incomplete vaccination status label -->
@@ -84,6 +86,8 @@
         <item quantity="few">"%1$d gün içinde tam aşı koruması"</item>
         <item quantity="many">"%1$d gün içinde tam aşı koruması"</item>
     </plurals>
+    <!-- XTXT: Homescreen card complete vaccination status label -->
+    <string name="vaccination_card_status_vaccination_complete">"Son geçerlilik tarihi: %s"</string>
 
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_invalid">"Bu QR kodu geçerli bir aşı sertifikası değildir.\n\nAşı sertifikanızı nasıl alacağınız hakkında daha fazla bilgi için lütfen SSS sayfamıza bakın."</string>
diff --git a/Corona-Warn-App/src/main/res/values/antigen_strings.xml b/Corona-Warn-App/src/main/res/values/antigen_strings.xml
index 525e5937055eca395d017b6a1745b89c8754106f..9b78fb440d21879e533c1213c19a069e122fbfda 100644
--- a/Corona-Warn-App/src/main/res/values/antigen_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/antigen_strings.xml
@@ -141,6 +141,10 @@
     <string name="coronatest_negative_antigen_result_third_info_title">"Remove Test"</string>
     <!-- XTXT: coronatest negative antigen result third info body -->
     <string name="coronatest_negative_antigen_restul_third_info_body">"Please delete the test from the Corona-Warn-App, so that you can save a new test code here if necessary."</string>
+    <!-- XTXT: coronatest negative result certificate info title -->
+    <string name="coronatest_negative_result_certificate_info_title">"Test Certificate"</string>
+    <!-- XTXT: coronatest negative result certificate info body -->
+    <string name="coronatest_negative_result_certificate_info_body">"The test certificate is available on the “Certificates” tab."</string>
 
     <!-- ####################################
      Rapid Antigen Test Profile
diff --git a/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml
index ece39a2784ecdbf679e5cbfca640fee0a8a12d3d..be47fa7e524f0f247498ed274e8647639a7a9727 100644
--- a/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml
@@ -9,7 +9,7 @@
     <!-- XTXT: Request green certificate body section 2 -->
     <string name="request_green_certificate_body_2">"The test certificate is valid proof of a negative test result within the EU (e.g. for travel)."</string>
     <!-- XTXT: Request green certificate body section 3 -->
-    <string name="request_green_certificate_body_3">"The test certificate contains valid that enables the verification app to validate your certificate."</string>
+    <string name="request_green_certificate_body_3">"The test certificate contains data that enables the verification app to validate your certificate."</string>
     <!-- XBUT: Request green certificate agree button -->
     <string name="request_green_certificate_agree_button">"Request Test Certificate"</string>
     <!-- XBUT: Request green certificate disagree button -->
@@ -33,21 +33,21 @@
     <!-- XTXT: Detail green certificate card title -->
     <string name="detail_green_certificate_card_title">"Test Certificate"</string>
     <!-- XTXT: Detail green certificate card subtitle -->
-    <string name="detail_green_certificate_card_subtitle">"Test durchgeführt am %1$s %2$s"</string>
+    <string name="detail_green_certificate_card_subtitle">"Test performed on %1$s %2$s"</string>
     <!-- XTXT: Detail green certificate travel notice link en -->
     <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string>
     <!-- XTXT: Detail green certificate travel notice link de -->
     <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string>
     <!-- XTXT: Detail green certificate menu item: delete -->
-    <string name="green_certificate_details_menu_item_delete">"Entfernen"</string>
+    <string name="green_certificate_details_menu_item_delete">"Remove"</string>
     <!-- XTXT: Detail green certificate diaglog title -->
-    <string name="green_certificate_details_dialog_remove_test_title">"Wollen Sie das Testzertifikat wirklich entfernen?"</string>
+    <string name="green_certificate_details_dialog_remove_test_title">"Do you want to remove the test certificate?"</string>
     <!-- XTXT: Detail green certificate diaglog message -->
-    <string name="green_certificate_details_dialog_remove_test_message">"Wenn Sie das Testzertifikat entfernen, können Sie es nicht mehr als Nachweis in der App verwenden."</string>
+    <string name="green_certificate_details_dialog_remove_test_message">"If you remove the test certificate, you can no longer use it as proof in the app."</string>
     <!-- XTXT: Detail green certificate diaglog positive -->
-    <string name="green_certificate_details_dialog_remove_test_button_positive">"Abbrechen"</string>
+    <string name="green_certificate_details_dialog_remove_test_button_positive">"Remove"</string>
     <!-- XTXT: Detail green certificate diaglog negative -->
-    <string name="green_certificate_details_dialog_remove_test_button_negative">"Entfernen"</string>
+    <string name="green_certificate_details_dialog_remove_test_button_negative">"Cancel"</string>
 
     <!-- XTXT: Green certificate main screen title -->
     <string name="certification_screen_title">"Certificates"</string>
@@ -61,42 +61,47 @@
     <string name="info_banner_title_2">"COVID Test Certificate"</string>
     <!-- XTXT: Green certificate info card body  -->
     <string name="info_banner_body">"Register a test on the home screen and agree to receive a digital test certificate. As soon as the certificate is available, it is displayed here."</string>
+
     <!-- XTXT: Test certificate time -->
-    <string name="test_certificate_time">Test durchgeführt am %1$s, %2$s</string>
+    <string name="test_certificate_time">"Test performed on %1$s, %2$s"</string>
     <!-- XTXT: Test error label -->
-    <string name="test_certificate_error_label">Fehler bei der Zertifikatsabfrage</string>
+    <string name="test_certificate_error_label">"Error during certificate retrieval"</string>
     <!-- XBUT: Test error retry button -->
-    <string name="test_certificate_error_retry_button">Nochmal versuchen</string>
+    <string name="test_certificate_error_retry_button">"Try Again"</string>
     <!-- XTXT: Error text -->
-    <string name="error_tc_try_again">Es konnte keine Verbindung hergestellt werden. Bitte versuchen Sie es erneut.</string>
+    <string name="error_tc_try_again">"Could not establish a connection. Please try again."</string>
     <!-- XTXT: Error text -->
-    <string name="error_tc_dcc_not_supported_by_lab">Ein Testzertifikat kann nicht angefordert werden, da diese Teststelle die Ausstellung von Testzertifikaten nicht unterstützt. Bitte entfernen Sie das Zertifikat oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.</string>
+    <string name="error_tc_dcc_not_supported_by_lab">"You cannot request a test certificate because this testing point does not support the issuing of test certificates. Please remove the certificate or contact the technical hotline via App Information -&gt; Technical Hotline."</string>
     <!-- XTXT: Error text -->
-    <string name="error_tc_no_network">Ihre Internetverbindung wurde unterbrochen. Bitte prüfen Sie die Verbindung und versuchen Sie es erneut.</string>
+    <string name="error_tc_no_network">"Your Internet connection was lost. Please check the connection and try again."</string>
     <!-- XTXT: Error text -->
-    <string name="error_tc_e2e_error_call_hotline">Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.</string>
+    <string name="error_tc_e2e_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -&gt; Technical Hotline."</string>
     <!-- XTXT: Error text -->
-    <string name="error_tc_try_again_dcc_not_available_yet">Ihr Zertifikat liegt noch nicht vor. Bitte versuchen Sie es noch einmal. Sollte der Fehler weiterhin bestehen, kontaktieren Sie bitte die technische Hotline über App-Informationen -> Technische Hotline.</string>
+    <string name="error_tc_try_again_dcc_not_available_yet">"Your certificate it not available yet. Please try again. If the error persists, please contact the technical hotline via App Information -&gt; Technical Hotline."</string>
     <!-- XTXT: Error text -->
-    <string name="error_tc_client_error_call_hotline">Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.></string>
+    <string name="error_tc_client_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -&gt; Technical Hotline."</string>
     <!-- XTXT: Error text -->
-    <string name="error_tc_dcc_expired">Das Zertifikat ist nicht mehr aktuell, Sie können es aus der Corona-App entfernen.</string>
+    <string name="error_tc_dcc_expired">"This certificate is no longer current. You can remove it from the Corona-Warn-App."</string>
     <!-- XBUT: Test error delete button -->
-    <string name="test_certificate_error_delete_button">Testzertifikat entfernen</string>
+    <string name="test_certificate_error_delete_button">"Remove Test Certificate"</string>
     <!-- XTXT: Test error refresh dialog title -->
-    <string name="test_certificate_refresh_dialog_title">"Es gibt weiterhin Probleme bei der Abfrage"</string>
+    <string name="test_certificate_refresh_dialog_title">"There are still problems with the request."</string>
     <!-- XBUT: Test error refresh dialog confirm button -->
     <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string>
     <!-- XTXT: Test error delete dialog title -->
-    <string name="test_certificate_delete_dialog_title">"Wollen Sie das Zertifikat wirklich entfernen?"</string>
+    <string name="test_certificate_delete_dialog_title">"Do you want to remove the certificate?"</string>
     <!-- XTXT: Test error delete dialog body -->
-    <string name="test_certificate_delete_dialog_body">"Wenn das Zertifikat enfernt wird, kann es nicht noch einmal angefordert werden."</string>
+    <string name="test_certificate_delete_dialog_body">"If you remove the certificate, you cannot request it again."</string>
     <!-- XBUT: Test error delete dialog confirm button -->
-    <string name="test_certificate_delete_dialog_confirm_button">"Entfernen"</string>
+    <string name="test_certificate_delete_dialog_confirm_button">"Remove"</string>
     <!-- XBUT: Test error delete dialog cancel button -->
-    <string name="test_certificate_delete_dialog_cancel_button">"Abbrechen"</string>
+    <string name="test_certificate_delete_dialog_cancel_button">"Cancel"</string>
     <!-- XTXT: Test error card body refreshing -->
-    <string name="test_certificate_error_label_refreshing">"Ihr Zertifikat wird gerade erstellt…"</string>
+    <string name="test_certificate_error_label_refreshing">"Your certificate is being created..."</string>
     <!-- XTXT: Test error card body refreshing -->
-    <string name="test_certificate_error_refreshing_status">"Ihr Zertifikat wird gerade angefragt, dies kann einige Minuten dauern…"</string>
-</resources>
+    <string name="test_certificate_error_refreshing_status">"Your certificate is being requested. This may take a few minutes..."</string>
+    <!-- XBUT: Text for invalid test certificate error button, linking to FAQ-->
+    <string name="test_certificate_error_invalid_labid_faq"></string>
+    <!-- XTXT: Explains user about test certificate: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#vac_cert_invalid -->
+    <string name="test_certificate_error_invalid_labid_faq_link"></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 876b0c4771416cfb31aa3f09c1a63789bf0ceba4..4ade14fe1a8c80fa96042d6eeeed1f2cb8b679f2 100644
--- a/Corona-Warn-App/src/main/res/values/legal_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/legal_strings.xml
@@ -171,13 +171,13 @@
     <!-- XHED: Title for privacy card -->
     <string name="vaccination_privacy_card_title_text" translatable="false">"Datenschutz und Datensicherheit"</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Die Verwendung der digitalen COVID-Zertifikate ist freiwillig. Die Nachweise des vollständigen Impfschutzes, eines negativen Testergebnisses oder einer überstandenen Corona-Infektion können auch auf andere Weise erbracht werden."</string>
+    <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Using the digital COVID-19 certificates is voluntary. There are other ways to prove that you are fully vaccinated or have a negative test result."</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"Die COVID-Zertifikate enthalten personenbezogene Daten (zu Impfungen, Testergebnissen oder einer überstandenen Infektion). Zum Nachweis in den gesetzlich vorgesehenen Fällen genügt das Vorzeigen des QR-Codes des Zertifikats in der App. Bei der Prüfung ist gegebenenfalls ein Ausweisdokument vorzulegen. Stellen Sie die Zertifikate und die QR-Codes niemandem zur Verfügung, wenn Sie nicht wollen, dass die Daten ausgelesen werden."</string>
+    <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"The COVID-19 certificates contain your personal data (concerning vaccinations or your test result). In cases where you are required by law to prove your vaccination status or test result, it is sufficient to show the QR code of the certificate in the app. Do not provide your certificates or the QR codes to anyone if you do not want the data to be read."</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"Wenn Sie den QR-Code eines Zertifikats in der App zur Prüfung vorzeigen, können andere Personen nachvollziehen, dass ein negatives Testergebnis, ein vollständiger Impfschutz oder ein Nachweis über eine überstandene Infektion vorliegt. Bei der Prüfung werden in der offiziellen Prüf-App auch der Name und das Geburtsdatum angezeigt. Bei Testzertifikaten wird zusätzlich auch das Datum der Probenahme angezeigt."</string>
+    <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"When you present the QR code of a certificate in the app for verification, others will be able to find out your test result or whether you are fully vaccinated. During verification, the official verification app will also display your name and date of birth."</string>
     <!-- XTXT: First bulletpoint title for privacy card -->
-    <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"Sie haben jederzeit die Möglichkeit, COVID-Zertifikate in der App wieder zu entfernen. Bis dahin bleiben die Zertifikate auf Ihrem Smartphone gespeichert."</string>
+    <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"You have the possibility to delete COVID-19 certificates in the app at any time. Until then, the certificates will be stored on your smartphone."</string>
 
     <!--    Green Certificate -->
     <!-- XTXT: Request GC title for privacy card -->
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index 4e9ce9aeabce50b87ad4a6fbdb1c58889e1f57f1..bbe46a803d93da0c18070d0d0196e90eb7ae68bb 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -1145,11 +1145,11 @@
     <!-- XHED: Dialog title for test removal  -->
     <string name="submission_test_result_dialog_remove_test_title">"The test can only be scanned once."</string>
     <!-- XHED: Dialog title for test removal no submission -->
-    <string name="submission_test_result_dialog_remove_test_title_no_submission">"Sind Sie sicher, dass Sie Ihren Test entfernen wollen?"</string>
+    <string name="submission_test_result_dialog_remove_test_title_no_submission">"Are you sure you want to remove your test?"</string>
     <!-- YTXT: Dialog text for test removal -->
     <string name="submission_test_result_dialog_remove_test_message">"If you delete the test, you can no longer retrieve your test result. You will receive your test result from the test center or laboratory regardless of the validity of the QR code. If you are diagnosed with coronavirus, the public health authority will be notified through the legally prescribed channel and will contact you."</string>
     <!-- YTXT: Dialog text for test removal no submission -->
-    <string name="submission_test_result_dialog_remove_test_message_no_submission">"Dadurch werden andere nicht gewarnt."</string>
+    <string name="submission_test_result_dialog_remove_test_message_no_submission">"If you do, others will not be warned."</string>
     <!-- XBUT: Positive button for test removal -->
     <string name="submission_test_result_dialog_remove_test_button_positive">"Delete"</string>
     <!-- XBUT: Negative button for test removal -->
@@ -1239,7 +1239,7 @@
     <!-- XBUT: Button for giving consent for key sharing -->
     <string name="submission_test_result_positive_no_consent_button_warn_others">"Warn Others"</string>
     <!-- XBUT: Button for test removal -->
-    <string name="submission_test_result_positive_no_consent_button_remove_test">Test entfernen</string>
+    <string name="submission_test_result_positive_no_consent_button_remove_test">"Remove Test"</string>
 
     <!-- Submission Country Selector -->
     <!-- XHED: Page title for the submission country selection page -->
diff --git a/Corona-Warn-App/src/main/res/values/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values/vaccination_strings.xml
index 7aa5ac42c7436ee03bacb7b5eca9c6748f669702..83b31ca56ea4b650ac899d9679bdd37c874209fc 100644
--- a/Corona-Warn-App/src/main/res/values/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/vaccination_strings.xml
@@ -71,11 +71,9 @@
     <!-- XBUT: button for Vaccination Certificate Registration Home Card -->
     <string name="vaccination_card_register">"Add"</string>
     <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title">"Digital Proof of Vaccination"</string>
+    <string name="vaccination_card_status_title_line_1">"Digital"</string>
     <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title_line_1">"Digitaler"</string>
-    <!-- XHED: Homescreen vaccination status card title -->
-    <string name="vaccination_card_status_title_line_2">"Impfnachweis"</string>
+    <string name="vaccination_card_status_title_line_2">"Proof of Vaccination"</string>
     <!-- XHED: Homescreen vaccination status card vaccination name -->
     <string name="vaccination_card_status_vaccination_name">"SARS-CoV-2 Vaccination"</string>
     <!-- XTXT: Homescreen card incomplete vaccination status label -->
@@ -90,7 +88,8 @@
         <item quantity="many">"Full vaccination protection in %1$d days"</item>
     </plurals>
     <!-- XTXT: Homescreen card complete vaccination status label -->
-    <string name="vaccination_card_status_vaccination_complete">"Gültig bis einschließlich %s"</string>
+    <string name="vaccination_card_status_vaccination_complete">"Valid through %s"</string>
+
     <!-- XTXT: Vaccination QR code scan error message-->
     <string name="error_vc_invalid">"This QR code is not a valid vaccination certificate.\n\nFor more information about how to receive your vaccination certificate, please see our FAQ page."</string>
     <!-- XTXT: Vaccination QR code scan error message-->
@@ -118,4 +117,4 @@
     <string name="vaccination_certificate_subtitle">"EU Digitales COVID-Zertifikat"</string>
 
 
-</resources>
+</resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
index 329aed6fb188a493fc6a7197f41e29b5ee9628f9..20309b770c3034c9cfe29faaf8f245e013842c6e 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt
@@ -1,6 +1,7 @@
 package de.rki.coronawarnapp.bugreporting.debuglog
 
 import android.app.Application
+import android.content.pm.PackageManager
 import dagger.Lazy
 import de.rki.coronawarnapp.bugreporting.censors.BugCensor
 import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogTree
@@ -10,11 +11,14 @@ import io.kotest.matchers.shouldBe
 import io.kotest.matchers.shouldNotBe
 import io.kotest.matchers.string.shouldEndWith
 import io.kotest.matchers.string.shouldStartWith
+import io.mockk.Called
 import io.mockk.MockKAnnotations
 import io.mockk.coEvery
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
 import io.mockk.mockkObject
+import io.mockk.verify
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.test.runBlockingTest
@@ -31,6 +35,7 @@ import java.io.File
 class DebugLoggerTest : BaseIOTest() {
 
     @MockK lateinit var application: Application
+    @MockK lateinit var packageManager: PackageManager
     @MockK lateinit var component: ApplicationComponent
     @MockK lateinit var coronaTestCensor1: BugCensor
     @MockK lateinit var coronaTestCensor2: BugCensor
@@ -51,6 +56,8 @@ class DebugLoggerTest : BaseIOTest() {
         testDir.exists() shouldBe true
 
         every { application.cacheDir } returns cacheDir
+        every { application.packageManager } returns packageManager
+
         every { component.inject(any<DebugLogger>()) } answers {
             val logger = arg<DebugLogger>(0)
             logger.bugCensors = Lazy { setOf(coronaTestCensor1, coronaTestCensor2) }
@@ -102,9 +109,13 @@ class DebugLoggerTest : BaseIOTest() {
     }
 
     @Test
-    fun `init calls start if it is a tester build`() = runBlockingTest {
+    fun `init calls start if it is a tester build and autologger pkg is installed`() = runBlockingTest {
         every { CWADebug.isDeviceForTestersBuild } returns true
 
+        every {
+            packageManager.getPackageInfo("de.rki.coronawarnapp.els.autologger", 0)
+        } returns mockk()
+
         val instance = createInstance(scope = this).apply {
             init()
             setInjectionIsReady(component)
@@ -116,6 +127,59 @@ class DebugLoggerTest : BaseIOTest() {
         instance.stop()
     }
 
+    @Test
+    fun `init does not call start on tester builds without the autologger pkg`() = runBlockingTest {
+        every { CWADebug.isDeviceForTestersBuild } returns true
+
+        every { application.packageManager } returns mockk<PackageManager>().apply {
+            every { getPackageInfo(any<String>(), any()) } throws PackageManager.NameNotFoundException()
+        }
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+            isLogging.value shouldBe false
+        }
+
+        runningLog.exists() shouldBe false
+
+        instance.stop()
+    }
+
+    @Test
+    fun `init does not call start on tester builds with ROM issues`() = runBlockingTest {
+        every { CWADebug.isDeviceForTestersBuild } returns true
+
+        every { application.packageManager } throws SecurityException()
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+            isLogging.value shouldBe false
+        }
+
+        runningLog.exists() shouldBe false
+
+        instance.stop()
+    }
+
+    @Test
+    fun `package check is not executed in PROD`() = runBlockingTest {
+        every { CWADebug.isDeviceForTestersBuild } returns false
+
+        val instance = createInstance(scope = this).apply {
+            init()
+            setInjectionIsReady(component)
+            isLogging.value shouldBe false
+        }
+
+        runningLog.exists() shouldBe false
+
+        instance.stop()
+
+        verify { packageManager wasNot Called }
+    }
+
     @Test
     fun `start plants a tree and starts a logging coroutine`() = runBlockingTest {
         val instance = createInstance(scope = this).apply {
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt
index 7eb566a58f9810a6458f0c3499b0d2d6243e4a1a..3ec942256f9095bacf839def1c1ed7a44e6b5f64 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt
@@ -1,7 +1,9 @@
 package de.rki.coronawarnapp.coronatest
 
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
+import de.rki.coronawarnapp.coronatest.errors.DuplicateCoronaTestException
 import de.rki.coronawarnapp.coronatest.migration.PCRTestMigration
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
@@ -9,6 +11,8 @@ import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
 import de.rki.coronawarnapp.coronatest.type.pcr.PCRTestProcessor
 import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
 import de.rki.coronawarnapp.coronatest.type.rapidantigen.RATestProcessor
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
 import io.mockk.coEvery
@@ -29,20 +33,28 @@ class CoronaTestRepositoryTest : BaseTest() {
     @MockK lateinit var legacyMigration: PCRTestMigration
     @MockK lateinit var contactDiaryRepository: ContactDiaryRepository
 
+    @MockK lateinit var pcrProcessor: PCRTestProcessor
+    @MockK lateinit var raProcessor: RATestProcessor
+
     private var coronaTestsInStorage = mutableSetOf<CoronaTest>()
 
+    private val pcrRegistrationRequest = CoronaTestQRCode.PCR(
+        qrCodeGUID = "pcr-guid"
+    )
     private val pcrTest = PCRCoronaTest(
-        identifier = "pcr-identifier",
+        identifier = pcrRegistrationRequest.identifier,
         lastUpdatedAt = Instant.EPOCH,
         registeredAt = Instant.EPOCH,
         registrationToken = "token",
         testResult = CoronaTestResult.PCR_REDEEMED,
     )
-    @MockK lateinit var pcrProcessor: PCRTestProcessor
 
-    @MockK lateinit var raProcessor: RATestProcessor
+    private val raRegistrationRequest = CoronaTestQRCode.RapidAntigen(
+        hash = "ra-hash",
+        createdAt = Instant.EPOCH
+    )
     private val raTest = RACoronaTest(
-        identifier = "ra-identifier",
+        identifier = raRegistrationRequest.identifier,
         lastUpdatedAt = Instant.EPOCH,
         registeredAt = Instant.EPOCH,
         registrationToken = "token",
@@ -54,9 +66,6 @@ class CoronaTestRepositoryTest : BaseTest() {
     fun setup() {
         MockKAnnotations.init(this)
 
-        coronaTestsInStorage.add(pcrTest)
-        coronaTestsInStorage.add(raTest)
-
         legacyMigration.apply {
             coEvery { startMigration() } returns emptySet()
             coEvery { finishMigration() } just Runs
@@ -75,11 +84,13 @@ class CoronaTestRepositoryTest : BaseTest() {
         }
 
         pcrProcessor.apply {
+            coEvery { create(pcrRegistrationRequest) } returns pcrTest
             coEvery { updateSubmissionConsent(any(), any()) } answers { arg<PCRCoronaTest>(0) }
             every { type } returns CoronaTest.Type.PCR
         }
 
         raProcessor.apply {
+            coEvery { create(raRegistrationRequest) } returns raTest
             coEvery { updateSubmissionConsent(any(), any()) } answers { arg<RACoronaTest>(0) }
             every { type } returns CoronaTest.Type.RAPID_ANTIGEN
         }
@@ -96,8 +107,31 @@ class CoronaTestRepositoryTest : BaseTest() {
 
     @Test
     fun `give submission consent`() = runBlockingTest2(ignoreActive = true) {
+        coronaTestsInStorage.add(pcrTest)
+
         createInstance(this).updateSubmissionConsent(pcrTest.identifier, true)
 
         coVerify { pcrProcessor.updateSubmissionConsent(pcrTest, true) }
     }
+
+    @Test
+    fun `test registration with default conditions`() = runBlockingTest2(ignoreActive = true) {
+        coronaTestsInStorage.clear()
+        val negativePcr = pcrTest.copy(testResult = CoronaTestResult.PCR_NEGATIVE)
+        coEvery { pcrProcessor.create(pcrRegistrationRequest) } returns negativePcr
+        val instance = createInstance(this)
+
+        instance.registerTest(pcrRegistrationRequest) shouldBe negativePcr
+    }
+
+    @Test
+    fun `test registration with default conditions and existing test`() = runBlockingTest2(ignoreActive = true) {
+        coronaTestsInStorage.add(pcrTest)
+
+        val instance = createInstance(this)
+
+        shouldThrow<DuplicateCoronaTestException> {
+            instance.registerTest(pcrRegistrationRequest) shouldBe pcrTest
+        }
+    }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt
index 9076ba3b7c9e0cda0069939b58e8889d959f2a90..bc48dcd234cb9e7c0bf0c6b43a30560bd3bd1d0f 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt
@@ -169,7 +169,8 @@ class VerificationApiV1Test : BaseIOTest() {
             requestBody
         ) shouldBe VerificationApiV1.TestResultResponse(
             testResult = 1,
-            sampleCollectedAt = null
+            sampleCollectedAt = null,
+            labId = null,
         )
 
         webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply {
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt
index 07f8cbc45e513d5797afe10e4a6fbe92dc193e0a..1f68b62c1311a2ec9e3fa64ab6bd967082c30b09 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt
@@ -166,12 +166,13 @@ class VerificationServerTest : BaseIOTest() {
                 registrationToken shouldBe "testRegistrationToken"
                 requestPadding.length shouldBe 170
             }
-            VerificationApiV1.TestResultResponse(testResult = 2, sampleCollectedAt = null)
+            VerificationApiV1.TestResultResponse(testResult = 2, sampleCollectedAt = null, labId = null)
         }
 
         server.pollTestResult("testRegistrationToken") shouldBe CoronaTestResultResponse(
             coronaTestResult = CoronaTestResult.PCR_POSITIVE,
-            sampleCollectedAt = null
+            sampleCollectedAt = null,
+            labId = null,
         )
 
         coVerify { verificationApi.getTestResult(any(), any(), any()) }
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
index 04e5a7110f6f46ccf7ebac591793fc89b0892b98..d11874da7c5eebd5dd5b51d64c768446a6d66716 100644
--- 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
@@ -67,6 +67,7 @@ class PCRProcessorTest : BaseTest() {
             coEvery { checkTestResult(any()) } returns CoronaTestResultResponse(
                 coronaTestResult = PCR_OR_RAT_PENDING,
                 sampleCollectedAt = null,
+                labId = null,
             )
             coEvery { registerTest(any()) } answers {
                 val request = arg<RegistrationRequest>(0)
@@ -76,6 +77,7 @@ class PCRProcessorTest : BaseTest() {
                     testResultResponse = CoronaTestResultResponse(
                         coronaTestResult = PCR_OR_RAT_PENDING,
                         sampleCollectedAt = null,
+                        labId = null,
                     ),
                 )
             }
@@ -131,6 +133,7 @@ class PCRProcessorTest : BaseTest() {
             testResultResponse = CoronaTestResultResponse(
                 coronaTestResult = PCR_OR_RAT_PENDING,
                 sampleCollectedAt = null,
+                labId = null,
             )
         )
         coEvery { submissionService.registerTest(any()) } answers { registrationData }
@@ -144,6 +147,7 @@ class PCRProcessorTest : BaseTest() {
                 testResultResponse = CoronaTestResultResponse(
                     coronaTestResult = it,
                     sampleCollectedAt = null,
+                    labId = null,
                 )
             )
             when (it) {
@@ -170,6 +174,7 @@ class PCRProcessorTest : BaseTest() {
             CoronaTestResultResponse(
                 coronaTestResult = pollResult,
                 sampleCollectedAt = null,
+                labId = null,
             )
         }
 
@@ -222,6 +227,7 @@ class PCRProcessorTest : BaseTest() {
             CoronaTestResultResponse(
                 coronaTestResult = PCR_POSITIVE,
                 sampleCollectedAt = null,
+                labId = null,
             )
         }
 
@@ -284,6 +290,7 @@ class PCRProcessorTest : BaseTest() {
             testResultResponse = CoronaTestResultResponse(
                 coronaTestResult = PCR_OR_RAT_PENDING,
                 sampleCollectedAt = null,
+                labId = null,
             )
         )
         coEvery { submissionService.registerTest(any()) } answers { registrationData }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt
index 2846d0f54b9f17b104b72c59e83d4295f94f8f4f..c9c60d3474d0b078bf79ca3596f3af9d6e4ab95b 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt
@@ -67,6 +67,7 @@ class RAProcessorTest : BaseTest() {
             coEvery { checkTestResult(any()) } returns CoronaTestResultResponse(
                 coronaTestResult = PCR_OR_RAT_PENDING,
                 sampleCollectedAt = null,
+                labId = null,
             )
 
             coEvery { registerTest(any()) } answers {
@@ -77,6 +78,7 @@ class RAProcessorTest : BaseTest() {
                     testResultResponse = CoronaTestResultResponse(
                         coronaTestResult = PCR_OR_RAT_PENDING,
                         sampleCollectedAt = null,
+                        labId = null,
                     )
                 )
             }
@@ -121,6 +123,7 @@ class RAProcessorTest : BaseTest() {
         coEvery { submissionService.checkTestResult(any()) } returns CoronaTestResultResponse(
             coronaTestResult = PCR_OR_RAT_PENDING,
             sampleCollectedAt = nowUTC,
+            labId = null,
         )
 
         (instance.pollServer(raTest) as RACoronaTest).sampleCollectedAt shouldBe nowUTC
@@ -171,6 +174,7 @@ class RAProcessorTest : BaseTest() {
             testResultResponse = CoronaTestResultResponse(
                 coronaTestResult = PCR_OR_RAT_PENDING,
                 sampleCollectedAt = null,
+                labId = null,
             ),
         )
         coEvery { submissionService.registerTest(any()) } answers { registrationData }
@@ -187,6 +191,7 @@ class RAProcessorTest : BaseTest() {
                 testResultResponse = CoronaTestResultResponse(
                     coronaTestResult = it,
                     sampleCollectedAt = null,
+                    labId = null,
                 )
             )
             when (it) {
@@ -212,6 +217,7 @@ class RAProcessorTest : BaseTest() {
             CoronaTestResultResponse(
                 coronaTestResult = pollResult,
                 sampleCollectedAt = null,
+                labId = null,
             )
         }
 
@@ -245,6 +251,7 @@ class RAProcessorTest : BaseTest() {
             CoronaTestResultResponse(
                 coronaTestResult = RAT_POSITIVE,
                 sampleCollectedAt = null,
+                labId = null,
             )
         }
 
@@ -312,6 +319,7 @@ class RAProcessorTest : BaseTest() {
             testResultResponse = CoronaTestResultResponse(
                 coronaTestResult = PCR_OR_RAT_PENDING,
                 sampleCollectedAt = null,
+                labId = null,
             )
         )
         coEvery { submissionService.registerTest(any()) } answers { registrationData }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt
index e6da753eb92a102b7916032147d183d729219e3d..3082a41b586ef74bbeed644dd8f272421d2d9869 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt
@@ -61,7 +61,8 @@ class TestCertificateTestData @Inject constructor(
             rsaPrivateKey = privateKey,
             certificateReceivedAt = Instant.ofEpochMilli(123456789),
             encryptedDataEncryptionkey = "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk=".decodeBase64()!!,
-            testCertificateQrCode = personATest2CertQRCodeString
+            testCertificateQrCode = personATest2CertQRCodeString,
+            certificateSeenByUser = true
         )
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt
index 9b486cef84ffea31dab4c38fb137d0b0c034678b..983f9549a849c04cde7d0ee2b517b9557f1a0070 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt
@@ -79,7 +79,8 @@ class TestCertificateStorageTest : BaseTest() {
                 "rsaPrivateKey": "MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDb5YIK/LRI2pkzL9XUVhgyQFMan/kFC383FRbWciSPzi7p26o01CHgaO6CgNzx6nKX/KLcPrUm3uoCvCT55Nydbj+MQ57FPmkJptEqsWbogyd1J59is/cJ5oPzplTju6GdG4IbdNw+EipQ3YTfsRs6wj1nn0ldzm2aYHvULFMXRuMTQ80uBtNTayeew7r6jP8kujsPafDoT3LFInwkGMzmP4ULixgu47adiL8HMAZkv/CwqtqODaGMDd/yvVyCy3wd3MAmMxQb3SlvdQbYrYpVFJxu8Xym9GvMsK8n+CoAYNdf8zTSOvjTu9TxXg7YD1h1Jq9oLTaJEVM1qKXrlxs5b3qnem2N56Oh+PyXk87l4FhYRGVrw2uUP0Rxb3f5pKazCfjD2JJKr6PfwYzKFi6Utw6WEpN5a6ZzmCgONwfKuLjwqLvR10bOvEXGCBoL8WqUIbzkDr/jd9VpYf9aLa48M/NpEY1DNdHLgsK4T929TOnxOWSlxPRAXL9Dgp7S7V0CAwEAAQKCAYBaazDh2682FczQ42aFfTFN2G1TkVwP2v5gY+eUHjMyfpGDz7NZLbEQWZVZTCuNvd2I6XT+IzrR1O9cWIjLyHN+uIqg3l02tcbzFQkFCRVLnkJnRfef2mhGRecUFNzrF4gI1frV12OIkmecALpWULjlnGErbq/4Rp2C0RGZ2PABrkBI96QyvNPAhVsxSUJlK/zt2TXXzLQmkiSbMubg4OG/+3Z1nKhA/5ljhYsnJXQ7kUEjI93ic3Bt6naflYWosop/jUa1QksEMv0HL2if8PIBymgTGKmU79MeQOuBJN0ggrmttk41df+lPzWQY0EFnBC7Kf1AtbenllDm8zCoqldwu5OBTp8pZs7vFbOaRp1zBdtQS9OeTy22HvRU14CMwJ7HXOUC4RuVhXXeNLqLjLEkXJPRGvUem0Wq+ppBliDDoq9ljHiqvR/LgnaH0OqxM6o4fo1OgKvgVhJ1ItPeTdYxu2ikuJUNzwFf32feectjncXUf18wF1OExlwgVpvTinECgcEA7lnLCAdufw18Moe2VqudLmU2vUsJl1SR2nLlIYNfM7bHlbXqT/Ido2odKXX8WVDZi/ChV43OAw2PKUgcVPIxSGmEiDg8bj+K+v8hZ/VFbQAjnfD9+olikRbNmFMOued2IazfFv2ydbZADjPDMcfK1W3+7qcHT2LxigEWB8XNA5NDBaMYU+EN+tATOcG0QZr3fNPxfUT4m4TKOY00jhBdOhubfyF5pU5rQQCZvkVqVIffcq6J1x7Jh7CGLwQ53lQ7AoHBAOwt5N4/GY/pFiIE/V85MlJN37HfBhB8K29CEPzqOdHfICnYZ3dqNXtXIAQqVE0lG+49O5moQjU/dTAr39kJwzDydzJaFsCGsR/rzxo+Ishz2SrjJ8+97g8B6Oxgy9qwMs9X5A+EvrWw5Lb3woZDjaZ0pPl4yb7y5IEDlYnNM/9QcmHFP0IFK2h6S3Qmm0XjboeVe1POz0oPD3z+xYruCnKr1Vj/X5eiLIbt2hxWlVQ9N+tvufFusR+OdgsBhxijRwKBwQCvfaN0ZOxhVY91MOD6zV5sc48rLl2Ac370JRY5Z52n2NL4krlTZYOW9yFDjqBfLp0OYPyaF0lwjAI1NefOT4gjtbUkCqvLzLNKfKCfB0K3r5uJxY9qcM8G3pA/sB+ulxIuVzbmmaJU8vwUuN3mACGCpXtHQemq9MG8h3IuBOAe2sVFGEFoONLvMVaGdu1+RFgmK3KpdifJcarnVuU0GC5cA0mo//+ty6BCeuu34SoZ1PSbXpEUt5FQe5NAeM8WuFMCgcEAuaA0kqz7dU1YVPKhBZeZwnBsUYudY5WEOdSuL2oUeawpxlnMsGFsmX1Xr45pZZy2ACBmWJWTO/CdNXg2Xoo6vJzFLHD8EuOKETGwO8r8YZoT5I5WuwNnOKpinG5TqpTzyl0k5UGK9piKmnfOjuJHUb258E2MGyUijXf4ry72IEPlMozp9ATGIj6EUU0Kmvpu4+eL38nayDVgEfjX4CLJWWlOrL1CL5aJ8p6836r5gRUAf233shcy5T997ZaMzMN/AoHADfrS372Vuovx21p8txO2w+VFTEUoR80XGGdy30NrIdweY2bfz4XYpGSiyXE41TWzpNBfrSZNCyBXzvJ7d3dBXhlruFZi3Ji3IR+fe+KpEz4FTssKLEWm+gSmbGIjFxGe0nAIy77jCMYjqfOjoFdhksQN1On1tcq3Y3XauAc4L82wDU30rOgxWt8kdbblJKCSdOaYPXm/D+4c+8ROvlcxY4afl+FDcroHNMvD3jjZ1TMd1Bef1E0qFN/oJJU2Pc2/",
                 "certificateReceivedAt": 123456789,
                 "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d",
-                "testCertificateQrCode": "${certificateTestData.personATest1CertQRCodeString}"
+                "testCertificateQrCode": "${certificateTestData.personATest1CertQRCodeString}",
+                "certificateSeenByUser": false
               }
             ]
         """.toComparableJsonPretty()
@@ -95,7 +96,8 @@ class TestCertificateStorageTest : BaseTest() {
                 "rsaPrivateKey": "MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCesnw9uZs6YQSYcH/zKDVueRRPuNwXhd/LYx7mKYmAtVhDRwv1Q0/3Vx8VSuAwVXd8c5zRS5rLXROasEtoR3OoMRlAfgqPS5n6FUK3Oe1UoW+K5SiHTzDEWWiC1zlLLQ2G5MbILVb+LYBfVIO963bExxzV8TjishNAQQCUbz6d8alumkUd7NQ7G5mOz1cFPFq8ONDJZ4XAwPfRQAkZqQ+E446PqKFcDX069D+1i5hMnkL1DN4r3QL+thuDZ+f6izcQaVvuIlnZXcoas0t0xwS2SIbkiIeJxo0Kfw5L9wjd3b2IkTgQJoXvXV/IXwwbUfa1uSNQtLduw81S/K8d8zZyM49GF6NrI6H4LFCRzfApqvwCm9AKOJVFbTmc7S58d08xxYmFSVyFZg7mY4lNI7y8b/1iqmdO4NzYZ6VziAwrsoBN0fe/UWwAI8o0l2m3rwh9leAZifku0nvMkHpjDxF7CPVJoX/AUpelh37knIrXzAlMtVhKmIPOJ2HgM/5HplMCAwEAAQKCAYEAhw8Bu4pduFZfEdkUm31J0+YJyjtaXE6cAr0ty9Xn5vjuz/sEC0ypHqgvlPBvUdM66FiASoMcjxx8lbaZxnqgzLBUfFWIaSF/Pp2fdM5A1Di79CpIzrcvmrs4vbmrUfZav8WuAyjLE3DoArmrkRN2tct7F/y+W/gPeCyZ8LmoQcUsXCvAzNIEYPWBP0/oEFWoJu33iqCm7T+M6LGlzQfbZE5BwrNR+ESmomjCW6AdEn/SHjlAT3Y9mUakrbXdcJXPAI+RleS90kn8AHiQuyjotlb32xhBVw6SOtfd0xkMyY67AbCo9R1f0ir54PayA38xs4yQ0O2OgNUSLTWYXV1T/mSQSQMaxNw556IEXWVQWRWIc91QwOI2TD/N+vIxLPbNtuW5lEyMCzrmBdxq7wIOGIpy62B11TW6UYU26GOkhHTXEnn7pmHGVtbCXPGoKzncxxKNhRFuGOPcd+yQkM5eYAf7dad2NySrOokMQ2eIacPwKxKFfA/QN0v9aPLj7Qa5AoHBAOynNxSKErA25ndt/xBaLwSdzPynkE5zkO3gedgqvO6/6bAGOsRkawTNGalkVTwhXEnGBUIidPqhW7ex5/ad1QPUsWT8YeeYzXoM+Gqgu6M8awpFK4cwUMCrpRJwaUBFUCNzYNDZgJoOZAOX2TKvSNH+9zFrmGrKY0KRZ0aK9T0Ksbxn8KrErvXYj05nG4A3MrsKRA8mwBtZm0/17bBtg0nYH6/Omt787LBO/sxCicwTZioJlUgndzAIwTw7BtFDzwKBwQCrq8Wx/MK+LA+HUzDmdTK04sgIebulBSTV95aSsWoN+MoNwmi9wVt3OJfpq4L7g1NVv0vNSIajk9BDIFeKvgmDcde8RV51LRCObQ9enCOQUH7e0eC3XI9Nxg3nIhQiYJuggG6QAtt07bybx3dWpYEXL4ZOPOEkTVXR5JRkx9MWw8VDTbLKTCfZJ34PPF4AdCrs3yId9FXk9pUmS68oJVRsFhnI+dSdky32Bc01G6kk0SlGOudKLzqx4fbr0itHIj0CgcAHDWCd0xOFfs1VZ8i/EwDtsUoniVLKk7UQ8ayP3Y4tyzhKj5T2v0tVJEuMebn0hcX7SNRlSSOVSHO0QK/58HAlohP7P24nea094t8QRmPxFF7YOoF2kOEHLNZJe2IXkTk3JTwQXTrw3FbsqHzHfuO7pk51gZBUNl3I4Q5j0sZGIGh1hd9tJ1lTaDW1D2uJYZu4aTDoBq6Y4g23z0tbA5hy/ebL1WtWE9F125TKP31dwII95HU3Zj2uB8TCZ7vnRo8CgcBfeUiZlFk6Kob4W+v2P3fT4cwd6pXRUOsLlIbJTqIM4zB8NoLKBZ84zuCttBVEi+Ts61bc9Fjs4GgS7QnCv63KzKWOr4W45Tcv/rdthqjAugPVKCQx1ehc+KkCwpEwDUqAGO1kajJi9VTPzj8wkRsaKfQnzvPnnJr+AIIHCpr7LiWnKK8mkvQWcUBKeOhOmEzHL9Fpl1mt3PVWNwFS8m/hLOlqPIdim1gUW2WlA50uPKUXyeqX92xNQb5xqJEpHoECgcEAw4FGJb47FivG25fD+e61GxzG/KrzQL0eVS3T2YRAiN5ZB7QyInm6vMTi0QKCScCRJjOjRyoI3VtCO7G8vUnm0UiCW4l11WqW9G4vVh5VuR0HJ+kH1CQcq1aheqF7bbZGjjK47iyZskehfa6kcEOfThE6n6G7mIE/oe5k8A6+wHoLGmBbdxwE2xuG3PorH0PgbAgva1KAgC57rTBJhHnm6ntT21vlPLev9QvrE5syo+LEDbagr5zHMC14qAwMH2fi",
                 "certificateReceivedAt": 123456789,
                 "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d",
-                "testCertificateQrCode": "${certificateTestData.personATest2CertQRCodeString}"
+                "testCertificateQrCode": "${certificateTestData.personATest2CertQRCodeString}",
+                "certificateSeenByUser": true
               }
             ]
         """.toComparableJsonPretty()
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt
index bd02d7face41c0a7a78cd705544b2e474caba389..f6b1d884f3e86404ec0b1f77a27b5c76ebabf91b 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt
@@ -85,14 +85,22 @@ class VaccinatedPersonTest : BaseTest() {
         )
 
         vaccinatedPerson.apply {
+            // Less than 14 days
             getVaccinationStatus(
                 Instant.parse("2021-04-27T12:00:00.000Z")
             ) shouldBe VaccinatedPerson.Status.COMPLETE
             getVaccinationStatus(
                 Instant.parse("2021-05-10T12:00:00.000Z")
             ) shouldBe VaccinatedPerson.Status.COMPLETE
+
+            // 14 days exactly
             getVaccinationStatus(
                 Instant.parse("2021-05-11T12:00:00.000Z")
+            ) shouldBe VaccinatedPerson.Status.COMPLETE
+
+            // More than 14 days
+            getVaccinationStatus(
+                Instant.parse("2021-05-12T12:00:00.000Z")
             ) shouldBe VaccinatedPerson.Status.IMMUNITY
         }
     }
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 dd870ce117d4d635b42aefaba8000c70443d9e47..2ed483adc823e40dbf465713789e2530e52a7f22 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
@@ -46,7 +46,8 @@ class DefaultPlaybookTest : BaseTest() {
         coEvery { verificationServer.retrieveRegistrationToken(any()) } returns "token"
         coEvery { verificationServer.pollTestResult(any()) } returns CoronaTestResultResponse(
             coronaTestResult = CoronaTestResult.PCR_OR_RAT_PENDING,
-            sampleCollectedAt = null
+            sampleCollectedAt = null,
+            labId = null,
         )
         coEvery { verificationServer.retrieveTanFake() } returns mockk()
         coEvery { verificationServer.retrieveTan(any()) } returns "tan"
@@ -214,7 +215,8 @@ class DefaultPlaybookTest : BaseTest() {
         val expectedResult = CoronaTestResult.PCR_OR_RAT_PENDING
         coEvery { verificationServer.pollTestResult(expectedToken) } returns CoronaTestResultResponse(
             coronaTestResult = expectedResult,
-            sampleCollectedAt = null
+            sampleCollectedAt = null,
+            labId = null,
         )
         coEvery { submissionServer.submitFakePayload() } throws TestException()
 
@@ -224,7 +226,8 @@ class DefaultPlaybookTest : BaseTest() {
         registrationToken shouldBe expectedToken
         testResult shouldBe CoronaTestResultResponse(
             coronaTestResult = expectedResult,
-            sampleCollectedAt = null
+            sampleCollectedAt = null,
+            labId = null,
         )
     }
 
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 7207e5d906348755d0391d887d1ba26c67c8103e..f57087d56331d08052724adc50dfa99c6e9fc151 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,6 +1,7 @@
 package de.rki.coronawarnapp.main
 
 import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings
+import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository
 import de.rki.coronawarnapp.covidcertificate.vaccination.core.VaccinationSettings
 import de.rki.coronawarnapp.environment.EnvironmentSetup
 import de.rki.coronawarnapp.playbook.BackgroundNoise
@@ -16,6 +17,7 @@ import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.mockkObject
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
@@ -36,6 +38,7 @@ class MainActivityViewModelTest : BaseTest() {
     @MockK lateinit var traceLocationSettings: TraceLocationSettings
     @MockK lateinit var checkInRepository: CheckInRepository
     @MockK lateinit var vaccinationSettings: VaccinationSettings
+    @MockK lateinit var testCertificateRepository: TestCertificateRepository
 
     @BeforeEach
     fun setup() {
@@ -50,6 +53,7 @@ class MainActivityViewModelTest : BaseTest() {
         )
         every { onboardingSettings.isBackgroundCheckDone } returns true
         every { checkInRepository.checkInsWithinRetention } returns MutableStateFlow(listOf())
+        every { testCertificateRepository.certificates } returns emptyFlow()
     }
 
     private fun createInstance(): MainActivityViewModel = MainActivityViewModel(
@@ -62,6 +66,7 @@ class MainActivityViewModelTest : BaseTest() {
         checkInRepository = checkInRepository,
         traceLocationSettings = traceLocationSettings,
         vaccinationSettings = vaccinationSettings,
+        testCertificateRepository = testCertificateRepository,
     )
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt
index b75849bb4e33f6cfa2b4f7a3e9e373e21d745e4c..cc973f05d4e2fcf3f3b050824c4025d6ea0c7a6d 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt
@@ -45,7 +45,8 @@ class CoronaTestServiceTest : BaseTest() {
             registrationToken = registrationToken,
             testResultResponse = CoronaTestResultResponse(
                 coronaTestResult = CoronaTestResult.PCR_OR_RAT_PENDING,
-                sampleCollectedAt = null
+                sampleCollectedAt = null,
+                labId = null,
             )
         )
     }
@@ -85,13 +86,15 @@ class CoronaTestServiceTest : BaseTest() {
     fun requestTestResultSucceeds() {
         coEvery { mockPlaybook.testResult(registrationToken) } returns CoronaTestResultResponse(
             coronaTestResult = CoronaTestResult.PCR_NEGATIVE,
-            sampleCollectedAt = null
+            sampleCollectedAt = null,
+            labId = null,
         )
 
         runBlocking {
             createInstance().checkTestResult(registrationToken) shouldBe CoronaTestResultResponse(
                 coronaTestResult = CoronaTestResult.PCR_NEGATIVE,
                 sampleCollectedAt = null,
+                labId = null,
             )
         }
         coVerify(exactly = 1) {
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 ae77a7e3ca94f391438e9a5f0aedc71cbcc48fe8..1a03d4997566f27f6f93997df101a9bfb352b7a4 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,15 +1,30 @@
 package de.rki.coronawarnapp.storage
 
 import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+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.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage
+import io.kotest.assertions.throwables.shouldThrow
+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 io.mockk.slot
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.emptyFlow
+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() {
 
@@ -17,9 +32,29 @@ class SubmissionRepositoryTest : BaseTest() {
     @MockK lateinit var tekHistoryStorage: TEKHistoryStorage
     @MockK lateinit var coronaTestRepository: CoronaTestRepository
 
+    private val pcrRegistrationRequest = CoronaTestQRCode.PCR(
+        qrCodeGUID = "pcr-guid"
+    )
+    private val pcrTest = PCRCoronaTest(
+        identifier = pcrRegistrationRequest.identifier,
+        lastUpdatedAt = Instant.EPOCH,
+        registeredAt = Instant.EPOCH,
+        registrationToken = "token",
+        testResult = CoronaTestResult.PCR_REDEEMED,
+    )
+
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this)
+
+        coronaTestRepository.apply {
+            every { coronaTests } returns emptyFlow()
+            coEvery { registerTest(pcrRegistrationRequest, any(), any()) } returns pcrTest
+        }
+
+        submissionSettings.apply {
+            every { symptoms } returns mockFlowPreference(null)
+        }
     }
 
     fun createInstance(scope: CoroutineScope) = SubmissionRepository(
@@ -30,7 +65,23 @@ class SubmissionRepositoryTest : BaseTest() {
     )
 
     @Test
-    fun todo() {
-        // TODO
+    fun `tryReplaceTest overrides register test conditions`() = runBlockingTest {
+        val precondition = slot<(Collection<CoronaTest>) -> Boolean>()
+        val postcondition = slot<(CoronaTest) -> Boolean>()
+
+        val instance = createInstance(scope = this)
+
+        instance.tryReplaceTest(pcrRegistrationRequest)
+
+        coVerify { coronaTestRepository.registerTest(any(), capture(precondition), capture(postcondition)) }
+
+        precondition.captured(emptyList()) shouldBe true
+        precondition.captured(listOf(pcrTest)) shouldBe true
+
+        shouldThrow<AlreadyRedeemedException> {
+            postcondition.captured(pcrTest)
+        }
+
+        postcondition.captured(pcrTest.copy(testResult = CoronaTestResult.PCR_NEGATIVE)) shouldBe true
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..39256d8213d830e06978280ad7d381407f12b7b2
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessorTest.kt
@@ -0,0 +1,303 @@
+package de.rki.coronawarnapp.submission
+
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
+import de.rki.coronawarnapp.exception.http.BadRequestException
+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 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
+
+class TestRegistrationStateProcessorTest : BaseTest() {
+
+    @MockK lateinit var submissionRepository: SubmissionRepository
+    @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
+    @MockK lateinit var registeredTestRA: CoronaTest
+    @MockK lateinit var registeredTestPCR: CoronaTest
+
+    private val raRequest: TestRegistrationRequest = CoronaTestQRCode.RapidAntigen(
+        hash = "ra-hash",
+        createdAt = Instant.EPOCH,
+    )
+    private val pcrRequest: TestRegistrationRequest = CoronaTestQRCode.PCR(
+        qrCodeGUID = "pcr-guid"
+    )
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        submissionRepository.apply {
+            coEvery { registerTest(raRequest) } returns registeredTestRA
+            coEvery { registerTest(pcrRequest) } returns registeredTestPCR
+
+            coEvery { tryReplaceTest(raRequest) } returns registeredTestRA
+            coEvery { tryReplaceTest(pcrRequest) } returns registeredTestPCR
+
+            coEvery { giveConsentToSubmission(any()) } just Runs
+        }
+
+        registeredTestRA.apply {
+            every { type } returns CoronaTest.Type.RAPID_ANTIGEN
+        }
+        registeredTestPCR.apply {
+            every { type } returns CoronaTest.Type.PCR
+        }
+
+        every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
+    }
+
+    private fun createInstance() = TestRegistrationStateProcessor(
+        submissionRepository = submissionRepository,
+        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector,
+    )
+
+    @Test
+    fun `register new RA test - with consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = raRequest,
+            isSubmissionConsentGiven = true,
+            allowReplacement = false
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestRA
+        )
+
+        coVerify {
+            submissionRepository.registerTest(raRequest)
+            submissionRepository.giveConsentToSubmission(CoronaTest.Type.RAPID_ANTIGEN)
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+        }
+    }
+
+    @Test
+    fun `register new RA test - without consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = raRequest,
+            isSubmissionConsentGiven = false,
+            allowReplacement = false
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestRA
+        )
+
+        coVerify {
+            submissionRepository.registerTest(raRequest)
+        }
+        coVerify(exactly = 0) {
+            submissionRepository.giveConsentToSubmission(any())
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+        }
+    }
+
+    @Test
+    fun `replace RA test - with consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = raRequest,
+            isSubmissionConsentGiven = true,
+            allowReplacement = true
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestRA
+        )
+
+        coVerify {
+            submissionRepository.tryReplaceTest(raRequest)
+            submissionRepository.giveConsentToSubmission(CoronaTest.Type.RAPID_ANTIGEN)
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+        }
+    }
+
+    @Test
+    fun `replace RA new test - without consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = raRequest,
+            isSubmissionConsentGiven = false,
+            allowReplacement = true
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestRA
+        )
+
+        coVerify {
+            submissionRepository.tryReplaceTest(raRequest)
+        }
+        coVerify(exactly = 0) {
+            submissionRepository.giveConsentToSubmission(any())
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+        }
+    }
+
+    @Test
+    fun `register new PCR test - with consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = pcrRequest,
+            isSubmissionConsentGiven = true,
+            allowReplacement = false
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestPCR
+        )
+
+        coVerify {
+            submissionRepository.registerTest(pcrRequest)
+            submissionRepository.giveConsentToSubmission(CoronaTest.Type.PCR)
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
+        }
+    }
+
+    @Test
+    fun `register new PCR test - without consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = pcrRequest,
+            isSubmissionConsentGiven = false,
+            allowReplacement = false
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestPCR
+        )
+
+        coVerify {
+            submissionRepository.registerTest(pcrRequest)
+        }
+        coVerify(exactly = 0) {
+            submissionRepository.giveConsentToSubmission(any())
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+        }
+    }
+
+    @Test
+    fun `replace PCR test - with consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = pcrRequest,
+            isSubmissionConsentGiven = true,
+            allowReplacement = true
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestPCR
+        )
+
+        coVerify {
+            submissionRepository.tryReplaceTest(pcrRequest)
+            submissionRepository.giveConsentToSubmission(CoronaTest.Type.PCR)
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
+        }
+    }
+
+    @Test
+    fun `replace PCR new test - without consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = pcrRequest,
+            isSubmissionConsentGiven = false,
+            allowReplacement = true
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered(
+            test = registeredTestPCR
+        )
+
+        coVerify {
+            submissionRepository.tryReplaceTest(pcrRequest)
+        }
+        coVerify(exactly = 0) {
+            submissionRepository.giveConsentToSubmission(any())
+            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+        }
+    }
+
+    @Test
+    fun `errors are mapped to state`() = runBlockingTest {
+        val instance = createInstance()
+
+        val expectedException = BadRequestException("")
+        coEvery { submissionRepository.registerTest(any()) } throws expectedException
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle
+
+        instance.startRegistration(
+            request = raRequest,
+            isSubmissionConsentGiven = true,
+            allowReplacement = false
+        )
+
+        advanceUntilIdle()
+
+        instance.state.first() shouldBe TestRegistrationStateProcessor.State.Error(
+            exception = expectedException
+        )
+
+        coVerify {
+            submissionRepository.registerTest(raRequest)
+        }
+        coVerify(exactly = 0) {
+            submissionRepository.giveConsentToSubmission(any())
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt
index 009f839c5c79d3bfed61a95225a0f6cea8335c89..61c9d776d4e008c867526a9e26b03ca1a6191b5b 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt
@@ -1,21 +1,15 @@
 package de.rki.coronawarnapp.ui.submission.covidcertificate
 
-import androidx.lifecycle.MutableLiveData
-import de.rki.coronawarnapp.coronatest.CoronaTestRepository
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
-import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
-import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 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.flowOf
 import org.joda.time.Instant
 import org.joda.time.LocalDate
@@ -30,10 +24,7 @@ import testhelpers.extensions.getOrAwaitValue
 @ExtendWith(InstantExecutorExtension::class)
 internal class RequestCovidCertificateViewModelTest : BaseTest() {
 
-    @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor
-    @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
-    @MockK lateinit var submissionRepository: SubmissionRepository
-    @MockK lateinit var coronaTestRepository: CoronaTestRepository
+    @MockK lateinit var testRegistrationStateProcessor: TestRegistrationStateProcessor
     @MockK lateinit var coronaTest: CoronaTest
 
     private val date = LocalDate.parse(
@@ -48,24 +39,25 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
     fun setup() {
         MockKAnnotations.init(this)
 
-        qrCodeRegistrationStateProcessor.apply {
-            coEvery { startQrCodeRegistration(any(), any()) } just Runs
-            coEvery { registrationError } returns SingleLiveEvent()
-            coEvery { showRedeemedTokenWarning } returns SingleLiveEvent()
-            coEvery { registrationState } returns MutableLiveData()
+        testRegistrationStateProcessor.apply {
+            coEvery { startRegistration(any(), any(), any()) } returns mockk()
+            coEvery { state } returns flowOf(TestRegistrationStateProcessor.State.Idle)
         }
 
-        submissionRepository.apply {
-            coEvery { registerTest(any()) } returns coronaTest
-            coEvery { testForType(any()) } returns flowOf(coronaTest)
-        }
-
-        coEvery { coronaTestRepository.removeTest(any()) } returns coronaTest
-
         every { coronaTest.identifier } returns "identifier"
-        every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
     }
 
+    private fun createInstance(
+        coronaTestQRCode: CoronaTestQRCode = pcrQRCode,
+        coronTestConsent: Boolean = true,
+        deleteOldTest: Boolean = false
+    ) = RequestCovidCertificateViewModel(
+        testRegistrationRequest = coronaTestQRCode,
+        coronaTestConsent = coronTestConsent,
+        deleteOldTest = deleteOldTest,
+        registrationStateProcessor = testRegistrationStateProcessor,
+    )
+
     @Test
     fun birthDateChanged() {
         createInstance().apply {
@@ -81,13 +73,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onAgreeGC()
 
             coVerify {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = true
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
             }
         }
     }
@@ -99,16 +89,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onAgreeGC()
 
             coVerify {
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = false
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-
-            coVerify(exactly = 0) {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
             }
         }
     }
@@ -119,13 +104,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onDisagreeGC()
 
             coVerify {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    pcrQRCode.copy(isDccConsentGiven = false),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = pcrQRCode.copy(isDccConsentGiven = false),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = true
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
             }
         }
     }
@@ -136,16 +119,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onDisagreeGC()
 
             coVerify {
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    pcrQRCode.copy(isDccConsentGiven = false),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = pcrQRCode.copy(isDccConsentGiven = false),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = false
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-
-            coVerify(exactly = 0) {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
             }
         }
     }
@@ -156,13 +134,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onAgreeGC()
 
             coVerify {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    ratQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = ratQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = true
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
             }
         }
     }
@@ -173,16 +149,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onAgreeGC()
 
             coVerify {
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    ratQRCode.copy(isDccConsentGiven = true),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = ratQRCode.copy(isDccConsentGiven = true),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = false
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-
-            coVerify(exactly = 0) {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
             }
         }
     }
@@ -193,13 +164,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onDisagreeGC()
 
             coVerify {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    ratQRCode.copy(isDccConsentGiven = false),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = ratQRCode.copy(isDccConsentGiven = false),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = true
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
             }
         }
     }
@@ -210,16 +179,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             onDisagreeGC()
 
             coVerify {
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
-                    ratQRCode.copy(isDccConsentGiven = false),
-                    any()
+                testRegistrationStateProcessor.startRegistration(
+                    request = ratQRCode.copy(isDccConsentGiven = false),
+                    isSubmissionConsentGiven = any(),
+                    allowReplacement = false
                 )
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-
-            coVerify(exactly = 0) {
-                submissionRepository.testForType(any())
-                coronaTestRepository.removeTest(any())
             }
         }
     }
@@ -247,58 +211,4 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() {
             events.getOrAwaitValue() shouldBe ToDispatcherScreen
         }
     }
-
-    @Test
-    fun `onAgreeGC reports analytics`() {
-        createInstance(coronTestConsent = true).apply {
-            onAgreeGC()
-            coVerify {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-        }
-    }
-
-    @Test
-    fun `onDisagreeGC reports analytics`() {
-        createInstance(coronTestConsent = true).apply {
-            onDisagreeGC()
-            coVerify {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-        }
-    }
-
-    @Test
-    fun `onAgreeGC does not report analytics`() {
-        createInstance(coronTestConsent = false).apply {
-            onAgreeGC()
-            coVerify(exactly = 0) {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-        }
-    }
-
-    @Test
-    fun `onDisagreeGC does not report analytics`() {
-        createInstance(coronTestConsent = false).apply {
-            onDisagreeGC()
-            coVerify(exactly = 0) {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
-            }
-        }
-    }
-
-    private fun createInstance(
-        coronaTestQRCode: CoronaTestQRCode = pcrQRCode,
-        coronTestConsent: Boolean = true,
-        deleteOldTest: Boolean = false
-    ) = RequestCovidCertificateViewModel(
-        coronaTestQrCode = coronaTestQRCode,
-        coronaTestConsent = coronTestConsent,
-        deleteOldTest = deleteOldTest,
-        coronaTestRepository = coronaTestRepository,
-        submissionRepository = submissionRepository,
-        qrCodeRegistrationStateProcessor = qrCodeRegistrationStateProcessor,
-        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector
-    )
 }
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 41eb4280816b12a78fe0c6ba1e830ae58188aeb9..36730890014ffbc629c94692b9501c5c47c2d550 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt
@@ -1,16 +1,13 @@
 package de.rki.coronawarnapp.ui.submission.qrcode.consent
 
-import androidx.lifecycle.MutableLiveData
 import com.google.android.gms.common.api.ApiException
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator
 import de.rki.coronawarnapp.nearby.modules.tekhistory.TEKHistoryProvider
 import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
 import de.rki.coronawarnapp.submission.SubmissionRepository
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import de.rki.coronawarnapp.ui.Country
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
@@ -21,6 +18,7 @@ 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 org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
@@ -34,7 +32,7 @@ class SubmissionConsentViewModelTest {
     @MockK lateinit var submissionRepository: SubmissionRepository
     @MockK lateinit var interoperabilityRepository: InteroperabilityRepository
     @MockK lateinit var tekHistoryProvider: TEKHistoryProvider
-    @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor
+    @MockK lateinit var testRegistrationStateProcessor: TestRegistrationStateProcessor
     @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator
 
     lateinit var viewModel: SubmissionConsentViewModel
@@ -46,16 +44,17 @@ class SubmissionConsentViewModelTest {
         MockKAnnotations.init(this)
         every { interoperabilityRepository.countryList } returns MutableStateFlow(countryList)
         coEvery { submissionRepository.giveConsentToSubmission(any()) } just Runs
-        coEvery { qrCodeRegistrationStateProcessor.showRedeemedTokenWarning } returns SingleLiveEvent()
-        coEvery { qrCodeRegistrationStateProcessor.registrationState } returns MutableLiveData(
-            QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE)
-        )
-        coEvery { qrCodeRegistrationStateProcessor.registrationError } returns SingleLiveEvent()
+
+        testRegistrationStateProcessor.apply {
+            every { state } returns flowOf(TestRegistrationStateProcessor.State.Idle)
+            coEvery { startRegistration(any(), any(), any()) } returns mockk()
+        }
+
         viewModel = SubmissionConsentViewModel(
             interoperabilityRepository,
             dispatcherProvider = TestDispatcherProvider(),
             tekHistoryProvider,
-            qrCodeRegistrationStateProcessor,
+            testRegistrationStateProcessor,
             submissionRepository,
             qrCodeValidator
         )
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 18b2c8425196c280a168f20a048ac15bb3e5bd9a..a15c1e8d97c7ed2453c7245640fd11199fc95710 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,28 +1,21 @@
 package de.rki.coronawarnapp.ui.submission.qrcode.scan
 
-import androidx.lifecycle.MutableLiveData
 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.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
 import de.rki.coronawarnapp.submission.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
-import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor.ValidationState
+import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor
 import de.rki.coronawarnapp.util.permission.CameraSettings
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 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.test.runBlockingTest
-import org.joda.time.Instant
+import kotlinx.coroutines.flow.flowOf
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
@@ -37,29 +30,27 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
     @MockK lateinit var submissionRepository: SubmissionRepository
     @MockK lateinit var cameraSettings: CameraSettings
     @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator
-    @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor
-    @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
+    @MockK lateinit var testRegistrationStateProcessor: TestRegistrationStateProcessor
 
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this)
 
         every { submissionRepository.testForType(any()) } returns MutableStateFlow<CoronaTest?>(null)
-        coEvery { qrCodeRegistrationStateProcessor.showRedeemedTokenWarning } returns SingleLiveEvent()
-        coEvery { qrCodeRegistrationStateProcessor.registrationState } returns MutableLiveData(
-            QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE)
-        )
-        coEvery { qrCodeRegistrationStateProcessor.registrationError } returns SingleLiveEvent()
+
+        testRegistrationStateProcessor.apply {
+            every { state } returns flowOf(TestRegistrationStateProcessor.State.Idle)
+            coEvery { startRegistration(any(), any(), any()) } returns mockk()
+        }
     }
 
     private fun createViewModel() = SubmissionQRCodeScanViewModel(
         isConsentGiven = true,
         dispatcherProvider = TestDispatcherProvider(),
         cameraSettings = cameraSettings,
-        qrCodeRegistrationStateProcessor = qrCodeRegistrationStateProcessor,
+        registrationStateProcessor = testRegistrationStateProcessor,
         submissionRepository = submissionRepository,
         qrCodeValidator = qrCodeValidator,
-        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector
     )
 
     @Test
@@ -74,22 +65,22 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
         val invalidQrCode = "https://no-guid-here"
 
         every { qrCodeValidator.validate(validQrCode) } returns coronaTestQRCode
-        every { qrCodeValidator.validate(invalidQrCode) } throws InvalidQRCodeException()
+
+        val expectedError = InvalidQRCodeException()
+        every { qrCodeValidator.validate(invalidQrCode) } throws expectedError
 
         val viewModel = createViewModel()
 
+        viewModel.qrCodeErrorEvent.observeForever {}
         // start
-        viewModel.qrCodeValidationState.value = ValidationState.STARTED
-
-        viewModel.qrCodeValidationState.value shouldBe ValidationState.STARTED
+        viewModel.qrCodeErrorEvent.value shouldBe null
 
         viewModel.registerCoronaTest(validQrCode)
-        viewModel.qrCodeValidationState.observeForever {}
-        viewModel.qrCodeValidationState.value shouldBe ValidationState.SUCCESS
+        viewModel.qrCodeErrorEvent.value shouldBe null
 
         // invalid guid
         viewModel.registerCoronaTest(invalidQrCode)
-        viewModel.qrCodeValidationState.value shouldBe ValidationState.INVALID
+        viewModel.qrCodeErrorEvent.value shouldBe expectedError
     }
 
     @Test
@@ -99,81 +90,4 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
 
         verify { cameraSettings.isCameraDeniedPermanently }
     }
-
-    @Test
-    fun `registerCoronaTest() should call analyticsKeySubmissionCollector for PCR tests`() =
-        runBlockingTest {
-            val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064")
-
-            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
-            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
-            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
-
-            createViewModel().registerCoronaTest(rawResult = "")
-
-            verify(exactly = 0) {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
-            }
-        }
-
-    @Test
-    fun `registerCoronaTest() should NOT call analyticsKeySubmissionCollector for RAT tests`() =
-        runBlockingTest {
-            val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064")
-
-            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
-            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
-            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
-
-            createViewModel().registerCoronaTest(rawResult = "")
-
-            verify(exactly = 0) {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
-            }
-        }
-
-    @Test
-    fun `registerCoronaTest() should call analyticsKeySubmissionCollector for RAT tests - no-dcc support`() =
-        runBlockingTest {
-            val coronaTestQRCode = CoronaTestQRCode.RapidAntigen(
-                hash = "123456-12345678-1234-4DA7-B166-B86D85475064",
-                createdAt = Instant.EPOCH,
-                isDccSupportedByPoc = false
-            )
-
-            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
-            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
-            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
-
-            createViewModel().registerCoronaTest(rawResult = "")
-
-            verify(exactly = 1) {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
-            }
-            verify(exactly = 0) {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
-            }
-        }
-
-    @Test
-    fun `registerCoronaTest() should Not call analyticsKeySubmissionCollector for RAT tests - dcc support`() =
-        runBlockingTest {
-            val coronaTestQRCode = CoronaTestQRCode.RapidAntigen(
-                hash = "123456-12345678-1234-4DA7-B166-B86D85475064",
-                createdAt = Instant.EPOCH,
-                isDccSupportedByPoc = true
-            )
-
-            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
-            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
-            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
-
-            createViewModel().registerCoronaTest(rawResult = "")
-            verify(exactly = 0) {
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
-                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
-            }
-        }
 }