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