diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index 3fb91cf51eb881b16865fbf07de00883d7533aa7..58d5fd5c02e869c3a4be36ba6cdf39b2ffed957e 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -32,8 +32,8 @@ android {
         applicationId 'de.rki.coronawarnapp'
         minSdkVersion 23
         targetSdkVersion 29
-        versionCode 14
-        versionName "0.8.7"
+        versionCode 15
+        versionName "0.8.8"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 
         buildConfigField "String", "DOWNLOAD_CDN_URL", "\"$DOWNLOAD_CDN_URL\""
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_submission_qr_code_scan_close.xml b/Corona-Warn-App/src/main/res/drawable/ic_submission_qr_code_scan_close.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ac734089e5e40b9d51cef33e80cd6840d9d5fc41
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_submission_qr_code_scan_close.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="40dp"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+
+    <group
+        android:pivotX="20"
+        android:pivotY="20"
+        android:scaleX="2"
+        android:scaleY="2">
+        <path
+            android:fillColor="#FFFFFF"
+            android:fillType="nonZero"
+            android:pathData="M14.2843,13l-1.2843,1.2843l5.7157,5.7157l-5.7157,5.7157l1.2843,1.2843l5.7157,-5.7157l5.7157,5.7157l1.2843,-1.2843l-5.7157,-5.7157l5.7157,-5.7157l-1.2843,-1.2843l-5.7157,5.7157z"
+            android:strokeWidth="1"
+            android:strokeColor="#00FFFFFF" />
+    </group>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_qr_code_scan.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_qr_code_scan.xml
index 817eb12f8ef18a3914fe1a0b204424c934dc5e47..10a3ae69d6d27f33391b4e9c1ef9b2050dd50f70 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_qr_code_scan.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_qr_code_scan.xml
@@ -21,8 +21,8 @@
 
         <com.journeyapps.barcodescanner.ViewfinderView
             android:id="@+id/submission_qr_code_scan_viewfinder_view"
-            android:layout_width="@dimen/submission_scan_qr_code_viewfinder_size"
-            android:layout_height="@dimen/submission_scan_qr_code_viewfinder_size"
+            android:layout_width="@dimen/match_constraint"
+            android:layout_height="@dimen/match_constraint"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -34,19 +34,18 @@
             style="@style/registrationQRCodeScanBody"
             android:layout_width="@dimen/submission_scan_qr_code_viewfinder_size"
             android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/spacing_small"
+            android:layout_marginTop="@dimen/submission_scan_qr_code_viewfinder_center_offset"
             android:text="@string/submission_qr_code_scan_body"
             app:layout_constraintEnd_toEndOf="@+id/submission_qr_code_scan_preview"
             app:layout_constraintStart_toStartOf="@+id/submission_qr_code_scan_preview"
-            app:layout_constraintTop_toBottomOf="@+id/submission_qr_code_scan_viewfinder_view" />
-
+            app:layout_constraintTop_toBottomOf="@+id/submission_qr_code_scan_guideline_center" />
 
         <include
             android:id="@+id/submission_qr_code_scan_close"
             layout="@layout/include_button_icon"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            app:icon="@{@drawable/ic_close}"
+            app:icon="@{@drawable/ic_submission_qr_code_scan_close}"
             app:layout_constraintBottom_toTopOf="@+id/submission_qr_code_scan_guideline_top"
             app:layout_constraintEnd_toStartOf="@+id/guideline_start"
             app:layout_constraintStart_toStartOf="@+id/guideline_start"
@@ -54,10 +53,10 @@
 
         <ToggleButton
             android:id="@+id/submission_qr_code_scan_torch"
-            android:layout_width="@dimen/icon_size"
-            android:layout_height="@dimen/icon_size"
+            android:layout_width="@dimen/icon_size_button"
+            android:layout_height="@dimen/icon_size_button"
             android:background="@drawable/ic_registration_qr_code_scan_torch_toggle"
-            android:backgroundTint="@color/colorTextPrimary3"
+            android:backgroundTint="@color/colorTextPrimary1"
             android:textOff=""
             android:textOn=""
             app:layout_constraintBottom_toTopOf="@+id/submission_qr_code_scan_guideline_top"
@@ -72,6 +71,13 @@
             android:orientation="horizontal"
             app:layout_constraintGuide_begin="@dimen/spacing_normal" />
 
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/submission_qr_code_scan_guideline_center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            app:layout_constraintGuide_percent="0.5" />
+
         <include layout="@layout/merge_guidelines_side" />
 
     </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml
index 2daec59e50e5b11f9759fa3b78d585a60727e30d..d123afa1ab84c8074dd4d93326af91141f48e2d7 100644
--- a/Corona-Warn-App/src/main/res/values-de/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/strings.xml
@@ -747,7 +747,7 @@
     <!-- YTXT: virus name text -->
     <string name="test_result_card_virus_name_text">"SARS-CoV-2"</string>
     <!-- YTXT: registered at text -->
-    <string name="test_result_card_registered_at_text">"Registriert am %tF"</string>
+    <string name="test_result_card_registered_at_text">"Registriert am %s"</string>
     <!-- YTXT: negative status text -->
     <string name="test_result_card_status_negative">"Negativ"</string>
     <!-- YTXT: positive status text -->
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 5c3e95b0d489b534fb02a09c1b02a4be0194d17d..3746e6810ddfdbc68811bfc0fb6ac479b663861e 100644
--- a/Corona-Warn-App/src/main/res/values-en/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-en/strings.xml
@@ -749,7 +749,7 @@
     <!-- YTXT: virus name text -->
     <string name="test_result_card_virus_name_text">"SARS-CoV-2"</string>
     <!-- YTXT: registered at text -->
-    <string name="test_result_card_registered_at_text">"Registered on %tF"</string>
+    <string name="test_result_card_registered_at_text">"Registered on %s"</string>
     <!-- YTXT: negative status text -->
     <string name="test_result_card_status_negative">"Negative"</string>
     <!-- YTXT: positive status text -->
diff --git a/Corona-Warn-App/src/main/res/values/dimens.xml b/Corona-Warn-App/src/main/res/values/dimens.xml
index 9bb11cf839181372ca138fb41056d94c623ff1d8..fc63f4a940f10742447a32c4465b622bc5b8ebc4 100644
--- a/Corona-Warn-App/src/main/res/values/dimens.xml
+++ b/Corona-Warn-App/src/main/res/values/dimens.xml
@@ -86,4 +86,5 @@
 
     <!-- Submission QR Code Scan -->
     <dimen name="submission_scan_qr_code_viewfinder_size">240dp</dimen>
+    <dimen name="submission_scan_qr_code_viewfinder_center_offset">120dp</dimen>
 </resources>
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransactionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransactionTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ec7798a860acc741c31d6bdd2af422511bc41837
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransactionTest.kt
@@ -0,0 +1,82 @@
+package de.rki.coronawarnapp.transaction
+
+import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
+import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
+import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService
+import de.rki.coronawarnapp.storage.LocalData
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.coVerifyOrder
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.unmockkAll
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import java.nio.file.Paths
+import java.util.Date
+
+/**
+ * RetrieveDiagnosisKeysTransaction test.
+ */
+class RetrieveDiagnosisKeysTransactionTest {
+
+    @Before
+    fun setUp() {
+        mockkObject(InternalExposureNotificationClient)
+        mockkObject(ApplicationConfigurationService)
+        mockkObject(RetrieveDiagnosisKeysTransaction)
+        mockkObject(LocalData)
+
+        coEvery { InternalExposureNotificationClient.asyncIsEnabled() } returns true
+        coEvery { InternalExposureNotificationClient.asyncProvideDiagnosisKeys(any(), any(), any()) } returns mockk()
+        coEvery { ApplicationConfigurationService.asyncRetrieveExposureConfiguration() } returns mockk()
+        every { LocalData.googleApiToken(any()) } just Runs
+        every { LocalData.lastTimeDiagnosisKeysFromServerFetch() } returns Date()
+        every { LocalData.lastTimeDiagnosisKeysFromServerFetch(any()) } just Runs
+    }
+
+    @Test
+    fun testTransactionNoFiles() {
+        coEvery { RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"](any<Date>()) } returns listOf<File>()
+
+        runBlocking {
+            RetrieveDiagnosisKeysTransaction.start()
+
+            coVerifyOrder {
+                RetrieveDiagnosisKeysTransaction["executeSetup"]()
+                RetrieveDiagnosisKeysTransaction["executeRetrieveRiskScoreParams"]()
+                RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"](any<Date>())
+                RetrieveDiagnosisKeysTransaction["executeFetchDateUpdate"](any<Date>())
+            }
+        }
+    }
+
+    @Test
+    fun testTransactionHasFiles() {
+        val file = Paths.get("src", "test", "resources", "keys.bin").toFile()
+
+        coEvery { RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"](any<Date>()) } returns listOf(file)
+
+        runBlocking {
+            RetrieveDiagnosisKeysTransaction.start()
+
+            coVerifyOrder {
+                RetrieveDiagnosisKeysTransaction["executeSetup"]()
+                RetrieveDiagnosisKeysTransaction["executeRetrieveRiskScoreParams"]()
+                RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"](any<Date>())
+                RetrieveDiagnosisKeysTransaction["executeAPISubmission"](any<String>(), listOf(file), any<ExposureConfiguration>())
+                RetrieveDiagnosisKeysTransaction["executeFetchDateUpdate"](any<Date>())
+            }
+        }
+    }
+
+    @After
+    fun cleanUp() {
+        unmockkAll()
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransactionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransactionTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dd296b9b7f44d4e934c709f6ea37b937012b6210
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransactionTest.kt
@@ -0,0 +1,79 @@
+package de.rki.coronawarnapp.transaction
+
+import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
+import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
+import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyService
+import de.rki.coronawarnapp.service.submission.SubmissionService
+import de.rki.coronawarnapp.storage.LocalData
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.coVerifyOrder
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockkObject
+import io.mockk.slot
+import io.mockk.unmockkAll
+import kotlinx.coroutines.runBlocking
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class SubmitDiagnosisKeysTransactionTest {
+    private val authString = "authString"
+
+    @Before
+    fun setUp() {
+        mockkObject(LocalData)
+        mockkObject(SubmissionService)
+        mockkObject(InternalExposureNotificationClient)
+        mockkObject(DiagnosisKeyService)
+        every { LocalData.numberOfSuccessfulSubmissions(any()) } just Runs
+        coEvery { SubmissionService.asyncRequestAuthCode(any()) } returns authString
+    }
+
+    @Test
+    fun testTransactionNoKeys() {
+        coEvery { InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() } returns listOf()
+        coEvery { DiagnosisKeyService.asyncSubmitKeys(authString, listOf()) } just Runs
+
+        runBlocking {
+            SubmitDiagnosisKeysTransaction.start("123")
+
+            coVerifyOrder {
+                DiagnosisKeyService.asyncSubmitKeys(authString, listOf())
+                SubmissionService.submissionSuccessful()
+            }
+        }
+    }
+
+    @Test
+    fun testTransactionHasKeys() {
+        val key = TemporaryExposureKey.TemporaryExposureKeyBuilder()
+            .setKeyData(ByteArray(1))
+            .setRollingPeriod(1)
+            .setRollingStartIntervalNumber(1)
+            .setTransmissionRiskLevel(1)
+            .build()
+        val testList = slot<List<KeyExportFormat.TemporaryExposureKey>>()
+        coEvery { InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() } returns listOf(key)
+        coEvery { DiagnosisKeyService.asyncSubmitKeys(authString, capture(testList)) } just Runs
+
+        runBlocking {
+            SubmitDiagnosisKeysTransaction.start("123")
+
+            coVerifyOrder {
+                DiagnosisKeyService.asyncSubmitKeys(authString, any())
+                SubmissionService.submissionSuccessful()
+            }
+            assertThat(testList.isCaptured, `is`(true))
+            assertThat(testList.captured.size, `is`(1))
+        }
+    }
+
+    @After
+    fun cleanUp() {
+        unmockkAll()
+    }
+}
diff --git a/Corona-Warn-App/src/test/resources/keys.bin b/Corona-Warn-App/src/test/resources/keys.bin
new file mode 100644
index 0000000000000000000000000000000000000000..61e9ea4b50e5458bcf8e9f46e6203b7c87b8a21e
Binary files /dev/null and b/Corona-Warn-App/src/test/resources/keys.bin differ