diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 334cbf113bc1c42348ed76a582f1ddacf28ddb5e..098fe846891336dbe9c4465f59ebbba03506e4f1 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,6 +1,6 @@ <component name="ProjectCodeStyleConfiguration"> <code_scheme name="Project" version="173"> - <JetCodeStyleSettings> + <JetCodeStyleSettings> <option name="PACKAGES_TO_USE_STAR_IMPORTS"> <value> <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" /> diff --git a/CODEOWNERS b/CODEOWNERS index 8111becb3ac79ded1372847d6f8367bbe784c836..889237e4267d9de3958cc27f4e0801d0fad009cc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,4 +8,4 @@ * @corona-warn-app/cwa-app-android-maintainers # Code Onwer of all german texts -/Corona-Warn-App/src/main/res/values-de/ @janetback +/Corona-Warn-App/src/main/res/values-de/ @janetback @SabineLoss diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 63c99edc3af68ee60b7955e94f4c8376e34b8a96..ddaef139c82a02a59c2aa5b32912f9259dacab1a 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -248,6 +248,8 @@ dependencies { implementation 'android.arch.lifecycle:extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0' implementation 'androidx.annotation:annotation:1.1.0' + implementation "androidx.recyclerview:recyclerview:1.1.0" + implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc02" implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' diff --git a/Corona-Warn-App/config/detekt.yml b/Corona-Warn-App/config/detekt.yml index 2555aa687375ea096032184e62adfedd4ce03604..5a483363dc80a7702453dac0b684dd37b27b49ec 100644 --- a/Corona-Warn-App/config/detekt.yml +++ b/Corona-Warn-App/config/detekt.yml @@ -100,7 +100,7 @@ complexity: ignoreStringsRegex: '$^' TooManyFunctions: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt', '**/LocalData.kt', '**/formatter/*Helper.kt'] + excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt', '**/LocalData.kt', '**/formatter/*Helper.kt', '**/*ViewModel.kt'] thresholdInFiles: 20 thresholdInClasses: 20 thresholdInInterfaces: 20 diff --git a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainFragment.kt index a6a339419ee9e366314f67cad85d81345bf607a4..171a19992a864283016e7b49c24059c66701dc29 100644 --- a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainFragment.kt +++ b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/ui/main/MainFragment.kt @@ -133,7 +133,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { private fun toSubmissionIntro() { findNavController().doNavigate( MainFragmentDirections.actionMainFragmentToSubmissionIntroFragment() - ) + ) } private fun showPopup(view: View) { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index f7094337bcd777cb4f52ec1e89e2fface92cbe1c..35fda975f43568685d84bbb8c441212b1c08aeb6 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -32,6 +32,7 @@ import com.google.zxing.BarcodeFormat import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentResult import com.google.zxing.qrcode.QRCodeWriter +import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R import de.rki.coronawarnapp.RiskLevelAndKeyRetrievalBenchmark import de.rki.coronawarnapp.databinding.FragmentTestForAPIBinding @@ -62,6 +63,7 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import kotlinx.android.synthetic.deviceForTesters.fragment_test_for_a_p_i.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.joda.time.DateTime @@ -92,6 +94,10 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), } } + private val enfClient by lazy { + AppInjector.component.enfClient + } + private var myExposureKeysJSON: String? = null private var myExposureKeys: List<TemporaryExposureKey>? = mutableListOf() private var otherExposureKey: AppleLegacyKeyExchange.Key? = null @@ -278,6 +284,51 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), } false } + + binding.testLogfileToggle.isChecked = CoronaWarnApplication.fileLogger?.isLogging ?: false + binding.testLogfileToggle.setOnClickListener { buttonView -> + CoronaWarnApplication.fileLogger?.let { + if (binding.testLogfileToggle.isChecked) { + it.start() + } else { + it.stop() + } + } + } + + binding.testLogfileShare.setOnClickListener { + CoronaWarnApplication.fileLogger?.let { + lifecycleScope.launch { + val targetPath = withContext(Dispatchers.IO) { + async { + if (!it.logFile.exists()) return@async null + + val externalPath = File( + requireContext().getExternalFilesDir(null), + "LogFile-${System.currentTimeMillis()}.log" + ) + + it.logFile.copyTo(externalPath) + + return@async externalPath + } + }.await() + if (targetPath != null) { + Toast.makeText( + requireActivity(), + "Logfile copied to $targetPath", + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText( + requireActivity(), + "No log file available", + Toast.LENGTH_SHORT + ).show() + } + } + } + } } override fun onResume() { @@ -434,7 +485,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys with token $token") try { // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API - InternalExposureNotificationClient.asyncProvideDiagnosisKeys( + enfClient.provideDiagnosisKeys( googleFileList, ApplicationConfigurationService.asyncRetrieveExposureConfiguration(), token!! diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 5628d6c968e6d6870818ee6cca169335f89d306f..7ba08ef6d67382d462fb1beca3af4f1519a90491 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -5,13 +5,12 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.google.android.gms.nearby.exposurenotification.ExposureInformation -import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.risk.DefaultRiskLevelCalculation import de.rki.coronawarnapp.risk.RiskLevel @@ -43,7 +42,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( @Assisted private val handle: SavedStateHandle, @Assisted private val exampleArg: String?, private val context: Context, // App context - private val exposureNotificationClient: ExposureNotificationClient, + private val enfClient: ENFClient, private val keyCacheRepository: KeyCacheRepository ) : CWAViewModel() { @@ -63,7 +62,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( try { RetrieveDiagnosisKeysTransaction.start() calculateRiskLevel() - } catch (e: TransactionException) { + } catch (e: Exception) { e.report(ExceptionCategory.INTERNAL) } } @@ -73,7 +72,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( viewModelScope.launch { try { RiskLevelTransaction.start() - } catch (e: TransactionException) { + } catch (e: Exception) { e.report(ExceptionCategory.INTERNAL) } } @@ -218,7 +217,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( private suspend fun asyncGetExposureInformation(token: String): List<ExposureInformation> = suspendCoroutine { cont -> - exposureNotificationClient.getExposureInformation(token) + enfClient.internalClient.getExposureInformation(token) .addOnSuccessListener { cont.resume(it) }.addOnFailureListener { @@ -261,7 +260,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys with token $token") try { // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API - InternalExposureNotificationClient.asyncProvideDiagnosisKeys( + enfClient.provideDiagnosisKeys( googleFileList, ApplicationConfigurationService.asyncRetrieveExposureConfiguration(), token diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml index 09ac74915257e9d58fbf690dc376ebd8e419ac2a..8c7d43bdaec80f9a104bd018f5a2145f27ce01ec 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml @@ -22,11 +22,17 @@ android:layout_margin="@dimen/spacing_normal" android:orientation="vertical"> + <de.rki.coronawarnapp.ui.calendar.CalendarView + android:id="@+id/calendar_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <TextView android:id="@+id/label_googlePlayServices_version" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <Switch android:id="@+id/test_api_switch_last_three_hours_from_server" style="@style/body1" @@ -43,6 +49,27 @@ android:text="@string/test_api_switch_background_notifications" android:theme="@style/switchBase" /> + <LinearLayout + android:layout_width="match_parent" + android:orientation="horizontal" + android:layout_height="wrap_content"> + + <Switch + android:id="@+id/test_logfile_toggle" + style="@style/body1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Logfile enabled" + android:theme="@style/switchBase" /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/test_logfile_share" + android:text="Share log" /> + + </LinearLayout> + <TextView android:id="@+id/label_exposure_summary" style="@style/headline6" diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml index 43fb6ab2caa3ce823f5a146236074240695ab3f8..89a9fb400b43b7b803e8ae9433043f7984eb8f8b 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/nav_graph.xml @@ -234,8 +234,8 @@ app:popUpTo="@id/mainFragment" app:popUpToInclusive="true" /> <action - android:id="@+id/action_submissionResultFragment_to_submissionResultPositiveOtherWarningFragment" - app:destination="@id/submissionResultPositiveOtherWarningFragment" /> + android:id="@+id/action_submissionResultFragment_to_submissionSymptomIntroductionFragment" + app:destination="@id/submissionSymptomIntroductionFragment" /> </fragment> <fragment @@ -323,4 +323,29 @@ app:argType="string" app:nullable="true" /> </fragment> + <fragment + android:id="@+id/submissionSymptomIntroductionFragment" + android:name="de.rki.coronawarnapp.ui.submission.SubmissionSymptomIntroductionFragment" + android:label="SubmissionSymptomIntroductionFragment" > + <action + android:id="@+id/action_submissionSymptomIntroductionFragment_to_submissionSymptomCalendarFragment" + app:destination="@id/submissionSymptomCalendarFragment" /> + <action + android:id="@+id/action_submissionSymptomIntroductionFragment_to_submissionResultFragment" + app:destination="@id/submissionResultFragment" /> + <action + android:id="@+id/action_submissionSymptomIntroductionFragment_to_submissionResultPositiveOtherWarningFragment" + app:destination="@id/submissionResultPositiveOtherWarningFragment" /> + </fragment> + <fragment + android:id="@+id/submissionSymptomCalendarFragment" + android:name="de.rki.coronawarnapp.ui.submission.SubmissionSymptomCalendarFragment" + android:label="SubmissionSymptomCalendarFragment" > + <action + android:id="@+id/action_submissionCalendarFragment_to_submissionSymptomIntroductionFragment" + app:destination="@id/submissionSymptomIntroductionFragment" /> + <action + android:id="@+id/action_submissionSymptomCalendarFragment_to_submissionResultPositiveOtherWarningFragment" + app:destination="@id/submissionResultPositiveOtherWarningFragment" /> + </fragment> </navigation> diff --git a/Corona-Warn-App/src/main/assets/privacy_de.html b/Corona-Warn-App/src/main/assets/privacy_de.html index d259720849b8dbd04ca0a9f6ebf0258853ed4d54..43a28a5ea27729b881fb63d5e9112899523b98a3 100644 --- a/Corona-Warn-App/src/main/assets/privacy_de.html +++ b/Corona-Warn-App/src/main/assets/privacy_de.html @@ -13,77 +13,77 @@ Damit diese Datenschutzerklärung für alle Nutzer verständlich ist, bemühen wir uns um eine einfache und möglichst untechnische Darstellung. </p> -<h2> +<h1> 1. Wer stellt Ihnen diese App zur Verfügung? -</h2> +</h1> <p> Der Anbieter der Corona-Warn-App (im Folgenden die „<strong>App</strong>“) ist das Robert Koch-Institut, Nordufer 20, 13353 Berlin (im Folgenden „<strong>RKI</strong>“). -</p> -<p> - Das RKI ist auch der datenschutzrechtlich Verantwortliche für die - Verarbeitung von personenbezogenen Daten der App-Nutzer. + Das RKI ist auch der datenschutzrechtlich + Verantwortliche für die Verarbeitung von personenbezogenen Daten der + App-Nutzer. </p> <p> Den Datenschutzbeauftragten des RKI erreichen Sie unter der oben genannten Anschrift (zu Händen „Behördlicher Datenschutzbeauftragter“) und per E-Mail - an: datenschutz@rki.de). + an: datenschutz@rki.de. </p> -<h2> +<h1> 2. Ist die Nutzung der App freiwillig? -</h2> -<p> - Die Benutzung der App basiert ausschließlich auf Freiwilligkeit. Es ist - daher allein Ihre Entscheidung, ob und wie Sie die App nutzen. -</p> +</h1> <p> - Auch wenn die Installation und die Benutzung der App freiwillig sind, - müssen Sie nach dem erstmaligen Aufruf der App gegenüber dem RKI durch - Antippen des Buttons „Risiko-Ermittlung aktivieren“ zustimmen, dass die App - im Rahmen der Risiko-Ermittlung Ihre personenbezogenen Daten verarbeiten - darf. Falls die App dabei ein Infektionsrisiko für Sie ermittelt, stellen - Ihre Daten auch Gesundheitsdaten dar. Ihre Zustimmung ist erforderlich, da - andernfalls die App nicht auf die Kontaktaufzeichnungs-Funktion Ihres - Smartphones zugreifen kann. Sie können die Risiko-Ermittlung jedoch - jederzeit über den Schieberegler innerhalb der App deaktivieren. In diesem - Fall stehen Ihnen nicht alle Funktionen der App zur Verfügung. Gesonderte - Einwilligungen sind darüber hinaus für die Datenverarbeitung der folgenden - Funktionen erforderlich: + Die Nutzung der App ist freiwillig. Es ist daher allein Ihre Entscheidung, + ob Sie die App installieren und welche Funktionen Sie nutzen. Ihre Daten + (zum Beispiel zu Ihren Begegnungen und Ihrem Infektionsrisiko) werden + ausschließlich auf Ihrem eigenen Smartphone erzeugt und gespeichert. Eine + Weitergabe Ihrer Daten an das RKI, das Betriebssystem Ihres Smartphones + oder andere Nutzer findet nur statt, wenn Sie eine der folgenden + App-Funktion nutzen und in die jeweils erforderliche Datenweitergabe + eingewilligt haben: </p> <ul> + <li> + Risiko-Ermittlung (siehe Ziffer 6 a.) + </li> <li> Test registrieren (siehe Ziffer 6 b.) </li> <li> - Testergebnis teilen (siehe Ziffer 6 c.) + Andere warnen (siehe Ziffer 6 c.) </li> </ul> +<p> + Sie sind nicht verpflichtet, diese Funktionen zu nutzen. Wenn Sie sich + entscheiden, eine von diesen Funktionen erbetene Einwilligung nicht zu + erteilen oder nachträglich zurückzuziehen, entstehen Ihnen keine Nachteile. + Sie können dann lediglich die einwilligungsbasierten App-Funktion nicht + bzw. nicht mehr nutzen. +</p> <p> Die Datenverarbeitung im Rahmen dieser Funktionen wird in den folgenden Abschnitten näher beschrieben. </p> -<h2> +<h1> 3. Auf welcher Rechtsgrundlage werden Ihre Daten verarbeitet? -</h2> +</h1> <p> - Das RKI verarbeitet Ihre personenbezogenen Daten grundsätzlich nur auf - Grundlage einer von Ihnen erteilten Einwilligung nach Artikel 6 Absatz 1 - Satz 1 Buchstabe a und Artikel 9 Absatz 2 Buchstabe a der - Datenschutzgrundverordnung (DSGVO). Sie können eine von Ihnen erteilte - Einwilligung jederzeit widerrufen. Weitere Informationen zu Ihrem - Widerrufsrecht und Hinweise, wie Sie dieses ausüben können, finden Sie - unter Ziffer 11. + Das RKI verarbeitet Ihre Daten grundsätzlich nur auf Grundlage einer von + Ihnen erteilten Einwilligung nach Artikel 6 Absatz 1 Satz 1 Buchstabe a und + Artikel 9 Absatz 2 Buchstabe a der Datenschutzgrundverordnung (DSGVO). Sie + können eine erteilte Einwilligung jederzeit widerrufen. Weitere + Informationen zu Ihrem Widerrufsrecht und Hinweise, wie Sie dieses ausüben + können, finden Sie unter Ziffer 11. </p> -<h2> +<h1> 4. An wen richtet sich die App? -</h2> +</h1> <p> Die App richtet sich an Personen, die sich in Deutschland aufhalten und mindestens 16 Jahre alt sind. </p> -<h2> +<h1> 5. Welche personenbezogenen Daten werden verarbeitet? -</h2> +</h1> <p> Die App ist so konzipiert, dass so wenig personenbezogene Daten wie möglich verarbeitet werden. Das bedeutet zum Beispiel, dass die App keine Daten @@ -96,41 +96,45 @@ Die von der App verarbeiteten Daten lassen sich den folgenden Kategorien zuordnen: </p> +<h2> + a. Zugriffsdaten +</h2> <p> - <strong>a. Zugriffsdaten</strong> -</p> -<p> - Zugriffsdaten fallen an, wenn Sie die folgenden Funktionen nutzen bzw. - aktivieren: + Bei jedem Datenaustausch über das Internet zwischen der App und dem + Serversystem der App werden vom Serversystem sogenannte Zugriffsdaten + verarbeitet. Dies ist erforderlich, damit die App aktuelle Daten vom + Serversystem abrufen oder bestimmte auf dem Smartphone gespeicherte Daten + an das Serversystem übermitteln kann. Folgende App-Funktionen erfordern + einen solchen Datenaustausch mit dem Serversystem der App: </p> <ul> <li> - Risiko-Ermittlung + Risiko-Ermittlung (Abruf der Liste mit den Zufalls-IDs von positiv + getesteten Nutzern) </li> <li> - Test registrieren + Test registrieren (Ãœbermitteln der Test-Kennzahl und Abruf des + Testergebnisses) </li> <li> - Testergebnis teilen + Andere warnen (Ãœbermitteln Ihrer Zufalls-IDs) </li> </ul> <p> - Bei jedem Abruf von Daten vom Serversystem der App wird Ihre IP-Adresse - (auf dem vorgelagerten Load Balancer) maskiert und im Weiteren nicht mehr - innerhalb des Serversystems der App verarbeitet. -</p> -<p> - Zusätzlich werden folgende Daten verarbeitet: + Die Zugriffsdaten umfassen folgende Daten: </p> <ul> + <li> + IP-Adresse + </li> <li> Datum und Uhrzeit des Abrufs (Zeitstempel) </li> <li> - übertragene Datenmenge (bzw. Paketlänge) + übertragene Datenmenge (bzw. Paketlänge) </li> <li> - Meldung über erfolgreichen Abruf + Meldung über erfolgreichen Abruf </li> </ul> <p> @@ -141,73 +145,81 @@ hinaus erfolgt nicht. </p> <p> - <strong>b. Begegnungsdaten</strong> + Um eine unbefugte Zuordnung Ihrer Daten anhand Ihrer IP-Adresse schon + während eines Nutzungsvorgangs zu erschweren, greift die App über einen + speziellen Eingangsserver auf das Serversystem zu. Dieser Eingangsserver + leitet die von der App angeforderten oder übermittelten Daten dann ohne + Ihre IP-Adresse an den jeweils zuständigen Server weiter, so dass sie im + Weiteren nicht mehr innerhalb des Serversystems verarbeitet wird. </p> +<h2> + b. Begegnungsdaten +</h2> <p> Wenn Sie auf Ihrem Smartphone die betriebssystemseitige Funktion zur - Aufzeichnung von Kontakten zu anderen Nutzern aktivieren, versendet Ihr - Smartphone per Bluetooth Low Energy kontinuierlich zufallsgenerierte - Kennnummern, auch als Zufallscodes bezeichnet (im Folgenden: „<strong>Zufalls-IDs“</strong>), - die von anderen Smartphones in Ihrer Nähe - mit ebenfalls aktivierter Kontaktaufzeichnung empfangen werden können. - Umgekehrt empfängt Ihr Smartphone auch die Zufalls-IDs der anderen - Smartphones. Zu den von anderen Smartphones empfangenen Zufalls-IDs werden - von der Kontaktaufzeichnungs-Funktion Ihres Smartphones zusätzlich folgende - Begegnungsdaten aufgezeichnet und gespeichert: + Begegnungs-Aufzeichnung aktivieren, versendet Ihr Smartphone per Bluetooth + Low Energy kontinuierlich zufallsgenerierte Kennnummern, auch als + Zufallscodes zu bezeichnet (im Folgenden: „<strong>Zufalls-IDs“</strong>), + die von anderen Smartphones in Ihrer Nähe mit ebenfalls aktivierter + Kontaktaufzeichnung empfangen werden können. Umgekehrt empfängt Ihr + Smartphone auch die Zufalls-IDs der anderen Smartphones. Zu den von anderen + Smartphones empfangenen Zufalls-IDs werden von der + Begegnungsaufzeichnung-Funktion Ihres Smartphones zusätzlich folgende + Begegnungsdaten aufgezeichnet: </p> <ul> - <li> - Datum und Zeitpunkt des Kontakts - </li> - <li> - Dauer des Kontakts - </li> - <li> - Bluetooth-Signalstärke des Kontakts - </li> - <li> - Verschlüsselte Metadaten (Protokollversion und Sendestärke). - </li> + <li>Datum und Zeitpunkt des Kontakts</li> + <li>Dauer des Kontakts</li> + <li>Bluetooth-Signalstärke des Kontakts</li> + <li>Verschlüsselte Metadaten (Protokollversion und Sendestärke)</li> </ul> <p> Die eigenen und von anderen Smartphones empfangenen Zufalls-IDs und die weiteren Begegnungsdaten (Datum und Zeitpunkt des Kontakts, Dauer des - Kontakts, Signalstärke des Kontakts und verschlüsselte Metadaten) werden - von Ihrem Smartphone in einem Kontaktprotokoll der - Kontaktaufzeichnungs-Funktion erfasst und dort zurzeit für 14 Tage - gespeichert. + Kontakts, Signalstärke des Kontakts und verschlüsselte Metadaten) werden + von der Begegnungsaufzeichnungs-Funktion Ihres Smartphones erfasst und dort + zurzeit für 14 Tage gespeichert. </p> <p> - Die Kontaktaufzeichnungs-Funktion heißt bei Android-Smartphones - "Benachrichtigungen zu möglicher Begegnung mit Infizierten" und bei iPhones - „COVID-19-Kontaktprotokoll“. Wir weisen Sie darauf hin, dass diese - Kontaktaufzeichnungs-Funktionen kein Bestandteil der App, sondern ein - integraler Bestandteil des Betriebssystems Ihres Smartphones sind. Die - Kontaktaufzeichnungs-Funktion wird Ihnen daher von Apple (iPhones) bzw. - Google (Android-Smartphones) bereitgestellt und unterliegt dementsprechend - den Datenschutzbestimmungen dieser Unternehmen. Die betriebssystemseitige - Datenverarbeitung im Rahmen der Kontaktaufzeichnungs-Funktion liegt - außerhalb des Einflussbereichs des RKI. + Die Begegnungsaufzeichnungs-Funktion heißt bei Android-Smartphones + "COVID-19-Benachrichtungen" und bei iPhones „Begegnungsmittelungen“. Wir + weisen Sie darauf hin, dass diese Funktionen kein Bestandteil der App, + sondern ein integraler Bestandteil Ihres Betriebssystems sind. Anbieter der + Begegnungsaufzeichnungs-Funktionen sind daher Apple (iPhones) bzw. Google + (Android-Smartphones). Dementsprechend unterliegt die betriebssystemseitige + Datenverarbeitung durch die Begegnungsaufzeichnungs-Funktionen den + jeweiligen Datenschutzbestimmungen dieser Unternehmen und liegt außerhalb + des Verantwortungs- und Einflussbereichs des RKI. </p> <p> - Weitere Informationen zu der Kontaktaufzeichnungs-Funktion von - Android-Smartphones finden Sie unter: - https://support.google.com/android/answer/9888358?hl=de. + Weitere Informationen stellen Ihnen die Anbieter der + Begegnungsaufzeichnungs-Funktion zur Verfügung: </p> +<ul> + <li> + Informationen von Google für Android-Smartphones: + https://support.google.com/android/answer/9888358?hl=de + </li> + <li> + Informationen von Apple für iPhones finden Sie auf Ihrem Gerät unter + „Einstellungen“ > "Begegnungsmitteilungen“ unter dem Link „So + funktionieren Begegnungsmitteilungen …“. + </li> +</ul> <p> - Weitere Informationen zu der Kontaktaufzeichnungs-Funktion von Apple finden - Sie in den Einstellungen Ihres iPhones unter "Datenschutz“ > „Health" - > „COVID-19-Kontaktprotokoll“. Bitte beachten Sie: Die - Kontaktaufzeichnungs-Funktion steht Ihnen nur zur Verfügung, wenn auf Ihrem - iPhone das Betriebssystem iOS ab Version 13.5 installiert ist. + Bitte beachten Sie, dass die tatsächlichen Bezeichnungen, Bedienschritte + und Einstellmöglichkeiten der Kontaktaufzeichnungs-Funktion je nach Version + und Konfiguration Ihres Betriebssystems von der der Darstellung in dieser + Datenschutzerklärung abweichen kann. </p> <p> - Die vom Smartphone erzeugten und gespeicherten Begegnungsdaten werden von - der App nur verarbeitet, wenn die Risiko-Ermittlung aktiviert ist. + Die von der Begegnungsaufzeichnungs-Funktion Ihres Betriebssystems + erzeugten und gespeicherten Begegnungsdaten werden von der App nur + verarbeitet, wenn die Risiko-Ermittlung aktiviert ist. </p> -<h3> - a. Gesundheitsdaten -</h3> +<h2> + c. Gesundheitsdaten +</h2> <p> Gesundheitsdaten sind alle Daten, die Informationen zum Gesundheitszustand einer bestimmten Person enthalten. Dazu gehören nicht nur Angaben zu @@ -216,74 +228,88 @@ infiziert hat). </p> <p> - In den folgenden Fällen handelt es sich um eine Verarbeitung von + Bei der Nutzung folgender Funktionen verarbeitet die App Ihre Gesundheitsdaten: </p> <ul> - <li> - Wenn die Risiko-Ermittlung erkennt, dass Sie möglicherweise Kontakt zu - einer Person hatten, die sich mit dem Corona-Virus infiziert hat. + <li>Risiko-Ermittlung, sobald eine Risiko-Begegnung festgestellt wird (d. h. + Sie hatten möglicherweise Kontakt zu einer Person, die sich mit dem + Corona-Virus infiziert hat). </li> - <li> - Wenn Sie einen Test registrieren. - </li> - <li> - Wenn Sie ein positives Testergebnis teilen. + <li>Andere warnen (Testergebnis, eventuelle Angaben zu Symptomen und + Symptombeginn) </li> + <li>Test registrieren (Teststatus und Testergebnis)</li> </ul> -<h2> +<h1> 6. Funktionen der App -</h2> -<h3> +</h1> +<h2> a. Risiko-Ermittlung -</h3> +</h2> <p> Die Risiko-Ermittlung ist die Kernfunktion der App. Sie dient dazu, - mögliche Kontakte zu mit dem Corona-Virus infizierten anderen Nutzern der - App nachzuverfolgen, das infolge für Sie bestehende Infektionsrisiko zu - bewerten und Ihnen, basierend auf dem für Sie ermittelten Risikowert, - Verhaltens- und Gesundheitshinweise bereitzustellen. + mögliche Kontakte zu mit dem Corona-Virus infizierten anderen Nutzern + (Risiko-Begegnungen) nachzuverfolgen, das infolge für Sie bestehende + Infektionsrisiko zu bewerten und Ihnen entsprechende Verhaltens- und + Gesundheitshinweise bereitzustellen. </p> <p> Wenn Sie die Risiko-Ermittlung aktivieren, ruft die App von den Serversystemen der App im Hintergrundbetrieb mehrmals täglich (oder wenn - Sie auf „Aktualisieren“ tippen) eine Liste mit Zufalls-IDs von Nutzern ab, - die positiv getestet wurden und Ihre eigenen Zufalls-IDs geteilt haben. Die - App gibt die Zufalls-IDs an die Kontaktaufzeichnungs-Funktion Ihres - Smartphones weiter, welche diese dann mit den im Kontaktprotokoll Ihres - Smartphones gespeicherten Zufalls-IDs abgleicht. Wenn die - Kontaktaufzeichnungs-Funktion Ihres Smartphones eine Ãœbereinstimmung + Sie auf „Aktualisieren“ tippen) eine Liste mit Zufalls-IDs mit jeweils + einem Ãœbertragungsrisiko-Wert (als Zahlenwert von 1-8) von Nutzern ab, die + positiv getestet wurden und Ihre eigenen Zufalls-IDs über die App mit der + Funktion „Andere warnen“ bereitgestellt haben. Der Ãœbertragungsrisiko-Wert + ist ein Schätzwert zur Höhe der Ansteckungswahrscheinlichkeit am Tag der + jeweiligen Risiko-Begegnung. Da die Infektiosität (also die Höhe des + Ansteckungsrisikos für die Kontakte einer positiv getesteten Person) nach + derzeitigem Wissensstand von der Dauer und dem Verlauf der Infektion + abhängt, kann somit beispielsweise berücksichtigt werden, dass die Gefahr + einer Ansteckung am Tag einer Risiko-Begegnung geringer ist, je mehr Zeit + seit Symptombeginn verstrichen ist. +</p> +<p> + Die App gibt die Zufalls-IDs an die Begegnungsaufzeichnungs-Funktion Ihres + Smartphones weiter, welche diese dann mit den im von der + Begegnungsaufzeichnung protokollierten Zufalls-IDs abgleicht. Wenn die + Begegnungsaufzeichnungs-Funktion Ihres Smartphones eine Ãœbereinstimmung feststellt, übergibt sie der App die Begegnungsdaten (Datum, Dauer, Signalstärke), nicht jedoch die Zufalls-ID des betreffenden Kontakts. </p> <p> - Im Fall eines Kontakts werden die von der Kontaktaufzeichnungs-Funktion - übergebenen Begegnungsdaten von der App analysiert, um Ihr individuelles - Infektionsrisiko zu ermitteln. Der Bewertungsalgorithmus, der festlegt, wie - die Begegnungsdaten interpretiert werden (z. B. welchen Einfluss die Dauer - eines Kontakts auf das Infektionsrisiko hat) basiert auf den aktuellen - wissenschaftlichen Erkenntnissen. Bei neuen Erkenntnissen kann der - Bewertungsalgorithmus daher durch das RKI aktualisiert werden, indem die - Einstellungen für den Bewertungsalgorithmus neu gesetzt werden. Die + Im Fall einer Risiko-Begegnung werden die von der + Begegnungsaufzeichnungs-Funktion übergebenen Begegnungsdaten sowie der + Ãœbertragungsrisiko-Wert von der App analysiert, um Ihr individuelles + Infektionsrisiko zu ermitteln. +</p> +<p> + Der Bewertungsalgorithmus, der festlegt, wie die Begegnungsdaten und der + Ãœbertragungsrisiko-Wert interpretiert werden (z. B. welchen Einfluss die + Dauer eines Kontakts auf das Infektionsrisiko hat) basiert auf den + aktuellen wissenschaftlichen Erkenntnissen. Bei neuen Erkenntnissen kann + der Bewertungsalgorithmus daher durch das RKI aktualisiert werden, indem + die Einstellungen für den Bewertungsalgorithmus neu gesetzt werden. Die Einstellungen für den Bewertungsalgorithmus werden dann zusammen mit der - Liste der Zufalls-IDs infizierter Personen an die App übermittelt. + Liste der Zufalls-IDs infizierter Nutzer an die App übermittelt. </p> <p> Die Ermittlung des Infektionsrisikos findet ausschließlich lokal auf Ihrem - Smartphone statt, das heißt die Daten werden offline verarbeitet. Das - ermittelte Infektionsrisiko wird ebenfalls ausschließlich in der App - gespeichert und an keine anderen Empfänger (auch nicht an das RKI, Apple, - Google und sonstige Dritte) weitergegeben. + Smartphone statt, das heißt die Daten werden offline ohne Zugriffe auf das + Serversystem der App verarbeitet. Das ermittelte Infektionsrisiko wird + ebenfalls ausschließlich in der App berechnet und an keine anderen + Empfänger (auch nicht an das RKI, Apple, Google und sonstige Dritte) + weitergegeben. </p> <p> Rechtsgrundlage der oben beschriebenen Verarbeitung Ihrer Zugriffsdaten, - Begegnungsdaten und ggf. Gesundheitsdaten (sofern für Sie ein - Infektionsrisiko ermittelt wird) ist Ihre Einwilligung, die Sie bei der - Aktivierung der Risiko-Ermittlung erteilt haben. + Begegnungsdaten und ggf. Gesundheitsdaten (sofern eine Risiko-Begegnung + erkannt wird) ist Ihre Einwilligung, die Sie bei der Aktivierung der + Risiko-Ermittlung erteilt haben. </p> -<h3> +<h2> b. Test registrieren -</h3> +</h2> <p> Wenn Sie auf eine Infektion mit dem Corona-Virus getestet wurden, können Sie den Test in der App registrieren, indem Sie den QR-Code, den Sie von @@ -338,7 +364,7 @@ speziellen Server innerhalb des Serversystems der App betrieben. Das Testlabor erzeugt die gehashte Kennzahl ebenfalls auf Basis der an Sie im ausgegebenen QR-Code enthaltenen Kennzahl unter Verwendung des gleichen - mathematischen Verfahrens, das auch die App einsetzt.<u></u> + mathematischen Verfahrens, das auch die App einsetzt. </p> <p> <u>Abruf des Testergebnisses</u> @@ -359,28 +385,36 @@ Kennzahl ein positives Testergebnis vorliegt. Sofern die Testergebnis-Datenbank dies bestätigt, erzeugt das Serversystem die TAN und übermittelt sie an die App. Eine Kopie der TAN verbleibt auf dem - Serversystem. + Serversystem. Die TAN wird benötigt, um im Fall einer Ãœbermittlung des + positiven Testergebnisses sicherzustellen, dass keine falschen + Informationen an andere Nutzer verteilt werden. Rechtsgrundlage der oben + beschriebenen Verarbeitung der zuvor genannten Daten ist Ihre Einwilligung + für die Funktion „Test registrieren“. </p> +<h2> + c. Andere warnen +</h2> <p> - Die TAN wird benötigt, um im Fall einer Ãœbermittlung des positiven - Testergebnisses sicherzustellen, dass keine falschen Informationen an - andere Nutzer verteilt werden. + Wenn Sie die Funktion „Andere warnen“ nutzen überträgt die App die von + Ihrem Smartphone gespeicherten eigenen Zufalls-IDs (einschließlich der + jeweiligen Ãœbertragungsrisiko-Werte) der letzten 14 Tage und die TAN an das + Serversystem der App. Dieses prüft zunächst, ob die TAN gültig ist und + trägt Ihre Zufalls-IDs sodann in die Liste der Zufalls-IDs von Nutzern, die + ihr positives Testergebnis bereitgestellt haben, ein. Ihre Zufalls-IDs + können nun von anderen Nutzern im Rahmen der Risiko-Ermittlung + heruntergeladen werden. </p> <p> - Rechtsgrundlage der oben beschriebenen Verarbeitung der zuvor genannten - Daten ist Ihre Einwilligung für die Funktion „Test registrieren“. -</p> -<h3> - c. Testergebnis teilen -</h3> -<p> - Wenn Sie die Funktion „Testergebnis teilen“ nutzen um andere Nutzer zu - warnen, überträgt die App die von Ihrem Smartphone gespeicherten eigenen - Zufalls-IDs der letzten 14 Tage und die TAN an das Serversystem der App. - Dieses prüft zunächst, ob die TAN gültig ist und trägt Ihre Zufalls-IDs - sodann in die Liste der Zufalls-IDs von Nutzern, die ihr positives - Testergebnis geteilt haben, ein. Ihre Zufalls-IDs können nun von anderen - Nutzern im Rahmen der Risiko-Ermittlung heruntergeladen werden. + Die von der Funktion „Andere warnen“ abgefragten Angaben zu Symptomen und + Symptombeginn sind optional und nicht erforderlich, um andere Nutzer zu + warnen. Diese Angaben können jedoch helfen, das Infektionsrisiko der + anderen Nutzer, denen Sie begegnet sind, genauer zu berechnen. Soweit Sie + keine oder nicht alle Fragen beantworten können oder wollen, wählen Sie + einfach „keine Angabe“. In diesem Fall werden die Ãœbertragungsrisiko-Werte + Ihrer Zufalls-IDs von der App anhand des Zeitablaufs seit Abruf des + Testergebnisses unter Zugrundelegung eines durchschnittlichen + Infektionsverlaufs festgelegt, d. h. je mehr Zeit seit Verwendung einer + Zufalls-ID vergangen ist, desto kleiner ist ihr Ãœbertragungsrisiko-Wert. </p> <p> <u>Wenn Sie Ihr Testergebnis nicht in der App abgerufen haben:</u> @@ -419,22 +453,22 @@ Gesundheitsdaten (Zufalls-IDs, Testergebnis, TAN und ggf. TeleTAN) ist Ihre Einwilligung für die Funktion „Testergebnis teilen“. </p> -<p> - <strong>d. Informatorische Nutzung der App</strong> -</p> +<h3> + d. Informatorische Nutzung der App +</h3> <p> Soweit Sie die App nur informatorisch nutzen, also keine der oben genannten Funktionen der App verwenden und keine Daten eingeben, findet die Verarbeitung ausschließlich lokal auf Ihrem Smartphone statt und es fallen - keine personenbezogenen Daten an. In der App verlinkte Webseiten z.B.: - www.bundesregierung.de werden im Standard-Browser Ihres Smartphones - geöffnet und angezeigt. Welche Daten dabei verarbeitet werden hängt von dem - genutzten Browser, dessen Konfiguration sowie der Datenverarbeitungspraxis - der aufgerufenen Webseite ab. + keine personenbezogenen Daten an. In der App verlinkte Webseiten z.B.: www.bundesregierung.de + werden je nach Betriebssystem + im Standard-Browser Ihres Smartphones (Android-Smartphones) oder innerhalb + der App (iPhone) geöffnet und angezeigt. Welche Daten dabei verarbeitet + werden hängt von der Datenverarbeitungspraxis der aufgerufenen Webseite ab. </p> -<h2> +<h1> 7. Welche Berechtigungen und Funktionen benötigt die App? -</h2> +</h1> <p> Die App benötigt Zugriff auf verschiedene Funktionen und Schnittstellen Ihres Smartphones. Dazu ist es erforderlich, dass Sie der App bestimmte @@ -445,9 +479,9 @@ beachten Sie, dass Sie im Falle der Ablehnung eines Zugriffs durch die App keine oder nur wenige Funktionen der App nutzen können. </p> -<h3> +<h2> a. Technische Voraussetzungen (alle Smartphones) -</h3> +</h2> <ul> <li> Internet @@ -489,9 +523,9 @@ Betriebssystem Ihres Smartphones deaktivieren, müssen Sie alle Aktionen in der App selbst starten. </p> -<h3> +<h2> b. Android-Smartphones -</h3> +</h2> <p> Wenn Sie ein Android-Gerät verwenden, müssen außerdem folgende Systemfunktionen aktiviert sein: @@ -539,16 +573,16 @@ Die App benötigt Zugriff auf die Kamera, um bei der Testregistrierung den QR-Code auslesen zu können. </p> -<h3> +<h2> c. iPhones (Apple iOS) -</h3> +</h2> <p> Wenn Sie ein iPhone verwenden, müssen folgende Systemfunktionen aktiviert sein: </p> <ul> <li> - COVID-19-Kontaktprotokoll + Begegnungsmitteilungen </li> </ul> <p> @@ -578,16 +612,16 @@ Die App benötigt Zugriff auf die Kamera, um bei der Testregistrierung den QR-Code auslesen zu können. </p> -<h2> +<h1> 8. Wann werden die Daten gelöscht? -</h2> +</h1> <p> Alle in der App gespeicherten Daten werden gelöscht, sobald sie für die Funktionen der App nicht mehr benötigt werden: </p> -<h3> +<h2> a. Risiko-Ermittlung -</h3> +</h2> <ul> <li> Die Liste der Zufalls-IDs von Nutzern, die ein positives Testergebnis @@ -595,26 +629,26 @@ Kontaktprotokoll Ihres Smartphones nach 14 Tagen automatisch gelöscht. </li> <li> - Auf die Löschung der Begegnungsdaten im Kontaktprotokoll Ihres - Smartphones (einschließlich Ihrer eigenen Zufalls-IDs) und die - Begegnungsdaten auf anderen Smartphones hat das RKI keinen Einfluss, da - diese Funktion von Apple bzw. Google bereitgestellt werden. Die - Löschung richtet sich nach den Festlegungen von Apple bzw. Google. - Zurzeit werden die Daten nach 14 Tagen automatisch gelöscht. Zudem - können Sie im Rahmen der von Apple und Google bereitgestellten - Funktionalitäten in den Systemeinstellungen Ihres Geräts gegebenenfalls - eine manuelle Löschung anstoßen. + Auf die Löschung der Begegnungsdaten im Kontaktprotokoll der + Begegnungsaufzeichnung Ihres Smartphones (einschließlich Ihrer eigenen + Zufalls-IDs) und die Begegnungsdaten auf anderen Smartphones hat das + RKI keinen Einfluss, da diese Funktionen von Apple bzw. Google + bereitgestellt werden. Die Löschung richtet sich nach den Festlegungen + von Apple bzw. Google. Zurzeit werden die Daten nach 14 Tagen + automatisch gelöscht. Zudem können Sie im Rahmen der von Apple und + Google bereitgestellten Funktionalitäten in den Systemeinstellungen + Ihres Geräts gegebenenfalls eine manuelle Löschung anstoßen. </li> <li> - Der in der App angezeigte Risikowert wird gelöscht, sobald ein neuer - Risikowert ermittelt worden ist. Ein neuer Risikowert wird in der Regel - ermittelt, nachdem die App eine neue Liste mit Zufalls-IDs erhalten - hat. + Das in der App angezeigte Infektionsrisiko wird gelöscht, sobald ein + neuer Risikowert ermittelt worden ist. Ein neuer Risikowert wird in der + Regel ermittelt, nachdem die App eine neue Liste mit Zufalls-IDs + erhalten hat. </li> </ul> -<h3> +<h2> b. Test registrieren -</h3> +</h2> <ul> <li> Die gehashte Kennzahl wird auf dem Serversystem der App nach 21 Tagen @@ -633,29 +667,35 @@ </li> <li> Das Token, das in der App gespeichert ist, wird nach Löschung der App - vom Smartphone oder nach Ausführung der Funktion „Testergebnis teilen“ + vom Smartphone oder nach Ausführung der Funktion „Andere warnen“ gelöscht. </li> </ul> -<h3> - c. Testergebnis teilen -</h3> +<h2> + c. Andere warnen +</h2> <ul> <li> - Die in der App geteilten eigenen Zufalls-IDs werden nach 14 Tagen vom - Serversystem gelöscht. + Die über die App bereitgestellten eigenen Zufalls-IDs und + Risikoübertragungs-Werte werden nach 14 Tagen vom Serversystem + gelöscht. + </li> + <li> + Ihre Angaben zu Symptomen und zum Symptombeginn werden direkt nach dem + Bereitstellen der Zufalls-IDs und Ãœbertragungsrisiko-Werte von der App + gelöscht. </li> <li> Die Kopie der TAN, die auf dem Serversystem gespeichert ist, wird nach 21 Tagen gelöscht. </li> <li> - Die TAN, die in der App gespeichert ist, wird nach Teilen des + Die TAN, die in der App gespeichert ist, wird nach Bereitstellen des Testergebnisses gelöscht. </li> <li> - Die TeleTAN, die in der App gespeichert ist, wird nach Teilen des - Testergebnisses gelöscht. + Die TeleTAN, die in der App gespeichert ist, wird nach Bereitstellen + des Testergebnisses gelöscht. </li> <li> Die TeleTAN, die auf dem Serversystem gespeichert ist, wird nach 21 @@ -670,16 +710,17 @@ gelöscht. </li> <li> - Das Token, das in der App gespeichert ist, wird nach Teilen des + Das Token, das in der App gespeichert ist, wird nach Bereitstellen des Testergebnisses gelöscht. </li> </ul> -<h2> +<h1> 9. An wen werden Ihre Daten weitergegeben? -</h2> +</h1> <p> - Wenn Sie ein Testergebnis teilen, um andere Nutzer zu warnen, werden Ihre - Zufalls-IDs der letzten 14 Tage an die Apps der anderen Nutzer + Wenn Sie ein Testergebnis über die Funktion „Andere warnen“ bereitstellen, + um andere Nutzer zu warnen, werden Ihre Zufalls-IDs und deren + Ãœbertragungsrisiko-Werte der letzten 14 Tage an die Apps der anderen Nutzer weitergegeben. </p> <p> @@ -696,17 +737,17 @@ erforderlich ist. Eine Weitergabe in anderen Fällen erfolgt grundsätzlich nicht. </p> -<h2> +<h1> 10. Werden Daten in ein Drittland übermittelt? -</h2> +</h1> <p> Die bei der Nutzung der App anfallenden Daten werden ausschließlich auf Servern in Deutschland oder in einem anderem EU- oder EWR-Mitgliedsstaat verarbeitet. </p> -<h2> +<h1> 11. Widerruf von Einwilligungen -</h2> +</h1> <p> Ihnen steht das Recht zu, die in der App erteilten Einwilligungen gegenüber dem RKI jederzeit mit Wirkung für die Zukunft zu widerrufen. Die @@ -728,20 +769,21 @@ werden Sie um eine neue Einwilligung gebeten. </p> <p> - Zum Widerruf Ihrer Einwilligung für die Funktion „Testergebnis teilen“ - müssen Sie die App löschen. Sämtliche Ihrer in der App gespeicherten - Zufalls-IDs werden dann entfernt und können Ihrem Smartphone nicht mehr - zugeordnet werden. Wenn Sie erneut ein Testergebnis melden möchten, können - Sie in der App erneut installieren und eine neue Einwilligung erteilen. - Alternativ können Sie Ihre eigenen Zufalls-IDs gegebenenfalls im Rahmen der - Kontaktaufzeichnungs-Funktion in den Systemeinstellungen Ihres Smartphones - löschen. Bitte beachten Sie, dass das RKI keine Möglichkeit hat, um Ihre - bereits übermittelten Zufalls-IDs unmittelbar aus den bereitgestellten - Listen und von Smartphones anderer Nutzer zu löschen. + Zum Widerruf Ihrer Einwilligung für die Funktion „Andere warnen“ müssen + Sie die App löschen. Sämtliche Ihrer in der App gespeicherten Zufalls-IDs + werden dann entfernt und können Ihrem Smartphone nicht mehr zugeordnet + werden. Wenn Sie erneut ein Testergebnis melden möchten, können Sie in der + App erneut installieren und eine neue Einwilligung erteilen. Alternativ + können Sie Ihre eigenen Zufalls-IDs gegebenenfalls im Rahmen der + Begegnungsaufzeichnungs-Funktion in den Systemeinstellungen Ihres + Smartphones löschen. Bitte beachten Sie, dass das RKI keine Möglichkeit + hat, um Ihre bereits übermittelten Zufalls-IDs und Ãœbertragungsrisiko-Werte + unmittelbar aus den bereitgestellten Listen und von Smartphones anderer + Nutzer zu löschen. </p> -<h2> +<h1> 12. Ihre weiteren Datenschutzrechte -</h2> +</h1> <p> Soweit das RKI personenbezogene Daten von Ihnen verarbeitet, stehen Ihnen außerdem folgende Datenschutzrechte zu: @@ -751,19 +793,24 @@ die Rechte aus den Artikeln 15, 16, 17, 18, 20 und 21 DSGVO, </li> <li> - das Recht, den behördlichen Datenschutzbeauftragten des RKI zu - kontaktieren und Ihr Anliegen vorzubringen (Artikel 38 Abs. 4 DSGVO) + das Recht, den behördlichen + Datenschutzbeauftragten des RKI + (https://www.rki.de/DE/Content/Institut/OrgEinheiten/Datenschutz/Datenschutz_node.html) + zu kontaktieren und Ihr Anliegen vorzubringen (Artikel 38 Abs. 4 DSGVO) und </li> <li> das Recht, sich bei einer zuständigen Aufsichtsbehörde für den Datenschutz zu beschweren. Dazu können Sie sich entweder an die - zuständige Aufsichtsbehörde an Ihrem Wohnort oder an die am Sitz des - RKI zuständige Behörde wenden. Die zuständige Aufsichtsbehörde für das - RKI ist der Bundesbeauftragte für den Datenschutz und die - Informationsfreiheit, Graurheindorfer Str. 153, 53117 Bonn. + zuständige </li> </ul> +<p> + Aufsichtsbehörde an Ihrem Wohnort oder an die am Sitz des RKI zuständige + Behörde wenden. Die zuständige Aufsichtsbehörde für das RKI ist der + Bundesbeauftragte für den Datenschutz und die Informationsfreiheit, + Graurheindorfer Str. 153, 53117 Bonn. +</p> <p> Es wird darauf hingewiesen, dass die vorgenannten Rechte vom RKI nur erfüllt werden können, wenn die Daten, auf die sich die geltend gemachten @@ -781,5 +828,5 @@ können. </p> <p> - Stand: 12.06.2020 -</p> + Stand: 05.10.2020 +</p> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/assets/privacy_en.html b/Corona-Warn-App/src/main/assets/privacy_en.html index 44edf5a86ba02563c67c8c807de87a5534b86902..b9f7a0f9feeec8d72c569ad83b20cf186fb1c038 100644 --- a/Corona-Warn-App/src/main/assets/privacy_en.html +++ b/Corona-Warn-App/src/main/assets/privacy_en.html @@ -32,30 +32,31 @@ 2. Is using the App voluntary? </h2> <p> - Using the App is entirely voluntary. It is your decision alone whether and - how you use the App. -</p> -<p> - Although installing and using the App is voluntary, if you wish to use the - exposure logging feature you still have to grant the RKI your consent to - let the App process your personal data. You do this by tapping on the - “Enable Exposure Logging†button the first time you open the App. If the - App identifies a potential risk of infection for you, then your data also - includes health data. Your consent is necessary because otherwise the App - will not be able to access your smartphone’s exposure logging feature. You - can, however, use the toggle switch in the App to disable the exposure - logging feature at any time. Doing this will mean that you are unable to - use the full functionality of the App. Separate consent is also required - for the data processing performed for the following features: + Using the App is voluntary. It is therefore your decision alone whether you + install the App and which features you use. Your data (such as information + about your exposures and your risk of infection) will only be generated and + stored on your own smartphone. Your data will only be shared with the RKI, + your smartphone’s operating system or other users if you use one of the + following App features and have consented to the necessary data transfer in + the individual case: </p> <ul> + <li> + Risk identification (see 6 a.) + </li> <li> Registering a test (see 6 b.) </li> <li> - Sharing your test result (see 6 c.). + Warning others (see 6 c.). </li> </ul> +<p> + You are not obliged to use these features. If you decide not to grant the + consent requested by one of these features, or if you subsequently withdraw + your consent, you will not suffer any disadvantages. This will only mean + that you cannot or can no longer use that consent-based feature of the App. +</p> <p> The data processing performed in connection with these features is described in more detail in the following sections. @@ -64,12 +65,12 @@ 3. On what legal basis is your data processed? </h2> <p> - In principle, the RKI will process your personal data only on the basis of - your consent granted pursuant to Article 6(1) Sentence 1(a) and Article - 9(2)(a) of the General Data Protection Regulation (GDPR). If you have - granted your consent, you can withdraw it at any time. Further information - on your right of withdrawal and instructions on how to exercise this right - can be found under 11. + In principle, the RKI will process your data only on the basis of your + consent granted pursuant to Article 6(1) Sentence 1(a) and Article 9(2)(a) + of the General Data Protection Regulation (GDPR). If you have granted your + consent, you can withdraw it at any time. Further information on your right + of withdrawal and instructions on how to exercise this right can be found + under 11. </p> <h2> 4. Who is the App aimed at? @@ -95,28 +96,33 @@ a. Access data </h3> <p> - Access data is generated when you use or enable the following features: + Every time data is exchanged over the internet between the App and the + App’s server system, the server system processes so-called access data. + This is necessary for the App to retrieve current data from the server + system or to transmit certain data stored on the smartphone to the server + system. The following App features require this type of data exchange with + the App’s server system: </p> <ul> <li> - Exposure Logging + Risk identification (retrieving the list of random IDs from users who + have tested positive) </li> <li> - Registering a test + Registering a test (transmitting the test code and retrieving the test + result) </li> <li> - Sharing your test result. + Warning others (transmitting your random IDs). </li> </ul> <p> - Each time data is retrieved from the App’s server system, your IP address - (on the upstream load balancer) is masked and no longer used within the - App’s server system. -</p> -<p> - The following data is also processed: + This access data comprises the following information: </p> <ul> + <li> + IP address + </li> <li> Date and time of retrieval (time stamp) </li> @@ -133,19 +139,26 @@ it is not possible to create a user profile. The IP address will not be saved beyond the end of the period of use. </p> +<p> + In order to prevent unauthorised identification of your data by means of + your IP address when you use the App, the App accesses the server system + via a special access server. This access server then forwards the data + requested or transmitted by the App to the appropriate server, but without + your IP address, so that your IP address is no longer processed within the + server system. +</p> <h3> - b. Contact data + b. Exposure data </h3> <p> - If you enable exposure logging in your smartphone’s operating system, which - serves to record encounters (contacts) with other users, then your - smartphone will continuously send out randomly generated identification - numbers (“<strong>random IDs</strong>â€) via Bluetooth Low Energy, which - other smartphones in your vicinity can receive if exposure logging is also - enabled on them. Your smartphone, in turn, also receives the random IDs of - the other smartphones. In addition to the random IDs received from other - smartphones, your smartphone’s exposure logging functionality records and - stores the following contact data: + If you enable exposure logging in your smartphone’s operating system, then + your smartphone will continuously send out randomly generated + identification numbers (“<strong>random IDs</strong>â€) via Bluetooth Low + Energy, which other smartphones in your vicinity can receive if exposure + logging is also enabled on them. Your smartphone, in turn, also receives + the random IDs of the other smartphones. In addition to the random IDs + received from other smartphones, your smartphone’s exposure logging + functionality records the following exposure data: </p> <ul> <li> @@ -163,36 +176,47 @@ </ul> <p> Your own random IDs and those received from other smartphones as well as - the other contact data (date and time of the contact, duration of the + the other exposure data (date and time of the contact, duration of the contact, signal strength of the contact and encrypted metadata) are - recorded by your smartphone in an exposure log and currently stored there - for 14 days. + recorded by your smartphone’s exposure logging functionality and currently + stored there for 14 days. </p> <p> - The functionality used to record encounters with other users is called - “COVID-19 Exposure Notifications†on Android smartphones and “COVID-19 - Exposure Logging†on iPhones. Please note that this exposure logging - functionality is not part of the App, but an integral part of your - smartphone's operating system. This means that the exposure logging - functionality is provided to you by Apple (iPhones) or Google (Android - smartphones) and is subject to these companies’ respective privacy - policies. The RKI has no influence on data processing performed by the - operating system in connection with exposure logging. + The exposure logging functionality is called “COVID-19 Exposure + Notifications†on Android smartphones and “Exposure Notification†on + iPhones. Please note that this functionality is not part of the App, but an + integral part of your operating system. The exposure logging functionality + is therefore provided to you by Apple (iPhones) or Google (Android + smartphones). Accordingly, any data processing performed by the operating + system using this exposure logging functionality is subject to these + companies’ respective privacy policies. The RKI is not responsible for this + and has no influence on it. </p> <p> - More information about the exposure logging functionality on Android - smartphones is available at: - https://support.google.com/android/answer/9888358?hl=en. + More information about the exposure logging functionality is available from + the providers: </p> +<ul> + <li> + Information from Google for Android smartphones: + https://support.google.com/android/answer/9888358?hl=en + </li> + <li> + Information from Apple for iPhones can be found on your device under “Settings†+ > "Exposure Notification“ and the link “How Exposure Notification + works…â€. + </li> +</ul> <p> - More information about Apple’s exposure logging functionality can be found - in your iPhone’s settings under “Privacy†> “Health†> "COVID-19 - Exposure Loggingâ€. Please note that the exposure logging functionality is - only available if iOS version 13.5 or higher is installed on your iPhone. + Please note that the actual named us, operating steps, and settings options + for the exposure logging functionality on your smartphone may differ from + those described in this privacy notice depending on the version and + configuration of your operating system. </p> <p> - The App will only process the contact data generated and stored by your - smartphone if the App’s exposure logging feature is enabled. + The App will only process the exposure data generated and stored by your + operating system’s exposure logging functionality if the App’s risk + identification feature is enabled. </p> <h3> c. Health data @@ -204,67 +228,84 @@ risk that the person has been infected with the coronavirus). </p> <p> - The following cases involve processing health data: + The App will process your health data if you use the following features: </p> <ul> <li> - If the exposure logging feature detects that you may have been in - contact with a person who has been infected with the coronavirus. + Risk identification, as soon as a potential exposure is detected (i.e. + you may have been in contact with a person who has been infected with + the coronavirus) </li> <li> - If you register your test. + Registering a test (test status and test result) </li> <li> - If you share a positive test result. + Warning others (test result, and possibly information about symptoms + and their onset). </li> </ul> <h2> 6. App features </h2> <h3> - a. Exposure Logging + a. Risk identification </h3> <p> - The App’s core functionality is exposure logging. This serves to track + The App’s core functionality is risk identification. This serves to track possible contacts with other users of the App who are infected with the - coronavirus, to evaluate the risk that you yourself have been infected, and - – based on the risk identified – to provide you with health advice and + coronavirus (possible exposures), to evaluate the risk that you yourself + have been infected, and to provide you with health advice and recommendations for what to do next. </p> <p> - If you enable the exposure logging feature, then several times a day while - the App runs in the background (or when you tap on “Updateâ€), the App will - retrieve a list from the App’s server system of random IDs from users who - have tested positive and shared their own random IDs. The App shares these - random IDs with your smartphone’s exposure logging functionality, which - then compares them with the random IDs stored in your smartphone’s exposure - log. If your smartphone’s exposure logging functionality detects a match, - it transfers the contact data (date, duration, signal strength) to the App, - but not the random ID of the contact in question. + If you enable the risk identification feature, then several times a day + while the App runs in the background (or when you tap on “Updateâ€), the App + will retrieve a list from the App’s server system of random IDs, each with + a transmission risk value (a numerical value from 1–8) from users who have + tested positive and provided their own random IDs via the App using the + feature for warning others. The transmission risk value is an estimate of + the likelihood of infection on the day of the possible exposure in + question. Since infectiousness (i.e. the risk of infection for those who + come into contact with a person who has tested positive) is currently + believed to depend on the duration and course of the infection, it can be + taken into account, for example, that the more time has passed since the + onset of symptoms, the lower the risk of infection on the day of a possible + exposure. +</p> +<p> + The App shares these random IDs with your smartphone’s exposure logging + functionality, which then compares them with the random + IDs logged by the exposure logging functionality. If your smartphone’s + exposure logging functionality detects a match, it transfers the exposure + data (date, duration, signal strength) to the App, but not the random ID of + the contact in question. +</p> +<p> + In the event of a potential exposure, the App analyses the exposure data + provided by the exposure logging functionality as well as the transmission + risk value in order to determine your individual risk of infection. </p> <p> - In the event of a contact, the App analyses the contact data provided by - the exposure logging functionality in order to determine your individual - risk of infection. The evaluation algorithm which determines how the - contact data is interpreted (for example, how the duration of a contact - influences the risk of infection) is based on current scientific findings. - To account for new findings as and when they arise, the RKI can update the - evaluation algorithm by adjusting its settings. The settings for the - evaluation algorithm are sent to the App together with the list of random - IDs of infected users. + The evaluation algorithm which determines how the exposure data and the + transmission risk value are interpreted (for example, how the duration of a + contact influences the risk of infection) is based on current scientific + findings. To account for new findings as and when they arise, the RKI can + update the evaluation algorithm by adjusting its settings. The settings for + the evaluation algorithm are sent to the App together with the list of + random IDs of infected users. </p> <p> The identification of your risk of infection is only carried out locally on - your smartphone, meaning that the data is processed offline. Once - identified, the risk of infection is also only stored in the App and is not - passed on to any other recipients (including the RKI, Apple, Google and - other third parties). + your smartphone, meaning that the data is processed offline without + accessing the App’s server system. Once identified, the risk of infection + is also only calculated in the App and is not passed on to any other + recipients (including the RKI, Apple, Google and other third parties). </p> <p> - The legal basis for the processing of your access data, contact data and, - if applicable, health data (if the App determines that you may have been - infected) described above is your consent which you gave when enabling the - exposure logging feature. + The legal basis for the processing of your access data, exposure data and, + if applicable, health data (if the App determines a possible exposure) + described above is your consent which you gave when enabling the risk + identification feature. </p> <h3> b. Registering a test @@ -320,7 +361,7 @@ number. The test result database is operated by the RKI on a special server within the App’s server system. Based on the code number contained in the QR code issued to you, the testing laboratory also generates the hashed - code number using the same mathematical procedure as the App. + code number using the same mathematical procedure as the App.<u></u> </p> <p> <u>Retrieval of the test result</u> @@ -351,22 +392,34 @@ above is your consent to using the test registration feature. </p> <h3> - c. Sharing your test result + c. Warning others </h3> <p> - If you use the feature for sharing your test result in order to warn other - users, the App will transfer the random IDs generated and stored by your - smartphone from the last 14 days and the TAN to the App’s server system. - The server system first checks whether the TAN is valid and then adds your - random IDs to the list of random IDs of users who have shared a positive - test result. Your random IDs can now be downloaded by other users as part - of the exposure logging process. + If you use the feature for warning others, the App will transfer the random + IDs (including the respective transmission risk values) generated and + stored by your smartphone from the last 14 days and the TAN to the App’s + server system. The server system first checks whether the TAN is valid and + then adds your random IDs to the list of random IDs of users who have + provided a positive test result. Your random IDs can now be downloaded by + other users as part of the risk identification process. +</p> +<p> + The information about symptoms and symptom onset requested by the feature + for warning others is optional and not required to warn other users. + However, this information can help to calculate more accurately the risk of + infection to other users you have encountered. If you can’t or don’t want + to answer some or all of the questions, just select “no answerâ€. In this + case, the transmission risk values determined by the App and assigned to + your random IDs will be based on the time that has passed since your test + result was retrieved, assuming an average infection – meaning that the more + time has passed since using a random ID, the lower its transmission risk + value will be. </p> <p> <u>If you have not retrieved your test result in the App:</u> </p> <p> - Even if you have not retrieved a positive test result in the app, you can + Even if you have not retrieved a positive test result in the App, you can share the test result via the App to warn other users. In this case, the App prompts you to enter a so-called TeleTAN, which acts as a TAN. </p> @@ -375,7 +428,7 @@ 7540002. The operator will first ask you some questions over the phone to check the plausibility of your call. These questions serve to prevent fraudulent reports of infections and any resulting incorrect warnings and - risk status. Once you have answered these questions sufficiently, you will + risk levels. Once you have answered these questions sufficiently, you will be asked for your mobile/telephone number. This is so that you can be called back later and given a TeleTAN to enter in the App. Your mobile/telephone number will only be temporarily stored for this purpose @@ -393,7 +446,7 @@ <p> The legal basis for this processing of your access data and health data (random IDs, test result, TAN and, if applicable, TeleTAN) is your consent - to using the feature for sharing your test result. + to using the feature for warning others. </p> <h3> d. Using the App for information purposes only @@ -402,9 +455,10 @@ As long as you use the App for information purposes only, i.e. do not use any of the App features mentioned above and do not enter any data, then processing only takes place locally on your smartphone and no personal data - is generated. Websites linked in the app, such as www.bundesregierung.de, - will open in your smartphone’s standard browser. The data processed here - depends on the browser used, your browser settings, and the data processing + is generated. Depending on your operating system, websites linked in the + App, such as www.bundesregierung.de, will + open in your smartphone’s standard browser (Android smartphones) or within + the App (iPhone). The data processed here depends on the data processing practices of the website you are visiting. </p> <h2> @@ -428,9 +482,9 @@ </li> </ul> <p> - The App requires an internet connection for the exposure logging feature, - and so that it can receive and transmit test results, so that it can - communicate with the App’s server system. + The App requires an internet connection for the risk identification + feature, and so that it can receive and transmit test results, so that it + can communicate with the App’s server system. </p> <ul> <li> @@ -476,10 +530,10 @@ </li> </ul> <p> - The App’s exposure logging feature requires this functionality. Otherwise, - no exposure log with the random IDs of your contacts will be available. The - functionality must be enabled within the App to allow the App to access the - exposure log. + The App’s risk identification feature requires this functionality. + Otherwise, no exposure log with the random IDs of your contacts will be + available. The functionality must be enabled within the App to allow the + App to access the exposure log. </p> <ul> <li> @@ -521,14 +575,14 @@ </p> <ul> <li> - COVID-19 Exposure Logging + Exposure Notification </li> </ul> <p> - The App’s exposure logging feature requires this functionality, otherwise - no exposure log with the random IDs of your contacts will be available. The - functionality must be enabled within the App to allow the App to access the - exposure log. + The App’s risk identification feature requires this functionality, + otherwise no exposure log with the random IDs of your contacts will be + available. The functionality must be enabled within the App to allow the + App to access the exposure log. </p> <ul> <li> @@ -559,7 +613,7 @@ the App features: </p> <h3> - a. Exposure Logging + a. Risk identification </h3> <ul> <li> @@ -568,18 +622,18 @@ deleted from your smartphone’s exposure log after 14 days. </li> <li> - The RKI has no way of influencing the deletion of contact data in your - smartphone’s exposure log (including your own random IDs) and contact - data on other smartphones, as this functionality is provided by Apple - or Google. In this case, the deletion depends on what Apple or Google - has determined. Currently, the data is automatically deleted after 14 - days. It may also be possible, using the functionality provided by - Apple and Google, to manually delete data in your device’s system - settings. + The RKI has no way of influencing the deletion of exposure data in the + exposure log of your smartphone’s exposure logging functionality + (including your own random IDs) and exposure data on other smartphones, + as this functionality is provided by Apple or Google. In this case, the + deletion depends on what Apple or Google has determined. Currently, the + data is automatically deleted after 14 days. It may also be possible, + using the functionality provided by Apple and Google, to manually + delete data in your device’s system settings. </li> <li> - The risk status displayed in the App will be deleted as soon as a new - risk status has been determined. A new risk status is usually + The risk of infection displayed in the App will be deleted as soon as a + new risk level has been determined. A new risk level is usually determined after the App has received a new list of random IDs. </li> </ul> @@ -602,18 +656,23 @@ The token stored on the server system will be deleted after 21 days. </li> <li> - The token stored in the app will be deleted from the smartphone after - the App is deleted or after using the feature for sharing the test - result. + The token stored in the App will be deleted from the smartphone after + the App is deleted or after using the feature for warning others. </li> </ul> <h3> - c. Sharing your test result + c. Warning others </h3> <ul> <li> - Your smartphone’s own random IDs which are shared in the App will be - deleted from the server system after 14 days. + Your smartphone’s own random IDs and transmission risk values which + are provided via the App will be deleted from the server system after + 14 days. + </li> + <li> + Your information about symptoms and the onset of symptoms will be + deleted from the App immediately after the random IDs and transmission + risk values have been provided. </li> <li> The copy of the TAN stored on the server system will be deleted after @@ -621,11 +680,11 @@ </li> <li> The TAN stored in the App will be deleted after the test result has - been shared. + been provided. </li> <li> The TeleTAN stored in the App will be deleted after the test result has - been shared. + been provided. </li> <li> The TeleTAN stored on the server system will be deleted after 21 days. @@ -639,22 +698,23 @@ </li> <li> The token stored in the App will be deleted after the test result has - been shared. + been provided. </li> </ul> <h2> 9. Who will receive your data? </h2> <p> - If you share a test result to warn other users, your random IDs from the - last 14 days will be passed on to the App on other users’ smartphones. + If you provide a test result by using the feature for warning other users, + your random IDs and their transmission risk values from the last 14 days + will be passed on to the App on other users’ smartphones. </p> <p> - The RKI has commissioned T-Systems International GmbH and SAP Deutschland - SE & Co. KG to operate and maintain part of the technical - infrastructure of the App (e.g. server system, hotline), meaning that these - two companies are processors under data protection law and acting on the - RKI’s behalf (Article 28 GDPR). + The RKI has commissioned Deutsche Telekom AG and SAP Deutschland SE & + Co. KG to operate and maintain part of the technical infrastructure of the + App (e.g. server system, hotline), meaning that these two companies are + processors under data protection law and acting on the RKI’s behalf + (Article 28 GDPR). </p> <p> Otherwise, the RKI will only pass on personal data collected in connection @@ -679,10 +739,10 @@ affect the lawfulness of the processing before the withdrawal. </p> <p> - To withdraw your consent to the exposure logging feature, you can disable - the feature using the toggle switch in the App or delete the App. If you - decide to use the exposure logging feature again, you can toggle the - feature back on or reinstall the App. + To withdraw your consent to the risk identification feature, you can + disable the feature using the toggle switch in the App or delete the App. + If you decide to use the risk identification feature again, you can toggle + the feature back on or reinstall the App. </p> <p> To withdraw your consent to the test registration feature, you can delete @@ -693,14 +753,15 @@ consent again. </p> <p> - To withdraw your consent to the sharing of your test result, you must - delete the App. All of your random IDs stored in the App will then be - removed and can no longer be assigned to your smartphone. If you wish to - report another test result, you can reinstall the App and grant your - consent again. Alternatively, you may be able to delete your own random IDs - in the exposure log in your smartphone’s system settings. Please note that, - once transmitted, the RKI has no way of deleting your random IDs from the - lists and from other users’ smartphones. + To withdraw your consent to the feature for warning others, you must delete + the App. All of your random IDs stored in the App will then be removed and + can no longer be assigned to your smartphone. If you wish to report another + test result, you can reinstall the App and grant your consent again. + Alternatively, you may be able to delete your own random IDs in the + exposure logging functionality in your smartphone’s system settings. Please + note that, once transmitted, the RKI has no way of deleting your random IDs + and transmission risk values from the lists provided and from other users’ + smartphones. </p> <h2> 12. Your other rights under data protection law @@ -714,8 +775,9 @@ the rights under Articles 15, 16, 17, 18, 20 and 21 GDPR, </li> <li> - the right to contact the official RKI data protection officer and raise - your concerns (Article 38(4) GDPR) and + the right to contact the official RKI data protection officer + (https://www.rki.de/DE/Content/Institut/OrgEinheiten/Datenschutz/Datenschutz_node.html) + and raise your concerns (Article 38(4) GDPR) and </li> <li> the right to lodge a complaint with a competent data protection @@ -741,5 +803,5 @@ you which is not available to the RKI. </p> <p> - Last amended: 12 June 2020 + Last amended: 05 October 2020 </p> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/assets/privacy_tr.html b/Corona-Warn-App/src/main/assets/privacy_tr.html index c92bb2077e79d129f06a306a75f3b0e8b298dc32..b421d7df2c895672d1aadedaab6d117d5dca8f05 100644 --- a/Corona-Warn-App/src/main/assets/privacy_tr.html +++ b/Corona-Warn-App/src/main/assets/privacy_tr.html @@ -10,126 +10,128 @@ koruma haklarına sahip olduÄŸunuzu öğrenirsiniz. </p> <p> - Tüm kullanıcıların anlayabilmesi için bu veri gizliliÄŸi beyanını mümkün - olduÄŸu kadar teknik olmayan ve basit ÅŸekilde açıklamaya çalıştık. + Bu veri gizliliÄŸi beyanının tüm kullanıcılar tarafından anlaşılabilmesi + için basit ve mümkün oldukça teknik olmayan ÅŸekilde anlatmaya çalıştık. </p> -<h2> +<h1> 1. Bu uygulamayı size kim sunuyor? -</h2> +</h1> <p> Corona-Warn-App (bundan böyle "<strong>UYGULAMA</strong>") Nordufer 20, 13353 Berlin adresinde bulunan Robert Koch-Institut (bundan böyle "<strong>RKI</strong>") - sunuyor. -</p> -<p> - RKI, veri koruma hukuku açısından da UYGULAMA kullanıcılarının kiÅŸisel - verilerinin iÅŸlenmesinden sorumludur. + sunuyor. RKI, veri koruma hukuku açısından da + UYGULAMA kullanıcılarının kiÅŸisel verilerinin iÅŸlenmesinden sorumludur. </p> <p> - Yukarıda anılan adres (zu Händen „Behördlicher Datenschutzbeauftragter“ - – "Resmi Veri Koruma Görevlisi" dikkatine) ve datenschutz@rki.de - e-posta adresi aracılığıyla RKI'nin veri koruma görevlisine - ulaÅŸabilirsiniz. + Yukarıda anılan adres (zu Händen "Behördlicher Datenschutzbeauftragter" – + "Resmi Veri Koruma Görevlisi" dikkatine) ve datenschutz@rki.de e-posta + adresi aracılığıyla RKI'nin veri koruma görevlisine ulaÅŸabilirsiniz. </p> -<h2> +<h1> 2. UYGULAMA'nın kullanımı isteÄŸe mi baÄŸlı? -</h2> -<p> - UYGULAMA'nın kullanımı tamamen isteÄŸe baÄŸlıdır. Bu sebeple, UYGULAMA'yı - kullanıp kullanmamaya ve nasıl kullanacağınıza bizzat kendiniz karar - verirsiniz. -</p> +</h1> <p> - UYGULAMA'nın yüklenmesi ve kullanımı tamamen isteÄŸe baÄŸlı olsa da, - UYGULAMA'yı ilk açtığınızda, UYGULAMA'nın maruz kalma günlüğü için kiÅŸisel - verilerinizi iÅŸleyebilmesi için "Maruz kalma günlüğünü etkinleÅŸtir" - düğmesine tıklayarak RKI'ye onayınızı beyan etmelisiniz. UYGULAMA sizin - için bir enfeksiyon riski tespit ederse, verileriniz, saÄŸlık verilerini de - oluÅŸturur. Aksi takdirde UYGULAMA akıllı telefonunuzun temas kayıt iÅŸlevine - eriÅŸemeyeceÄŸi için, Kabul etmeniz gereklidir. Maruz kalma günlüğünü - dilediÄŸinizde UYGULAMA içerisindeki butondan devre dışı bırakabilirsiniz. - Bu durumda UYGULAMA'nın tüm iÅŸlevlerini kullanamazsınız. AÅŸağıdaki - iÅŸlevlerin veri iÅŸlemesi için özel onayınız ayrıca gereklidir: + UYGULAMA'nın kullanımı, isteÄŸe baÄŸlıdır. Bu yüzden, Uygulamayı yükleyip + yüklememe ve kullanacağınız iÅŸlevleri seçme kararı size aittir. Verileriniz + (örneÄŸin, temas ve enfeksiyon riskleriniz) sadece kendi akıllı + telefonunuzda oluÅŸturulur ve saklanır. Verileriniz RKI, akıllı + telefonunuzun iÅŸletim sistemi ya da diÄŸer kullanıcılara sadece aÅŸağıdaki + uygulama iÅŸlevlerinden birini kullanmanız ve gerekli veri aktarımına izin + vermeniz halinde aktarılır: </p> <ul> + <li> + Maruz kalma (6 a numaralı baÅŸlığa bakınız) + </li> <li> Test kaydetme (6 b numaralı baÅŸlığa bakınız) </li> <li> - Test sonucunuzu paylaÅŸma (6 c numaralı baÅŸlığa bakınız) + DiÄŸerlerini uyar (6 c numaralı baÅŸlığa bakınız) </li> </ul> +<p> + Bu iÅŸlevleri kullanmak zorunda deÄŸilsiniz. Bu iÅŸlevlerin gerektirdiÄŸi + onayları vermek istemez ya da bunları sonradan geri almayı isterseniz, bu + durum sizin açınızdan dezavantaj oluÅŸturmaz. Onaya tabi uygulama + iÅŸlevlerini dilerseniz kullanmayabilirsiniz. +</p> <p> Bu iÅŸlevler çerçevesinde yapılan veri iÅŸleme aÅŸağıdaki bölümlerde ayrıntılı ÅŸekilde açıklanmıştır. </p> -<h2> +<h1> 3. Verileriniz hangi hukuki dayanakla iÅŸlenir? -</h2> +</h1> <p> RKI, esas itibariyle Avrupa BirliÄŸi Genel Veri Koruma Tüzüğü (GDPR) madde 6 paragraf 1 cümle 1 bent a ve madde 9 paragraf 2 bent a uyarınca kiÅŸisel - verilerinizi sadece verdiÄŸiniz onaya dayanarak iÅŸler. VerdiÄŸiniz onayı, - dilediÄŸinizde geri alabilirsiniz. Cayma hakkınız hakkındaki diÄŸer bilgileri - ve bu hakkı nasıl kullanacağınız hakkındaki talimatları madde 11'de - bulabilirsiniz. + verilerinizi sadece verdiÄŸiniz onaya dayanarak iÅŸler. VerdiÄŸiniz onayı + dilediÄŸinizde geri alabilirsiniz. Geri alma hakkı ve bu hakkı nasıl + kullanacağınıza iliÅŸkin açıklamalar hakkında diÄŸer bilgileri 11 numaralı + baÅŸlıkta bulabilirsiniz. </p> -<h2> +<h1> 4. UYGULAMA kimlere yöneliktir? -</h2> +</h1> <p> UYGULAMA, Almanya'da ikamet eden ve en az 16 yaşında olan kiÅŸilere yöneliktir. </p> -<h2> +<h1> 5. Hangi kiÅŸisel veriler iÅŸlenir? -</h2> +</h1> <p> UYGULAMA, mümkün olduÄŸu kadar az sayıda kiÅŸisel veri iÅŸlenecek ÅŸekilde - tasarlanmıştır. Bununla kastedilen, örneÄŸin UYGULAMA RKI ya da diÄŸer + tasarlanmıştır. Bununla kastedilen, UYGULAMA’nın, RKI ya da diÄŸer kullanıcıların kimliÄŸinizi, saÄŸlık durumunuzu ya da konumunuzu öğrenmesine - imkan verecek bilgileri toplamadığıdır. UYGULAMA ayrıca, kullanım + imkan verecek bilgileri toplamamasıdır. UYGULAMA ayrıca, kullanım davranışınızın izleme araçları (Tracking Tool) tarafından tespit ya da analiz edilmesine imkan tanımaz. </p> <p> UYGULAMA tarafından iÅŸlenen veriler aÅŸağıdaki kategorilere ayrılır: </p> +<h2> + a. EriÅŸim verileri +</h2> <p> - <strong>a. EriÅŸim verileri</strong> -</p> -<p> - AÅŸağıdaki iÅŸlevleri kullanıyorsanız veya etkinleÅŸtiriyorsanız, eriÅŸim - verileri oluÅŸur: + UYGULAMA ile UYGULAMA'nın sunucu sistemi arasında internet aracılığıyla + yapılan her veri alışveriÅŸinde, sunucu sistemi tarafından eriÅŸim verileri + iÅŸlenir. Bu durum, UYGULAMA'nın sunucu sisteminden güncel verileri + çağırabilmesi ya da akıllı telefonda saklanan belirli verileri sunucu + sistemine aktarabilmesi için gereklidir. AÅŸağıdaki UYGULAMA iÅŸlevleri, + UYGULAMA'nın sunucu sistemi ile bu tür bir veri alışveriÅŸi yapmasını + gerektirir: </p> <ul> <li> - Maruz kalma günlüğü + Maruz kalma (test sonucu pozitif kullanıcıların rastgele kimliklerinden + oluÅŸan listenin çaÄŸrılması) </li> <li> - Test kaydetme + Test kaydetme (test tanıtma sayısının iletilmesi ve test sonucunun + çaÄŸrılması) </li> <li> - Test sonucunuzu paylaÅŸma + DiÄŸerlerini uyar (rastgele kimliÄŸinizin iletilmesi) </li> </ul> <p> - UYGULAMA'nın sunucu sisteminden her veri çağırdığınızda, IP adresiniz - (sunucular arası yükü dengeleyen Load Balancer'da) gizlenir ve sonraki - iÅŸlemlerde UYGULAMA'nın sunucu sistemleri dahilinde iÅŸlenmez. -</p> -<p> - Ek olarak, aÅŸağıdaki veriler iÅŸlenir: + EriÅŸim verileri, aÅŸağıdaki verileri kapsar: </p> <ul> + <li> + IP adresi + </li> <li> Çağırmanın tarih ve saati (zaman kaydı) </li> <li> - taşınan veri miktarı (örn., paket uzunluÄŸu) + iletilen veri miktarı (örn., paket uzunluÄŸu) </li> <li> - BaÅŸarılı çağırmalar hakkında bildirim + BaÅŸarılı çağırma hakkında bildirim </li> </ul> <p> @@ -139,143 +141,156 @@ saklanmaz. </p> <p> - <strong>b. Maruz kalma verileri</strong> + Kullanım esnasında IP adresiniz aracılığıyla verilerinizin yetkisiz ÅŸekilde + sınıflandırılmasını engellemek için UYGULAMA, sunucu sistemine özel bir + baÅŸlangıç sunucusu aracılığıyla eriÅŸir. Bu baÅŸlangıç sunucusu, UYGULAMA + tarafından istenen ya da iletilen verileri, IP adresiniz olmadan söz konusu + yetkili sunucuya ilettiÄŸi için bu veriler artık sunucu sisteminde + iÅŸlenemez. </p> +<h2> + b. Temas verileri +</h2> <p> - Akıllı telefonunuzda, iÅŸletim sistemindeki diÄŸer kullanıcılar için temas - kaydı iÅŸlevini etkinleÅŸtirirseniz, akıllı telefonunuz yakın çevrenizdeki - temas kaydı etkinleÅŸtirilmiÅŸ akıllı telefonlardan alınan rastgele - kimlikleri (bundan böyle: "<strong>Rastgele Kimlikler"</strong>) olarak da - anılan, sürekli rastgele oluÅŸturulan tanıtıcı numaraları Bluetooth Low - Energy aracılığıyla gönderir. Aynı ÅŸekilde, akıllı telefonunuz da diÄŸer - akıllı telefonların rastgele kimliklerini alır. DiÄŸer akıllı telefonlardan - alınan rastgele kimliklere, akıllı telefonunuzun temas kayıt iÅŸlevi ve - ayrıca aÅŸağıdaki maruz kalma verileri kaydedilir ve saklanır: + Akıllı telefonunuzda, iÅŸletim sistemindeki maruz kalma günlüğünü + etkinleÅŸtirirseniz, akıllı telefonunuz temas kaydı etkin olan yakınınızdaki + diÄŸer akıllı telefonlardan alınan, rastgele kodlar olarak da tanımlanan, + sürekli ve rastgele oluÅŸturulan tanıtma sayılarını (bundan sonra: "<strong>Rastgele + Kimlikler"</strong>) Bluetooth Low Energy aracılığıyla + gönderir. Aynı ÅŸekilde, akıllı telefonunuz da diÄŸer akıllı telefonların + rastgele kimliklerini alır. DiÄŸer akıllı telefonlardan alınan rastgele + kimliklere, akıllı telefonunuzun temas kayıt iÅŸlevinden ek olarak aÅŸağıdaki + temas verileri tanımlanır: </p> <ul> - <li> - Temasın tarihi ve saati - </li> - <li> - Temasın süresi - </li> - <li> - Temasın Bluetooth sinyal gücü - </li> - <li> - ÅžifrelenmiÅŸ meta veriler (protokol sürümü ve verici gücü). - </li> + <li>Temasın tarihi ve saati</li> + <li>Temasın süresi</li> + <li>Temasın Bluetooth sinyal gücü</li> + <li>Åžifreli meta veriler (protokol sürümü ve gönderme gücü)</li> </ul> <p> - Akıllı telefonunuzun kendi rastgele kimliÄŸi ve diÄŸer akıllı telefonlardan - alınan rastgele kimlikler ve diÄŸer maruz kalma verileri (temasın tarihi ve - saati, temasın süresi, temasın sinyal gücü ve ÅŸifrelenmiÅŸ meta veriler) - akıllı telefonunuz tarafından temas kayıt iÅŸlevinin temas protokolünde - kaydedilir ve halihazırda 14 gün boyunca saklanır. + Akıllı telefonun kendi ve diÄŸer akıllı telefonlardan aldığı rastgele + kimlikler ve diÄŸer temas verileri (temasın tarihi ve saati, temasın süresi, + temasın sinyal gücü ve ÅŸifreli meta veriler) akıllı telefonunuzun temas + kayıt iÅŸlevi tarafından toplanır ve orada 14 gün boyunca saklanır. </p> <p> - Android akıllı telefonlarda bu temas kayıt iÅŸlevinin adı "COVID-19 Temas - Bildirimleri Sistemi" ve iPhone'larda "COVID-19'a Maruz Kalma Günlüğü"dür. - Bu temas kayıt iÅŸlevlerinin UYGULAMA'nın parçası olmadığını, aksine akıllı - telefonunuzun iÅŸletim sisteminin ayrılmaz bir parçası olduÄŸunu - hatırlatırız. Temas kayıt iÅŸlevi bu yüzden size Apple (iPhone) ve Google - (Android akıllı telefonlar) tarafından hazırlanır ve bu yüzden bu - iÅŸletmelerin veri koruma kurallarına tabidir. Temas kayıt iÅŸlevi - çerçevesindeki iÅŸletim sistemindeki veri iÅŸleme, RKI'nin etki alanının - dışında kalır. + Temas kayıt iÅŸlevi, Android akıllı telefonlarda "COVID 19 bildirimleri" ve + iPhone akıllı telefonlarda "temas bildirimleri" olarak anılır. Bu + iÅŸlevlerin, UYGULAMA'nın parçası olmadığını, aksine iÅŸletim sisteminizin + ayrılmaz bir parçası olduÄŸunu hatırlatırız. Bu yüzden, temas kayıt + iÅŸlevlerinin saÄŸlayıcısı Apple (iPhone) ve Google (Android akıllı + telefonlar)'dır. Temas kayıt iÅŸlevleri aracılığıyla iÅŸletim sistemi + tarafından veri iÅŸleme, bu ÅŸirketlerin ilgili veri koruma kurallarına + tabidir ve RKI'nin sorumluluk ve etki alanının haricindedir. </p> <p> - Android akıllı telefonların temas kayıt iÅŸlevine iliÅŸkin diÄŸer bilgilere ÅŸu - adresten eriÅŸebilirsiniz: - https://support.google.com/android/answer/9888358?hl=de. + Bu konuya iliÅŸkin diÄŸer bilgileri, temas kayıt iÅŸlevinin saÄŸlayıcıları + sunar: </p> +<ul> + <li> + Android akıllı telefonlar için Google bilgileri: + https://support.google.com/android/answer/9888358?hl=de + </li> + <li> + iPhone için Apple bilgilerini aygıtınızda "Ayarlar" > "Temas + bildirimleri" kısmında "Temas bildirimleri bu ÅŸekilde çalışır …" + baÄŸlantısında bulabilirsiniz. + </li> +</ul> <p> - Apple'ın temas kayıt iÅŸlevi ile ilgili diÄŸer bilgilere, iPhone'unuzun - ayarlar kısmındaki "Gizlilik" > "SaÄŸlık" > "COVID-19'a Maruz Kalma - Günlüğü" kısmından eriÅŸebilirsiniz. Lütfen dikkat ediniz: Temas kayıt - iÅŸlevini, iPhone'unuzda Sürüm 13.5 ve üstü iOS iÅŸletim sistemleri yüklüyse, - kullanabilirsiniz. + Tanımlamalar, iÅŸletim adımları ve temas kayıt iÅŸlevi ayar olanaklarının, + iÅŸletim sisteminizin sürüm ve yapılandırmasına göre bu veri gizliliÄŸi + beyanındaki ÅŸeklinden farklı olabileceÄŸini dikkate alın. </p> <p> - Akıllı telefonun oluÅŸturduÄŸu ve sakladığı maruz kalma verileri UYGULAMA - tarafından, sadece maruz kalma günlüğü etkinleÅŸtirildiyse iÅŸlenir. + Ä°ÅŸletim sisteminizin oluÅŸturduÄŸu ve sakladığı temas verileri, maruz kalma + günlüğünün etkinleÅŸtirilmiÅŸ olması halinde UYGULAMA tarafından iÅŸlenir. </p> -<h3> - a. SaÄŸlık verileri -</h3> +<h2> + c. SaÄŸlık verileri +</h2> <p> SaÄŸlık verileri, belirli bir kiÅŸinin saÄŸlık durumuna iliÅŸkin bilgiler içeren tüm verilerdir. Bunlara sadece eski ve güncel hastalıklara iliÅŸkin bilgiler deÄŸil, ayrıca bir kiÅŸinin hastalık risklerine iliÅŸkin bilgiler de - dahildir (örn. kiÅŸiye koronavirüsün bulaÅŸma riski). + dahildir (örn. kiÅŸiye koronavirüsün bulaÅŸ riski). </p> <p> - AÅŸağıdaki durumlarda saÄŸlık verilerinin iÅŸlenmesi söz konusudur: + AÅŸağıdaki iÅŸlevler kullanılırken UYGULAMA, saÄŸlık verilerinizi iÅŸler: </p> <ul> - <li> - Maruz kalma günlüğü, koronavirüs ile enfekte olan kiÅŸi ile olası - temasını olduÄŸunu tespit ettiÄŸinde. + <li>Maruz kalma tespit edilir edilmez (örn. koronavirüs bulaÅŸmış bir kiÅŸi ile + olası temasınız olduysa) maruz kalma günlüğü. </li> - <li> - Bir testi kaydettiÄŸinizde. - </li> - <li> - Pozitif test sonucu paylaÅŸtığınızda. + <li>Test kaydetme (test durumu ve test sonucu)</li> + <li>DiÄŸerlerini uyar (test sonucu, bulgu ve bulgu baÅŸlangıcına iliÅŸkin güncel + bilgiler) </li> </ul> -<h2> +<h1> 6. UYGULAMA'nın iÅŸlevleri -</h2> -<h3> +</h1> +<h2> a. Maruz kalma günlüğü -</h3> +</h2> <p> - Maruz kalma günlüğü, UYGULAMA'nın çekirdek iÅŸlevidir. UYGULAMA'nın amacı - koronavirüs ile enfekte olmuÅŸ diÄŸer kullanıcılara olası bir teması takip - etmek, bu yüzden sizin için oluÅŸan enfeksiyon riskini deÄŸerlendirmek ve - sizin için tespit edilen toplam risk puanına göre davranış ve saÄŸlık - talimatları hazırlamaktır. + Maruz kalma günlüğü, UYGULAMA'nın temel iÅŸlevidir. Koronavirüs bulaÅŸmış + diÄŸer kullanıcılar ile olası temasları (maruz kalmalar) takip etmeye, + enfeksiyon riskini deÄŸerlendirmeye ve sizin için davranış ve saÄŸlık + tavsiyeleri vermeye yarar. </p> <p> - Maruz kalma günlüğünü etkinleÅŸtirdiÄŸinizde, UYGULAMA arka plan iÅŸletimdeki - sunucu sistemlerinden test sonuçları pozitif çıkmış ve kendi rastgele - kimliklerini paylaÅŸmış olan diÄŸer kullanıcıların rastgele kimliklerinden - oluÅŸan listeyi günde bir kaç defa (ya da "Güncelleme" düğmesini - tıklattığınızda) çağırır. UYGULAMA, akıllı telefonunuzun temas protokolünde - saklanan rastgele kimlikleri ile karşılaÅŸtıracağı rastgele kimlikleri, - akıllı telefonunuzun temas kayıt iÅŸlevine iletir. Akıllı telefonunuzun - temas kayıt iÅŸlevi bir eÅŸleÅŸtirme belirlerse, UYGULAMA'ya maruz kalma - verilerini (tarih, süre, sinyal gücü) verir, ancak ilgili temasın rastgele - kimliÄŸini vermez. + Maruz kalma günlüğünü etkinleÅŸtirirseniz, UYGULAMA, UYGULAMA'nın arka + plandaki sunucu sistemlerinden günde birden fazla defa (ya da "Güncelle" + seçeneÄŸine tıklarsanız) taşıma riski deÄŸerleri (1 ile 8 arasında sayısal + deÄŸerler) ve rastgele kimlikleri ile test sonucu pozitif olan ve kendi + rastgele kimliklerini "DiÄŸerlerini uyar" iÅŸlevi ile UYGULAMA üzerinden + sunan kullanıcıların listesini çağırır. Taşıma riski deÄŸeri, ilgili maruz + kalmanın bulaÅŸma olasılığının seviyesinin deÄŸeridir. Bulaşıcılık (test + sonuçları pozitif kiÅŸilerin temaslarının bulaÅŸ riski seviyesi), o andaki + bilgi seviyesine göre bulaşıcı hastalığın seyir ve süresine baÄŸlı + olduÄŸundan, bulgu baÅŸlangıcından itibaren geçen zamana göre maruz kalmanın + bulaÅŸ riski azaldığı dikkate alınır. </p> <p> - Temas halinde, bireysel enfeksiyon riskinizi tespit etmek amacıyla temas - kayıt iÅŸlevi tarafından iletilen maruz kalma verileri UYGULAMA tarafından - analiz edilir. Maruz kalma verilerinin nasıl yorumlanması gerektiÄŸini + UYGULAMA, rastgele kimlikleri akıllı telefonunuzun temas kayıt iÅŸlevine + iletir ve bunlar da temas kayıt tarafından düzenlenen rastgele kimlikler + ile birleÅŸtirilir. Akıllı telefonunuzun temas kayıt iÅŸlevi bir eÅŸleÅŸme + tespit ederse, UYGULAMA'ya temas verilerini (tarih, süre, sinyal gücü) + iletir, ilgili temasın rastgele kimliklerini iletmez. +</p> +<p> + Maruz kalma halinde, temas kayıt iÅŸlevinin verdiÄŸi temas verileri ve de + taşıma risk deÄŸeri, bireysel bulaşıcı hastalık riskinizin tespit + edilebilmesi için UYGULAMA tarafından analiz edilir. +</p> +<p> + Temas verileri ve taşıma risk deÄŸerinin nasıl yorumlanması gerektiÄŸini belirleyen (örn. temas süresinin, enfeksiyon riski üzerinde ne tür bir etkisinin olduÄŸu) deÄŸerlendirme algoritması, sahip olunan güncel bilimsel bilgilere dayanır. Bu yüzden yeni bilgiler edinildiÄŸinde deÄŸerlendirme algoritmasının ayarları RKI tarafından yeniden yapılır ve deÄŸerlendirme algoritması bu ÅŸekilde güncellenebilir. DeÄŸerlendirme algoritmasının - ayarları, enfekte olan kiÅŸilerin rastgele kimliklerinden oluÅŸan liste ile - birlikte UYGULAMA'ya iletilir. + ayarları, enfekte olan kullanıcıların rastgele kimlikleri ile birlikte + UYGULAMA'ya iletilir. </p> <p> - Enfeksiyon riskinin tespiti sadece akıllı telefonunuzda lokal olarak - gerçekleÅŸir, yani veriler çevrimdışı iÅŸlenir. Tespit edilen enfeksiyon - riski de aynı ÅŸekilde sadece UYGULAMA'da saklanır ve baÅŸka alıcılara (RKI, - Apple, Google ve diÄŸer üçüncü taraflara) verilmez. + BulaÅŸ riski tespiti sadece lokal olarak akıllı telefonunuzda yapılır; + bununla kastedilen, verilerin UYGULAMA'nın sunucu sistemine eriÅŸimler + olmaksızın çevrimdışı iÅŸleneceÄŸidir. Tespit edilen enfeksiyon riski de + sadece UYGULAMA tarafından hesaplanır ve diÄŸer alıcılara (RKI, Apple, + Google ve üçüncü taraflara da iletilmez) iletilmez. </p> <p> - EriÅŸim verilerinizin, maruz kalma verilerinizin ve gerekirse saÄŸlık - verilerinizin (sizin için bir enfeksiyon riski tespit edilmiÅŸse) yukarıda - anıldığı gibi iÅŸlenmesinin hukuki dayanağı, maruz kalma günlüğü - etkinleÅŸtirme esnasında verdiÄŸiniz onaydır. + EriÅŸim verileriniz, temas verileriniz ve varsa, saÄŸlık verilerinize (maruz + kalma algılanırsa) iliÅŸkin yukarıda anılan veri iÅŸlemenin hukuki dayanağı, + maruz kalma günlüğünü etkinleÅŸtirme esnasında vermiÅŸ olduÄŸunuz onayınızdır. </p> -<h3> +<h2> b. Test kaydetme -</h3> +</h2> <p> Koronavirüs enfeksiyonu için test olduysanız, bu testi doktorunuz ya da test kuruluÅŸundan aldığınız QR kodunu UYGULAMA'da tarayarak, UYGULAMA'ya @@ -346,29 +361,35 @@ tabanından, karma kod numarasının pozitif bir test sonucu bulunduÄŸuna dair bir onay ister. Test sonucu veri tabanı bunu onaylarsa, sunucu sistemi TAN'ı oluÅŸturur ve UYGULAMA'ya iletir. TAN'ın bir kopyası sunucu sisteminde - kalır. -</p> -<p> - Pozitif bir test sonucunun iletilmesi halinde yanlış bilgilerin diÄŸer - kullanıcılar ile paylaşılmamasını saÄŸlamak için TAN gereklidir. -</p> -<p> + kalır. Pozitif bir test sonucunun iletilmesi halinde yanlış bilgilerin + diÄŸer kullanıcılar ile paylaşılmamasını saÄŸlamak için TAN gereklidir. Anılan verilerin yukarıda açıklandığı ÅŸekilde iÅŸlenmesinin hukuki dayanağı, "Test kaydetme" iÅŸlevine verdiÄŸiniz onaydır. </p> -<h3> - c. Test sonucunuzu paylaÅŸma -</h3> +<h2> + c. DiÄŸerlerini uyar +</h2> <p> - DiÄŸer kullanıcıları uyarmak için "Test sonucunuzu paylaÅŸma" iÅŸlevini - kullanırsanız, UYGULAMA akıllı telefonunuzda kayıtlı olan son 14 güne ait - rastgele kimliklerinizi ve TAN'ı UYGULAMA'nın sunucu sistemine iletir. - Sunucu sistemi öncelikle TAN'ın geçerli olup olmadığını kontrol eder ve - rastgele kimliklerinizi, pozitif test sonucu paylaÅŸan kullanıcıların - rastgele kimliklerinden oluÅŸan listeye taşır. Bundan böyle rastgele + "DiÄŸerlerini uyar" iÅŸlevini kullanıyorsanız, UYGULAMA, akıllı telefonunuzda + saklanan, son 14 güne ait rastgele kimlikleri (ilgili taşıma risk deÄŸerleri + dahil) ve TAN'ı UYGULAMA'nın sunucu sistemine iletir. Burada ilk olarak + TAN'ın geçerli olup olmadığı denetlenir ve rastgele kimliklerinizi, pozitif + test sonucunu sunan kullanıcı listesine kaydeder. Bundan böyle rastgele kimlikleriniz, maruz kalma günlüğü çerçevesinde diÄŸer kullanıcılar tarafından indirilebilir. </p> +<p> + "DiÄŸerlerini uyar" iÅŸlevinin sorguladığı, bulgu ve bulgu baÅŸlangıcına + iliÅŸkin bilgiler, tercihe baÄŸlıdır ve diÄŸer kullanıcıları uyarmak için + gerekli deÄŸildir. Ancak, bu bilgiler, karşılaÅŸtığınız diÄŸer kullanıcıların + enfeksiyon riskini daha iyi hesaplamaya yardım eder. BaÅŸka ya da hiçbir + soruyu yanıtlamak istemiyorsanız, "bilgi yok" seçeneÄŸini seçebilirsiniz. Bu + durumda, rastgele kimliÄŸinizin taşıma risk deÄŸerleri, test sonucunun + çaÄŸrılmasından itibaren geçen zaman aracılığıyla UYGULAMA tarafından + ortalama bir enfeksiyon sürecinin esas alınması ile belirlenir, daha + doÄŸrusu bir rastgele kimliÄŸin kullanılmasından uzun süre geçtiyse, taşıma + risk deÄŸeriniz daha düşüktür. +</p> <p> <u>Test sonucunuzu UYGULAMA'da çağırmadıysanız:</u> </p> @@ -403,35 +424,36 @@ sonucu, TAN ve TeleTAN) iÅŸlenmesinin hukuki dayanağı, "Test sonucunuzu paylaÅŸma" iÅŸlevine verdiÄŸiniz onaydır. </p> -<p> - <strong>d. UYGULAMA'nın bilgi amaçlı kullanımı</strong> -</p> +<h3> + d. UYGULAMA'nın bilgi amaçlı kullanımı +</h3> <p> UYGULAMA'yı sadece bilgi amaçlı kullanıyorsanız, daha doÄŸrusu UYGULAMA'nın yukarıda anılan hiçbir iÅŸlevini kullanmıyorsanız ve hiçbir veri girmiyorsanız, veri iÅŸleme sadece akıllı telefonunuzda yapılır ve hiçbir - kiÅŸisel veri meydana gelmez. UYGULAMA'da baÄŸlantı kurulan: - www.bundesregierung.de gibi web sayfaları akıllı telefonunuzun standart - tarayıcısında açılır ve gösterilir. Hangi verilerin iÅŸleneceÄŸi, kullanılan - tarayıcıya, yapılandırmasına ve de çaÄŸrılan web sayfasının veri iÅŸleme + kiÅŸisel veri meydana gelmez. UYGULAMA'da baÄŸlantı kurulan: www.bundesregierung.de gibi web + siteleri iÅŸletim + sistemine baÄŸlı olarak akıllı telefonunuzun (Android akıllı telefonlar) + standart tarayıcısında ya da Uygulamada (iPhone) açılır ve görüntülenir. Bu + esnada hangi verilerin iÅŸleneceÄŸi, çaÄŸrılan web sitesinin veri iÅŸleme uygulamasına baÄŸlıdır. </p> -<h2> +<h1> 7. UYGULAMA hangi yetkilendirme ve iÅŸlevleri gerektirir? -</h2> +</h1> <p> UYGULAMA, akıllı telefonunuzun çeÅŸitli iÅŸlevlerine ve ara yüzlerine eriÅŸim gerektirir. Bunun için, UYGULAMA'nın belirli yetkilendirmeleri istemesi gereklidir. Yetkilendirmeler, çeÅŸitli üreticiler tarafından farklı programlanır. Bu sayede, yetkilendirme kategorisini topluca kabul - edebileceÄŸiniz ÅŸekilde örn., yetkilendirme kategorilerine iliÅŸkin tekil + edebileceÄŸiniz ÅŸekilde örneÄŸin, yetkilendirme kategorilerine iliÅŸkin tekil yetkilendirmeler toplanabilir. UYGULAMA aracılığıyla bir eriÅŸimin reddi halinde UYGULAMA'nın hiçbir iÅŸlevini kullanamayacağınızı ya da çok az iÅŸlevini kullanabileceÄŸinizi lütfen dikkate alın. </p> -<h3> +<h2> a. Teknik ÅŸartlar (tüm akıllı telefonlar) -</h3> +</h2> <ul> <li> Ä°nternet @@ -473,22 +495,22 @@ iÅŸletimini devre dışı bırakırsanız, tüm eylemleri UYGULAMA'da bizzat baÅŸlatmalısınız. </p> -<h3> +<h2> b. Android akıllı telefonlar -</h3> +</h2> <p> Android cihaz kullanıyorsanız, aÅŸağıdaki sistem iÅŸlevleri ayrıca etkinleÅŸtirilmelidir: </p> <ul> <li> - COVID-19 Temas Bildirimleri Sistemi + COVID-19 Bildirimleri </li> </ul> <p> - Temaslarınızın rastgele kimlikleri ile aksi takdirde hiçbir temas protokolü - sunulmadığından, maruz kalma günlüğü bu iÅŸlevi gerektirir. Ä°ÅŸlev, - UYGULAMA'nın temas protokolüne eriÅŸebilmesi için UYGULAMA dahilinde + Maruz kalma günlüğü temaslarınızın rastgele kimlikleri ile baÅŸka hiçbir + temas protokolü sunulmadığından bu iÅŸlevi gerektirir. Ä°ÅŸlev, UYGULAMA'nın + temas protokolüne eriÅŸebilmesi için UYGULAMA dahilinde etkinleÅŸtirilmelidir. </p> <ul> @@ -523,15 +545,15 @@ UYGULAMA, test kaydı esnasında QR kodunu okuyabilmek için kameraya eriÅŸebilmelidir. </p> -<h3> +<h2> c. iPhone (Apple iOS) -</h3> +</h2> <p> iPhone kullanıyorsanız, aÅŸağıdaki sistem iÅŸlevleri etkinleÅŸtirilmelidir: </p> <ul> <li> - COVID-19'a Maruz Kalma Günlüğü + Temas bildirimleri </li> </ul> <p> @@ -561,16 +583,16 @@ UYGULAMA, test kaydı esnasında QR kodunu okuyabilmek için kameraya eriÅŸebilmelidir. </p> -<h2> +<h1> 8. Veriler ne zaman silinir? -</h2> +</h1> <p> UYGULAMA'da saklanan tüm veriler, UYGULAMA'nın iÅŸlevleri için ihtiyaç kalmadığında silinir: </p> -<h3> +<h2> a. Maruz kalma günlüğü -</h3> +</h2> <ul> <li> Pozitif test sonucu paylaÅŸan kullanıcılara iliÅŸkin liste, UYGULAMA'da @@ -578,25 +600,25 @@ sonra otomatik silinir. </li> <li> - Akıllı telefonunuzun temas protokolünde maruz kalma verileri (kendi - rastgele kimlikleriniz dahil) ve diÄŸer akıllı telefonlardaki maruz - kalma verilerinin silinmesine iliÅŸkin olarak RKI'nin, bu iÅŸlev Apple - veya Google tarafından hazırlandığı için hiçbir etkisi yoktur. Silme - iÅŸlemi Apple veya Google'ın kararlarına göre gerçekleÅŸir. Veriler ÅŸu - anda 14 gün sonra otomatik silinir. Ayrıca cihazınızın sistem - ayarlarında Apple ve Google tarafından sunulan fonksiyonları kullanarak - bunları manuel olarak silebilirsiniz. + Bu iÅŸlevler Apple ya da Google tarafından sunulduÄŸu için akıllı + telefonunuzun temas kayıt iletiÅŸim protokolündeki temas verilerinin + (kendi rastgele kimlikleriniz dahil) ve diÄŸer akıllı telefonlardaki + temas verilerinin silinmesine iliÅŸkin olarak RKI'nin hiçbir etkisi + yoktur. Silme iÅŸlemi Apple veya Google'ın kararlarına göre gerçekleÅŸir. + Veriler ÅŸu anda 14 gün sonra otomatik silinir. Ayrıca cihazınızın + sistem ayarlarında Apple ve Google tarafından sunulan fonksiyonları + kullanarak bunları manuel olarak silebilirsiniz. </li> <li> - Yeni toplam risk puanı tespit edildiÄŸinde UYGULAMA'da görüntülenen - toplam risk puanı silinir. Yeni toplam risk puanı kural olarak + Yeni toplam risk puanı tespit edilir edilmez UYGULAMA'da görüntülenen + enfeksiyon risk deÄŸeri silinir. Yeni toplam risk puanı kural olarak UYGULAMA, rastgele kimliklerinden oluÅŸan listeyi aldıktan sonra tespit edilir. </li> </ul> -<h3> +<h2> b. Test kaydetme -</h3> +</h2> <ul> <li> Karma kod numarası, 21 gün sonra UYGULAMA'nın sunucu sisteminden @@ -612,28 +634,33 @@ Sunucu sisteminde saklanan belirteç, 21 gün sonra silinir. </li> <li> - UYGULAMA'da saklanan belirteç, UYGULAMA akıllı telefondan silindikten - sonra ya da "Test sonucunuzu paylaÅŸma" iÅŸlevi gerçekleÅŸtirildikten - sonra silinir. + UYGULAMA'da saklanan belirteç, UYGULAMA, akıllı telefondan silindikten + sonra ya da "DiÄŸerlerini uyar" iÅŸlevi gerçekleÅŸtirildikten sonra + silinir. </li> </ul> -<h3> - c. Test sonucunuzu paylaÅŸma -</h3> +<h2> + c. DiÄŸerlerini uyar +</h2> <ul> <li> - UYGULAMA'da paylaşılan rastgele kimlikler, 14 gün sonra sunucu - sisteminden silinir. + UYGULAMA aracılığıyla sunulan rastgele kimlikler ve risk bulaÅŸ + deÄŸerleri, 14 gün sonra sunucu sistemi tarafından silinir. + </li> + <li> + Bulgu ve bulgu baÅŸlangıcına iliÅŸkin bilgileriniz, rastgele kimlik ve + taşıma risk deÄŸerlerinin hazırlanmasından hemen sonra UYGULAMA + tarafından silinir. </li> <li> Sunucu sisteminde saklanan TAN kopyası, 21 gün sonra silinir. </li> <li> - UYGULAMA'da saklanan TAN, test sonucunun paylaşılmasından sonra + UYGULAMA'da saklanan TAN, test sonucunun hazırlanmasından sonra silinir. </li> <li> - UYGULAMA'da saklanan TeleTAN, test sonucunun paylaşılmasından sonra + UYGULAMA'da saklanan TeleTAN, test sonucunun hazırlanmasından sonra silinir. </li> <li> @@ -647,23 +674,24 @@ Sunucu sisteminde saklanan belirteç, 21 gün sonra silinir. </li> <li> - UYGULAMA'da saklanan belirteç, test sonucunun paylaşılmasından sonra + UYGULAMA'da saklanan belirteç, test sonucunun hazırlanmasından sonra silinir. </li> </ul> -<h2> +<h1> 9. Verileriniz kimlere iletilir? -</h2> +</h1> <p> - DiÄŸer kullanıcıları uyarmak için test sonucu paylaÅŸtığınızda, son 14 güne - ait rastgele kimlikleriniz diÄŸer kullanıcıların UYGULAMA'larına iletilir. + DiÄŸer kullanıcıları uyarmak için "DiÄŸerlerini uyar" iÅŸlevi aracılığıyla bir + test sonucu hazırlarsanız, son 14 güne iliÅŸkin rastgele kimlikleriniz ve + bunlara iliÅŸkin taşıma risk deÄŸerleri diÄŸer kullanıcıların UYGULAMALARINA + iletilir. </p> <p> RKI, UYGULAMA'nın teknik alt yapısının bir bölümünün iÅŸletimi ve bakımı - (örn., sunucu sistemleri, yardım hattı) için Deutsche Telekom AG ve SAP - Deutschland SE & Co. KG'yi görevlendirmiÅŸtir (AB Genel Veri Koruma - Tüzüğü madde 28) ve bu firmalar bununla ilgili olarak RKI'nin veri - iÅŸlemcisi sıfatıyla faaliyet gösterecektir. + (örn., sunucu sistemleri, yardım hattı) için RKI'nin veri iÅŸlemcisi olarak + faaliyet gösteren Deutsche Telekom AG ve SAP Deutschland SE & Co. KG'yi + görevlendirmiÅŸtir (AB Genel Veri Koruma Tüzüğü madde 28). </p> <p> Bundan baÅŸka, RKI, UYGULAMA'nın kullanımı ile baÄŸlantılı olarak toplanan @@ -672,18 +700,18 @@ gerekli olursa üçüncü kiÅŸilere iletebilir. Bundan baÅŸka durumlarda veriler esas itibariyle baÅŸkalarına iletilmez. </p> -<h2> +<h1> 10. Veriler üçüncü ülkelere aktarılır mı? -</h2> +</h1> <p> UYGULAMA'nın kullanımında toplanan veriler, sadece Almanya'daki sunucularda ya da baÅŸka bir AB ya da Avrupa Ekonomik Alanı üye devletinde iÅŸlenir. </p> -<h2> +<h1> 11. Onayı geri alma -</h2> +</h1> <p> - UYGULAMA'da RKI'ya yönelik verdiÄŸiniz onayları dilediÄŸinizde ve gelecekte + UYGULAMA'da RKI'ye yönelik verdiÄŸiniz onayları dilediÄŸinizde ve gelecekte etki etmek ÅŸartıyla geri alabilirsiniz. Ancak, geri alma anına kadarki veri iÅŸlemenin hukuka uygunluÄŸu, bu geri almadan etkilenmez. </p> @@ -697,24 +725,24 @@ <p> "Test kaydetme" iÅŸlevine verdiÄŸiniz onayı geri almak için UYGULAMA'daki test kaydını silebilirsiniz. Test sonucunun çaÄŸrılması için alınan - belirteç, bundan sonra cihazınızdan silinir. RKI ve test laboravutarı, - iletilen verileri UYGULAMA'nıza ya da cihazınıza ekleyemez. BaÅŸka bir testi - kaydettirmek isterseniz sizden yeni bir onay vermeniz istenir. -</p> -<p> - "Test sonucunuzu paylaÅŸma" iÅŸlevine verdiÄŸiniz onayı geri almak için - UYGULAMA'yı silmelisiniz. UYGULAMA'da saklanan tüm rastgele kimlikleriniz - silinir ve akıllı telefonunuzla artık iliÅŸkilendirilemez. Yeniden bir test - sonucu bildirmek isterseniz, UYGULAMA'yı yeniden yükleyebilir ve yeni bir - onay verebilirsiniz. Alternatif olarak, ÅŸayet varsa, akıllı telefonunuzun - sistem ayarlarından temas bildirim iÅŸlevi çerçevesinde rastgele - kimliklerinizi silebilirsiniz. Lütfen RKI'nin iletmiÅŸ olduÄŸunuz rastgele - kimlikleri diÄŸer kullanıcıların akıllı telefonlarından ve hazırlanan - listelerden doÄŸrudan silme imkanının olmadığını dikkate alın. -</p> -<h2> + belirteç, bundan sonra cihazınızdan silinir. RKI ve test laboratuvarı, + iletilen verileri UYGULAMA'nıza ya da akıllı telefonunuza ekleyemez. BaÅŸka + bir testi kaydettirmek isterseniz sizden yeni bir onay vermeniz istenir. +</p> +<p> + "DiÄŸerlerini uyar" iÅŸlevine verdiÄŸiniz onayı geri almak için UYGULAMA'yı + silmelisiniz. UYGULAMA'da saklanan tüm rastgele kimlikleriniz silinir ve + akıllı telefonunuzla artık iliÅŸkilendirilemez. Yeniden bir test sonucu + bildirmek isterseniz, UYGULAMA'yı yeniden yükleyebilir ve yeni bir onay + verebilirsiniz. Alternatif olarak, rastgele kimliklerinizi gerekirse temas + kayıt iÅŸlevi çerçevesinde akıllı telefonunuzun sistem ayarlarından + silebilirsiniz. Lütfen RKI'nin iletilmiÅŸ rastgele kimlikleriniz ve taşıma + risk deÄŸerlerini, diÄŸer kullanıcıların akıllı telefonlarından ve + hazırlanmış listelerden doÄŸrudan silemeyeceÄŸini dikkate alın. +</p> +<h1> 12. DiÄŸer veri koruma haklarınız -</h2> +</h1> <p> KiÅŸisel verileriniz, RKI tarafından iÅŸlendiÄŸi sürece aÅŸağıdaki veri koruma haklarına da sahipsiniz: @@ -725,33 +753,37 @@ kaynaklanan haklar, </li> <li> - RKI resmi veri koruma görevlisi ile iletiÅŸime geçme ve talebinizi sunma - hakkı (AB Genel Veri Koruma Tüzüğü madde 38 paragraf 4) ve + RKI resmi veri koruma görevlisi + (https://www.rki.de/DE/Content/Institut/OrgEinheiten/Datenschutz/Datenschutz_node.html) + ile iletiÅŸime geçme ve talebinizi sunma hakkı (AB Genel Veri Koruma + Tüzüğü madde 38 paragraf 4) ve </li> <li> yetkili denetim kurumu huzurunda veri korumaya iliÅŸkin ÅŸikayette - bulunma hakkı. Bunun için, ikamet ettiÄŸiniz ya da RKI'nin ÅŸirket - merkezinin bulunduÄŸu yerdeki yetkili denetim kurumuna - baÅŸvurabilirsiniz. RKI için yetkili denetim kurumu, Graurheindorfer - Str. 153, 53117 Bonn adresinde bulunan Veri Koruma ve Bilgi Özgürlüğü - federal görevlisidir. + bulunma hakkı. Bunun için, oturduÄŸunuz yerdeki yetkili denetim kurumu </li> </ul> +<p> + ya da RKI'nin ÅŸirket merkezinin olduÄŸu yerdeki yetkili kuruma + baÅŸvurabilirsiniz. RKI için yetkili denetim kurumu, Graurheindorfer Str. + 153, 53117 Bonn adresinde bulunan Veri Koruma ve Bilgi Özgürlüğü federal + görevlisidir. +</p> <p> RKI'nin yukarıda anılan haklarının sadece, ileri sürülen taleplerin dayanağı verilerin açıkça ÅŸahsınıza baÄŸlanması halinde ifa edilebileceÄŸine - dikkat çekeriz. Bu da, yukarıda anılan verilerin ÅŸahsınıza ya da cihazınıza - eklenmesine izin verilen diÄŸer kiÅŸisel verilerin RKI tarafından toplanması - halinde mümkündür. Bu husus da UYGULAMA'nın amaçları için gerekli - olmadığından – ve ayrıca istenmediÄŸinden – RKI bu tür bir ek veri toplama - ile sorumlu deÄŸildir (AB Genel Veri Koruma Tüzüğü madde 11 paragraf. 2). - Ayrıca, bu husus UYGULAMA çerçevesindeki veri iÅŸlemenin mümkün olduÄŸu kadar - veri koruma tasarrufu yaparak gerçekleÅŸtirilmesi olan amaca ve de açıklanan - amaca aykırıdır. Bu yüzden, yukarıda anılan AB Genel Veri Koruma Tüzüğü - maddeler 15, 16, 17, 18, 20 ve 21'den kaynaklanan veri koruma hakları, - kural olarak derhal uygulanmaz ve RKI'ye sunulmayan, ÅŸahsınızla ilgili ek - bilgiler ile uygulanır. -</p> -<p> - Versiyon: 09.06.2020 + dikkat çekeriz. Bu da, yukarıda anılan verilerin ÅŸahsınıza ya da akıllı + telefonunuza eklenmesine izin verilen diÄŸer kiÅŸisel verilerin RKI + tarafından toplanması halinde mümkündür. Bu husus da UYGULAMA'nın amaçları + için gerekli olmadığından – ve ayrıca istenmediÄŸinden – RKI bu tür bir ek + veri toplama ile sorumlu deÄŸildir (AB Genel Veri Koruma Tüzüğü madde 11 + paragraf. 2). Ayrıca, bu husus UYGULAMA çerçevesindeki veri iÅŸlemenin + mümkün olduÄŸu kadar veri koruma tasarrufu yaparak gerçekleÅŸtirilmesi olan + amaca ve de açıklanan amaca aykırıdır. Bu yüzden, yukarıda anılan AB Genel + Veri Koruma Tüzüğü maddeler 15, 16, 17, 18, 20 ve 21'den kaynaklanan veri + koruma hakları, kural olarak derhal uygulanmaz ve RKI'ye sunulmayan, + ÅŸahsınızla ilgili ek bilgiler ile uygulanır. +</p> +<p> + Versiyon: 05.10.2020 </p> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/assets/terms_de.html b/Corona-Warn-App/src/main/assets/terms_de.html index abecebc46f1160e3457dc5ffb11a929448541ab0..d129ee47173b2c6fe1109a1761848197e41324b6 100644 --- a/Corona-Warn-App/src/main/assets/terms_de.html +++ b/Corona-Warn-App/src/main/assets/terms_de.html @@ -91,7 +91,7 @@ </p> <p> Sie sind für die Einhaltung dieser Nutzungsbedingungen auch dann - verantwortlich, wenn Sie das Endgerät, auf dem Sie die App installiert + verantwortlich, wenn Sie das Smartphone, auf dem Sie die App installiert haben, Dritten überlassen und diese die App verwenden. </p> <p> @@ -119,9 +119,9 @@ Hintergrund </h4> <p> - Die App läuft auf dem Endgerät im Hintergrund und speichert automatisiert + Die App läuft auf dem Smartphone im Hintergrund und speichert automatisiert und verschlüsselt die Zufallscodes (<em>rolling proximity identifier</em>) - anderer in der Nähe befindlicher Endgeräte. In regelmäßigen Abständen holt + anderer in der Nähe befindlicher Smartphones. In regelmäßigen Abständen holt sich die App über die CWADienste eine Liste der Zufallscodes (<em>temporary exposure keys</em>) der Personen, die sich freiwillig infiziert gemeldet haben, und vergleicht diese mit den gespeicherten @@ -129,7 +129,7 @@ </p> <p> Die App kann nur Begegnungen mit Personen registrieren, die ihrerseits ein - Endgerät mit installierter App bei sich führen und alle Voraussetzungen für + Smartphone mit installierter App bei sich führen und alle Voraussetzungen für die Nutzung der App erfüllen (siehe unten Ziffer 7). Begegnungen mit anderen Personen kann die App nicht registrieren. </p> @@ -161,7 +161,7 @@ Im Fall eines positiven SARS-CoV-2-Befunds können Sie freiwillig die in der App gespeicherten eigenen Zufallscodes der letzten 14 Tage als Positivkennungen (<em>diagnosis keys</em>) veröffentlichen, damit andere - Personen, die die App nutzen, auf ihrem eigenen Endgerät abgleichen können, + Personen, die die App nutzen, auf ihrem eigenen Smartphone abgleichen können, ob sie mit Ihnen eine Risiko-Begegnung hatten. </p> <h4> @@ -260,9 +260,9 @@ <strong> Die Benachrichtigung über eine Risiko-Begegnung kann unzutreffend sein. </strong> - Die Risiko-Begegnung kann von Ihrem Endgerät beispielsweise zu einem + Die Risiko-Begegnung kann von Ihrem Smartphone beispielsweise zu einem Zeitpunkt registriert worden sein, zu dem Sie sich nicht in der Nähe Ihres - Endgeräts aufgehalten haben oder während eine andere Person Ihr Endgerät + Smartphones aufgehalten haben oder während eine andere Person Ihr Smartphone verwendet hat. Die Risiko-Begegnung kann auch aufgrund bestehender Grenzen bei der Kontaktmessung fälschlicherweise registriert worden sein (siehe unten Ziffer 8). @@ -292,7 +292,7 @@ </p> <p> o Die App registriert nicht alle Ihre Begegnungen mit anderen Personen, - z.B. weil andere Personen die App nicht verwenden, Sie Ihr Endgerät nicht + z.B. weil andere Personen die App nicht verwenden, Sie Ihr Smartphone nicht immer bei sich tragen oder die App nicht immer in Betrieb haben oder weil die Kontaktmessung gewissen Grenzen unterliegt (siehe unten Ziffer 8). </p> @@ -411,35 +411,35 @@ <p> Bestimmte Funktionen der App setzen auf zentrale Dienste und Systeme auf, die über die CWA-Dienste zur Verfügung gestellt werden. Diese Funktionen - stehen daher nur zur Verfügung, wenn Ihr Endgerät über eine Datenverbindung + stehen daher nur zur Verfügung, wenn Ihr Smartphone über eine Datenverbindung mit dem Internet verfügt, z.B. über UMTS, LTE oder WLAN, um hierüber auf die CWA-Dienste zugreifen zu können. Ohne Datenverbindung stehen einige oder alle Funktionen der App nicht zur Verfügung. Dies gilt auch, wenn Sie - Ihr Endgerät in den Flugmodus versetzen oder ausschalten. + Ihr Smartphone in den Flugmodus versetzen oder ausschalten. </p> <h4> - Die App muss auf dem Endgerät laufen und eingeschaltet sein + Die App muss auf dem Smartphone laufen und eingeschaltet sein </h4> <p> - Die App muss auf Ihrem Endgerät im Vorder- oder Hintergrund laufen und + Die App muss auf Ihrem Smartphone im Vorder- oder Hintergrund laufen und eingeschaltet sein. Hierzu müssen Sie die App starten. Wenn Sie die App nicht starten, ausschalten oder beenden, speichert die App keine Begegnungen mit anderen Personen und erzeugt keine Zufallscodes zur - Speicherung durch andere Personen. Wenn Sie das Endgerät neu starten (z.B. + Speicherung durch andere Personen. Wenn Sie das Smartphone neu starten (z.B. nach dem Ausschalten, nachdem die Batterie leer war oder nach einem Update des Betriebssystems), müssen Sie auch die App neu starten. </p> <h4> - Einstellungen im Endgerät + Einstellungen im Smartphone </h4> <p> Für die Nutzung der App müssen Sie ferner die Bluetooth (BLE)-Funktionen - auf Ihrem Endgerät aktivieren und ggf. zur Verwendung durch die App + auf Ihrem Smartphone aktivieren und ggf. zur Verwendung durch die App freigegeben. </p> <p> Für die Nutzung der App empfiehlt das RKI ferner folgende Funktionen auf - Ihrem Endgerät zu aktivieren und ggf. zur Verwendung durch die App + Ihrem Smartphone zu aktivieren und ggf. zur Verwendung durch die App freizugegeben, auch wenn diese nicht Voraussetzung für die Nutzung der grundlegenden Funktionen der App sind: </p> @@ -450,7 +450,7 @@ • Kamerafunktion </p> <p> - Bitte prüfen Sie in den Einstellungen ihres Endgeräts, ob diese Funktionen + Bitte prüfen Sie in den Einstellungen ihres Smartphones, ob diese Funktionen aktiviert und für die Verwendung der App freigegeben sind. </p> <p> @@ -497,11 +497,11 @@ <p> Für die Entfernungsmessung wird die Dämpfung des Bluetooth-Signals verwendet. Eine geringere Dämpfung bedeutet dabei grundsätzlich, dass das - andere Endgerät näher ist. Eine höhere Dämpfung kann entweder bedeuten, - dass das andere Endgerät weiter entfernt ist (also eine Entfernung von mehr - als zwei Metern) oder dass sich zwischen den beiden Endgeräten etwas + andere Smartphone näher ist. Eine höhere Dämpfung kann entweder bedeuten, + dass das andere Smartphone weiter entfernt ist (also eine Entfernung von mehr + als zwei Metern) oder dass sich zwischen den beiden Smartphones etwas befindet, was das Signal blockiert. Das können Objekte wie eine Wand oder - eine Tasche, in der sich das Endgerät befindet, sein, aber genauso Personen + eine Tasche, in der sich das Smartphone befindet, sein, aber genauso Personen oder Tiere. </p> <h1> @@ -567,7 +567,7 @@ Sie dürfen die App und die Schnittstellen zu den CWA-Diensten nicht missbräuchlich verwenden. Sie dürfen die CWA-Dienste nicht für andere Zwecke nutzen als den bestimmungsgemäßen Betrieb der App auf Ihrem - Endgerät. Sie dürfen auf die CWADienste ausschließlich über die App + Smartphone. Sie dürfen auf die CWADienste ausschließlich über die App zugreifen. </p> <h1> @@ -613,7 +613,7 @@ Fall werden Sie beim Start der App aufgefordert, den geänderten Nutzungsbedingungen zuzustimmen. Wenn Sie den geänderten Nutzungsbedingungen nicht zustimmen, können Sie die App und die CWA- - Dienste nicht mehr nutzen und müssen die App von Ihrem Endgerät löschen. + Dienste nicht mehr nutzen und müssen die App von Ihrem Smartphone löschen. </p> <h4> Risk Score @@ -654,9 +654,9 @@ sein werden. </p> <p> - Für die Datensicherung Ihres Endgeräts sowie ggf. damit verbundener Systeme + Für die Datensicherung Ihres Smartphones sowie ggf. damit verbundener Systeme sind Sie verantwortlich, inklusive der Datensicherung sämtlicher anderer - Apps, welche auf Ihrem Endgerät gespeichert sind. + Apps, welche auf Ihrem Smartphone gespeichert sind. </p> <h1> 12. BESONDERE BEDINGUNGEN FÃœR DIE IOSVERSION DER APP diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt index cd26d162082c879c4b44e3a4beba10c9c18403e8..184ec476975eeee93a524b35091a2208054fc6ad 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt @@ -27,6 +27,7 @@ import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.ConnectivityHelper +import de.rki.coronawarnapp.util.debug.FileLogger import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent import de.rki.coronawarnapp.worker.BackgroundWorkHelper @@ -56,6 +57,7 @@ class CoronaWarnApplication : Application(), LifecycleObserver, instance.applicationContext const val TEN_MINUTE_TIMEOUT_IN_MS = 10 * 60 * 1000L + var fileLogger: FileLogger? = null } private lateinit var errorReceiver: ErrorReportReceiver @@ -88,6 +90,9 @@ class CoronaWarnApplication : Application(), LifecycleObserver, if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } + if ((BuildConfig.FLAVOR == "deviceForTesters" || BuildConfig.DEBUG)) { + fileLogger = FileLogger(this) + } // notification to test the WakeUpService from Google when the app // was force stopped @@ -99,27 +104,9 @@ class CoronaWarnApplication : Application(), LifecycleObserver, ProcessLifecycleOwner.get().lifecycleScope.launch { // we want a wakelock as the OS does not handle this for us like in the background // job execution - val wakeLock: PowerManager.WakeLock = - (getSystemService(Context.POWER_SERVICE) as PowerManager).run { - newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - TAG + "-WAKE-" + UUID.randomUUID().toString() - ).apply { - acquire(TEN_MINUTE_TIMEOUT_IN_MS) - } - } - + val wakeLock = createWakeLock() // we keep a wifi lock to wake up the wifi connection in case the device is dozing - val wifiLock: WifiManager.WifiLock = - (getSystemService(Context.WIFI_SERVICE) as WifiManager).run { - createWifiLock( - WifiManager.WIFI_MODE_FULL_HIGH_PERF, - TAG + "-WIFI-" + UUID.randomUUID().toString() - ).apply { - acquire() - } - } - + val wifiLock = createWifiLock() try { BackgroundWorkHelper.sendDebugNotification( "Automatic mode is on", "Check if we have downloaded keys already today" @@ -146,6 +133,28 @@ class CoronaWarnApplication : Application(), LifecycleObserver, } } + private fun createWakeLock(): PowerManager.WakeLock = + (getSystemService(Context.POWER_SERVICE) as PowerManager) + .run { + newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + TAG + "-WAKE-" + UUID.randomUUID().toString() + ).apply { + acquire(TEN_MINUTE_TIMEOUT_IN_MS) + } + } + + private fun createWifiLock(): WifiManager.WifiLock = + (getSystemService(Context.WIFI_SERVICE) as WifiManager) + .run { + createWifiLock( + WifiManager.WIFI_MODE_FULL_HIGH_PERF, + TAG + "-WIFI-" + UUID.randomUUID().toString() + ).apply { + acquire() + } + } + /** * Callback when the app is open but backgrounded */ diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt new file mode 100644 index 0000000000000000000000000000000000000000..526b5e05270bad8de02da844dbda29a1292a1ff1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt @@ -0,0 +1,35 @@ +@file:Suppress("DEPRECATION") + +package de.rki.coronawarnapp.nearby + +import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import timber.log.Timber +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ENFClient @Inject constructor( + private val googleENFClient: ExposureNotificationClient, + private val diagnosisKeyProvider: DiagnosisKeyProvider +) : DiagnosisKeyProvider { + + // TODO Remove this once we no longer need direct access to the ENF Client, + // i.e. in **[InternalExposureNotificationClient]** + internal val internalClient: ExposureNotificationClient + get() = googleENFClient + + override suspend fun provideDiagnosisKeys( + keyFiles: Collection<File>, + configuration: ExposureConfiguration?, + token: String + ): Boolean { + Timber.d( + "asyncProvideDiagnosisKeys(keyFiles=%s, configuration=%s, token=%s)", + keyFiles, configuration, token + ) + return diagnosisKeyProvider.provideDiagnosisKeys(keyFiles, configuration, token) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClientLocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClientLocalData.kt new file mode 100644 index 0000000000000000000000000000000000000000..26564ab4af748c81731269262f78141e6b43c7de --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClientLocalData.kt @@ -0,0 +1,34 @@ +package de.rki.coronawarnapp.nearby + +import android.content.Context +import androidx.core.content.edit +import org.joda.time.Instant +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ENFClientLocalData @Inject constructor( + private val context: Context +) { + + private val prefs by lazy { + context.getSharedPreferences("enfclient_localdata", Context.MODE_PRIVATE) + } + + var lastQuotaResetAt: Instant + get() = Instant.ofEpochMilli(prefs.getLong(PKEY_QUOTA_LAST_RESET, 0L)) + set(value) = prefs.edit(true) { + putLong(PKEY_QUOTA_LAST_RESET, value.millis) + } + + var currentQuota: Int + get() = prefs.getInt(PKEY_QUOTA_CURRENT, 0) + set(value) = prefs.edit(true) { + putInt(PKEY_QUOTA_CURRENT, value) + } + + companion object { + private const val PKEY_QUOTA_LAST_RESET = "enfclient.quota.lastreset" + private const val PKEY_QUOTA_CURRENT = "enfclient.quota.current" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..4b7094c8f709b45f7765061d8078771d5a0ec118 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt @@ -0,0 +1,24 @@ +package de.rki.coronawarnapp.nearby + +import android.content.Context +import com.google.android.gms.nearby.Nearby +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import dagger.Module +import dagger.Provides +import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DefaultDiagnosisKeyProvider +import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import javax.inject.Singleton + +@Module +class ENFModule { + + @Singleton + @Provides + fun exposureNotificationClient(context: Context): ExposureNotificationClient = + Nearby.getExposureNotificationClient(context) + + @Singleton + @Provides + fun diagnosisKeySubmitter(submitter: DefaultDiagnosisKeyProvider): DiagnosisKeyProvider = + submitter +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt index 020dd1d4065eaa2df04500b4a2c7561a38ba4f2c..06b33b5503e44f33c4c7127f993fece9d6df485d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt @@ -1,8 +1,5 @@ package de.rki.coronawarnapp.nearby -import com.google.android.gms.nearby.Nearby -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration.ExposureConfigurationBuilder import com.google.android.gms.nearby.exposurenotification.ExposureSummary import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.CoronaWarnApplication @@ -10,7 +7,7 @@ import de.rki.coronawarnapp.risk.TimeVariables import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository import de.rki.coronawarnapp.util.TimeAndDateExtensions.millisecondsToSeconds -import java.io.File +import de.rki.coronawarnapp.util.di.AppInjector import java.util.Date import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -24,7 +21,7 @@ object InternalExposureNotificationClient { // reference to the client from the Google framework with the given application context private val exposureNotificationClient by lazy { - Nearby.getExposureNotificationClient(CoronaWarnApplication.getAppContext()) + AppInjector.component.enfClient.internalClient } /**************************************************** @@ -101,36 +98,6 @@ object InternalExposureNotificationClient { } } - /** - * Takes an ExposureConfiguration object. Inserts a list of files that contain key - * information into the on-device database. Provide the keys of confirmed cases retrieved - * from your internet-accessible server to the Google Play service once requested from the - * API. Information about the file format is in the Exposure Key Export File Format and - * Verification document that is linked from google.com/covid19/exposurenotifications. - * - * @param keyFiles - * @param configuration - * @param token - * @return - */ - suspend fun asyncProvideDiagnosisKeys( - keyFiles: Collection<File>, - configuration: ExposureConfiguration?, - token: String - ): Void = suspendCoroutine { cont -> - val exposureConfiguration = configuration ?: ExposureConfigurationBuilder().build() - exposureNotificationClient.provideDiagnosisKeys( - keyFiles.toList(), - exposureConfiguration, - token - ) - .addOnSuccessListener { - cont.resume(it) - }.addOnFailureListener { - cont.resumeWithException(it) - } - } - /** * Retrieves key history from the data store on the device for uploading to your * internet-accessible server. Calling this method prompts Google Play services to display diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..bca8dfdd48723b685774623dfcd72733203aec66 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt @@ -0,0 +1,106 @@ +@file:Suppress("DEPRECATION") + +package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider + +import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import de.rki.coronawarnapp.util.GoogleAPIVersion +import timber.log.Timber +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Singleton +class DefaultDiagnosisKeyProvider @Inject constructor( + private val googleAPIVersion: GoogleAPIVersion, + private val submissionQuota: SubmissionQuota, + private val enfClient: ExposureNotificationClient +) : DiagnosisKeyProvider { + + override suspend fun provideDiagnosisKeys( + keyFiles: Collection<File>, + configuration: ExposureConfiguration?, + token: String + ): Boolean { + return try { + if (keyFiles.isEmpty()) { + Timber.d("No key files submitted, returning early.") + return true + } + + val usedConfiguration = if (configuration == null) { + Timber.w("Passed configuration was NULL, creating fallback.") + ExposureConfiguration.ExposureConfigurationBuilder().build() + } else { + configuration + } + + if (googleAPIVersion.isAtLeast(GoogleAPIVersion.V16)) { + provideKeys(keyFiles, usedConfiguration, token) + } else { + provideKeysLegacy(keyFiles, usedConfiguration, token) + } + } catch (e: Exception) { + Timber.e( + e, "Error during provideDiagnosisKeys(keyFiles=%s, configuration=%s, token=%s)", + keyFiles, configuration, token + ) + throw e + } + } + + private suspend fun provideKeys( + files: Collection<File>, + configuration: ExposureConfiguration, + token: String + ): Boolean { + Timber.d("Using non-legacy key provision.") + + if (!submissionQuota.consumeQuota(1)) { + Timber.w("Not enough quota available.") + // TODO Currently only logging, we'll be more strict in a future release + // return false + } + + performSubmission(files, configuration, token) + return true + } + + /** + * We use Batch Size 1 and thus submit multiple times to the API. + * This means that instead of directly submitting all files at once, we have to split up + * our file list as this equals a different batch for Google every time. + */ + private suspend fun provideKeysLegacy( + keyFiles: Collection<File>, + configuration: ExposureConfiguration, + token: String + ): Boolean { + Timber.d("Using LEGACY key provision.") + + if (!submissionQuota.consumeQuota(keyFiles.size)) { + Timber.w("Not enough quota available.") + // TODO What about proceeding with partial submission? + // TODO Currently only logging, we'll be more strict in a future release + // return false + } + + keyFiles.forEach { performSubmission(listOf(it), configuration, token) } + return true + } + + private suspend fun performSubmission( + keyFiles: Collection<File>, + configuration: ExposureConfiguration, + token: String + ): Void = suspendCoroutine { cont -> + Timber.d("Performing key submission.") + enfClient + .provideDiagnosisKeys(keyFiles.toList(), configuration, token) + .addOnSuccessListener { cont.resume(it) } + .addOnFailureListener { cont.resumeWithException(it) } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..accedeed05ba0a7be5e2259326da4960db3ab3b7 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt @@ -0,0 +1,25 @@ +package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider + +import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration +import java.io.File + +interface DiagnosisKeyProvider { + + /** + * Takes an ExposureConfiguration object. Inserts a list of files that contain key + * information into the on-device database. Provide the keys of confirmed cases retrieved + * from your internet-accessible server to the Google Play service once requested from the + * API. Information about the file format is in the Exposure Key Export File Format and + * Verification document that is linked from google.com/covid19/exposurenotifications. + * + * @param keyFiles + * @param configuration + * @param token + * @return + */ + suspend fun provideDiagnosisKeys( + keyFiles: Collection<File>, + configuration: ExposureConfiguration?, + token: String + ): Boolean +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt new file mode 100644 index 0000000000000000000000000000000000000000..d9bd53506983a3d65e700bd694c21e6ca8d2a618 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt @@ -0,0 +1,91 @@ +package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider + +import androidx.annotation.VisibleForTesting +import de.rki.coronawarnapp.nearby.ENFClientLocalData +import de.rki.coronawarnapp.util.TimeStamper +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.joda.time.DateTimeZone +import org.joda.time.Duration +import org.joda.time.Instant +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubmissionQuota @Inject constructor( + private val enfData: ENFClientLocalData, + private val timeStamper: TimeStamper +) { + + private var currentQuota: Int + get() = enfData.currentQuota + set(value) { + enfData.currentQuota = value + } + + private var lastQuotaReset: Instant + get() = enfData.lastQuotaResetAt + set(value) { + enfData.lastQuotaResetAt = value + } + + private val mutex = Mutex() + + /** + * Attempts to consume quota, and returns true if enough quota was available. + */ + suspend fun consumeQuota(wanted: Int): Boolean = mutex.withLock { + attemptQuotaReset() + + if (currentQuota < wanted) { + Timber.d("Not enough quota: want=%d, have=%d", wanted, currentQuota) + return false + } + + run { + val oldQuota = currentQuota + val newQuota = currentQuota - wanted + Timber.d("Consuming quota: old=%d, new=%d", oldQuota, newQuota) + currentQuota = newQuota + } + return true + } + + /** + * Attempts to reset the quota + * On initial launch, the lastQuotaReset is set to Instant.EPOCH, + * thus the quota will be immediately set to 20. + */ + private fun attemptQuotaReset() { + val oldQuota = currentQuota + val oldQuotaReset = lastQuotaReset + + val now = timeStamper.nowUTC + + val nextQuotaReset = lastQuotaReset + .toDateTime(DateTimeZone.UTC) + .withTimeAtStartOfDay() + .plus(Duration.standardDays(1)) + + if (now.isAfter(nextQuotaReset)) { + currentQuota = DEFAULT_QUOTA + lastQuotaReset = now + + Timber.i( + "Quota reset: oldQuota=%d, lastReset=%s -> newQuota=%d, thisReset=%s", + oldQuota, oldQuotaReset, currentQuota, now + ) + } else { + Timber.d( + "No new quota available (now=%s, availableAt=%s)", + now, nextQuotaReset + ) + } + } + + companion object { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal const val DEFAULT_QUOTA = 20 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt index e33668fa5ddabfdda14f8a7564555a5fff992786..8695e37cfa9bee099f71465faf9fd4ab52f2160b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.http.playbook.BackgroundNoise import de.rki.coronawarnapp.http.playbook.PlaybookImpl import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction import de.rki.coronawarnapp.util.formatter.TestResult import de.rki.coronawarnapp.worker.BackgroundWorkScheduler @@ -51,10 +52,10 @@ object SubmissionService { SubmissionRepository.updateTestResult(testResult) } - suspend fun asyncSubmitExposureKeys(keys: List<TemporaryExposureKey>) { + suspend fun asyncSubmitExposureKeys(keys: List<TemporaryExposureKey>, symptoms: Symptoms) { val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() - SubmitDiagnosisKeysTransaction.start(registrationToken, keys) + SubmitDiagnosisKeysTransaction.start(registrationToken, keys, symptoms) } suspend fun asyncRequestTestResult(): TestResult { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt index 48bc37e409fea1553cc046a816338afa94529ee2..889ab566942df188a9780255f3301afafc45daa3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt @@ -6,7 +6,6 @@ import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.util.security.SecurityHelper.globalEncryptedSharedPreferencesInstance -import org.joda.time.Instant import java.util.Date /** @@ -19,11 +18,6 @@ object LocalData { private val TAG: String? = LocalData::class.simpleName - private const val PREFERENCE_NEXT_TIME_RATE_LIMITING_UNLOCKS = - "preference_next_time_rate_limiting_unlocks" - private const val PREFERENCE_GOOGLE_API_PROVIDE_DIAGNOSIS_KEYS_CALL_COUNT = - "preference_google_api_provide_diagnosis_keys_call_count" - /**************************************************** * ONBOARDING DATA ****************************************************/ @@ -396,40 +390,6 @@ object LocalData { } } - var nextTimeRateLimitingUnlocks: Instant - get() { - return Instant.ofEpochMilli( - getSharedPreferenceInstance().getLong( - PREFERENCE_NEXT_TIME_RATE_LIMITING_UNLOCKS, - 0L - ) - ) - } - set(value) { - getSharedPreferenceInstance().edit(true) { - putLong( - PREFERENCE_NEXT_TIME_RATE_LIMITING_UNLOCKS, - value.millis - ) - } - } - - var googleAPIProvideDiagnosisKeysCallCount: Int - get() { - return getSharedPreferenceInstance().getInt( - PREFERENCE_GOOGLE_API_PROVIDE_DIAGNOSIS_KEYS_CALL_COUNT, - 0 - ) - } - set(value) { - getSharedPreferenceInstance().edit(true) { - putInt( - PREFERENCE_GOOGLE_API_PROVIDE_DIAGNOSIS_KEYS_CALL_COUNT, - value - ) - } - } - /** * Gets the last time of successful risk level calculation as long * from the EncryptedSharedPrefs diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DefaultKeyConverter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DefaultKeyConverter.kt new file mode 100644 index 0000000000000000000000000000000000000000..e467c0f033231f096dd3710771f18586ab402298 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DefaultKeyConverter.kt @@ -0,0 +1,21 @@ +package de.rki.coronawarnapp.submission + +import KeyExportFormat +import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey +import com.google.protobuf.ByteString + +class DefaultKeyConverter : KeyConverter { + + override fun toExternalFormat(key: TemporaryExposureKey, riskValue: Int) = + KeyExportFormat.TemporaryExposureKey.newBuilder() + .setKeyData(ByteString.readFrom(key.keyData.inputStream())) + .setRollingStartIntervalNumber(key.rollingStartIntervalNumber) + .setRollingPeriod(ROLLING_PERIOD) + .setTransmissionRiskLevel(riskValue) + .build() + + companion object { + + private const val ROLLING_PERIOD = 144 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculations.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculations.kt new file mode 100644 index 0000000000000000000000000000000000000000..5a57de881eaa95ac5c9c865eec4aa5ee4da76f8f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculations.kt @@ -0,0 +1,39 @@ +package de.rki.coronawarnapp.submission + +import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey + +class ExposureKeyHistoryCalculations( + private val transmissionRiskVectorDeterminator: TransmissionRiskVectorDeterminator, + private val keyConverter: KeyConverter +) { + + fun transformToKeyHistoryInExternalFormat( + keys: List<TemporaryExposureKey>, + symptoms: Symptoms + ) = + toExternalFormat( + toSortedHistory(limitKeyCount(keys)), + transmissionRiskVectorDeterminator.determine(symptoms) + ) + + fun <T> limitKeyCount(keys: List<T>): List<T> = + keys.take(MAXIMUM_KEYS) + + fun toExternalFormat( + keys: List<TemporaryExposureKey>, + transmissionRiskVector: TransmissionRiskVector + ) = + keys.mapIndexed { index, key -> + // The latest key we receive is from yesterday (i.e. 1 day ago), + // thus we need use index+1 + keyConverter.toExternalFormat(key, transmissionRiskVector.getRiskValue(index + 1)) + } + + fun toSortedHistory(keys: List<TemporaryExposureKey>) = + keys.sortedWith(compareByDescending { it.rollingStartIntervalNumber }) + + companion object { + + private const val MAXIMUM_KEYS = 14 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/KeyConverter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/KeyConverter.kt new file mode 100644 index 0000000000000000000000000000000000000000..f50795ca52f4bdb3a31d6edb8b82aacb064ce721 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/KeyConverter.kt @@ -0,0 +1,12 @@ +package de.rki.coronawarnapp.submission + +import KeyExportFormat +import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey + +interface KeyConverter { + + fun toExternalFormat( + key: TemporaryExposureKey, + riskValue: Int + ): KeyExportFormat.TemporaryExposureKey +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e36a7d32b7cc173977a8a8a157d35efd0110a45 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt @@ -0,0 +1,21 @@ +package de.rki.coronawarnapp.submission + +data class Symptoms( + val startOfSymptoms: StartOf?, + val symptomIndication: Indication +) { + sealed class StartOf { + + data class Date(val millis: Long) : StartOf() + object LastSevenDays : StartOf() + object OneToTwoWeeksAgo : StartOf() + object MoreThanTwoWeeks : StartOf() + object NoInformation : StartOf() + } + + enum class Indication { + POSITIVE, + NEGATIVE, + NO_INFORMATION + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVector.kt new file mode 100644 index 0000000000000000000000000000000000000000..92fea5e3a939b15be08558ea1d5bb876a315dce3 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVector.kt @@ -0,0 +1,15 @@ +package de.rki.coronawarnapp.submission + +class TransmissionRiskVector(private val values: IntArray) { + + val raw: IntArray + get() = values + + fun getRiskValue(index: Int) = + if (index < values.size) values[index] else DEFAULT_TRANSMISSION_RISK_LEVEL + + companion object { + + private const val DEFAULT_TRANSMISSION_RISK_LEVEL = 1 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt new file mode 100644 index 0000000000000000000000000000000000000000..2908a339197fe986eb4142cef51e875c0a229749 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt @@ -0,0 +1,52 @@ +package de.rki.coronawarnapp.submission + +import org.joda.time.Duration +import org.joda.time.Instant + +class TransmissionRiskVectorDeterminator { + + @Suppress("MagicNumber") + fun determine(symptoms: Symptoms): TransmissionRiskVector = TransmissionRiskVector( + when (symptoms.symptomIndication) { + Symptoms.Indication.POSITIVE -> when (symptoms.startOfSymptoms) { + is Symptoms.StartOf.Date -> when ( + numberOfDays(symptoms.startOfSymptoms.millis)) { + 0 -> intArrayOf(8, 8, 7, 6, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1) + 1 -> intArrayOf(8, 8, 8, 7, 6, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1) + 2 -> intArrayOf(6, 8, 8, 8, 7, 6, 4, 2, 1, 1, 1, 1, 1, 1, 1) + 3 -> intArrayOf(5, 6, 8, 8, 8, 7, 6, 4, 2, 1, 1, 1, 1, 1, 1) + 4 -> intArrayOf(3, 5, 6, 8, 8, 8, 7, 6, 4, 2, 1, 1, 1, 1, 1) + 5 -> intArrayOf(2, 3, 5, 6, 8, 8, 8, 7, 6, 4, 2, 1, 1, 1, 1) + 6 -> intArrayOf(2, 2, 3, 5, 6, 8, 8, 8, 7, 6, 4, 2, 1, 1, 1) + 7 -> intArrayOf(1, 2, 2, 3, 5, 6, 8, 8, 8, 7, 6, 4, 2, 1, 1) + 8 -> intArrayOf(1, 1, 2, 2, 3, 5, 6, 8, 8, 8, 7, 6, 4, 2, 1) + 9 -> intArrayOf(1, 1, 1, 2, 2, 3, 5, 6, 8, 8, 8, 7, 6, 4, 2) + 10 -> intArrayOf(1, 1, 1, 1, 2, 2, 3, 5, 6, 8, 8, 8, 7, 6, 4) + 11 -> intArrayOf(1, 1, 1, 1, 1, 2, 2, 3, 5, 6, 8, 8, 8, 7, 6) + 12 -> intArrayOf(1, 1, 1, 1, 1, 1, 2, 2, 3, 5, 6, 8, 8, 8, 7) + 13 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 5, 6, 8, 8, 8) + 14 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 5, 6, 8, 8) + 15 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 5, 6, 8) + 16 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 5, 6) + 17 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 5) + 18 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3) + 19 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2) + 20 -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2) + else -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + } + is Symptoms.StartOf.LastSevenDays -> intArrayOf(4, 5, 6, 7, 7, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1) + is Symptoms.StartOf.MoreThanTwoWeeks -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5) + is Symptoms.StartOf.NoInformation -> intArrayOf(5, 6, 8, 8, 8, 7, 5, 3, 2, 1, 1, 1, 1, 1, 1) + is Symptoms.StartOf.OneToTwoWeeksAgo -> intArrayOf(1, 1, 1, 1, 2, 3, 4, 5, 6, 6, 7, 7, 6, 6, 4) + else -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + } + Symptoms.Indication.NEGATIVE -> intArrayOf(4, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + Symptoms.Indication.NO_INFORMATION -> intArrayOf(5, 6, 7, 7, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 1) + } + ) + + companion object { + fun numberOfDays(t0: Long, t1: Long = System.currentTimeMillis()) = + Duration(Instant.ofEpochMilli(t0), Instant.ofEpochMilli(t1)).standardDays.toInt() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt index 69598eedc3bdd08e445d4f317330285a3d009b84..38ff9dc344166b971806756daadcfe0c2bf5363a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.transaction +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.util.GoogleAPIVersion import javax.inject.Inject import javax.inject.Singleton @@ -8,5 +9,6 @@ import javax.inject.Singleton @Singleton data class RetrieveDiagnosisInjectionHelper @Inject constructor( val transactionScope: TransactionCoroutineScope, - val googleAPIVersion: GoogleAPIVersion + val googleAPIVersion: GoogleAPIVersion, + val cwaEnfClient: ENFClient ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt index ff77253438bce970b14a08837aadc11fcf1f5460..f2c0eb1499cf3fd69e225e44d910f1340c0fe0e5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt @@ -23,6 +23,7 @@ import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import de.rki.coronawarnapp.diagnosiskeys.download.KeyFileDownloader import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService import de.rki.coronawarnapp.storage.LocalData @@ -30,23 +31,17 @@ import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.Retriev import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.RetrieveDiagnosisKeysTransactionState.CLOSE import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.RetrieveDiagnosisKeysTransactionState.FETCH_DATE_UPDATE import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.RetrieveDiagnosisKeysTransactionState.FILES_FROM_WEB_REQUESTS -import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.RetrieveDiagnosisKeysTransactionState.QUOTA_CALCULATION import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.RetrieveDiagnosisKeysTransactionState.RETRIEVE_RISK_SCORE_PARAMS import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.RetrieveDiagnosisKeysTransactionState.SETUP import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.RetrieveDiagnosisKeysTransactionState.TOKEN import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.rollback import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.start import de.rki.coronawarnapp.util.CWADebug -import de.rki.coronawarnapp.util.GoogleAPIVersion -import de.rki.coronawarnapp.util.GoogleQuotaCalculator -import de.rki.coronawarnapp.util.QuotaCalculator import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.worker.BackgroundWorkHelper import org.joda.time.DateTime import org.joda.time.DateTimeZone -import org.joda.time.Duration import org.joda.time.Instant -import org.joda.time.chrono.GJChronology import timber.log.Timber import java.io.File import java.util.Date @@ -97,9 +92,6 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { /** Initial Setup of the Transaction and Transaction ID Generation and Date Lock */ SETUP, - /** calculates the Quota so that the rate limiting is caught gracefully*/ - QUOTA_CALCULATION, - /** Initialisation of the identifying token used during the entire transaction */ TOKEN, @@ -128,8 +120,6 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { /** atomic reference for the rollback value for created files during the transaction */ private val exportFilesForRollback = AtomicReference<List<File>>() - private val progressTowardsQuotaForRollback = AtomicReference<Int>() - private val transactionScope: TransactionCoroutineScope by lazy { AppInjector.component.transRetrieveKeysInjection.transactionScope } @@ -146,19 +136,8 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { var onKeyFilesDownloadStarted: (() -> Unit)? = null var onKeyFilesDownloadFinished: ((keyCount: Int, fileSize: Long) -> Unit)? = null - private const val QUOTA_RESET_PERIOD_IN_HOURS = 24 - - private val quotaCalculator: QuotaCalculator<Int> = GoogleQuotaCalculator( - incrementByAmount = 14, - quotaLimit = 20, - quotaResetPeriod = Duration.standardHours(QUOTA_RESET_PERIOD_IN_HOURS.toLong()), - quotaTimeZone = DateTimeZone.UTC, - quotaChronology = GJChronology.getInstanceUTC() - ) - - private val googleAPIVersion: GoogleAPIVersion by lazy { - AppInjector.component.transRetrieveKeysInjection.googleAPIVersion - } + private val enfClient: ENFClient + get() = AppInjector.component.transRetrieveKeysInjection.cwaEnfClient suspend fun startWithConstraints() { val currentDate = DateTime(Instant.now(), DateTimeZone.UTC) @@ -166,7 +145,6 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { LocalData.lastTimeDiagnosisKeysFromServerFetch(), DateTimeZone.UTC ) - if (LocalData.lastTimeDiagnosisKeysFromServerFetch() == null || currentDate.withTimeAtStartOfDay() != lastFetch.withTimeAtStartOfDay() ) { @@ -201,31 +179,14 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { ****************************************************/ val currentDate = executeSetup() - /**************************************************** - * CALCULATE QUOTA FOR PROVIDE DIAGNOSIS KEYS - ****************************************************/ - val hasExceededQuota = executeQuotaCalculation() - - // When we are above the Quote, cancel the execution entirely - if (hasExceededQuota) { - Timber.tag(TAG).w("above quota, skipping RetrieveDiagnosisKeys") - executeClose() - return@lockAndExecute - } - /**************************************************** * RETRIEVE TOKEN ****************************************************/ val token = executeToken() - /**************************************************** - * RETRIEVE RISK SCORE PARAMETERS - ****************************************************/ + // RETRIEVE RISK SCORE PARAMETERS val exposureConfiguration = executeRetrieveRiskScoreParams() - /**************************************************** - * FILES FROM WEB REQUESTS - ****************************************************/ val countries = requestedCountries ?: ApplicationConfigurationService .asyncRetrieveApplicationConfiguration() .supportedCountriesList @@ -235,14 +196,18 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { onKeyFilesDownloadStarted = null } - val keyFiles = executeFetchKeyFilesFromServer(countries) + val availableKeyFiles = executeFetchKeyFilesFromServer(countries) + + if (availableKeyFiles.isEmpty()) { + Timber.tag(TAG).w("No keyfiles were available!") + } if (CWADebug.isDebugBuildOrMode) { - val totalFileSize = keyFiles.fold(0L, { acc, file -> + val totalFileSize = availableKeyFiles.fold(0L, { acc, file -> file.length() + acc }) - onKeyFilesDownloadFinished?.invoke(keyFiles.size, totalFileSize) + onKeyFilesDownloadFinished?.invoke(availableKeyFiles.size, totalFileSize) onKeyFilesDownloadFinished = null } @@ -251,28 +216,19 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { onApiSubmissionStarted = null } - if (keyFiles.isNotEmpty()) { - /**************************************************** - * SUBMIT FILES TO API - ****************************************************/ - executeAPISubmission(token, keyFiles, exposureConfiguration) - } else { - Timber.tag(TAG).w("no key files, skipping submission to internal API.") - } + val isSubmissionSuccessful = executeAPISubmission( + exportFiles = availableKeyFiles, + exposureConfiguration = exposureConfiguration, + token = token + ) if (CWADebug.isDebugBuildOrMode) { onApiSubmissionFinished?.invoke() onApiSubmissionFinished = null } - /**************************************************** - * Fetch Date Update - ****************************************************/ - executeFetchDateUpdate(currentDate) + if (isSubmissionSuccessful) executeFetchDateUpdate(currentDate) - /**************************************************** - * CLOSE TRANSACTION - ****************************************************/ executeClose() } @@ -285,10 +241,6 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { if (TOKEN.isInStateStack()) { rollbackToken() } - // we reset the quota only if the submission has not happened yet - if (QUOTA_CALCULATION.isInStateStack() && !API_SUBMISSION.isInStateStack()) { - rollbackProgressTowardsQuota() - } } catch (e: Exception) { // We handle every exception through a RollbackException to make sure that a single EntryPoint // is available for the caller. @@ -306,11 +258,6 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { LocalData.googleApiToken(googleAPITokenForRollback.get()) } - private fun rollbackProgressTowardsQuota() { - Timber.tag(TAG).v("rollback $QUOTA_CALCULATION") - quotaCalculator.resetProgressTowardsQuota(progressTowardsQuotaForRollback.get()) - } - /** * Executes the INIT Transaction State */ @@ -321,16 +268,6 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { currentDate } - /** - * Executes the QUOTA_CALCULATION Transaction State - */ - private suspend fun executeQuotaCalculation() = executeState( - QUOTA_CALCULATION - ) { - progressTowardsQuotaForRollback.set(quotaCalculator.getProgressTowardsQuota()) - quotaCalculator.calculateQuota() - } - /** * Executes the TOKEN Transaction State */ @@ -359,34 +296,19 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { keyFileDownloader.asyncFetchKeyFiles(locationCodes) } - /** - * Executes the API_SUBMISSION Transaction State - * - * We currently use Batch Size 1 and thus submit multiple times to the API. - * This means that instead of directly submitting all files at once, we have to split up - * our file list as this equals a different batch for Google every time. - */ private suspend fun executeAPISubmission( token: String, exportFiles: Collection<File>, exposureConfiguration: ExposureConfiguration? - ) = executeState(API_SUBMISSION) { - if (googleAPIVersion.isAtLeast(GoogleAPIVersion.V16)) { - InternalExposureNotificationClient.asyncProvideDiagnosisKeys( - exportFiles, - exposureConfiguration, - token - ) - } else { - exportFiles.forEach { batch -> - InternalExposureNotificationClient.asyncProvideDiagnosisKeys( - listOf(batch), - exposureConfiguration, - token - ) - } - } - Timber.tag(TAG).d("Diagnosis Keys provided successfully, Token: $token") + ): Boolean = executeState(API_SUBMISSION) { + Timber.tag(TAG).d("Attempting submission to ENF") + val success = enfClient.provideDiagnosisKeys( + keyFiles = exportFiles, + configuration = exposureConfiguration, + token = token + ) + Timber.tag(TAG).d("Diagnosis Keys provided (success=%s, token=%s)", success, token) + return@executeState success } /** @@ -395,6 +317,7 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { private suspend fun executeFetchDateUpdate( currentDate: Date ) = executeState(FETCH_DATE_UPDATE) { + Timber.tag(TAG).d("executeFetchDateUpdate(currentDate=%s)", currentDate) LocalData.lastTimeDiagnosisKeysFromServerFetch(currentDate) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt index dddc3798253c03cee64fc09869c52d359c1e5ad0..ea6a4720f187cba0d66d1ea9916ce5152015838a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt @@ -4,12 +4,14 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.http.WebRequestBuilder import de.rki.coronawarnapp.http.playbook.PlaybookImpl import de.rki.coronawarnapp.service.submission.SubmissionService +import de.rki.coronawarnapp.submission.ExposureKeyHistoryCalculations +import de.rki.coronawarnapp.submission.DefaultKeyConverter +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.TransmissionRiskVectorDeterminator import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.CLOSE import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TAN_AND_SUBMIT_KEYS import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.STORE_SUCCESS -import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.limitKeyCount -import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat import de.rki.coronawarnapp.util.di.AppInjector /** @@ -55,14 +57,17 @@ object SubmitDiagnosisKeysTransaction : Transaction() { /** initiates the transaction. This suspend function guarantees a successful transaction once completed. */ suspend fun start( registrationToken: String, - keys: List<TemporaryExposureKey> + keys: List<TemporaryExposureKey>, + symptoms: Symptoms ) = lockAndExecute(unique = true, scope = transactionScope) { /**************************************************** * RETRIEVE TEMPORARY EXPOSURE KEY HISTORY ****************************************************/ val temporaryExposureKeyList = executeState(RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY) { - keys.limitKeyCount() - .transformKeyHistoryToExternalFormat() + ExposureKeyHistoryCalculations( + TransmissionRiskVectorDeterminator(), + DefaultKeyConverter() + ).transformToKeyHistoryInExternalFormat(keys, symptoms) } /**************************************************** * RETRIEVE TAN & SUBMIT KEYS diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/SingleLiveEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/SingleLiveEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..03fff86ccbc7ad804151e68875e07b093837d956 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/SingleLiveEvent.kt @@ -0,0 +1,67 @@ + +package de.rki.coronawarnapp.ui + +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import androidx.annotation.MainThread +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + * + * + * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + * + * + * Note that only one observer is going to be notified of changes. + */ +class SingleLiveEvent<T> : MutableLiveData<T>() { + private val pending = AtomicBoolean(false) + @MainThread + override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { + if (hasActiveObservers()) { + Timber + .w("Multiple observers registered but only one will be notified of changes.") + } + // Observe the internal MutableLiveData + super.observe(owner, Observer { t -> + if (pending.compareAndSet(true, false)) { + observer.onChanged(t) + } + }) + } + + @MainThread + override fun setValue(t: T?) { + pending.set(true) + super.setValue(t) + } + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + fun call() { + value = null + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..f9346c71339d9a40efa65926622fb2b837bca970 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarAdapter.kt @@ -0,0 +1,78 @@ +package de.rki.coronawarnapp.ui.calendar + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import de.rki.coronawarnapp.R +import org.joda.time.LocalDate + +/** + * Calendar adapter for recycler view + * + * @param clickListener (Day) -> Unit - on item click event listener + */ +class CalendarAdapter(private val clickListener: (Day) -> Unit) : + RecyclerView.Adapter<CalendarDayViewHolder>() { + + /** + * Mutable list of days + * + * @see Day + */ + private val data = mutableListOf<Day>() + + init { + setHasStableIds(true) + } + + /** + * Create new calendar day view holders + * + * @see CalendarDayViewHolder + */ + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CalendarDayViewHolder { + // Create a new view. + val v = LayoutInflater.from(viewGroup.context) + .inflate(R.layout.fragment_calendar_day, viewGroup, false) + + return CalendarDayViewHolder(v) + } + + /** + * Update calendar day view holders + * + * @see CalendarDayViewHolder.bind + */ + override fun onBindViewHolder(viewHolder: CalendarDayViewHolder, position: Int) { + viewHolder.bind(data[position], clickListener) + } + + /** + * Update days list and notify that data set was changed + * + * @see CalendarDayViewHolder.bind + */ + fun update(days: List<Day>) { + data.clear() + data.addAll(days) + notifyDataSetChanged() + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getItemCount(): Int { + return data.size + } + + /** + * Data class for calendar day + * + * @param date LocalDate + * @param isSelected Boolean + * + * @see LocalDate + */ + data class Day(val date: LocalDate, val isSelected: Boolean = false) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarCalculation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarCalculation.kt new file mode 100644 index 0000000000000000000000000000000000000000..a78f20525bdfb4c195b4115250264cbb3c27e248 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarCalculation.kt @@ -0,0 +1,121 @@ +package de.rki.coronawarnapp.ui.calendar + +import dagger.Reusable +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import org.joda.time.Instant +import org.joda.time.LocalDate +import java.util.Locale +import javax.inject.Inject + +@Reusable +class CalendarCalculation @Inject constructor() { + + /** + * Get month text view text + * + * Algorithm: + * Case 1: + * first date month != last date month + * first date year == last date year + * Result: September - October 2020 + * + * Case 2: + * first date month != last date month + * first date year != last date year + * Result: December 2020 - January 2021 + * + * Case 3: + * first date month == last date month + * Result: September 2020 + * + * NOTE: This algorithm does not cover same month, but different year - calendar has + * strict constant of 28 days to display, so this case would never happen. + * + * @param firstDate LocalDate - first displayed date + * @param lastDate LocalDate - last displayed date + * + * @return String + * + * @see StringBuilder + */ + fun getMonthText(firstDate: LocalDate, lastDate: LocalDate): String { + val monthText = StringBuilder() + // Append first date month as it would always be displayed + monthText.append(firstDate.monthOfYear().getAsText(Locale.getDefault())) + if (firstDate.monthOfYear() != lastDate.monthOfYear()) { + // Different month + if (firstDate.year() == lastDate.year()) { + // Same year (Case 1) + monthText.append(" - ") + .append(lastDate.monthOfYear().getAsText(Locale.getDefault())) + } else { + // Different year (Case 2) + monthText.append(" ") + .append(firstDate.year().get()) + .append(" - ") + .append(lastDate.monthOfYear().getAsText(Locale.getDefault())) + } + // Append last date year + monthText.append(" ") + .append(lastDate.year().get()) + } else { + // Same month + monthText.append(" ") + .append(firstDate.year().get()) + } + return monthText.toString() + } + + /** + * Calculate dates for calendar + * Input constants: + * - 4 Weeks (TotalWeeks) + * - 7 Days (DaysInWeekCount) + * - Current week - last row + * - Week starts from Monday + * + * Algorithm: + * Goal: calculate days to add with JodaTime lib to current date + * + * Input: Today = 9 September (Wednesday) + * + * Step 1: Define day shift in the week + * |_M_|_T_|_W_|_T_|_F_|_S_|_S_| + * | -2| -1| 9 | +1| +2| +3| +4| <- Current Week (4th row) + * Code: (CurrentDayOfTheWeek * -1) + dayId + * + * Step 2: Apply week shift + * |_M_|_T_|_W_|_T_|_F_|_S_|_S_| + * | -9| -8| -7| -6| -5| -4| -3| <- Previous Week (3d row) + * | -2| -1| 9 | +1| +2| +3| +4| <- Current Week (4th row) + * Code: (DaysInWeekCount * (TotalWeeks - weekId)) * -1 + */ + fun getDates(currentDate: DateTime = DateTime(Instant.now(), DateTimeZone.UTC)): List<CalendarAdapter.Day> { + // Create mutable list of DateTime as a result + val result = mutableListOf<CalendarAdapter.Day>() + // Get current day of the week (where 1 = Monday, 7 = Sunday) + val currentDayOfTheWeek = currentDate.dayOfWeek().get() + // Week count + val weeksCount = WEEKS_COUNT - 1 + for (weekId in 0..weeksCount) { + for (dayId in 1..DAYS_IN_WEEK) { + val daysDiff = (currentDayOfTheWeek * -1) + dayId - (DAYS_IN_WEEK * (weeksCount - weekId)) + result.add(CalendarAdapter.Day(currentDate.plusDays(daysDiff).toLocalDate())) + } + } + return result + } + + companion object { + /** + * Total days in week + */ + const val DAYS_IN_WEEK = 7 + + /** + * Weeks count + */ + private const val WEEKS_COUNT = 4 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarDayViewHolder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarDayViewHolder.kt new file mode 100644 index 0000000000000000000000000000000000000000..393f585c04f71bb7fabbd91de19db2fbf6fd05bb --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarDayViewHolder.kt @@ -0,0 +1,70 @@ +package de.rki.coronawarnapp.ui.calendar + +import android.view.View +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import de.rki.coronawarnapp.R +import org.joda.time.LocalDate +import org.joda.time.format.DateTimeFormat + +/** + * Calendar day view holder + * + * @param v View - view for holder + */ +class CalendarDayViewHolder(v: View) : RecyclerView.ViewHolder(v) { + + /** + * Day text view + */ + private val textView: TextView = v.findViewById(R.id.dayText) + + /** + * Accessibility talk back date format + */ + private val talkBackDateFormat = DateTimeFormat.forPattern("EEEE d MMMMM") + + /** + * Bind data to view + */ + fun bind(day: CalendarAdapter.Day, clickListener: (CalendarAdapter.Day) -> Unit) { + val context = textView.context + val today = LocalDate.now() + + // Set day text + textView.text = day.date.dayOfMonth.toString() + + // Set day content description for talk back + textView.contentDescription = day.date.toString(talkBackDateFormat) + + // If date is after today - then disable click listener + if (!day.date.isAfter(today)) { + textView.setOnClickListener { clickListener(day) } + } + + // Update visuals + when { + // Selected + day.isSelected -> { + textView.setBackgroundResource(R.drawable.calendar_selected_day_back) + textView.setTextColor(ContextCompat.getColor(context, R.color.colorTextEmphasizedButton)) + } + // Today + day.date.isEqual(today) -> { + textView.setBackgroundResource(R.drawable.calendar_today_back) + textView.setTextColor(ContextCompat.getColor(context, R.color.colorCalendarTodayText)) + } + // Future + day.date.isAfter(today) -> { + textView.setBackgroundResource(0) + textView.setTextColor(ContextCompat.getColor(context, R.color.colorTextPrimary3)) + } + // Past + day.date.isBefore(today) -> { + textView.setBackgroundResource(0) + textView.setTextColor(ContextCompat.getColor(context, R.color.colorTextPrimary1)) + } + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarView.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarView.kt new file mode 100644 index 0000000000000000000000000000000000000000..c5045db27e35fb1f32c8b9f943644bd4d81863ea --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarView.kt @@ -0,0 +1,199 @@ +package de.rki.coronawarnapp.ui.calendar + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import de.rki.coronawarnapp.R +import org.joda.time.DateTime +import org.joda.time.Instant +import org.joda.time.LocalDate +import java.util.Locale + +/** + * Custom calendar view with rules: + * - 4 Weeks + * - 7 Days + * - Current week - last row + * - Week starts from Monday + */ +class CalendarView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + + /** + * Calendar layout + */ + private var calendarLayout: LinearLayout + + /** + * Calendar header + */ + private var headerTextView: TextView + + /** + * Recycler view for dates + * + * @see RecyclerView + */ + private var recyclerView: RecyclerView + + /** + * Layout manager for recycler view + * + * @see RecyclerView.LayoutManager + */ + private var layoutManager: RecyclerView.LayoutManager + + /** + * Mutable list of Day + * + * @see CalendarAdapter.Day + */ + private val days = mutableListOf<CalendarAdapter.Day>() + + /** + * Recycler view adapter + * + * @see CalendarAdapter + */ + private lateinit var adapter: CalendarAdapter + + /** + * Fragment click listener + */ + private var listener: ((LocalDate?) -> Unit)? = null + + /** + * On item click event listener + * + * @see CalendarAdapter.update + * @see updateSelection + */ + private val onItemClickListener: (CalendarAdapter.Day) -> Unit = { selectedDay -> + // Update data set + val updateData = days.map { oldDay -> oldDay.copy(isSelected = selectedDay == oldDay) } + // Update selection + updateSelection(updateData.any { it.isSelected }) + + adapter.update(updateData) + + // Invoke fragment on click + listener?.invoke(updateData.find { it.isSelected }?.date) + } + + /** + * Unset selection of each date shown + * + * @see CalendarAdapter.update + */ + fun unsetSelection() { + val updateData = days.map { oldDay -> oldDay.copy(isSelected = false) } + updateSelection(false) + adapter.update(updateData) + } + + init { + LayoutInflater.from(context) + .inflate(R.layout.fragment_calendar, this, true) + + // Get linear layout + calendarLayout = findViewById<LinearLayout>(R.id.calendar_layout) + + // Get header view + headerTextView = findViewById<TextView>(R.id.calendar_header) + + // Get recycler view + recyclerView = findViewById<RecyclerView>(R.id.calendar_recycler_view) + + // Create layout manager + layoutManager = LinearLayoutManager(context) + // Set to grid layout + layoutManager = GridLayoutManager(context, CalendarCalculation.DAYS_IN_WEEK) + + with(recyclerView) { + layoutManager = this@CalendarView.layoutManager + scrollToPosition(0) + } + + // Calculate dates to display + days.addAll(CalendarCalculation().getDates()) + + // Set calendar adapter as adapter for recycler view + adapter = CalendarAdapter(onItemClickListener) + adapter.update(days) + + recyclerView.adapter = adapter + + // Setup day legend + setUpDayLegend(this) + + // Setup month + setUpMonthTextView(this) + } + + /** + * Set fragment click listener + * + * @see listener + */ + fun setDateSelectedListener(listener: (LocalDate?) -> Unit) { + this.listener = listener + } + + /** + * Update header and top level layout background + */ + private fun updateSelection(isSelected: Boolean) { + calendarLayout.isSelected = isSelected + headerTextView.isSelected = isSelected + } + + /** + * SetUp day legend (week day) + * + * NOTE: DaysOfWeek is impossible to use due to API 23 + * + * @param view View - CalendarView + */ + private fun setUpDayLegend(view: View) { + // Get day legend layout + val dayLegendLayout = findViewById<LinearLayout>(R.id.calendar_day_legend) + // Get current week day + val date = LocalDate() + val currentWeekDay = DateTime(Instant.now()).dayOfWeek().get() + for (dayId in 1..CalendarCalculation.DAYS_IN_WEEK) { + val dayOfWeek = CalendarWeekDayView(context) + val weekDay = date.withDayOfWeek(dayId).dayOfWeek() + // weekDay.getAsText returns in either "Fri" or "Friday" format, substring first latter + dayOfWeek.setUp(weekDay.getAsText(Locale.getDefault()).take(1), + weekDay.get() == currentWeekDay) + dayLegendLayout.addView(dayOfWeek) + } + } + + /** + * SetUp month text view + * + * @param view View - CalendarView + * + * @see CalendarCalculation.getMonthText + */ + private fun setUpMonthTextView(view: View) { + // Get month text view + val monthTextView = findViewById<TextView>(R.id.calendar_month) + + // Get first and last days + val firstDate = days.first().date + val lastDate = days.last().date + + monthTextView.text = CalendarCalculation().getMonthText(firstDate, lastDate) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarWeekDayView.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarWeekDayView.kt new file mode 100644 index 0000000000000000000000000000000000000000..52ff504e3d219e5fee43e5c51e86c5dea38a4afe --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/calendar/CalendarWeekDayView.kt @@ -0,0 +1,51 @@ +package de.rki.coronawarnapp.ui.calendar + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import android.widget.TextView +import de.rki.coronawarnapp.R + +/** + * Week day custom view + */ +class CalendarWeekDayView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + + private val textView: TextView + + /** + * Initialize the view + * + * Get TextView for day setup + * SetUp layout params + */ + init { + LayoutInflater.from(context) + .inflate(R.layout.fragment_calendar_day, this, true) + textView = findViewById(R.id.dayText) + + layoutParams = LayoutParams( + 0, + LayoutParams.WRAP_CONTENT, + 1.0f + ) + } + + /** + * SetUp the view from CalendarFragment + */ + fun setUp(text: String, isSelected: Boolean = false) { + textView.text = text + + if (isSelected) { + textView.setTextAppearance(R.style.calendarWeekDaySelected) + } else { + textView.setTextAppearance(R.style.calendarWeekDayNormal) + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..cc4cc4b727fe926c05916149d053dcaa33a93e5b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragment.kt @@ -0,0 +1,154 @@ +package de.rki.coronawarnapp.ui.submission + +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentSubmissionSymptomCalendarBinding +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.ui.doNavigate +import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel +import de.rki.coronawarnapp.util.formatter.formatCalendarBackgroundButtonStyleByState +import de.rki.coronawarnapp.util.formatter.formatCalendarButtonStyleByState +import de.rki.coronawarnapp.util.formatter.isEnableSymptomCalendarButtonByState + +class SubmissionSymptomCalendarFragment : Fragment() { + + private var _binding: FragmentSubmissionSymptomCalendarBinding? = null + private val binding: FragmentSubmissionSymptomCalendarBinding get() = _binding!! + private val submissionViewModel: SubmissionViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentSubmissionSymptomCalendarBinding.inflate(inflater) + binding.submissionViewModel = submissionViewModel + binding.lifecycleOwner = this + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setButtonOnClickListener() + + binding.symptomCalendarContainer.setDateSelectedListener(submissionViewModel::onDateSelected) + + submissionViewModel.symptomCalendarEvent.observe(viewLifecycleOwner, Observer { + when (it) { + is SymptomCalendarEvent.NavigateToNext -> navigateToSymptomFinish() + is SymptomCalendarEvent.NavigateToPrevious -> navigateToPreviousScreen() + } + }) + + submissionViewModel.symptomStart.observe(viewLifecycleOwner, Observer { + updateButtons(it) + if (it !is Symptoms.StartOf.Date) { + binding.symptomCalendarContainer.unsetSelection() + } + }) + + submissionViewModel.initSymptomStart() + } + + private fun updateButtons(symptomStart: Symptoms.StartOf?) { + binding.symptomCalendarChoiceSelection.calendarButtonSevenDays + .findViewById<Button>(R.id.calendar_button_seven_days) + .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.LastSevenDays)) + binding.symptomCalendarChoiceSelection.targetLayout + .findViewById<Button>(R.id.calendar_button_seven_days).backgroundTintList = + ColorStateList.valueOf( + formatCalendarBackgroundButtonStyleByState( + symptomStart, Symptoms.StartOf.LastSevenDays + ) + ) + + binding.symptomCalendarChoiceSelection.targetLayout + .findViewById<Button>(R.id.calendar_button_one_two_weeks) + .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.OneToTwoWeeksAgo)) + binding.symptomCalendarChoiceSelection.targetLayout + .findViewById<Button>(R.id.calendar_button_one_two_weeks).backgroundTintList = + ColorStateList.valueOf( + formatCalendarBackgroundButtonStyleByState( + symptomStart, Symptoms.StartOf.OneToTwoWeeksAgo + ) + ) + + binding.symptomCalendarChoiceSelection.targetLayout + .findViewById<Button>(R.id.calendar_button_more_than_two_weeks) + .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.MoreThanTwoWeeks)) + binding.symptomCalendarChoiceSelection.targetLayout + .findViewById<Button>(R.id.calendar_button_more_than_two_weeks).backgroundTintList = + ColorStateList.valueOf( + formatCalendarBackgroundButtonStyleByState( + symptomStart, Symptoms.StartOf.MoreThanTwoWeeks + ) + ) + + binding.symptomCalendarChoiceSelection.targetLayout + .findViewById<Button>(R.id.target_button_verify) + .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.NoInformation)) + binding.symptomCalendarChoiceSelection.targetLayout + .findViewById<Button>(R.id.target_button_verify).backgroundTintList = + ColorStateList.valueOf( + formatCalendarBackgroundButtonStyleByState( + symptomStart, Symptoms.StartOf.NoInformation + ) + ) + + binding + .symptomButtonNext.findViewById<Button>(R.id.symptom_button_next).isEnabled = + isEnableSymptomCalendarButtonByState( + symptomStart + ) + } + + private fun navigateToSymptomFinish() { + findNavController().doNavigate(SubmissionSymptomCalendarFragmentDirections + .actionSubmissionSymptomCalendarFragmentToSubmissionResultPositiveOtherWarningFragment()) + } + + private fun navigateToPreviousScreen() { + findNavController().doNavigate(SubmissionSymptomCalendarFragmentDirections + .actionSubmissionCalendarFragmentToSubmissionSymptomIntroductionFragment()) + } + + private fun setButtonOnClickListener() { + binding + .submissionSymptomCalendarHeader.headerButtonBack.buttonIcon + .setOnClickListener { submissionViewModel.onCalendarPreviousClicked() } + + binding + .symptomButtonNext + .setOnClickListener { submissionViewModel.onCalendarNextClicked() } + + binding.symptomCalendarChoiceSelection + .calendarButtonSevenDays + .setOnClickListener { submissionViewModel.onLastSevenDaysStart() } + + binding.symptomCalendarChoiceSelection + .calendarButtonOneTwoWeeks + .setOnClickListener { submissionViewModel.onOneToTwoWeeksAgoStart() } + + binding.symptomCalendarChoiceSelection + .calendarButtonMoreThanTwoWeeks + .setOnClickListener { submissionViewModel.onMoreThanTwoWeeksStart() } + + binding.symptomCalendarChoiceSelection + .targetButtonVerify + .setOnClickListener { submissionViewModel.onNoInformationStart() } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroductionFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroductionFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..dd6042ec6663217dd340b46833e6fa59d4b844aa --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroductionFragment.kt @@ -0,0 +1,145 @@ +package de.rki.coronawarnapp.ui.submission + +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentSubmissionSymptomIntroBinding +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.ui.doNavigate +import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel +import de.rki.coronawarnapp.util.formatter.formatBackgroundButtonStyleByState +import de.rki.coronawarnapp.util.formatter.formatButtonStyleByState +import de.rki.coronawarnapp.util.formatter.isEnableSymptomIntroButtonByState + +class SubmissionSymptomIntroductionFragment : Fragment() { + + private var _binding: FragmentSubmissionSymptomIntroBinding? = null + private val binding: FragmentSubmissionSymptomIntroBinding get() = _binding!! + private val submissionViewModel: SubmissionViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentSubmissionSymptomIntroBinding.inflate(inflater) + binding.submissionViewModel = submissionViewModel + binding.lifecycleOwner = this + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setButtonOnClickListener() + + submissionViewModel.symptomIntroductionEvent.observe(viewLifecycleOwner, Observer { + when (it) { + is SymptomIntroductionEvent.NavigateToSymptomCalendar -> navigateToNext() + is SymptomIntroductionEvent.NavigateToPreviousScreen -> navigateToPreviousScreen() + } + }) + + submissionViewModel.symptomIndication.observe(viewLifecycleOwner, Observer { + updateButtons(it) + }) + + submissionViewModel.initSymptoms() + } + + private fun updateButtons(symptomIndication: Symptoms.Indication?) { + binding.submissionSymptomContainer.findViewById<Button>(R.id.target_button_apply) + .setTextColor(formatButtonStyleByState(symptomIndication, Symptoms.Indication.POSITIVE)) + binding.submissionSymptomContainer.findViewById<Button>(R.id.target_button_apply).backgroundTintList = + ColorStateList.valueOf( + formatBackgroundButtonStyleByState( + symptomIndication, + Symptoms.Indication.POSITIVE + ) + ) + binding.submissionSymptomContainer.findViewById<Button>(R.id.target_button_reject) + .setTextColor(formatButtonStyleByState(symptomIndication, Symptoms.Indication.NEGATIVE)) + binding.submissionSymptomContainer.findViewById<Button>(R.id.target_button_reject).backgroundTintList = + ColorStateList.valueOf( + formatBackgroundButtonStyleByState( + symptomIndication, + Symptoms.Indication.NEGATIVE + ) + ) + binding.submissionSymptomContainer.findViewById<Button>(R.id.target_button_verify) + .setTextColor( + formatButtonStyleByState( + symptomIndication, + Symptoms.Indication.NO_INFORMATION + ) + ) + binding.submissionSymptomContainer.findViewById<Button>(R.id.target_button_verify).backgroundTintList = + ColorStateList.valueOf( + formatBackgroundButtonStyleByState( + symptomIndication, + Symptoms.Indication.NO_INFORMATION + ) + ) + binding + .symptomButtonNext.findViewById<Button>(R.id.symptom_button_next).isEnabled = + isEnableSymptomIntroButtonByState( + symptomIndication + ) + } + + private fun navigateToNext() { + + if (submissionViewModel.symptomIndication.value!! == Symptoms.Indication.POSITIVE) { + findNavController().doNavigate( + SubmissionSymptomIntroductionFragmentDirections + .actionSubmissionSymptomIntroductionFragmentToSubmissionSymptomCalendarFragment() + ) + } else { + findNavController().doNavigate( + SubmissionSymptomIntroductionFragmentDirections + .actionSubmissionSymptomIntroductionFragmentToSubmissionResultPositiveOtherWarningFragment() + ) + } + } + + private fun navigateToPreviousScreen() { + findNavController().doNavigate( + SubmissionSymptomIntroductionFragmentDirections + .actionSubmissionSymptomIntroductionFragmentToSubmissionResultFragment() + ) + } + + private fun setButtonOnClickListener() { + binding + .submissionSymptomHeader.headerButtonBack.buttonIcon + .setOnClickListener { submissionViewModel.onPreviousClicked() } + + binding + .symptomButtonNext + .setOnClickListener { submissionViewModel.onNextClicked() } + + binding + .symptomChoiceSelection.targetButtonApply + .setOnClickListener { submissionViewModel.onPositiveSymptomIndication() } + + binding + .symptomChoiceSelection.targetButtonReject + .setOnClickListener { submissionViewModel.onNegativeSymptomIndication() } + + binding + .symptomChoiceSelection.targetButtonVerify + .setOnClickListener { submissionViewModel.onNoInformationSymptomIndication() } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SymptomCalendarEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SymptomCalendarEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..922d0ff4c44846e25266eb7093fa67b3f843422d --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SymptomCalendarEvent.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.ui.submission + +sealed class SymptomCalendarEvent { + object NavigateToNext : SymptomCalendarEvent() + object NavigateToPrevious : SymptomCalendarEvent() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SymptomIntroductionEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SymptomIntroductionEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..96eb49ef2e3e6ba19beee220f6276b51c5ad7a73 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SymptomIntroductionEvent.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.ui.submission + +sealed class SymptomIntroductionEvent { + object NavigateToSymptomCalendar : SymptomIntroductionEvent() + object NavigateToPreviousScreen : SymptomIntroductionEvent() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt index cc109856925f49d24993b308bafea5a2bbe672a8..f1491771d7a10538e5ddb3b6709b24f5acae2b3a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt @@ -173,7 +173,7 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_ findNavController().doNavigate( SubmissionTestResultFragmentDirections - .actionSubmissionResultFragmentToSubmissionResultPositiveOtherWarningFragment() + .actionSubmissionResultFragmentToSubmissionSymptomIntroductionFragment() ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt index c5f670cb06796888458824bdbeb98cc15d6bee50..c40ebc853875ddd53c2b390da228f5a4a7240ec7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt @@ -13,11 +13,17 @@ import de.rki.coronawarnapp.service.submission.QRScanResult import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.ui.SingleLiveEvent import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.ui.submission.ScanStatus +import de.rki.coronawarnapp.ui.submission.SymptomCalendarEvent +import de.rki.coronawarnapp.ui.submission.SymptomIntroductionEvent import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.Event import kotlinx.coroutines.launch +import org.joda.time.LocalDate +import timber.log.Timber import java.util.Date class SubmissionViewModel : ViewModel() { @@ -34,6 +40,9 @@ class SubmissionViewModel : ViewModel() { val scanStatus: LiveData<Event<ScanStatus>> = _scanStatus + val symptomIntroductionEvent: SingleLiveEvent<SymptomIntroductionEvent> = SingleLiveEvent() + val symptomCalendarEvent: SingleLiveEvent<SymptomCalendarEvent> = SingleLiveEvent() + val registrationState: LiveData<Event<ApiRequestState>> = _registrationState val registrationError: LiveData<Event<CwaWebException>> = _registrationError @@ -50,24 +59,44 @@ class SubmissionViewModel : ViewModel() { val deviceUiState: LiveData<DeviceUIState> = SubmissionRepository.deviceUIState - fun submitDiagnosisKeys(keys: List<TemporaryExposureKey>) = viewModelScope.launch { - try { - _submissionState.value = ApiRequestState.STARTED - SubmissionService.asyncSubmitExposureKeys(keys) - _submissionState.value = ApiRequestState.SUCCESS - } catch (err: CwaWebException) { - _submissionError.value = Event(err) - _submissionState.value = ApiRequestState.FAILED - } catch (err: TransactionException) { - if (err.cause is CwaWebException) { - _submissionError.value = Event(err.cause) - } else { - err.report(ExceptionCategory.INTERNAL) + val symptomIndication = MutableLiveData<Symptoms.Indication?>() + val symptomStart = MutableLiveData<Symptoms.StartOf?>() + + fun initSymptoms() { + symptomIndication.postValue(null) + } + + fun initSymptomStart() { + symptomStart.postValue(null) + } + + fun submitDiagnosisKeys(keys: List<TemporaryExposureKey>) { + val indication = symptomIndication.value + if (indication == null) { + Timber.w("symptoms indicator is null") + return + } + Symptoms(symptomStart.value, indication).also { + viewModelScope.launch { + try { + _submissionState.value = ApiRequestState.STARTED + SubmissionService.asyncSubmitExposureKeys(keys, it) + _submissionState.value = ApiRequestState.SUCCESS + } catch (err: CwaWebException) { + _submissionError.value = Event(err) + _submissionState.value = ApiRequestState.FAILED + } catch (err: TransactionException) { + if (err.cause is CwaWebException) { + _submissionError.value = Event(err.cause) + } else { + err.report(ExceptionCategory.INTERNAL) + } + _submissionState.value = ApiRequestState.FAILED + } catch (err: Exception) { + _submissionState.value = ApiRequestState.FAILED + err.report(ExceptionCategory.INTERNAL) + } } - _submissionState.value = ApiRequestState.FAILED - } catch (err: Exception) { - _submissionState.value = ApiRequestState.FAILED - err.report(ExceptionCategory.INTERNAL) } } @@ -142,4 +171,52 @@ class SubmissionViewModel : ViewModel() { } } } + + fun onNextClicked() { + symptomIntroductionEvent.postValue(SymptomIntroductionEvent.NavigateToSymptomCalendar) + } + + fun onPreviousClicked() { + symptomIntroductionEvent.postValue(SymptomIntroductionEvent.NavigateToPreviousScreen) + } + + fun onCalendarNextClicked() { + symptomCalendarEvent.postValue(SymptomCalendarEvent.NavigateToNext) + } + + fun onCalendarPreviousClicked() { + symptomCalendarEvent.postValue(SymptomCalendarEvent.NavigateToPrevious) + } + + fun onPositiveSymptomIndication() { + symptomIndication.postValue(Symptoms.Indication.POSITIVE) + } + + fun onNegativeSymptomIndication() { + symptomIndication.postValue(Symptoms.Indication.NEGATIVE) + } + + fun onNoInformationSymptomIndication() { + symptomIndication.postValue(Symptoms.Indication.NO_INFORMATION) + } + + fun onLastSevenDaysStart() { + symptomStart.postValue(Symptoms.StartOf.LastSevenDays) + } + + fun onOneToTwoWeeksAgoStart() { + symptomStart.postValue(Symptoms.StartOf.OneToTwoWeeksAgo) + } + + fun onMoreThanTwoWeeksStart() { + symptomStart.postValue(Symptoms.StartOf.MoreThanTwoWeeks) + } + + fun onNoInformationStart() { + symptomStart.postValue(Symptoms.StartOf.NoInformation) + } + + fun onDateSelected(localDate: LocalDate?) { + symptomStart.postValue(if (localDate == null) null else Symptoms.StartOf.Date(localDate.toDate().time)) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleQuotaCalculator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleQuotaCalculator.kt deleted file mode 100644 index e7aace190ed9a6217487d3f76b6356c07c6aa267..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleQuotaCalculator.kt +++ /dev/null @@ -1,62 +0,0 @@ -package de.rki.coronawarnapp.util - -import de.rki.coronawarnapp.storage.LocalData -import org.joda.time.Chronology -import org.joda.time.DateTime -import org.joda.time.DateTimeZone -import org.joda.time.Duration -import org.joda.time.Instant - -/** - * This Calculator class takes multiple parameters to check if the Google API - * can be called or the Rate Limit has been reached. The Quota is expected to reset at - * the start of the day in the given timeZone and Chronology - * - * @property incrementByAmount The amount of Quota Calls to increment per Call - * @property quotaLimit The maximum amount of Quota Calls allowed before Rate Limiting - * @property quotaResetPeriod The Period after which the Quota Resets - * @property quotaTimeZone The Timezone to work in - * @property quotaChronology The Chronology to work in - */ -class GoogleQuotaCalculator( - val incrementByAmount: Int, - val quotaLimit: Int, - val quotaResetPeriod: Duration, - val quotaTimeZone: DateTimeZone, - val quotaChronology: Chronology -) : QuotaCalculator<Int> { - override var hasExceededQuota: Boolean = false - - override fun calculateQuota(): Boolean { - if (Instant.now().isAfter(LocalData.nextTimeRateLimitingUnlocks)) { - LocalData.nextTimeRateLimitingUnlocks = DateTime - .now(quotaTimeZone) - .withChronology(quotaChronology) - .plus(quotaResetPeriod) - .withTimeAtStartOfDay() - .toInstant() - LocalData.googleAPIProvideDiagnosisKeysCallCount = 0 - } - - if (LocalData.googleAPIProvideDiagnosisKeysCallCount <= quotaLimit) { - LocalData.googleAPIProvideDiagnosisKeysCallCount += incrementByAmount - } - - hasExceededQuota = LocalData.googleAPIProvideDiagnosisKeysCallCount > quotaLimit - - return hasExceededQuota - } - - override fun resetProgressTowardsQuota(newProgress: Int) { - if (newProgress > quotaLimit) { - throw IllegalArgumentException("cannot reset progress to a value higher than the quota limit") - } - if (newProgress % incrementByAmount != 0) { - throw IllegalArgumentException("supplied progress is no multiple of $incrementByAmount") - } - LocalData.googleAPIProvideDiagnosisKeysCallCount = newProgress - hasExceededQuota = false - } - - override fun getProgressTowardsQuota(): Int = LocalData.googleAPIProvideDiagnosisKeysCallCount -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt index b7597b2cafa3faa737ac5786c8aed84aaf0deedb..6f3db1758c31fec40607709d11f494f9bd006f25 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt @@ -1,55 +1,10 @@ package de.rki.coronawarnapp.util import KeyExportFormat -import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey -import com.google.protobuf.ByteString import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange object ProtoFormatConverterExtensions { - private const val ROLLING_PERIOD = 144 - private const val DEFAULT_TRANSMISSION_RISK_LEVEL = 1 - private const val TRANSMISSION_RISK_DAY_0 = 5 - private const val TRANSMISSION_RISK_DAY_1 = 6 - private const val TRANSMISSION_RISK_DAY_2 = 8 - private const val TRANSMISSION_RISK_DAY_3 = 8 - private const val TRANSMISSION_RISK_DAY_4 = 8 - private const val TRANSMISSION_RISK_DAY_5 = 5 - private const val TRANSMISSION_RISK_DAY_6 = 3 - private const val TRANSMISSION_RISK_DAY_7 = 1 - private val DEFAULT_TRANSMISSION_RISK_VECTOR = intArrayOf( - TRANSMISSION_RISK_DAY_0, - TRANSMISSION_RISK_DAY_1, - TRANSMISSION_RISK_DAY_2, - TRANSMISSION_RISK_DAY_3, - TRANSMISSION_RISK_DAY_4, - TRANSMISSION_RISK_DAY_5, - TRANSMISSION_RISK_DAY_6, - TRANSMISSION_RISK_DAY_7 - ) - private const val MAXIMUM_KEYS = 14 - - fun List<TemporaryExposureKey>.limitKeyCount() = - this.sortedWith(compareByDescending { it.rollingStartIntervalNumber }).take(MAXIMUM_KEYS) - - fun List<TemporaryExposureKey>.transformKeyHistoryToExternalFormat() = - this.sortedWith(compareByDescending { it.rollingStartIntervalNumber }) - .mapIndexed { index, it -> - // The latest key we receive is from yesterday (i.e. 1 day ago), - // thus we need use index+1 - val riskValue = - if (index + 1 <= DEFAULT_TRANSMISSION_RISK_VECTOR.lastIndex) - DEFAULT_TRANSMISSION_RISK_VECTOR[index + 1] - else - DEFAULT_TRANSMISSION_RISK_LEVEL - KeyExportFormat.TemporaryExposureKey.newBuilder() - .setKeyData(ByteString.readFrom(it.keyData.inputStream())) - .setRollingStartIntervalNumber(it.rollingStartIntervalNumber) - .setRollingPeriod(ROLLING_PERIOD) - .setTransmissionRiskLevel(riskValue) - .build() - } - fun AppleLegacyKeyExchange.Key.convertToGoogleKey(): KeyExportFormat.TemporaryExposureKey = KeyExportFormat.TemporaryExposureKey.newBuilder() .setKeyData(this.keyData) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/QuotaCalculator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/QuotaCalculator.kt deleted file mode 100644 index 682f4a6002b06ef548e04da1c68012372f3c6743..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/QuotaCalculator.kt +++ /dev/null @@ -1,29 +0,0 @@ -package de.rki.coronawarnapp.util - -/** - * Class to check if a Quota has been reached based on the calculation done inside - * the Calculator - * - */ -interface QuotaCalculator<T> { - val hasExceededQuota: Boolean - - /** - * This function is called to recalculate an old quota score - */ - fun calculateQuota(): Boolean - - /** - * Reset the quota progress - * - * @param newProgress new progress towards the quota - */ - fun resetProgressTowardsQuota(newProgress: T) - - /** - * Retrieve the current progress towards the quota - * - * @return current progress count - */ - fun getProgressTowardsQuota(): T -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/FileLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/FileLogger.kt new file mode 100644 index 0000000000000000000000000000000000000000..eb3b29093e4a6c772d65dd7167ecbeda941588e0 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/FileLogger.kt @@ -0,0 +1,40 @@ +package de.rki.coronawarnapp.util.debug + +import android.content.Context +import timber.log.Timber +import java.io.File + +class FileLogger constructor(private val context: Context) { + + val logFile = File(context.cacheDir, "FileLoggerTree.log") + val triggerFile = File(context.filesDir, "FileLoggerTree.trigger") + private var loggerTree: FileLoggerTree? = null + + val isLogging: Boolean + get() = loggerTree != null + + init { + if (triggerFile.exists()) { + start() + } + } + + fun start() { + if (loggerTree != null) return + + loggerTree = FileLoggerTree(logFile).also { + Timber.plant(it) + it.start() + triggerFile.createNewFile() + } + } + + fun stop() { + loggerTree?.let { + it.stop() + logFile.delete() + triggerFile.delete() + loggerTree = null + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/FileLoggerTree.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/FileLoggerTree.kt new file mode 100644 index 0000000000000000000000000000000000000000..84bd21ad68562197d35c5e136283ccd22ef73f7f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/FileLoggerTree.kt @@ -0,0 +1,86 @@ +package de.rki.coronawarnapp.util.debug + +import android.annotation.SuppressLint +import android.util.Log +import timber.log.Timber +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStreamWriter + +@SuppressLint("LogNotTimber") +class FileLoggerTree(private val logFile: File) : Timber.DebugTree() { + private var logWriter: OutputStreamWriter? = null + + @SuppressLint("SetWorldReadable") + @Synchronized + fun start() { + if (logWriter != null) return + + logFile.parentFile.mkdirs() + if (logFile.createNewFile()) { + Log.i(TAG, "File logger writing to " + logFile.path) + } + if (logFile.setReadable(true, false)) { + Log.i(TAG, "Debug run log read permission set") + } + + try { + logWriter = OutputStreamWriter(FileOutputStream(logFile, true)) + logWriter!!.write("=== BEGIN ===\n") + logWriter!!.write("Logfile: $logFile\n") + logWriter!!.flush() + Log.i(TAG, "File logger started.") + } catch (e: IOException) { + e.printStackTrace() + + logFile.delete() + if (logWriter != null) logWriter!!.close() + } + } + + @Synchronized + fun stop() { + logWriter?.let { + logWriter = null + try { + it.write("=== END ===\n") + it.close() + } catch (ignore: IOException) { + } + Log.i(TAG, "File logger stopped.") + } + } + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + logWriter?.let { + try { + it.write("${System.currentTimeMillis()} ${priorityToString(priority)}/$tag: $message\n") + it.flush() + } catch (e: IOException) { + Timber.tag(TAG).e(e) + try { + it.close() + } catch (ignore: Exception) { + } + logWriter = null + } + } + } + + override fun toString(): String { + return "FileLoggerTree(file=$logFile)" + } + + companion object { + private const val TAG = "FileLoggerTree" + private fun priorityToString(priority: Int): String = when (priority) { + Log.ERROR -> "E" + Log.WARN -> "W" + Log.INFO -> "I" + Log.DEBUG -> "D" + Log.VERBOSE -> "V" + else -> priority.toString() + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt index 03340dbfbb1ba4e43a4da4ed8f984fde8755b4c5..01f3dca1df3cf004af06d63b402e42f585e1e8d2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt @@ -5,13 +5,14 @@ import dagger.Component import dagger.android.AndroidInjector import dagger.android.support.AndroidSupportInjectionModule import de.rki.coronawarnapp.CoronaWarnApplication -import de.rki.coronawarnapp.nearby.NearbyModule import de.rki.coronawarnapp.diagnosiskeys.DiagnosisKeysModule import de.rki.coronawarnapp.diagnosiskeys.download.KeyFileDownloader import de.rki.coronawarnapp.diagnosiskeys.server.AppConfigServer import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.http.HttpModule import de.rki.coronawarnapp.http.ServiceFactory +import de.rki.coronawarnapp.nearby.ENFClient +import de.rki.coronawarnapp.nearby.ENFModule import de.rki.coronawarnapp.receiver.ReceiverBinder import de.rki.coronawarnapp.risk.RiskModule import de.rki.coronawarnapp.service.ServiceBinder @@ -37,9 +38,9 @@ import javax.inject.Singleton RiskModule::class, UtilModule::class, DeviceModule::class, + ENFModule::class, HttpModule::class, - DiagnosisKeysModule::class, - NearbyModule::class + DiagnosisKeysModule::class ] ) interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { @@ -59,6 +60,8 @@ interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { val appConfigServer: AppConfigServer + val enfClient: ENFClient + @Component.Factory interface Factory { fun create(@BindsInstance app: CoronaWarnApplication): ApplicationComponent diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt index 59965289788a0a3ffe127833ea18dcfec897d450..ea0ea81e282531d04ad27e41a0d6bf0288c40854 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt @@ -11,12 +11,45 @@ import android.text.style.ForegroundColorSpan import android.view.View import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUIFormat import java.util.Date import java.util.Locale +fun formatButtonStyleByState( + currentState: Symptoms.Indication?, + state: Symptoms.Indication? +): Int = + formatColor(currentState == state, R.color.colorTextSixteenWhite, R.color.colorTextPrimary1) + +fun formatBackgroundButtonStyleByState( + currentState: Symptoms.Indication?, + state: Symptoms.Indication? +): Int = + formatColor(currentState == state, R.color.colorTextSemanticNeutral, R.color.colorSurface2) + +fun formatCalendarButtonStyleByState( + currentState: Symptoms.StartOf?, + state: Symptoms.StartOf? +): Int = + formatColor(currentState == state, R.color.colorTextSixteenWhite, R.color.colorTextPrimary1) + +fun formatCalendarBackgroundButtonStyleByState( + currentState: Symptoms.StartOf?, + state: Symptoms.StartOf? +): Int = + formatColor(currentState == state, R.color.colorTextSemanticNeutral, R.color.colorSurface2) + +fun isEnableSymptomIntroButtonByState(currentState: Symptoms.Indication?): Boolean { + return currentState != null +} + +fun isEnableSymptomCalendarButtonByState(currentState: Symptoms.StartOf?): Boolean { + return currentState != null +} + fun formatTestResultSpinnerVisible(uiStateState: ApiRequestState?): Int = formatVisibility(uiStateState != ApiRequestState.SUCCESS) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt index c7e797fa3a075a6cfb7cded9f63f8058533aa00c..9e9fb0a702ba1f8268bd0e1c231776fccb7f55fe 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt @@ -5,6 +5,7 @@ import androidx.work.Constraints import androidx.work.NetworkType import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.storage.LocalData +import timber.log.Timber import kotlin.random.Random /** @@ -90,6 +91,7 @@ object BackgroundWorkHelper { * @see LocalData.backgroundNotification() */ fun sendDebugNotification(title: String, content: String) { + Timber.d("sendDebugNotification(title=%s, content=%s)", title, content) if (!LocalData.backgroundNotification()) return NotificationHelper.sendNotification(title, content, NotificationCompat.PRIORITY_HIGH, true) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt index e45eb28baae14e26ea910330763c46ccf2a766b1..95cf66a25bc91be467412f2b161929af7e1d7594 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt @@ -15,10 +15,6 @@ import timber.log.Timber class DiagnosisKeyRetrievalOneTimeWorker(val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { - companion object { - private val TAG: String? = DiagnosisKeyRetrievalOneTimeWorker::class.simpleName - } - /** * Work execution * @@ -27,28 +23,40 @@ class DiagnosisKeyRetrievalOneTimeWorker(val context: Context, workerParams: Wor * @see RetrieveDiagnosisKeysTransaction */ override suspend fun doWork(): Result { - Timber.d("Background job started. Run attempt: $runAttemptCount ") + Timber.d("$id: doWork() started. Run attempt: $runAttemptCount") + BackgroundWorkHelper.sendDebugNotification( - "KeyOneTime Executing: Start", "KeyOneTime started. Run attempt: $runAttemptCount ") + "KeyOneTime Executing: Start", "KeyOneTime started. Run attempt: $runAttemptCount " + ) var result = Result.success() try { RetrieveDiagnosisKeysTransaction.startWithConstraints() } catch (e: Exception) { + Timber.w( + e, "$id: Error during RetrieveDiagnosisKeysTransaction.startWithConstraints()." + ) + if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) { + Timber.w(e, "$id: Retry attempts exceeded.") BackgroundWorkHelper.sendDebugNotification( - "KeyOneTime Executing: Failure", "KeyOneTime failed with $runAttemptCount attempts") + "KeyOneTime Executing: Failure", + "KeyOneTime failed with $runAttemptCount attempts" + ) return Result.failure() } else { + Timber.d(e, "$id: Retrying.") result = Result.retry() } } BackgroundWorkHelper.sendDebugNotification( - "KeyOneTime Executing: End", "KeyOneTime result: $result ") + "KeyOneTime Executing: End", "KeyOneTime result: $result " + ) + Timber.d("$id: doWork() finished with %s", result) return result } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt index 79f091610676c9184dfef85882a5dfa83e972eb2..f7baa0f0855e09a3fef92a67d03b125b25452a42 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt @@ -15,10 +15,6 @@ import timber.log.Timber class DiagnosisKeyRetrievalPeriodicWorker(val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { - companion object { - private val TAG: String? = DiagnosisKeyRetrievalPeriodicWorker::class.simpleName - } - /** * Work execution * @@ -28,28 +24,40 @@ class DiagnosisKeyRetrievalPeriodicWorker(val context: Context, workerParams: Wo * @see BackgroundWorkScheduler.scheduleDiagnosisKeyOneTimeWork() */ override suspend fun doWork(): Result { - Timber.d("Background job started. Run attempt: $runAttemptCount") + Timber.d("$id: doWork() started. Run attempt: $runAttemptCount") + BackgroundWorkHelper.sendDebugNotification( - "KeyPeriodic Executing: Start", "KeyPeriodic started. Run attempt: $runAttemptCount ") + "KeyPeriodic Executing: Start", "KeyPeriodic started. Run attempt: $runAttemptCount" + ) var result = Result.success() try { BackgroundWorkScheduler.scheduleDiagnosisKeyOneTimeWork() } catch (e: Exception) { + Timber.w( + e, "$id: Error during BackgroundWorkScheduler.scheduleDiagnosisKeyOneTimeWork()." + ) + if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) { + Timber.w(e, "$id: Retry attempts exceeded.") BackgroundWorkHelper.sendDebugNotification( - "KeyPeriodic Executing: Failure", "KeyPeriodic failed with $runAttemptCount attempts") + "KeyPeriodic Executing: Failure", + "KeyPeriodic failed with $runAttemptCount attempts" + ) return Result.failure() } else { + Timber.d(e, "$id: Retrying.") result = Result.retry() } } BackgroundWorkHelper.sendDebugNotification( - "KeyPeriodic Executing: End", "KeyPeriodic result: $result ") + "KeyPeriodic Executing: End", "KeyPeriodic result: $result " + ) + Timber.d("$id: doWork() finished with %s", result) return result } } diff --git a/Corona-Warn-App/src/main/res/color/calendar_header.xml b/Corona-Warn-App/src/main/res/color/calendar_header.xml new file mode 100644 index 0000000000000000000000000000000000000000..727593c505031885cbff514ce8ff43b1c55a430a --- /dev/null +++ b/Corona-Warn-App/src/main/res/color/calendar_header.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="@color/colorTextEmphasizedButton"/> + <item android:color="@color/colorTextPrimary1"/> +</selector> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_header_background.xml b/Corona-Warn-App/src/main/res/drawable/calendar_header_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..50e670c9803c7983d2edb9f1a72d94970c76ad7d --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_header_background.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/calendar_header_background_focus_on" android:state_selected="true" /> + <item android:drawable="@drawable/calendar_header_background_focus_off" /> +</selector> diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_header_background_focus_off.xml b/Corona-Warn-App/src/main/res/drawable/calendar_header_background_focus_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..c147a5441c7c653875066a2975624234f89f59c1 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_header_background_focus_off.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners + android:radius="@dimen/calendar_header_initial_radius" + android:bottomLeftRadius="@dimen/calendar_header_bottom_radius" + android:bottomRightRadius="@dimen/calendar_header_bottom_radius" + android:topLeftRadius="@dimen/calendar_header_top_radius" + android:topRightRadius="@dimen/calendar_header_top_radius" /> + <solid android:color="@color/colorSurface2" /> +</shape> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_header_background_focus_on.xml b/Corona-Warn-App/src/main/res/drawable/calendar_header_background_focus_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..9eebae7774300955c275ad819b2f61d40c548160 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_header_background_focus_on.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners + android:radius="@dimen/calendar_header_initial_radius" + android:bottomLeftRadius="@dimen/calendar_header_bottom_radius" + android:bottomRightRadius="@dimen/calendar_header_bottom_radius" + android:topLeftRadius="@dimen/calendar_header_top_radius" + android:topRightRadius="@dimen/calendar_header_top_radius" /> + <solid android:color="@color/colorCalendarLayoutFocusOn" /> +</shape> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_layout_background.xml b/Corona-Warn-App/src/main/res/drawable/calendar_layout_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..806443454d9218c4a8b8634703edd42d45767652 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_layout_background.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/calendar_layout_background_focus_on" android:state_selected="true" /> + <item android:drawable="@drawable/calendar_layout_background_focus_off" /> +</selector> diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_layout_background_focus_off.xml b/Corona-Warn-App/src/main/res/drawable/calendar_layout_background_focus_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..10c1626fc85807bc3080af27a816cfbf9424fb67 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_layout_background_focus_off.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/radius_card" /> + <stroke + android:width="@dimen/calendar_layout_stroke" + android:color="@color/colorSurface2" /> +</shape> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_layout_background_focus_on.xml b/Corona-Warn-App/src/main/res/drawable/calendar_layout_background_focus_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..39ecfa7c0532579cd21e7f158b1693e377ef0c57 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_layout_background_focus_on.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/radius_card" /> + <stroke + android:width="@dimen/calendar_layout_stroke" + android:color="@color/colorCalendarLayoutFocusOn" /> +</shape> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_selected_day_back.xml b/Corona-Warn-App/src/main/res/drawable/calendar_selected_day_back.xml new file mode 100644 index 0000000000000000000000000000000000000000..a76b8d017908dd7a02e2c7fcb3dce47f87d76ec7 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_selected_day_back.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@color/colorCalendarSelectedDayBackground" /> +</shape> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/calendar_today_back.xml b/Corona-Warn-App/src/main/res/drawable/calendar_today_back.xml new file mode 100644 index 0000000000000000000000000000000000000000..874c81cd881e26340720b8352be966257dbda8f8 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/calendar_today_back.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@android:color/transparent" /> + <stroke + android:width="1dp" + android:color="@color/colorCalendarTodayBorder" /> +</shape> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/fragment_calendar.xml b/Corona-Warn-App/src/main/res/layout/fragment_calendar.xml new file mode 100644 index 0000000000000000000000000000000000000000..73c16a8413200ca445513e7feb48b8b30d9c48e8 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/fragment_calendar.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/calendar_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/calendar_layout_background" + android:orientation="vertical"> + + <TextView + android:id="@+id/calendar_header" + android:layout_width="match_parent" + android:layout_height="@dimen/calendar_header_height" + android:padding="@dimen/spacing_normal" + android:textSize="@dimen/font_title" + android:textColor="@color/calendar_header" + android:text="@string/symptoms_calendar_exact_date_button" + android:background="@drawable/calendar_header_background" + android:focusable="true" + android:focusableInTouchMode="true" /> + + <TextView + android:id="@+id/calendar_month" + style="@style/calendarMonthText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacing_normal" + android:layout_marginTop="@dimen/spacing_small" + android:layout_marginBottom="@dimen/spacing_small" + android:focusable="true" + android:focusableInTouchMode="true" /> + + <LinearLayout + android:id="@+id/calendar_day_legend" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacing_small" + android:layout_marginEnd="@dimen/spacing_small" + android:gravity="center" + android:orientation="horizontal" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/calendar_recycler_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/spacing_small" + android:layout_marginEnd="@dimen/spacing_small" + android:layout_marginBottom="@dimen/spacing_small" + android:importantForAccessibility="no" + android:scrollbars="vertical" /> + +</LinearLayout> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_calendar_day.xml b/Corona-Warn-App/src/main/res/layout/fragment_calendar_day.xml new file mode 100644 index 0000000000000000000000000000000000000000..8daa8976ed087640d62f8eba9277ae3b3f084feb --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/fragment_calendar_day.xml @@ -0,0 +1,15 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/calendar_day_size" + android:layout_height="@dimen/calendar_day_size" + android:layout_marginTop="@dimen/calendar_day_spacing" + android:layout_marginBottom="@dimen/calendar_day_spacing" > + + <TextView + android:id="@+id/dayText" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:textColor="@color/colorTextPrimary1" + android:textSize="@dimen/font_button" /> + +</LinearLayout> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_calendar.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_calendar.xml new file mode 100644 index 0000000000000000000000000000000000000000..eb19ee84f777439eb95616fd29cd758dfc369bc5 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_calendar.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + + <data> + + <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" /> + + <import type="de.rki.coronawarnapp.submission.Symptoms.StartOf" /> + + <variable + name="submissionViewModel" + type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" /> + + </data> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fillViewport="true" + android:focusable="true" + tools:context=".ui.submission.fragment.SubmissionSymptomCalendarFragment"> + + <include + android:id="@+id/submission_symptom_calendar_header" + layout="@layout/include_header" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + app:icon="@{@drawable/ic_back}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:title="@{@string/submission_symptom_calendar_title}" /> + + <TextView + android:id="@+id/submission_symptom_calendar_headline" + style="@style/headline5" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:accessibilityHeading="true" + android:text="@string/submission_symptom_calendar_headline" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/submission_symptom_calendar_header" /> + + <TextView + android:id="@+id/submission_symptom_calendar_body" + style="@style/body1" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:accessibilityHeading="true" + android:text="@string/submission_symptom_calendar_body" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/submission_symptom_calendar_headline" /> + + <de.rki.coronawarnapp.ui.calendar.CalendarView + android:id="@+id/symptom_calendar_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/submission_symptom_calendar_body"/> + + <include + android:id="@+id/symptom_calendar_choice_selection" + layout="@layout/include_submission_symptom_length_selection" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:focusable="true" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/symptom_calendar_container" /> + + <Button + android:id="@+id/symptom_button_next" + style="@style/buttonPrimary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_small" + android:layout_marginTop="@dimen/spacing_small" + android:text="@string/submission_symptom_further_button" + app:layout_constraintBottom_toTopOf="@id/guideline_bottom" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/symptom_calendar_choice_selection"/> + + <include layout="@layout/merge_guidelines_side" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_begin="@dimen/guideline_top" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_end="@dimen/spacing_small" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </ScrollView> + +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml new file mode 100644 index 0000000000000000000000000000000000000000..09df7481b0c54f8241fadfaabc7bd95ccbb3e865 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + + <data> + + <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" /> + + <variable + name="submissionViewModel" + type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" /> + + </data> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/submission_symptom_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fillViewport="true" + android:focusable="true" + tools:context=".ui.submission.fragment.SubmissionIntroFragment"> + + <include + android:id="@+id/submission_symptom_header" + layout="@layout/include_header" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + app:icon="@{@drawable/ic_back}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:title="@{@string/submission_symptom_title}" /> + + <TextView + android:id="@+id/submission_symptom_initial_headline" + style="@style/headline5" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:accessibilityHeading="true" + android:text="@string/submission_symptom_initial_headline" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/submission_symptom_header" /> + + <de.rki.coronawarnapp.ui.view.BulletPointList + android:id="@+id/further_info_text" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:focusable="true" + android:layout_marginTop="@dimen/spacing_normal" + app:entries="@array/submission_symptom_symptom_bullets" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/submission_symptom_initial_headline" /> + + <include + android:id="@+id/symptom_choice_selection" + layout="@layout/include_submission_target_selection" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:focusable="true" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/further_info_text" /> + + <Button + android:id="@+id/symptom_button_next" + style="@style/buttonPrimary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_small" + android:layout_marginTop="@dimen/spacing_small" + android:text="@string/submission_symptom_further_button" + app:layout_constraintBottom_toTopOf="@id/guideline_bottom" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/symptom_choice_selection"/> + + <include layout="@layout/merge_guidelines_side" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_begin="@dimen/guideline_top" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_end="@dimen/spacing_small" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </ScrollView> + +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_symptom_length_selection.xml b/Corona-Warn-App/src/main/res/layout/include_submission_symptom_length_selection.xml new file mode 100644 index 0000000000000000000000000000000000000000..d95f4ed9f6f4201980e57caa01ec1e06c2d1ff6e --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/include_submission_symptom_length_selection.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <data> + + <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" /> + + <import type="de.rki.coronawarnapp.submission.Symptoms.StartOf" /> + + <variable + name="submissionViewModel" + type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" /> + + </data> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/target_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="true"> + + <Button + android:id="@+id/calendar_button_seven_days" + style="@style/selectionButton" + android:enabled="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@{@string/submission_symptom_less_seven}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + /> + + <Button + android:id="@+id/calendar_button_one_two_weeks" + style="@style/selectionButton" + android:enabled="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@{@string/submission_symptom_one_two_weeks}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/calendar_button_seven_days" /> + + <Button + android:id="@+id/calendar_button_more_than_two_weeks" + style="@style/selectionButton" + android:enabled="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@{@string/submission_symptom_more_two_weeks}" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/calendar_button_one_two_weeks" /> + + <Button + android:id="@+id/target_button_verify" + style="@style/selectionButton" + android:enabled="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@{@string/submission_symptom_verify}" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/calendar_button_more_than_two_weeks" /> + + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml b/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml index aeb76381c1855966593e8a09f145aeb2aaef3e82..795e2414432a623b48ec39c9887c2a07db4e690d 100644 --- a/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml +++ b/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml @@ -46,13 +46,13 @@ app:layout_constraintTop_toBottomOf="@+id/submission_tan_body" /> <TextView - android:id="@+id/submission_tan_character_error" + android:id="@+id/submission_tan_error" style="@style/subtitle" android:layout_width="@dimen/match_constraint" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_small" android:accessibilityLiveRegion="assertive" - android:text="@string/submission_tan_character_error" + android:text="@string/submission_tan_error" android:textColor="@color/colorTextSemanticRed" android:visibility="gone" app:layout_constraintEnd_toStartOf="@+id/guideline_end" @@ -61,18 +61,18 @@ tools:visibility="visible" /> <TextView - android:id="@+id/submission_tan_error" + android:id="@+id/submission_tan_character_error" style="@style/subtitle" android:layout_width="@dimen/match_constraint" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_small" android:accessibilityLiveRegion="assertive" - android:text="@string/submission_tan_error" + android:text="@string/submission_tan_character_error" android:textColor="@color/colorTextSemanticRed" android:visibility="gone" app:layout_constraintEnd_toStartOf="@+id/guideline_end" app:layout_constraintStart_toStartOf="@+id/guideline_start" - app:layout_constraintTop_toBottomOf="@id/submission_tan_character_error" + app:layout_constraintTop_toBottomOf="@id/submission_tan_error" tools:visibility="visible" /> <include layout="@layout/merge_guidelines_side" /> diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_target_selection.xml b/Corona-Warn-App/src/main/res/layout/include_submission_target_selection.xml new file mode 100644 index 0000000000000000000000000000000000000000..885289e1986f84037042802f1800f3aa632997b4 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/include_submission_target_selection.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/target_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="true"> + + <Button + android:id="@+id/target_button_apply" + style="@style/selectionButton" + android:enabled="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@{@string/submission_symptom_positive_button}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/target_button_reject" + style="@style/selectionButton" + android:enabled="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@{@string/submission_symptom_negative_button}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/target_button_apply" /> + + <Button + android:id="@+id/target_button_verify" + style="@style/selectionButton" + android:enabled="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@{@string/submission_symptom_no_information_button}" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/target_button_reject" /> + + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> \ No newline at end of file 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 61b00b60017520df908020800160fe9c24967683..07332d9b90e4451e047f039e29d0dad243c77db2 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -26,9 +26,6 @@ <action android:id="@+id/action_mainFragment_to_mainSharingFragment" app:destination="@id/mainSharingFragment" /> - <action - android:id="@+id/action_mainFragment_to_submissionIntroFragment" - app:destination="@id/submissionIntroFragment" /> <action android:id="@+id/action_mainFragment_to_submissionResultFragment" app:destination="@id/submissionResultFragment" /> @@ -38,6 +35,9 @@ <action android:id="@+id/action_mainFragment_to_mainOverviewFragment" app:destination="@id/mainOverviewFragment" /> + <action + android:id="@+id/action_mainFragment_to_submissionIntroFragment" + app:destination="@id/submissionIntroFragment" /> </fragment> <fragment @@ -205,6 +205,12 @@ app:destination="@id/submissionDoneFragment" app:popUpTo="@id/submissionDoneFragment" app:popUpToInclusive="true" /> + <action + android:id="@+id/action_submissionResultPositiveOtherWarningFragment_to_submissionSymptomCalendarFragment" + app:destination="@id/submissionSymptomCalendarFragment" /> + <action + android:id="@+id/action_submissionResultPositiveOtherWarningFragment_to_submissionSymptomIntroductionFragment" + app:destination="@id/submissionSymptomIntroductionFragment" /> </fragment> <fragment android:id="@+id/submissionResultFragment" @@ -221,8 +227,8 @@ app:popUpTo="@id/mainFragment" app:popUpToInclusive="true" /> <action - android:id="@+id/action_submissionResultFragment_to_submissionResultPositiveOtherWarningFragment" - app:destination="@id/submissionResultPositiveOtherWarningFragment" /> + android:id="@+id/action_submissionResultFragment_to_submissionSymptomIntroductionFragment" + app:destination="@id/submissionSymptomIntroductionFragment" /> </fragment> <fragment @@ -230,6 +236,11 @@ android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionTanFragment" android:label="fragment_submission_tan" tools:layout="@layout/fragment_submission_tan"> + <action + android:id="@+id/action_submissionTanFragment_to_submissionDispatcherFragment" + app:destination="@id/submissionDispatcherFragment" + app:popUpTo="@id/submissionDispatcherFragment" + app:popUpToInclusive="true" /> <action android:id="@+id/action_submissionTanFragment_to_submissionResultFragment" app:destination="@id/submissionResultFragment" @@ -300,4 +311,29 @@ android:id="@+id/action_submissionContactFragment_to_submissionTanFragment" app:destination="@id/submissionTanFragment" /> </fragment> + <fragment + android:id="@+id/submissionSymptomIntroductionFragment" + android:name="de.rki.coronawarnapp.ui.submission.SubmissionSymptomIntroductionFragment" + android:label="SubmissionSymptomIntroductionFragment" > + <action + android:id="@+id/action_submissionSymptomIntroductionFragment_to_submissionSymptomCalendarFragment" + app:destination="@id/submissionSymptomCalendarFragment" /> + <action + android:id="@+id/action_submissionSymptomIntroductionFragment_to_submissionResultFragment" + app:destination="@id/submissionResultFragment" /> + <action + android:id="@+id/action_submissionSymptomIntroductionFragment_to_submissionResultPositiveOtherWarningFragment" + app:destination="@id/submissionResultPositiveOtherWarningFragment" /> + </fragment> + <fragment + android:id="@+id/submissionSymptomCalendarFragment" + android:name="de.rki.coronawarnapp.ui.submission.SubmissionSymptomCalendarFragment" + android:label="SubmissionSymptomCalendarFragment" > + <action + android:id="@+id/action_submissionCalendarFragment_to_submissionSymptomIntroductionFragment" + app:destination="@id/submissionSymptomIntroductionFragment" /> + <action + android:id="@+id/action_submissionSymptomCalendarFragment_to_submissionResultPositiveOtherWarningFragment" + app:destination="@id/submissionResultPositiveOtherWarningFragment" /> + </fragment> </navigation> diff --git a/Corona-Warn-App/src/main/res/values-bg/strings.xml b/Corona-Warn-App/src/main/res/values-bg/strings.xml index 0e77b972ba7c4a35b683dead150fcc14cab0443b..b34a979f3b9cde949f588bc8b5e3a23f6232b326 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -425,7 +425,7 @@ <!-- XHED: onboarding(tracing) - headline for consent information --> <string name="onboarding_tracing_headline_consent">"ПоверителноÑÑ‚"</string> <!-- YTXT: onboarding(tracing) - body for consent information --> - <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the COVID-19 Exposure Logging functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the \"Exposure Notifications\" functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_button_next">"Ðктивиране на региÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк"</string> <!-- XTXT: onboarding(tracing) - dialog about tracing permission declined --> @@ -495,7 +495,7 @@ <string name="sixteen_title_text">"Минимална възраÑÑ‚: 16 год."</string> <!-- XACT: onboarding(sixteen) title --> - <string name="sixteen_description_text">"Това приложение е предназначено за лица Ñ Ð¿Ð¾ÑтоÑнно пребиваване в ГерманиÑ, които имат навършени 16 години."</string> + <string name="sixteen_description_text">"Употребата на това приложение е предназначено за лица, навършили 16 години, Ñ Ð¿Ð¾ÑтоÑнно пребиваване в ГерманиÑ."</string> <!-- #################################### @@ -541,7 +541,9 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_location_headline">"Разрешаване на доÑтъп до данните за меÑтоположение"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled --> - <string name="settings_tracing_status_location_body">"Ðе може да бъде оÑъщеÑтвен доÑтъп до Вашето меÑтоположение. За да използвате Bluetooth, Google и/или Android изиÑкват от Ð’Ð°Ñ Ð´Ð° предоÑтавите доÑтъп до меÑтоположението на уÑтройÑтвото Ñи."</string> + <string name="settings_tracing_status_location_body">"Ðктивирайте уÑлугите за ÑподелÑне на меÑтоположение. Bluetooth Ñ Ð½Ð¸Ñък разход на ÐµÐ½ÐµÑ€Ð³Ð¸Ñ Ð¸Ð·Ð¸Ñква активирани уÑлуги за ÑподелÑне на меÑтоположението, за да може да изчиÑли физичеÑкото разÑтоÑние, но в момента нÑма доÑтъп до Вашето меÑтоположение. Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите на Ñтраницата “ЧеÑто задавани въпроÑиâ€."</string> + <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled: URL --> + <string name="settings_tracing_status_location_body_url">"https://www.coronawarn.app/en/faq/#android_location"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card - location --> <string name="settings_tracing_status_location_button">"Към наÑтройките за уÑтройÑтвото"</string> <!--XHED : settings(tracing) - headline on card about the current status and what to do --> @@ -720,7 +722,7 @@ <!-- NOTR: subtitle for legal information page, open contact form for languages other than English and German --> <string name="information_legal_subtitle_contact_form_non_en_de">"Contact Form in "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/en/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"English"</a>" or "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"German"</a></string> <!-- XHED: Headline for legal information page, tax section --> - <string name="information_legal_headline_taxid">"Идентификационен номер за ДДС"</string> + <string name="information_legal_headline_taxid">"Идентификационен номер \nза ДДС"</string> <!-- YTXT: subtitle for legal information page, tax section --> <string name="information_legal_subtitle_taxid">"DE 165 893 430"</string> <!-- XACT: describes illustration --> @@ -740,10 +742,14 @@ <!-- XBUT: Positive button for generic web request error --> <string name="submission_error_dialog_web_generic_error_button_positive">"Ðазад"</string> - <!-- XHED: Dialog title for already paired test error --> + <!-- XHED: Dialog title for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_title">"Ðевалиден QR код"</string> - <!-- XMSG: Dialog body for already paired test error --> + <!-- XMSG: Dialog body for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_body">"QR кодът е невалиден или вече е региÑтриран на друг Ñмартфон. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза COVID-19, Ñлужбата за общеÑтвено оÑигурÑване ще Ви уведоми за това."</string> + <!-- XHED: Dialog title for already paired test error: tan --> + <string name="submission_error_dialog_web_test_paired_title_tan">"ТÐРкодът е невалиден"</string> + <!-- XMSG: Dialog body for already paired test via tan - error: tan --> + <string name="submission_error_dialog_web_test_paired_body_tan">"ТÐРкодът е невалиден или вече е използван. За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ñе обадете на номера, поÑочен под “ЗаÑвÑване на ТÐРкодâ€."</string> <!-- XBUT: Positive button for already paired test error --> <string name="submission_error_dialog_web_test_paired_button_positive">"Ðазад"</string> @@ -768,6 +774,15 @@ <!-- XBUT: Positive button for submission tan redeemed --> <string name="submission_error_dialog_web_tan_redeemed_button_positive">"OK"</string> + <!-- XHED: Dialog title for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_title">"Желаете ли отмÑна?"</string> + <!-- XMSG: Dialog body for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_body">"Въведените от Ð’Ð°Ñ Ð´Ð°Ð½Ð½Ð¸ нÑма да бъдат запазени."</string> + <!-- XBUT: Positive button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_positive">"Да"</string> + <!-- XBUT: Negative button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_negative">"Ðе"</string> + <!-- Permission Rationale Dialog --> <!-- XHED: Dialog headline QR Scan permission rationale --> <string name="submission_qr_code_scan_permission_rationale_dialog_headline">"ИзиÑква Ñе разрешение за използване на камерата"</string> @@ -869,9 +884,9 @@ <!-- XACT: Submission Tan page title --> <string name="submission_tan_accessibility_title">"Въвеждане на ТÐРкод"</string> <!-- YTXT: Error text for the tan submission page --> - <string name="submission_tan_error">"Ðевалиден ТÐРкод. Проверете въведените данни."</string> + <string name="submission_tan_error">"Ðевалиден ТÐРкод. МолÑ, проверете въведените данни!"</string> <!-- YTXT: Error text for the tan submission page (wrong characters) --> - <string name="submission_tan_character_error">"Въведете данни Ñа невалидни. МолÑ, проверете."</string> + <string name="submission_tan_character_error">"Въвели Ñте невалидни Ñимволи. МолÑ, проверете данните!"</string> <!-- Submission Intro --> <!-- XHED: Page title for menu at the start of the submission process --> @@ -931,9 +946,9 @@ <!-- XHED: Title for the privacy card--> <string name="submission_positive_other_warning_privacy_title">"ПоверителноÑÑ‚"</string> <!-- YTXT: Body text for the privacy card--> - <string name="submission_positive_other_warning_privacy_body">"By tapping on “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="submission_positive_other_warning_privacy_body">"By tapping “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacyâ€."</string> <!-- XBUT: other warning continue button --> - <string name="submission_positive_other_warning_button">"Ðапред"</string> + <string name="submission_positive_other_warning_button">"Приемам"</string> <!-- XACT: other warning - illustration description, explanation image --> <string name="submission_positive_other_illustration_description">"УÑтройÑтво предава на ÑиÑтемата Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° положителен резултат от теÑÑ‚."</string> @@ -961,7 +976,33 @@ <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Готово"</string> <!-- XACT: submission finished - illustration description, explanation image --> - <string name="submission_done_illustration_description">"Ð’Ñички в групата аплодират, защото нÑкой е Ñподелил резултата от ÑÐ²Ð¾Ñ Ñ‚ÐµÑÑ‚."</string> + <string name="submission_done_illustration_description">"Имали ли Ñте един или повече от Ñледните Ñимптоми през поÑледните нÑколко дни?"</string> + + <!-- Submission Symptoms --> + <!-- XHED: Page title for symptom screens --> + <string name="submission_symptom_title">"Симптоми"</string> + <!-- YTXT: headline text for initial symptom screen --> + <string name="submission_symptom_initial_headline">"Имали ли Ñте един или повече от Ñледните Ñимптоми през поÑледните нÑколко дни?"</string> + <!-- YTXT: Bullet points for symptoms --> + <string-array name="submission_symptom_symptom_bullets"> + <item>"Повишена температура или треÑка"</item> + <item>"Затруднено дишане"</item> + <item>"Загуба на обонÑние/вкуÑ"</item> + <item>"Кашлица"</item> + <item>"Хрема"</item> + <item>"Болки в гърлото"</item> + <item>"Главоболие и болки в крайниците"</item> + <item>"Обща ÑлабоÑÑ‚ и умора"</item> + </string-array> + <!-- XBUT: symptom initial screen yes button --> + <string name="submission_symptom_positive_button">"Да"</string> + <!-- XBUT: symptom initial screen no button --> + <string name="submission_symptom_negative_button">"Ðе"</string> + <!-- XBUT: symptom initial screen no information button --> + <string name="submission_symptom_no_information_button">"Без коментар"</string> + <!-- XBUT: symptom initial screen continue button --> + <string name="submission_symptom_further_button">"Ðапред"</string> + <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> @@ -992,6 +1033,22 @@ <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"Във втората Ñтъпка региÑтрирате теÑта Ñи, като въвеждате ТÐРкода в приложението."</string> + <!-- Submission Symptom Calendar --> + <!-- XHED: Page title for calendar page in submission symptom flow --> + <string name="submission_symptom_calendar_title">"Ðачало на Ñимптомите"</string> + <!-- XHED: Page headline for calendar page in symptom submission flow --> + <string name="submission_symptom_calendar_headline">"Кога Ñе поÑвиха за първи път тези Ñимптоми? "</string> + <!-- YTXT: Body text for calendar page in symptom submission flow--> + <string name="submission_symptom_calendar_body">"Изберете точната дата от календара или, ако не можете да Ñи Ñ Ñпомните, нÑÐºÐ¾Ñ Ð¾Ñ‚ другите опции."</string> + <!-- XBUT: symptom calendar screen less than 7 days button --> + <string name="submission_symptom_less_seven">"През поÑледните 7 дни"</string> + <!-- XBUT: symptom calendar screen 1-2 weeks button --> + <string name="submission_symptom_one_two_weeks">"Преди 1-2 Ñедмици"</string> + <!-- XBUT: symptom calendar screen more than 2 weeks button --> + <string name="submission_symptom_more_two_weeks">"Преди повече от 2 Ñедмици"</string> + <!-- XBUT: symptom calendar screen verify button --> + <string name="submission_symptom_verify">"Без коментар"</string> + <!-- Submission Status Card --> <!-- XHED: Page title for the various submission status: fetching --> <string name="submission_status_card_title_fetching">"Извършва Ñе извличане на данни"</string> @@ -1055,6 +1112,9 @@ <item>"Ðе ходете на работа, ако не Ñе чувÑтвате добре, за да не излагате други хора на риÑк. Ðко Ñимптомите Ви Ñе влошат, може да Ñе наложи да направите нов теÑÑ‚ за SARS-CoV-2."</item> </string-array> + <!-- XBUT Symptoms exact date button --> + <string name="symptoms_calendar_exact_date_button">"Точна дата"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1083,7 +1143,7 @@ <!-- XTXT: error dialog - detailed text if there is an error during external navigation / external action --> <string name="errors_external_action">"Ðа може да извършите това дейÑтвие. МолÑ, обадете Ñе на горещата линиÑ."</string> <!-- XTXT: error dialog - phone still needs Google Play Services or Google Mobile Services update --> - <string name="errors_google_update_needed">"Вашето Corona-Warn-App приложение е инÑталирано правилно, но уÑлугата “извеÑÑ‚Ð¸Ñ Ð·Ð° Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк от заразÑване Ñ COVID-19†не Ñе предлага за операционната ÑиÑтема на Ð’Ð°ÑˆÐ¸Ñ Ñмартфон. Това означава, че не можете да използвате приложението Corona-Warn-App. Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите в Ñтраницата „ЧЗВ“ на адреÑ: https://www.coronawarn.app/en/faq/"</string> + <string name="errors_google_update_needed">"Вашето приложение Corona-Warn-App е инÑталирано правилно, но „СиÑтемата за извеÑÑ‚Ñване при Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк от заразÑване Ñ COVID-19†не Ñе предлага за операционната ÑиÑтема на Ð’Ð°ÑˆÐ¸Ñ Ñмартфон. Това означава, че не можете да използвате приложението Corona-Warn-App. Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите в Ñтраницата „ЧЗВ“ на адреÑ: https://www.coronawarn.app/en/faq/"</string> <!-- XTXT: error dialog - either Google API Error (10) or reached request limit per day --> <string name="errors_google_api_error">"Приложението Corona-Warn-App работи правилно, но не можем да актуализираме Ð’Ð°ÑˆÐ¸Ñ ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° риÑк. РегиÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк вÑе още е активно и функционира правилно. За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ñетете Ñтраницата „ЧЗВ“ на адреÑ: https://www.coronawarn.app/en/faq/"</string> diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml index 025aae05313214dc9e21d1be6f25c8ad75feb407..a2457e0b1def1e0499fa0260762f0aea10c30c75 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -345,7 +345,7 @@ <!-- YTXT: risk details - low risk explanation text --> <string name="risk_details_information_body_low_risk">"Sie haben ein niedriges Infektionsrisiko, da keine Begegnung mit nachweislich Corona-positiv getesteten Personen aufgezeichnet wurde oder sich Ihre Begegnung auf kurze Zeit und einen größeren Abstand beschränkt hat."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> - <string name="risk_details_information_body_low_risk_with_encounter">"Das Infektionsrisiko wird anhand der Daten der Risiko-Ermittlung unter Berücksichtigung des Abstands und der Dauer von Begegnungen mit nachweislich Corona-positiv getesteten Personen sowie deren vermutlicher Infektiosität lokal auf Ihrem Endgerät berechnet. Ihr Infektionsrisiko ist für niemanden einsehbar und wird nicht weitergegeben."</string> + <string name="risk_details_information_body_low_risk_with_encounter">"Das Infektionsrisiko wird anhand der Daten der Risiko-Ermittlung unter Berücksichtigung des Abstands und der Dauer von Begegnungen mit nachweislich Corona-positiv getesteten Personen sowie deren vermutlicher Infektiosität lokal auf Ihrem Smartphone berechnet. Ihr Infektionsrisiko ist für niemanden einsehbar und wird nicht weitergegeben."</string> <!-- YTXT: risk details - increased risk explanation text with variable for day(s) since last contact --> <plurals name="risk_details_information_body_increased_risk"> <item quantity="one">"Sie haben ein erhöhtes Infektionsrisiko, da Sie zuletzt vor %1$s Tag mindestens einer nachweislich Corona-positiv getesteten Person über einen längeren Zeitraum und mit einem geringen Abstand begegnet sind."</item> @@ -407,7 +407,7 @@ <string name="onboarding_privacy_headline">"Datenschutz"</string> <!-- XACT: onboarding(privacy) - illustraction description, header image --> - <string name="onboarding_privacy_illustration_description">"Eine Frau mit einem Handy benutzt die Corona-Warn-App, ein Vorhängeschloss auf einem Schild steht als Symbol für verschlüsselte Daten."</string> + <string name="onboarding_privacy_illustration_description">"Eine Frau mit einem Smartphone benutzt die Corona-Warn-App, ein Vorhängeschloss auf einem Schild steht als Symbol für verschlüsselte Daten."</string> <!-- XACT: Onboarding (tracing) page title --> <string name="onboarding_tracing_accessibility_title">"Einführung Seite 3 von 5: Wie Sie die Risiko Ermittlung ermöglichen"</string> <!-- XHED: onboarding(tracing) - how to enable tracing --> @@ -415,7 +415,7 @@ <!-- XHED: onboarding(tracing) - two/three line headline under an illustration --> <string name="onboarding_tracing_subtitle">"Um zu erkennen, ob für Sie ein Infektionsrisiko vorliegt, müssen Sie die Risiko-Ermittlung aktivieren."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> - <string name="onboarding_tracing_body">"Die Risiko-Ermittlung funktioniert, indem Ihr Handy per Bluetooth verschlüsselte Zufallscodes anderer Nutzerinnen und Nutzer empfängt und Ihren eigenen Zufallscode an deren Smartphones weitergibt. Die Funktion lässt sich jederzeit wieder deaktivieren."</string> + <string name="onboarding_tracing_body">"Die Risiko-Ermittlung funktioniert, indem Ihr Smartphone per Bluetooth verschlüsselte Zufallscodes anderer Nutzerinnen und Nutzer empfängt und Ihren eigenen Zufallscode an deren Smartphones weitergibt. Die Funktion lässt sich jederzeit wieder deaktivieren."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> <string name="onboarding_tracing_body_emphasized">"Die verschlüsselten Zufallscodes geben nur Auskunft über das Datum, die Dauer und die anhand der Signalstärke berechnete Entfernung zu Ihren Mitmenschen. Persönliche Daten wie Name, Adresse oder Aufenthaltsort werden zu keiner Zeit erfasst. Konkrete Rückschlüsse auf Personen sind nicht möglich."</string> <!-- YTXT: onboarding(tracing) - easy language explain tracing link--> @@ -521,7 +521,7 @@ <!-- XTXT: settings(tracing) - shows status under header in home, inactive location --> <string name="settings_tracing_body_inactive_location">"Standortdienste deaktiviert"</string> <!-- YTXT: settings(tracing) - explains tracings --> - <string name="settings_tracing_body_text">"Um zu erkennen, ob für Sie ein Infektionsrisiko vorliegt, müssen Sie die Risiko-Ermittlung aktivieren. Die Risiko-Ermittlung funktioniert, indem Ihr Handy per Bluetooth verschlüsselte Zufallscodes anderer Nutzerinnen und Nutzer empfängt und Ihren eigenen Zufallscode an deren Smartphones weitergibt. Die Funktion lässt sich jederzeit wieder deaktivieren."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Die verschlüsselten Zufallscodes geben nur Auskunft über das Datum, die Dauer und die anhand der Signalstärke berechnete Entfernung zu Ihren Mitmenschen. Persönliche Daten wie Name, Adresse oder Aufenthaltsort werden zu keiner Zeit erfasst. Konkrete Rückschlüsse auf Personen sind nicht möglich."</string> + <string name="settings_tracing_body_text">"Um zu erkennen, ob für Sie ein Infektionsrisiko vorliegt, müssen Sie die Risiko-Ermittlung aktivieren. Die Risiko-Ermittlung funktioniert, indem Ihr Smartphone per Bluetooth verschlüsselte Zufallscodes anderer Nutzerinnen und Nutzer empfängt und Ihren eigenen Zufallscode an deren Smartphones weitergibt. Die Funktion lässt sich jederzeit wieder deaktivieren."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Die verschlüsselten Zufallscodes geben nur Auskunft über das Datum, die Dauer und die anhand der Signalstärke berechnete Entfernung zu Ihren Mitmenschen. Persönliche Daten wie Name, Adresse oder Aufenthaltsort werden zu keiner Zeit erfasst. Konkrete Rückschlüsse auf Personen sind nicht möglich."</string> <!-- XTXT: settings(tracing) - status next to switch under title --> <string name="settings_tracing_status_active">"Aktiv"</string> <!-- XTXT: settings(tracing) - status next to switch under title --> @@ -662,7 +662,7 @@ <!-- XHED: Page title for privacy information page, also menu item / button text --> <string name="information_privacy_title">"Datenschutz"</string> <!-- XACT: describes illustration --> - <string name="information_privacy_illustration_description">"Eine Frau mit einem Handy benutzt die Corona-Warn-App, ein Vorhängeschloss auf einem Schild steht als Symbol für verschlüsselte Daten."</string> + <string name="information_privacy_illustration_description">"Eine Frau mit einem Smartphone benutzt die Corona-Warn-App, ein Vorhängeschloss auf einem Schild steht als Symbol für verschlüsselte Daten."</string> <!-- XTXT: Path to the full blown privacy html, to translate it exchange "_de" to "_en" and provide the corresponding html file --> <string name="information_privacy_html_path">"privacy_de.html"</string> <!-- XHED: Page title for terms of use information page, also menu item / button text --> @@ -749,7 +749,7 @@ <!-- XHED: Dialog title for already paired test error: tan --> <string name="submission_error_dialog_web_test_paired_title_tan">"TAN ist ungültig"</string> <!-- XMSG: Dialog body for already paired test via tan - error: tan --> - <string name="submission_error_dialog_web_test_paired_body_tan">"Die TAN ist ungültig oder wurde bereits verwendet. Bitte rufen Sie die unter „TAN anfragen" angegebene Nummer an, um weitere Informationen zu erhalten."</string> + <string name="submission_error_dialog_web_test_paired_body_tan">"Die TAN ist ungültig oder wurde bereits verwendet. Bitte rufen Sie die unter „TAN anfragen“ angegebene Nummer an, um weitere Informationen zu erhalten."</string> <!-- XBUT: Positive button for already paired test error --> <string name="submission_error_dialog_web_test_paired_button_positive">"Zurück"</string> @@ -884,9 +884,9 @@ <!-- XACT: Submission Tan page title --> <string name="submission_tan_accessibility_title">"TAN-Eingabe"</string> <!-- YTXT: Error text for the tan submission page --> - <string name="submission_tan_error">"Ungültige TAN, bitte überprüfen Sie Ihre Eingabe."</string> + <string name="submission_tan_error">"Ungültige TAN. Bitte überprüfen Sie Ihre Eingabe."</string> <!-- YTXT: Error text for the tan submission page (wrong characters) --> - <string name="submission_tan_character_error">"Ungültige Eingabe, bitte überprüfen Sie das Zeichen."</string> + <string name="submission_tan_character_error">"Ihre Eingabe enthält ein ungültiges Zeichen. Bitte überprüfen Sie Ihre Eingabe."</string> <!-- Submission Intro --> <!-- XHED: Page title for menu at the start of the submission process --> @@ -942,13 +942,13 @@ <!-- XHED: Page headline for the positive result additional warning page--> <string name="submission_positive_other_warning_headline">"Helfen Sie mit!"</string> <!-- YTXT: Body text for the positive result additional warning page--> - <string name="submission_positive_other_warning_body">"Als Nächstes können Sie dafür sorgen, dass das Corona-Warn-System Ihre lokal gespeicherten Zufallscodes der letzten 14 Tage an andere verteilt. So können Sie Ihre Mitmenschen warnen und helfen, die Infektionskette zu unterbrechen.\n\nDa nur unpersönliche Zufallscodes übertragen werden, bleibt Ihre Identität unbekannt."</string> + <string name="submission_positive_other_warning_body">"Als Nächstes können Sie dafür sorgen, dass das Corona-Warn-System Ihre lokal gespeicherten Zufallscodes der letzten 14 Tage an andere verteilt. So können Sie Ihre Mitmenschen warnen und helfen, die Infektionskette zu unterbrechen.\n\nDa ein Ansteckungsrisiko schon vor dem Erkrankungsbeginn bestehen und im Zeitverlauf variieren kann, bitten wir Sie auch anzugeben, wann eventuelle Corona-Symptome (z. B. Fieber oder Husten) zum ersten Mal bei Ihnen aufgetreten sind. Dadurch kann das Infektionsrisiko von anderen App-Nutzern, die Ihnen begegnet sind, genauer berechnet werden. Die Angabe des Symptombeginns ist optional. Wenn Sie keine Symptome haben oder keine Angaben machen möchten, können Sie „keine Angabe“ auswählen."</string> <!-- XHED: Title for the privacy card--> <string name="submission_positive_other_warning_privacy_title">"Datenschutz"</string> <!-- YTXT: Body text for the privacy card--> - <string name="submission_positive_other_warning_privacy_body">"Durch Antippen von „Weiter“ willigen Sie ein, dass die App Ihr positives Testergebnis zusammen mit Ihren Zufalls-IDs der letzten 14 Tage an das Serversystem der App übermittelt, damit andere App-Nutzer mit aktivierter Risiko-Ermittlung automatisch informiert werden können, dass sie möglicherweise einem Infektionsrisiko ausgesetzt waren. Die übermittelten Zufalls-IDs enthalten keine Angaben, die Rückschlüsse auf Ihre Identität oder Ihre Person zulassen.\n\nDie Ãœbermittlung Ihres Testergebnisses per App ist freiwillig. Wenn Sie Ihr Testergebnis nicht übermitteln, entstehen Ihnen keine Nachteile. Da weder nachvollzogen noch kontrolliert werden kann, ob und wie Sie die App verwenden, erfährt außer Ihnen niemand, ob Sie eine Infektion übermittelt haben.\n\nSie können Ihre Einwilligung jederzeit widerrufen, indem Sie die App löschen. Durch den Widerruf der Einwilligung wird die Rechtmäßigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten Verarbeitung nicht berührt. Weitere Informationen finden Sie unter dem Menüpunkt „Datenschutzinformation“."</string> + <string name="submission_positive_other_warning_privacy_body">"Durch Antippen von „Einverstanden“ willigen Sie ein, dass die App Ihr positives Testergebnis zusammen mit Ihren Zufalls-IDs der letzten 14 Tage an das Serversystem der App übermittelt, damit andere App-Nutzer mit aktivierter Risiko-Ermittlung automatisch informiert werden können, dass sie möglicherweise einem Infektionsrisiko ausgesetzt waren. Wenn Sie optionale Angaben zum Symptombeginn machen, enthalten die übermittelten Zufalls-IDs auch einen aus Ihren Angaben abgeleiteten Risikowert, der Ihr Ansteckungsrisiko am Gültigkeitstag der jeweiligen Zufalls-ID angibt. Die übermittelten Zufalls-IDs enthalten keine Angaben, die Rückschlüsse auf Ihre Identität oder Ihre Person zulassen.\n\nDie Ãœbermittlung Ihres Testergebnisses per App ist freiwillig. Wenn Sie Ihr Testergebnis nicht übermitteln, entstehen Ihnen keine Nachteile. Da weder nachvollzogen noch kontrolliert werden kann, ob und wie Sie die App verwenden, erfährt außer Ihnen niemand, ob Sie eine Infektion übermittelt haben.\n\nSie können Ihre Einwilligung jederzeit widerrufen, indem Sie die App löschen. Durch den Widerruf der Einwilligung wird die Rechtmäßigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten Verarbeitung nicht berührt. Weitere Informationen finden Sie unter dem Menüpunkt „App-Informationen“ > „Datenschutz“."</string> <!-- XBUT: other warning continue button --> - <string name="submission_positive_other_warning_button">"Weiter"</string> + <string name="submission_positive_other_warning_button">"Einverstanden"</string> <!-- XACT: other warning - illustration description, explanation image --> <string name="submission_positive_other_illustration_description">"Ein Smartphone übermittelt einen positiven Testbefund verschlüsselt ins System."</string> @@ -989,7 +989,33 @@ <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Fertig"</string> <!-- XACT: submission finished - illustration description, explanation image --> - <string name="submission_done_illustration_description">"Eine vielfältige Gruppe begrüßt durch Jubel, dass jemand sein Testergebnis mit anderen geteilt hat."</string> + <string name="submission_done_illustration_description">"Sind eines oder mehrere der folgenden Symptome in den letzten Tagen bei Ihnen neu aufgetreten?"</string> + + <!-- Submission Symptoms --> + <!-- XHED: Page title for symptom screens --> + <string name="submission_symptom_title">"Symptome"</string> + <!-- YTXT: headline text for initial symptom screen --> + <string name="submission_symptom_initial_headline">"Sind eines oder mehrere der folgenden Symptome in den letzten Tagen bei Ihnen neu aufgetreten?"</string> + <!-- YTXT: Bullet points for symptoms --> + <string-array name="submission_symptom_symptom_bullets"> + <item>"Erhöhte Temperatur oder Fieber"</item> + <item>"Kurzatmigkeit"</item> + <item>"Verlust des Geruchs-/Geschmackssinns"</item> + <item>"Husten"</item> + <item>"Schnupfen"</item> + <item>"Halsschmerzen"</item> + <item>"Kopf- und Gliederschmerzen"</item> + <item>"Allgemeine Schwäche und Abgeschlagenheit"</item> + </string-array> + <!-- XBUT: symptom initial screen yes button --> + <string name="submission_symptom_positive_button">"Ja"</string> + <!-- XBUT: symptom initial screen no button --> + <string name="submission_symptom_negative_button">"Nein"</string> + <!-- XBUT: symptom initial screen no information button --> + <string name="submission_symptom_no_information_button">"Keine Angabe"</string> + <!-- XBUT: symptom initial screen continue button --> + <string name="submission_symptom_further_button">"Weiter"</string> + <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> @@ -1020,6 +1046,22 @@ <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"Im zweiten Schritt registrieren Sie den Test per TAN-Eingabe in der App."</string> + <!-- Submission Symptom Calendar --> + <!-- XHED: Page title for calendar page in submission symptom flow --> + <string name="submission_symptom_calendar_title">"Symptom-Beginn"</string> + <!-- XHED: Page headline for calendar page in symptom submission flow --> + <string name="submission_symptom_calendar_headline">"Wann sind die Symptome bei Ihnen aufgetreten? "</string> + <!-- YTXT: Body text for calendar page in symptom submission flow--> + <string name="submission_symptom_calendar_body">"Selektieren Sie entweder das genaue Datum in dem Kalender oder wenn Sie sich nicht genau erinnern, eine der anderen Optionen."</string> + <!-- XBUT: symptom calendar screen less than 7 days button --> + <string name="submission_symptom_less_seven">"In den letzten 7 Tagen"</string> + <!-- XBUT: symptom calendar screen 1-2 weeks button --> + <string name="submission_symptom_one_two_weeks">"Vor 1-2 Wochen"</string> + <!-- XBUT: symptom calendar screen more than 2 weeks button --> + <string name="submission_symptom_more_two_weeks">"Vor mehr als 2 Wochen"</string> + <!-- XBUT: symptom calendar screen verify button --> + <string name="submission_symptom_verify">"Keine Angabe"</string> + <!-- Submission Status Card --> <!-- XHED: Page title for the various submission status: fetching --> <string name="submission_status_card_title_fetching">"Datenabruf…"</string> @@ -1083,6 +1125,9 @@ <item>"Gehen Sie nicht krank zur Arbeit, um andere Personen nicht zu gefährden. Falls sich Ihre Symptome verschlechtern, kann die Notwendigkeit eines weiteren SARS-CoV-2-Tests bestehen."</item> </string-array> + <!-- XBUT Symptoms exact date button --> + <string name="symptoms_calendar_exact_date_button">"Genaues Datum"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> 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 1cea318c8050ccf66aef881f190c159ee1ba8cca..3f9469b81bf7f494dadacc2bd68721cf22c60466 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -425,7 +425,7 @@ <!-- XHED: onboarding(tracing) - headline for consent information --> <string name="onboarding_tracing_headline_consent">"Declaration of Consent"</string> <!-- YTXT: onboarding(tracing) - body for consent information --> - <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the COVID-19 Exposure Logging functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the \"Exposure Notifications\" functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_button_next">"Activate Exposure Logging"</string> <!-- XTXT: onboarding(tracing) - dialog about tracing permission declined --> @@ -495,7 +495,7 @@ <string name="sixteen_title_text">"Age Limit: 16 and Up"</string> <!-- XACT: onboarding(sixteen) title --> - <string name="sixteen_description_text">"This app is intended for people who reside in Germany and who are at least 16 years of age."</string> + <string name="sixteen_description_text">"The use of this app is intended for persons who are at least 16 years of age and who reside in Germany."</string> <!-- #################################### @@ -541,7 +541,9 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_location_headline">"Allow location access"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled --> - <string name="settings_tracing_status_location_body">"Your location cannot be accessed. Google and/or Android requires access to your device\'s location to use Bluetooth."</string> + <string name="settings_tracing_status_location_body">"Activate your location services. Bluetooth Low Energy requires activated location services to calculate physical distances, but does not access your location. For further information, please see our FAQ page."</string> + <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled: URL --> + <string name="settings_tracing_status_location_body_url">"https://www.coronawarn.app/en/faq/#android_location"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card - location --> <string name="settings_tracing_status_location_button">"Open Device Settings"</string> <!--XHED : settings(tracing) - headline on card about the current status and what to do --> @@ -720,7 +722,7 @@ <!-- NOTR: subtitle for legal information page, open contact form for languages other than English and German --> <string name="information_legal_subtitle_contact_form_non_en_de">"Contact Form in "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/en/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"English"</a>" or "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"German"</a></string> <!-- XHED: Headline for legal information page, tax section --> - <string name="information_legal_headline_taxid">"VAT identification number"</string> + <string name="information_legal_headline_taxid">"VAT identification\nnumber"</string> <!-- YTXT: subtitle for legal information page, tax section --> <string name="information_legal_subtitle_taxid">"DE 165 893 430"</string> <!-- XACT: describes illustration --> @@ -740,10 +742,14 @@ <!-- XBUT: Positive button for generic web request error --> <string name="submission_error_dialog_web_generic_error_button_positive">"Back"</string> - <!-- XHED: Dialog title for already paired test error --> + <!-- XHED: Dialog title for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_title">"QR code is invalid"</string> - <!-- XMSG: Dialog body for already paired test error --> + <!-- XMSG: Dialog body for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_body">"The QR code is invalid or has been registered on another smartphone already. 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 COVID-19, you will be notified by the public health authority."</string> + <!-- XHED: Dialog title for already paired test error: tan --> + <string name="submission_error_dialog_web_test_paired_title_tan">"TAN is invalid"</string> + <!-- XMSG: Dialog body for already paired test via tan - error: tan --> + <string name="submission_error_dialog_web_test_paired_body_tan">"The TAN is invalid or has already been used. For further information, call the number listed under \"Request TAN\"."</string> <!-- XBUT: Positive button for already paired test error --> <string name="submission_error_dialog_web_test_paired_button_positive">"Back"</string> @@ -768,6 +774,15 @@ <!-- XBUT: Positive button for submission tan redeemed --> <string name="submission_error_dialog_web_tan_redeemed_button_positive">"OK"</string> + <!-- XHED: Dialog title for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_title">"Do you want to cancel?"</string> + <!-- XMSG: Dialog body for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_body">"Your entries will not be saved."</string> + <!-- XBUT: Positive button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_positive">"Yes"</string> + <!-- XBUT: Negative button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_negative">"No"</string> + <!-- Permission Rationale Dialog --> <!-- XHED: Dialog headline QR Scan permission rationale --> <string name="submission_qr_code_scan_permission_rationale_dialog_headline">"Camera authorization required"</string> @@ -869,9 +884,9 @@ <!-- XACT: Submission Tan page title --> <string name="submission_tan_accessibility_title">"TAN entry"</string> <!-- YTXT: Error text for the tan submission page --> - <string name="submission_tan_error">"Invalid TAN, please check your entry."</string> + <string name="submission_tan_error">"Invalid TAN. Please check your entry."</string> <!-- YTXT: Error text for the tan submission page (wrong characters) --> - <string name="submission_tan_character_error">"Invalid entry. Please check your entry."</string> + <string name="submission_tan_character_error">"Your entry contains invalid characters. Please check your entry."</string> <!-- Submission Intro --> <!-- XHED: Page title for menu at the start of the submission process --> @@ -931,9 +946,9 @@ <!-- XHED: Title for the privacy card--> <string name="submission_positive_other_warning_privacy_title">"Data Privacy"</string> <!-- YTXT: Body text for the privacy card--> - <string name="submission_positive_other_warning_privacy_body">"By tapping on “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="submission_positive_other_warning_privacy_body">"By tapping “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacyâ€."</string> <!-- XBUT: other warning continue button --> - <string name="submission_positive_other_warning_button">"Next"</string> + <string name="submission_positive_other_warning_button">"Accept"</string> <!-- XACT: other warning - illustration description, explanation image --> <string name="submission_positive_other_illustration_description">"A device transmits an encrypted positive test diagnosis to the system."</string> @@ -961,7 +976,33 @@ <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Done"</string> <!-- XACT: submission finished - illustration description, explanation image --> - <string name="submission_done_illustration_description">"Everyone in the group is cheering because someone has shared the test result."</string> + <string name="submission_done_illustration_description">"Have you experienced one or more of the following symptoms in the past few days?"</string> + + <!-- Submission Symptoms --> + <!-- XHED: Page title for symptom screens --> + <string name="submission_symptom_title">"Symptoms"</string> + <!-- YTXT: headline text for initial symptom screen --> + <string name="submission_symptom_initial_headline">"Have you experienced one or more of the following symptoms in the past few days?"</string> + <!-- YTXT: Bullet points for symptoms --> + <string-array name="submission_symptom_symptom_bullets"> + <item>"Increased temperature or fever"</item> + <item>"Shortness of breath"</item> + <item>"Loss of sense of smell/taste"</item> + <item>"Cough"</item> + <item>"Runny nose"</item> + <item>"Sore throat"</item> + <item>"Headache and aching limbs"</item> + <item>"General weakness and exhaustion"</item> + </string-array> + <!-- XBUT: symptom initial screen yes button --> + <string name="submission_symptom_positive_button">"Yes"</string> + <!-- XBUT: symptom initial screen no button --> + <string name="submission_symptom_negative_button">"No"</string> + <!-- XBUT: symptom initial screen no information button --> + <string name="submission_symptom_no_information_button">"No comment"</string> + <!-- XBUT: symptom initial screen continue button --> + <string name="submission_symptom_further_button">"Next"</string> + <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> @@ -992,6 +1033,22 @@ <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"In the second step, you register your test with your TAN in the app."</string> + <!-- Submission Symptom Calendar --> + <!-- XHED: Page title for calendar page in submission symptom flow --> + <string name="submission_symptom_calendar_title">"Start of Symptoms"</string> + <!-- XHED: Page headline for calendar page in symptom submission flow --> + <string name="submission_symptom_calendar_headline">"When did you first start to experience these symptoms? "</string> + <!-- YTXT: Body text for calendar page in symptom submission flow--> + <string name="submission_symptom_calendar_body">"Select the exact date in the calendar or, if you cannot remember the exact date, choose one of the other options."</string> + <!-- XBUT: symptom calendar screen less than 7 days button --> + <string name="submission_symptom_less_seven">"In the last 7 days"</string> + <!-- XBUT: symptom calendar screen 1-2 weeks button --> + <string name="submission_symptom_one_two_weeks">"1-2 weeks ago"</string> + <!-- XBUT: symptom calendar screen more than 2 weeks button --> + <string name="submission_symptom_more_two_weeks">"More than 2 weeks ago"</string> + <!-- XBUT: symptom calendar screen verify button --> + <string name="submission_symptom_verify">"No comment"</string> + <!-- Submission Status Card --> <!-- XHED: Page title for the various submission status: fetching --> <string name="submission_status_card_title_fetching">"Data being retrieved...."</string> @@ -1055,6 +1112,9 @@ <item>"Do not go to work if you feel unwell to ensure you do not put other people at risk. If your symptoms worsen, you might need a further SARS-CoV-2 test."</item> </string-array> + <!-- XBUT Symptoms exact date button --> + <string name="symptoms_calendar_exact_date_button">"Exact date"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1083,7 +1143,7 @@ <!-- XTXT: error dialog - detailed text if there is an error during external navigation / external action --> <string name="errors_external_action">"You cannot perform this action. Please contact the hotline."</string> <!-- XTXT: error dialog - phone still needs Google Play Services or Google Mobile Services update --> - <string name="errors_google_update_needed">"Your Corona-Warn-App is correctly installed, but the \"COVID-19 exposure notifications\" service is not available on your smartphone\'s operating system. This means that you cannot use the Corona-Warn-App. For further information, please see our FAQ page: https://www.coronawarn.app/en/faq/"</string> + <string name="errors_google_update_needed">"Your Corona-Warn-App is correctly installed, but the \"COVID-19 Exposure Notifications System\" is not available on your smartphone\'s operating system. This means that you cannot use the Corona-Warn-App. For further information, please see our FAQ page: https://www.coronawarn.app/en/faq/"</string> <!-- XTXT: error dialog - either Google API Error (10) or reached request limit per day --> <string name="errors_google_api_error">"The Corona-Warn-App is running correctly, but we cannot update your current risk status. Exposure logging remains active and is working correctly. For further information, please see our FAQ page: https://www.coronawarn.app/en/faq/"</string> diff --git a/Corona-Warn-App/src/main/res/values-night/colors.xml b/Corona-Warn-App/src/main/res/values-night/colors.xml index b2fda3b41a4aabd5763535f0ca7ffd85fe4ff600..0481128c930d6fa77df913daa7402f94e00b4a51 100644 --- a/Corona-Warn-App/src/main/res/values-night/colors.xml +++ b/Corona-Warn-App/src/main/res/values-night/colors.xml @@ -53,4 +53,6 @@ <color name="colorStableHairlineLight">#33FFFFFF</color> <color name="colorStableHairlineDark">#3317191A</color> + <!-- Calendar --> + <color name="colorCalendarMonthText">#DEFFFFFF</color> </resources> 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 ee8a348dc7fbb995706322514d4fb96a18dacab1..2b43331da8bd5a3ba585f59fc0436457664d7671 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -425,7 +425,7 @@ <!-- XHED: onboarding(tracing) - headline for consent information --> <string name="onboarding_tracing_headline_consent">"OÅ›wiadczenie o wyrażeniu zgody"</string> <!-- YTXT: onboarding(tracing) - body for consent information --> - <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the COVID-19 Exposure Logging functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the \"Exposure Notifications\" functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_button_next">"Aktywuj rejestrowanie narażenia"</string> <!-- XTXT: onboarding(tracing) - dialog about tracing permission declined --> @@ -495,7 +495,7 @@ <string name="sixteen_title_text">"Limit wieku: 16 i wiÄ™cej"</string> <!-- XACT: onboarding(sixteen) title --> - <string name="sixteen_description_text">"Ta aplikacja jest przeznaczona dla osób mieszkajÄ…cych w Niemczech, które ukoÅ„czyÅ‚y 16 lat."</string> + <string name="sixteen_description_text">"Ta aplikacja jest przeznaczona dla osób, które ukoÅ„czyÅ‚y 16 lat i mieszkajÄ… w Niemczech."</string> <!-- #################################### @@ -541,7 +541,9 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_location_headline">"Zezwól na dostÄ™p do lokalizacji"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled --> - <string name="settings_tracing_status_location_body">"Nie można uzyskać dostÄ™pu do Twojej lokalizacji. Google i/lub Android wymaga dostÄ™pu do lokalizacji Twojego urzÄ…dzenia w celu użycia Bluetooth."</string> + <string name="settings_tracing_status_location_body">"Aktywuj swoje usÅ‚ugi lokalizacji. Bluetooth Low Energy wymaga aktywowanych usÅ‚ug lokalizacji do obliczenia fizycznego dystansu, ale nie uzyskuje dostÄ™pu do Twojej lokalizacji. WiÄ™cej informacji znajduje siÄ™ na stronie CzÄ™sto zadawane pytania."</string> + <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled: URL --> + <string name="settings_tracing_status_location_body_url">"https://www.coronawarn.app/en/faq/#android_location"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card - location --> <string name="settings_tracing_status_location_button">"Otwórz ustawienia urzÄ…dzenia"</string> <!--XHED : settings(tracing) - headline on card about the current status and what to do --> @@ -740,10 +742,14 @@ <!-- XBUT: Positive button for generic web request error --> <string name="submission_error_dialog_web_generic_error_button_positive">"Wstecz"</string> - <!-- XHED: Dialog title for already paired test error --> + <!-- XHED: Dialog title for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_title">"Niepoprawny kod QR"</string> - <!-- XMSG: Dialog body for already paired test error --> + <!-- XMSG: Dialog body for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_body">"Kod QR jest niepoprawny lub zostaÅ‚ już zarejestrowany na innym smartfonie. 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 COVID-19 otrzymasz powiadomienie z organu ds. zdrowia publicznego."</string> + <!-- XHED: Dialog title for already paired test error: tan --> + <string name="submission_error_dialog_web_test_paired_title_tan">"TAN jest nieprawidÅ‚owy."</string> + <!-- XMSG: Dialog body for already paired test via tan - error: tan --> + <string name="submission_error_dialog_web_test_paired_body_tan">"TAN jest nieprawidÅ‚owy lub zostaÅ‚ już użyty. WiÄ™cej informacji można uzyskać dzwoniÄ…c na numer wymieniony w sekcji „PoproÅ› o TANâ€."</string> <!-- XBUT: Positive button for already paired test error --> <string name="submission_error_dialog_web_test_paired_button_positive">"Wstecz"</string> @@ -768,6 +774,15 @@ <!-- XBUT: Positive button for submission tan redeemed --> <string name="submission_error_dialog_web_tan_redeemed_button_positive">"OK"</string> + <!-- XHED: Dialog title for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_title">"Czy chcesz anulować?"</string> + <!-- XMSG: Dialog body for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_body">"Twoje wpisy nie zostanÄ… zapisane."</string> + <!-- XBUT: Positive button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_positive">"Tak"</string> + <!-- XBUT: Negative button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_negative">"Nie"</string> + <!-- Permission Rationale Dialog --> <!-- XHED: Dialog headline QR Scan permission rationale --> <string name="submission_qr_code_scan_permission_rationale_dialog_headline">"Wymagana autoryzacja aparatu"</string> @@ -871,7 +886,7 @@ <!-- YTXT: Error text for the tan submission page --> <string name="submission_tan_error">"NieprawidÅ‚owy TAN, sprawdź swój wpis."</string> <!-- YTXT: Error text for the tan submission page (wrong characters) --> - <string name="submission_tan_character_error">"NieprawidÅ‚owy wpis, sprawdź go."</string> + <string name="submission_tan_character_error">"Twój wpis zawiera nieprawidÅ‚owe znaki, Sprawdź swój wpis."</string> <!-- Submission Intro --> <!-- XHED: Page title for menu at the start of the submission process --> @@ -931,9 +946,9 @@ <!-- XHED: Title for the privacy card--> <string name="submission_positive_other_warning_privacy_title">"Prywatność danych"</string> <!-- YTXT: Body text for the privacy card--> - <string name="submission_positive_other_warning_privacy_body">"By tapping on “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="submission_positive_other_warning_privacy_body">"By tapping “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacyâ€."</string> <!-- XBUT: other warning continue button --> - <string name="submission_positive_other_warning_button">"Dalej"</string> + <string name="submission_positive_other_warning_button">"Akceptuj"</string> <!-- XACT: other warning - illustration description, explanation image --> <string name="submission_positive_other_illustration_description">"Zaszyfrowana diagnoza zakażenia jest przesyÅ‚ana do systemu."</string> @@ -961,7 +976,33 @@ <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Gotowe"</string> <!-- XACT: submission finished - illustration description, explanation image --> - <string name="submission_done_illustration_description">"Wszyscy w grupie siÄ™ cieszÄ…, ponieważ ktoÅ› podzieliÅ‚ siÄ™ wynikiem testu."</string> + <string name="submission_done_illustration_description">"Czy w ciÄ…gu ostatnich kilku dni wystÄ…piÅ‚ u Ciebie jeden lub kilka z wymienionych poniżej symptomów?"</string> + + <!-- Submission Symptoms --> + <!-- XHED: Page title for symptom screens --> + <string name="submission_symptom_title">"Symptomy"</string> + <!-- YTXT: headline text for initial symptom screen --> + <string name="submission_symptom_initial_headline">"Czy w ciÄ…gu ostatnich kilku dni wystÄ…piÅ‚ u Ciebie jeden lub kilka z wymienionych poniżej symptomów?"</string> + <!-- YTXT: Bullet points for symptoms --> + <string-array name="submission_symptom_symptom_bullets"> + <item>"Podwyższona temperatura lub gorÄ…czka"</item> + <item>"DusznoÅ›ci"</item> + <item>"Utrata wÄ™chu/smaku"</item> + <item>"Kaszel"</item> + <item>"Katar"</item> + <item>"Ból gardÅ‚a"</item> + <item>"Ból gÅ‚owy i koÅ„czyn"</item> + <item>"Ogólne osÅ‚abienie i wyczerpanie"</item> + </string-array> + <!-- XBUT: symptom initial screen yes button --> + <string name="submission_symptom_positive_button">"Tak"</string> + <!-- XBUT: symptom initial screen no button --> + <string name="submission_symptom_negative_button">"Nie"</string> + <!-- XBUT: symptom initial screen no information button --> + <string name="submission_symptom_no_information_button">"Bez komentarza"</string> + <!-- XBUT: symptom initial screen continue button --> + <string name="submission_symptom_further_button">"Dalej"</string> + <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> @@ -992,6 +1033,22 @@ <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"NastÄ™pnie zarejestruj test przy użyciu numeru TAN w aplikacji."</string> + <!-- Submission Symptom Calendar --> + <!-- XHED: Page title for calendar page in submission symptom flow --> + <string name="submission_symptom_calendar_title">"PoczÄ…tek wystÄ…pienia symptomów"</string> + <!-- XHED: Page headline for calendar page in symptom submission flow --> + <string name="submission_symptom_calendar_headline">"Kiedy zaczÄ…Å‚eÅ›(-Å‚aÅ›) odczutwać te symptomy? "</string> + <!-- YTXT: Body text for calendar page in symptom submission flow--> + <string name="submission_symptom_calendar_body">"Wybierz dokÅ‚adnÄ… datÄ™ w kalendarzu lub, jeÅ›li nie pamiÄ™tasz dokÅ‚adnej daty, wybierz jednÄ… z innych opcji."</string> + <!-- XBUT: symptom calendar screen less than 7 days button --> + <string name="submission_symptom_less_seven">"W ciÄ…gu ostatnich 7 dni"</string> + <!-- XBUT: symptom calendar screen 1-2 weeks button --> + <string name="submission_symptom_one_two_weeks">"1-2 tygodnie temu"</string> + <!-- XBUT: symptom calendar screen more than 2 weeks button --> + <string name="submission_symptom_more_two_weeks">"Ponad 2 tygodnie temu"</string> + <!-- XBUT: symptom calendar screen verify button --> + <string name="submission_symptom_verify">"Bez komentarza"</string> + <!-- Submission Status Card --> <!-- XHED: Page title for the various submission status: fetching --> <string name="submission_status_card_title_fetching">"Pobieranie danych..."</string> @@ -1055,6 +1112,9 @@ <item>"Nie idź do pracy, jeÅ›li nie czujesz siÄ™ dobrze, aby nie stwarzać zagrożenia dla innych osób. JeÅ›li objawy nasilÄ… siÄ™, może być konieczne wykonanie kolejnego testu SARS-CoV-2."</item> </string-array> + <!-- XBUT Symptoms exact date button --> + <string name="symptoms_calendar_exact_date_button">"DokÅ‚adna data"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1083,7 +1143,7 @@ <!-- XTXT: error dialog - detailed text if there is an error during external navigation / external action --> <string name="errors_external_action">"Nie możesz wykonać tej czynnoÅ›ci. Skontaktuj siÄ™ z infoliniÄ…."</string> <!-- XTXT: error dialog - phone still needs Google Play Services or Google Mobile Services update --> - <string name="errors_google_update_needed">"Twoja aplikacja Corona-Warn-App jest poprawnie zainstalowana, ale usÅ‚uga „Powiadomienia o narażeniu na COVID-19†nie jest dostÄ™pna w systemie operacyjnym Twojego smartfona. Oznacza to, że nie możesz korzystać z aplikacji Corona-Warn-App. WiÄ™cej informacji znajduje siÄ™ na naszej stronie „CzÄ™sto zadawane pytaniaâ€: https://www.coronawarn.app/en/faq/."</string> + <string name="errors_google_update_needed">"Twoja aplikacja Corona-Warn-App jest poprawnie zainstalowana, ale system „Powiadomienia o narażeniu na COVID-19†nie jest dostÄ™pny w systemie operacyjnym Twojego smartfona. Oznacza to, że nie możesz korzystać z aplikacji Corona-Warn-App. WiÄ™cej informacji znajduje siÄ™ na naszej stronie „CzÄ™sto zadawane pytaniaâ€: https://www.coronawarn.app/en/faq/."</string> <!-- XTXT: error dialog - either Google API Error (10) or reached request limit per day --> <string name="errors_google_api_error">"Aplikacja Corona-Warn-App dziaÅ‚a prawidÅ‚owo, ale nie możemy zaaktualizować Twojego aktualnego statusu ryzyka. Rejestrowanie narażenia pozostaje aktywne i dziaÅ‚a prawidÅ‚owo. WiÄ™cej informacji można znaleźć na naszej stronie „CzÄ™sto zadawane pytaniaâ€: https://www.coronawarn.app/en/faq/"</string> diff --git a/Corona-Warn-App/src/main/res/values-ro/strings.xml b/Corona-Warn-App/src/main/res/values-ro/strings.xml index a38d1fc6e6e39a509c0ffce0a13855905460cd66..cbcc88284abfc41b988d42d536e00801301a16c7 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -425,7 +425,7 @@ <!-- XHED: onboarding(tracing) - headline for consent information --> <string name="onboarding_tracing_headline_consent">"DeclaraÈ›ie de consimțământ"</string> <!-- YTXT: onboarding(tracing) - body for consent information --> - <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the COVID-19 Exposure Logging functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the \"Exposure Notifications\" functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_button_next">"ActivaÈ›i înregistrarea în jurnal a expunerilor"</string> <!-- XTXT: onboarding(tracing) - dialog about tracing permission declined --> @@ -495,7 +495,7 @@ <string name="sixteen_title_text">"Limita de vârstă: începând cu 16 ani"</string> <!-- XACT: onboarding(sixteen) title --> - <string name="sixteen_description_text">"Această aplicaÈ›ie este destinată persoanelor care locuiesc în Germania È™i care au vârsta de cel puÈ›in 16 ani."</string> + <string name="sixteen_description_text">"Utilizarea acestei aplicaÈ›ii este destinată persoanelor care au vârsta de cel puÈ›in 16 ani È™i care locuiesc în Germania."</string> <!-- #################################### @@ -541,7 +541,9 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_location_headline">"PermiteÈ›i accesul la locaÈ›ie"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled --> - <string name="settings_tracing_status_location_body">"LocaÈ›ia dvs. nu poate fi accesată. Google È™i/sau Android necesită acces la locaÈ›ia dispozitivului dvs. pentru a utiliza Bluetooth-ul."</string> + <string name="settings_tracing_status_location_body">"ActivaÈ›i serviciile de localizare. Bluetooth Low Energy necesită ca serviciile de localizare să fie activate pentru a calcula distanÈ›ele fizice, dar nu vă accesează locaÈ›ia. Pentru informaÈ›ii suplimentare, consultaÈ›i pagina noastră de întrebări frecvente."</string> + <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled: URL --> + <string name="settings_tracing_status_location_body_url">"https://www.coronawarn.app/en/faq/#android_location"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card - location --> <string name="settings_tracing_status_location_button">"DeschideÈ›i setările dispozitivului"</string> <!--XHED : settings(tracing) - headline on card about the current status and what to do --> @@ -720,7 +722,7 @@ <!-- NOTR: subtitle for legal information page, open contact form for languages other than English and German --> <string name="information_legal_subtitle_contact_form_non_en_de">"Contact Form in "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/en/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"English"</a>" or "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"German"</a></string> <!-- XHED: Headline for legal information page, tax section --> - <string name="information_legal_headline_taxid">"Număr de înregistrare în scop de TVA"</string> + <string name="information_legal_headline_taxid">"Număr de înregistrare\nîn scop de TVA"</string> <!-- YTXT: subtitle for legal information page, tax section --> <string name="information_legal_subtitle_taxid">"DE 165 893 430"</string> <!-- XACT: describes illustration --> @@ -740,10 +742,14 @@ <!-- XBUT: Positive button for generic web request error --> <string name="submission_error_dialog_web_generic_error_button_positive">"ÃŽnapoi"</string> - <!-- XHED: Dialog title for already paired test error --> + <!-- XHED: Dialog title for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_title">"Codul QR este nevalabil"</string> - <!-- XMSG: Dialog body for already paired test error --> + <!-- XMSG: Dialog body for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_body">"Codul QR este nevalabil sau a fost deja înregistrat pe un alt smartphone. VeÈ›i primi rezultatul testului dvs. de la centrul sau laboratorul de testare, indiferent de valabilitatea codului QR. Dacă sunteÈ›i diagnosticat cu COVID-19, veÈ›i fi notificat de autoritatea de sănătate publică."</string> + <!-- XHED: Dialog title for already paired test error: tan --> + <string name="submission_error_dialog_web_test_paired_title_tan">"TAN-ul este nevalabil"</string> + <!-- XMSG: Dialog body for already paired test via tan - error: tan --> + <string name="submission_error_dialog_web_test_paired_body_tan">"TAN-ul este nevalabil sau a fost deja utilizat. Pentru informaÈ›ii suplimentare, apelaÈ›i numărul afiÈ™at la „Solicitare TANâ€."</string> <!-- XBUT: Positive button for already paired test error --> <string name="submission_error_dialog_web_test_paired_button_positive">"ÃŽnapoi"</string> @@ -768,6 +774,15 @@ <!-- XBUT: Positive button for submission tan redeemed --> <string name="submission_error_dialog_web_tan_redeemed_button_positive">"OK"</string> + <!-- XHED: Dialog title for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_title">"DoriÈ›i să anulaÈ›i?"</string> + <!-- XMSG: Dialog body for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_body">"Intrările dvs. nu vor fi salvate."</string> + <!-- XBUT: Positive button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_positive">"Da"</string> + <!-- XBUT: Negative button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_negative">"Nu"</string> + <!-- Permission Rationale Dialog --> <!-- XHED: Dialog headline QR Scan permission rationale --> <string name="submission_qr_code_scan_permission_rationale_dialog_headline">"Este necesară autorizaÈ›ia pentru camera foto"</string> @@ -869,9 +884,9 @@ <!-- XACT: Submission Tan page title --> <string name="submission_tan_accessibility_title">"Intrare TAN"</string> <!-- YTXT: Error text for the tan submission page --> - <string name="submission_tan_error">"TAN nevalabil. VerificaÈ›i intrarea."</string> + <string name="submission_tan_error">"TAN nevalabil. VerificaÈ›i intrarea dvs."</string> <!-- YTXT: Error text for the tan submission page (wrong characters) --> - <string name="submission_tan_character_error">"Intrare nevalabilă. VerificaÈ›i intrarea."</string> + <string name="submission_tan_character_error">"Intrarea dvs. conÈ›ine caractere nevalabile. Vă rugăm să o verificaÈ›i."</string> <!-- Submission Intro --> <!-- XHED: Page title for menu at the start of the submission process --> @@ -931,9 +946,9 @@ <!-- XHED: Title for the privacy card--> <string name="submission_positive_other_warning_privacy_title">"ConfidenÈ›ialitatea datelor"</string> <!-- YTXT: Body text for the privacy card--> - <string name="submission_positive_other_warning_privacy_body">"By tapping on “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="submission_positive_other_warning_privacy_body">"Apăsând pe „Acceptâ€, consimÈ›iÈ›i ca aplicaÈ›ia să trimită rezultatul pozitiv al testului dvs. către sistemul de servere al aplicaÈ›iei, împreună cu ID-urile dvs. aleatorii din ultimele 14 zile, pentru ca ceilalÈ›i utilizatori ai aplicaÈ›iei care au activat caracteristica de înregistrare în jurnal a expunerilor să poată fi notificaÈ›i automat că se poate să fi fost expuÈ™i la riscul de infectare. ID-urile aleatorii transmise în acest scop nu conÈ›in informaÈ›ii care să poată permite tragerea unor concluzii privind identitatea dvs. sau persoana dvs. \n\nTransmiterea rezultatului testului prin intermediul aplicaÈ›iei este voluntară. Nu veÈ›i fi penalizat dacă nu transmiteÈ›i rezultatul testului. Din moment ce nu este posibilă urmărirea sau verificarea modului în care utilizaÈ›i aplicaÈ›ia sau dacă o utilizaÈ›i sau nu, doar dvs. veÈ›i È™ti dacă aÈ›i transmis informaÈ›iile că sunteÈ›i infectat.\n\nPuteÈ›i oricând să vă retrageÈ›i consimțământul prin È™tergerea acestei aplicaÈ›ii. Retragerea consimțământului nu va afecta legalitatea procesării efectuate în baza consimțământului acordat înainte de retragerea acestuia. Pentru informaÈ›ii suplimentare, consultaÈ›i meniul din „ConfidenÈ›ialitatea datelorâ€."</string> <!-- XBUT: other warning continue button --> - <string name="submission_positive_other_warning_button">"ÃŽnainte"</string> + <string name="submission_positive_other_warning_button">"Accept"</string> <!-- XACT: other warning - illustration description, explanation image --> <string name="submission_positive_other_illustration_description">"Un dispozitiv transmite un diagnostic de test pozitiv criptat către sistem."</string> @@ -961,7 +976,33 @@ <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Gata"</string> <!-- XACT: submission finished - illustration description, explanation image --> - <string name="submission_done_illustration_description">"Toată lumea din grup aclamă deoarece o persoană È™i-a împărtășit rezultatul testului."</string> + <string name="submission_done_illustration_description">"AÈ›i resimÈ›it unul sau mai multe dintre următoarele simptome în ultimele zile?"</string> + + <!-- Submission Symptoms --> + <!-- XHED: Page title for symptom screens --> + <string name="submission_symptom_title">"Simptome"</string> + <!-- YTXT: headline text for initial symptom screen --> + <string name="submission_symptom_initial_headline">"AÈ›i resimÈ›it unul sau mai multe dintre următoarele simptome în ultimele zile?"</string> + <!-- YTXT: Bullet points for symptoms --> + <string-array name="submission_symptom_symptom_bullets"> + <item>"Temperatură crescută sau febră"</item> + <item>"Scurtarea respiraÈ›iei"</item> + <item>"Pierderea mirosului/gustului"</item> + <item>"Tuse"</item> + <item>"Curgerea nasului"</item> + <item>"Durere în gât"</item> + <item>"Durere de cap È™i dureri de membre"</item> + <item>"Slăbiciune generală È™i epuizare"</item> + </string-array> + <!-- XBUT: symptom initial screen yes button --> + <string name="submission_symptom_positive_button">"Da"</string> + <!-- XBUT: symptom initial screen no button --> + <string name="submission_symptom_negative_button">"Nu"</string> + <!-- XBUT: symptom initial screen no information button --> + <string name="submission_symptom_no_information_button">"Nu comentez"</string> + <!-- XBUT: symptom initial screen continue button --> + <string name="submission_symptom_further_button">"ÃŽnainte"</string> + <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> @@ -992,6 +1033,22 @@ <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"Al doilea pas: înregistraÈ›i-vă testul cu codul dvs. TAN în aplicaÈ›ie."</string> + <!-- Submission Symptom Calendar --> + <!-- XHED: Page title for calendar page in submission symptom flow --> + <string name="submission_symptom_calendar_title">"ÃŽnceputul simptomelor"</string> + <!-- XHED: Page headline for calendar page in symptom submission flow --> + <string name="submission_symptom_calendar_headline">"Când aÈ›i început să resimÈ›iÈ›i aceste simptome? "</string> + <!-- YTXT: Body text for calendar page in symptom submission flow--> + <string name="submission_symptom_calendar_body">"SelectaÈ›i data exactă din calendar sau, în cazul în care nu È›ineÈ›i minte data exactă, alegeÈ›i una dintre celelalte opÈ›iuni:"</string> + <!-- XBUT: symptom calendar screen less than 7 days button --> + <string name="submission_symptom_less_seven">"ÃŽn ultimele 7 zile"</string> + <!-- XBUT: symptom calendar screen 1-2 weeks button --> + <string name="submission_symptom_one_two_weeks">"Acum 1-2 săptămâni"</string> + <!-- XBUT: symptom calendar screen more than 2 weeks button --> + <string name="submission_symptom_more_two_weeks">"Cu peste 2 săptămâni în urmă"</string> + <!-- XBUT: symptom calendar screen verify button --> + <string name="submission_symptom_verify">"Nu comentez"</string> + <!-- Submission Status Card --> <!-- XHED: Page title for the various submission status: fetching --> <string name="submission_status_card_title_fetching">"Datele sunt citite...."</string> @@ -1055,6 +1112,9 @@ <item>"Nu mergeÈ›i la serviciu dacă nu vă simÈ›iÈ›i bine pentru a nu expune la risc alte persoane. Dacă simptomele dvs. se agravează, se poate să aveÈ›i nevoie de un test SARS-CoV-2 suplimentar."</item> </string-array> + <!-- XBUT Symptoms exact date button --> + <string name="symptoms_calendar_exact_date_button">"Data exactă"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> 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 0b5195ad18370c52bcfc538eebe18a445c0743c9..37da8aabb8cba5c6415109d67a207c791a12169d 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -425,7 +425,7 @@ <!-- XHED: onboarding(tracing) - headline for consent information --> <string name="onboarding_tracing_headline_consent">"Kabul Beyanı"</string> <!-- YTXT: onboarding(tracing) - body for consent information --> - <string name="onboarding_tracing_body_consent">"Enfekte olan biriyle temas halinde olup olmadığınızı ve sizin de enfeksiyon riski altında olup olmadığınızı öğrenmek için Uygulamanın maruz kalma günlüğü özelliÄŸini etkinleÅŸtirmeniz gerekir. \"EtkinleÅŸtir\" düğmesine dokunarak Uygulamanın maruz kalma günlüğü özelliÄŸini etkinleÅŸtirmeyi ve iliÅŸkili verilerin iÅŸlenmesini kabul edersiniz."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Uygulamanın maruz kalma günlüğü özelliÄŸini kullanmak için akıllı telefonunuzda Google tarafından sunulan COVID-19\'a Maruz Kalma Günlüğü iÅŸlevini etkinleÅŸtirmeniz ve Corona-Warn-App\'e bu iÅŸlevi kullanma izni vermeniz gerekecektir."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Maruz kalma günlüğü etkinleÅŸtirildiÄŸinde akıllı telefonunuz sürekli olarak rastgele kimlikler oluÅŸturur ve bu rastgele kimlikleri maruz kalma günlüğü etkinleÅŸtirilmiÅŸ olan yakınınızdaki diÄŸer Android veya Apple akıllı telefonlara Bluetooth üzerinden aktarır. Bunun karşılığında akıllı telefonunuz diÄŸer akıllı telefonların rastgele kimliklerini alır. Size ait olan ve diÄŸer akıllı telefonlardan alınan rastgele kimlikler, maruz kalma günlüğüne kaydedilir ve 14 gün süreyle burada saklanır."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Uygulama, enfeksiyon riskinizi belirlemek için koronavirüs enfeksiyonu olduÄŸunu Uygulamada belirten tüm kullanıcıların rastgele kimliklerinin listesini (günde birkaç kez veya talep üzerine) yükler. Ardından bu liste maruz kalma günlüğünde tutulan rastgele kimliklerle karşılaÅŸtırılır. Uygulama enfekte olan bir kullanıcı ile temas halinde olabileceÄŸinizi saptadığında bu konuda bilgilendirilirsiniz ve sizin de enfeksiyon riski taşıdığınız belirtilir. Bu durumda Uygulama akıllı telefonunuzun maruz kalma günlüğünde tutulan diÄŸer verilere (tarih, süre ve temas sırasındaki Bluetooth sinyalinin gücü) eriÅŸir."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Bluetooth sinyal gücü, fiziksel mesafenin belirlenmesi için kullanılır (sinyal ne kadar güçlü olursa mesafe o kadar kısa olur). Ardından Uygulama, koronavirüs enfeksiyonu olasılığınızı deÄŸerlendirmek ve sonraki adımlara yönelik öneriler sunmak üzere bu bilgileri analiz eder. Bu analiz akıllı telefonunuzda yalnızca yerel olarak gerçekleÅŸtirilir. Sizin dışınızda hiç kimse (RKI bile) enfekte olan biriyle temas ettiÄŸinizi ve sizin için belirlenen riski bilmez."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Maruz kalma günlüğüne verdiÄŸiniz onayı geri çekmek için Uygulamadaki düğmeyi kullanarak veya Uygulamayı silerek özelliÄŸi devre dışı bırakabilirsiniz. Maruz kalma günlüğü özelliÄŸini yeniden kullanmaya karar verirseniz özelliÄŸi geri açabilir veya Uygulamayı yeniden yükleyebilirsiniz. Maruz kalma günlüğü özelliÄŸini devre dışı bırakırsanız Uygulama artık enfekte olan bir kullanıcı ile temas halinde olup olmadığınızı kontrol etmez. Cihazınızın rastgele kimlik göndermesini ve almasını durdurmak isterseniz de akıllı telefonunuzun ayarlarında COVID-19\'a Maruz Kalma Günlüğü özelliÄŸini devre dışı bırakmanız gerekir. Size ait olan ve diÄŸer akıllı telefonlardan alınan maruz kalma günlüğündeki rastgele kimliklerin Uygulamada silinmeyeceÄŸini unutmayın. Akıllı telefonunuzun ayarlarında yalnızca maruz kalma günlüğünde tutulan verileri kalıcı olarak silebilirsiniz."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Uygulamanın gizlilik bildirimini (maruz kalma günlüğü özelliÄŸi için gerçekleÅŸtirilen veri iÅŸlemeye iliÅŸkin açıklama dahil) menüde \"Veri GizliliÄŸi Bilgileri\" baÅŸlığında bulabilirsiniz."</string> + <string name="onboarding_tracing_body_consent">"Enfekte olan biriyle temas halinde olup olmadığınızı ve sizin de enfeksiyon riski altında olup olmadığınızı öğrenmek için Uygulamanın maruz kalma günlüğü özelliÄŸini etkinleÅŸtirmeniz gerekir. \"EtkinleÅŸtir\" düğmesine dokunarak Uygulamanın maruz kalma günlüğü özelliÄŸini etkinleÅŸtirmeyi ve iliÅŸkili verilerin iÅŸlenmesini kabul edersiniz."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Uygulamanın maruz kalma günlüğü özelliÄŸini kullanmak için akıllı telefonunuzda Google tarafından sunulan \"Maruz Kalma Bildirimleri\" iÅŸlevini etkinleÅŸtirmeniz ve Corona-Warn-App\'e bu iÅŸlevi kullanma izni vermeniz gerekecektir."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Maruz kalma günlüğü etkinleÅŸtirildiÄŸinde akıllı telefonunuz sürekli olarak rastgele kimlikler oluÅŸturur ve bu rastgele kimlikleri maruz kalma günlüğü etkinleÅŸtirilmiÅŸ olan yakınınızdaki diÄŸer Android veya Apple akıllı telefonlara Bluetooth üzerinden aktarır. Bunun karşılığında akıllı telefonunuz diÄŸer akıllı telefonların rastgele kimliklerini alır. Size ait olan ve diÄŸer akıllı telefonlardan alınan rastgele kimlikler, maruz kalma günlüğüne kaydedilir ve 14 gün süreyle burada saklanır."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Uygulama, enfeksiyon riskinizi belirlemek için koronavirüs enfeksiyonu olduÄŸunu Uygulamada belirten tüm kullanıcıların rastgele kimliklerinin listesini (günde birkaç kez veya talep üzerine) yükler. Ardından bu liste maruz kalma günlüğünde tutulan rastgele kimliklerle karşılaÅŸtırılır. Uygulama enfekte olan bir kullanıcı ile temas halinde olabileceÄŸinizi saptadığında bu konuda bilgilendirilirsiniz ve sizin de enfeksiyon riski taşıdığınız belirtilir. Bu durumda Uygulama akıllı telefonunuzun maruz kalma günlüğünde tutulan diÄŸer verilere (tarih, süre ve temas sırasındaki Bluetooth sinyalinin gücü) eriÅŸir."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Bluetooth sinyal gücü, fiziksel mesafenin belirlenmesi için kullanılır (sinyal ne kadar güçlü olursa mesafe o kadar kısa olur). Ardından Uygulama, koronavirüs enfeksiyonu olasılığınızı deÄŸerlendirmek ve sonraki adımlara yönelik öneriler sunmak üzere bu bilgileri analiz eder. Bu analiz akıllı telefonunuzda yalnızca yerel olarak gerçekleÅŸtirilir. Sizin dışınızda hiç kimse (RKI bile) enfekte olan biriyle temas ettiÄŸinizi ve sizin için belirlenen riski bilmez."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Maruz kalma günlüğüne verdiÄŸiniz onayı geri çekmek için Uygulamadaki düğmeyi kullanarak veya Uygulamayı silerek özelliÄŸi devre dışı bırakabilirsiniz. Maruz kalma günlüğü özelliÄŸini yeniden kullanmaya karar verirseniz özelliÄŸi geri açabilir veya Uygulamayı yeniden yükleyebilirsiniz. Maruz kalma günlüğü özelliÄŸini devre dışı bırakırsanız Uygulama artık enfekte olan bir kullanıcı ile temas halinde olup olmadığınızı kontrol etmez. Cihazınızın rastgele kimlik göndermesini ve almasını durdurmak isterseniz de akıllı telefonunuzun ayarlarında COVID-19\'a Maruz Kalma Günlüğü özelliÄŸini devre dışı bırakmanız gerekir. Size ait olan ve diÄŸer akıllı telefonlardan alınan maruz kalma günlüğündeki rastgele kimliklerin Uygulamada silinmeyeceÄŸini unutmayın. Akıllı telefonunuzun ayarlarında yalnızca maruz kalma günlüğünde tutulan verileri kalıcı olarak silebilirsiniz."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"Uygulamanın gizlilik bildirimini (maruz kalma günlüğü özelliÄŸi için gerçekleÅŸtirilen veri iÅŸlemeye iliÅŸkin açıklama dahil) menüde \"Veri GizliliÄŸi Bilgileri\" baÅŸlığında bulabilirsiniz."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_button_next">"Maruz Kalma Günlüğünü EtkinleÅŸtir"</string> <!-- XTXT: onboarding(tracing) - dialog about tracing permission declined --> @@ -495,7 +495,7 @@ <string name="sixteen_title_text">"YaÅŸ Sınırı: 16 ve Ãœzeri"</string> <!-- XACT: onboarding(sixteen) title --> - <string name="sixteen_description_text">"Uygulama, Almanya\'da yaÅŸayan ve 16 yaÅŸ ve üzerindeki kiÅŸiler için hazırlanmıştır."</string> + <string name="sixteen_description_text">"Bu uygulama, 16 yaÅŸ ve üzerinde olan ve Almanya\'da yaÅŸayan kiÅŸilerin kullanması için hazırlanmıştır."</string> <!-- #################################### @@ -541,7 +541,9 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_location_headline">"Konum eriÅŸimine izin ver"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled --> - <string name="settings_tracing_status_location_body">"Konumunuza eriÅŸilemiyor. Bluetooth\'u kullanmak için Google ve/veya Android\'in cihazınızın konumuna eriÅŸmesi gerekiyor."</string> + <string name="settings_tracing_status_location_body">"Konum hizmetlerinizi etkinleÅŸtirin. Bluetooth Düşük Enerji iÅŸlevi ile fiziksel mesafeleri hesaplamak için konum hizmetlerinin etkinleÅŸtirilmesi gerekir ancak konumunuza eriÅŸmez. Daha fazla bilgi için lütfen SSS sayfamıza bakın."</string> + <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled: URL --> + <string name="settings_tracing_status_location_body_url">"https://www.coronawarn.app/en/faq/#android_location"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card - location --> <string name="settings_tracing_status_location_button">"Cihaz Ayarlarını Aç"</string> <!--XHED : settings(tracing) - headline on card about the current status and what to do --> @@ -740,10 +742,14 @@ <!-- XBUT: Positive button for generic web request error --> <string name="submission_error_dialog_web_generic_error_button_positive">"Geri"</string> - <!-- XHED: Dialog title for already paired test error --> + <!-- XHED: Dialog title for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_title">"QR kod geçersiz"</string> - <!-- XMSG: Dialog body for already paired test error --> + <!-- XMSG: Dialog body for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_body">"QR kod geçersiz veya baÅŸka bir akıllı telefona zaten kaydedilmiÅŸ. QR kod geçerli olsun veya olmasın test sonucunuzu test merkezinden veya laboratuvardan alacaksınız. COVID-19 tanısı alırsanız kamu saÄŸlığı yetkilisi tarafından bilgilendirileceksiniz."</string> + <!-- XHED: Dialog title for already paired test error: tan --> + <string name="submission_error_dialog_web_test_paired_title_tan">"TAN geçersiz"</string> + <!-- XMSG: Dialog body for already paired test via tan - error: tan --> + <string name="submission_error_dialog_web_test_paired_body_tan">"TAN geçersiz veya zaten kullanılmış. Daha fazla bilgi için \"TAN Talebi\" bölümünün altında listelenen numarayı arayın."</string> <!-- XBUT: Positive button for already paired test error --> <string name="submission_error_dialog_web_test_paired_button_positive">"Geri"</string> @@ -768,6 +774,15 @@ <!-- XBUT: Positive button for submission tan redeemed --> <string name="submission_error_dialog_web_tan_redeemed_button_positive">"Tamam"</string> + <!-- XHED: Dialog title for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_title">"Ä°ptal etmek istiyor musunuz?"</string> + <!-- XMSG: Dialog body for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_body">"GiriÅŸleriniz kaydedilmeyecektir."</string> + <!-- XBUT: Positive button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_positive">"Evet"</string> + <!-- XBUT: Negative button for keys submission process cancellation --> + <string name="submission_error_dialog_confirm_cancellation_button_negative">"Hayır"</string> + <!-- Permission Rationale Dialog --> <!-- XHED: Dialog headline QR Scan permission rationale --> <string name="submission_qr_code_scan_permission_rationale_dialog_headline">"Kamera yetkisi gereklidir"</string> @@ -869,9 +884,9 @@ <!-- XACT: Submission Tan page title --> <string name="submission_tan_accessibility_title">"TAN giriÅŸi"</string> <!-- YTXT: Error text for the tan submission page --> - <string name="submission_tan_error">"TAN geçersiz, lütfen giriÅŸinizi kontrol edin."</string> + <string name="submission_tan_error">"TAN geçersiz. Lütfen giriÅŸinizi kontrol edin."</string> <!-- YTXT: Error text for the tan submission page (wrong characters) --> - <string name="submission_tan_character_error">"GiriÅŸ geçersiz. Lütfen giriÅŸinizi kontrol edin."</string> + <string name="submission_tan_character_error">"GiriÅŸiniz geçersiz karakterler içeriyor. Lütfen giriÅŸinizi kontrol edin."</string> <!-- Submission Intro --> <!-- XHED: Page title for menu at the start of the submission process --> @@ -931,9 +946,9 @@ <!-- XHED: Title for the privacy card--> <string name="submission_positive_other_warning_privacy_title">"Veri GizliliÄŸi"</string> <!-- YTXT: Body text for the privacy card--> - <string name="submission_positive_other_warning_privacy_body">"\"Kabul et\" seçeneÄŸine dokunduÄŸunuzda, Uygulamanın pozitif test sonucunuzu son 14 güne ait rastgele kimliklerinizle birlikte uygulamanın sunucu sistemine göndermesine izin vermiÅŸ olursunuz, bu sayede maruz kalma günlüğünü etkinleÅŸtirmiÅŸ olan diÄŸer Uygulama kullanıcıları otomatik ÅŸekilde bilgilendirilebilir. Ä°letilen rastgele kimlikler, kimliÄŸiniz ya da kiÅŸiliÄŸinize iliÅŸkin sonuç çıkartacak hiçbir bilgi içermez. \n\nTest sonucunuzun Uygulama aracılığıyla iletilmesi, isteÄŸe baÄŸlıdır. Test sonucunuzu iletmezseniz size hiçbir dezavantajı olmaz. Uygulamayı kullanıp kullanmadığınızı ve nasıl kullandığınızı anlayamadığımız ve kontrol edemediÄŸimiz için enfeksiyonu bulaÅŸtırıp bulaÅŸtırmadığınızı sizden baÅŸka kimse öğrenemez.\n\nUygulamayı silerek, onayınızı dilediÄŸinizde geri alabilirsiniz. Onayın geri alınmasıyla, geri almaya kadar yapılmış veri iÅŸlemelerin hukuka uygunlukları bu durumdan etkilenmez. \"Veri GizliliÄŸi Bilgileri\" menüsünden diÄŸer bilgilere ulaÅŸabilirsiniz."</string> + <string name="submission_positive_other_warning_privacy_body">"\"Kabul Et\" seçeneÄŸine dokunduÄŸunuzda, Uygulamanın pozitif test sonucunuzu son 14 güne ait rastgele kimliklerinizle birlikte uygulamanın sunucu sistemine göndermesine izin vermiÅŸ olursunuz, bu sayede maruz kalma günlüğünü etkinleÅŸtirmiÅŸ olan diÄŸer Uygulama kullanıcıları otomatik ÅŸekilde bilgilendirilebilir. Ä°letilen rastgele kimlikler, kimliÄŸiniz ya da kiÅŸiliÄŸinize iliÅŸkin sonuç çıkartacak hiçbir bilgi içermez. \n\nTest sonucunuzun Uygulama aracılığıyla iletilmesi, isteÄŸe baÄŸlıdır. Test sonucunuzu iletmezseniz size hiçbir dezavantajı olmaz. Uygulamayı kullanıp kullanmadığınızı ve nasıl kullandığınızı anlayamadığımız ve kontrol edemediÄŸimiz için enfeksiyonu bulaÅŸtırıp bulaÅŸtırmadığınızı sizden baÅŸka kimse öğrenemez.\n\nUygulamayı silerek, onayınızı dilediÄŸinizde geri alabilirsiniz. Onayın geri alınmasıyla, geri almaya kadar yapılmış veri iÅŸlemelerin hukuka uygunlukları bu durumdan etkilenmez. \"Veri GizliliÄŸi\" menüsünden diÄŸer bilgilere ulaÅŸabilirsiniz."</string> <!-- XBUT: other warning continue button --> - <string name="submission_positive_other_warning_button">"Sonraki"</string> + <string name="submission_positive_other_warning_button">"Kabul Et"</string> <!-- XACT: other warning - illustration description, explanation image --> <string name="submission_positive_other_illustration_description">"Bir cihaz ÅŸifrelenmiÅŸ pozitif test tanısını sisteme aktarır."</string> @@ -961,7 +976,33 @@ <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Tamamlandı"</string> <!-- XACT: submission finished - illustration description, explanation image --> - <string name="submission_done_illustration_description">"Bir kiÅŸi test sonucunu paylaÅŸtığı için gruptaki herkes gülüyor."</string> + <string name="submission_done_illustration_description">"Son birkaç günde aÅŸağıdaki belirtilerden birini veya daha fazlasını yaÅŸadınız mı?"</string> + + <!-- Submission Symptoms --> + <!-- XHED: Page title for symptom screens --> + <string name="submission_symptom_title">"Belirtiler"</string> + <!-- YTXT: headline text for initial symptom screen --> + <string name="submission_symptom_initial_headline">"Son birkaç günde aÅŸağıdaki belirtilerden birini veya daha fazlasını yaÅŸadınız mı?"</string> + <!-- YTXT: Bullet points for symptoms --> + <string-array name="submission_symptom_symptom_bullets"> + <item>"Artan vücut sıcaklığı veya ateÅŸ"</item> + <item>"Nefes darlığı"</item> + <item>"Koku/tat kaybı"</item> + <item>"Öksürük"</item> + <item>"Burun akıntısı"</item> + <item>"BoÄŸaz aÄŸrısı"</item> + <item>"BaÅŸ aÄŸrısı ve kol/bacak aÄŸrısı"</item> + <item>"Genel zayıflık ve bitkinlik"</item> + </string-array> + <!-- XBUT: symptom initial screen yes button --> + <string name="submission_symptom_positive_button">"Evet"</string> + <!-- XBUT: symptom initial screen no button --> + <string name="submission_symptom_negative_button">"Hayır"</string> + <!-- XBUT: symptom initial screen no information button --> + <string name="submission_symptom_no_information_button">"Yorum yok"</string> + <!-- XBUT: symptom initial screen continue button --> + <string name="submission_symptom_further_button">"Sonraki"</string> + <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> @@ -992,6 +1033,22 @@ <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"Ä°kinci adımda, TAN\'nizi kullanarak testinizi uygulamaya kaydedersiniz."</string> + <!-- Submission Symptom Calendar --> + <!-- XHED: Page title for calendar page in submission symptom flow --> + <string name="submission_symptom_calendar_title">"Belirtilerin baÅŸlangıcı"</string> + <!-- XHED: Page headline for calendar page in symptom submission flow --> + <string name="submission_symptom_calendar_headline">"Bu belirtileri ilk kez ne zaman yaÅŸamaya baÅŸladınız? "</string> + <!-- YTXT: Body text for calendar page in symptom submission flow--> + <string name="submission_symptom_calendar_body">"Takvimde tam tarihi seçin veya tam tarihi hatırlayamıyorsanız diÄŸer seçeneklerden birini seçin."</string> + <!-- XBUT: symptom calendar screen less than 7 days button --> + <string name="submission_symptom_less_seven">"Son 7 gün içinde"</string> + <!-- XBUT: symptom calendar screen 1-2 weeks button --> + <string name="submission_symptom_one_two_weeks">"1-2 hafta önce"</string> + <!-- XBUT: symptom calendar screen more than 2 weeks button --> + <string name="submission_symptom_more_two_weeks">"2 haftadan uzun süre önce"</string> + <!-- XBUT: symptom calendar screen verify button --> + <string name="submission_symptom_verify">"Yorum yok"</string> + <!-- Submission Status Card --> <!-- XHED: Page title for the various submission status: fetching --> <string name="submission_status_card_title_fetching">"Veriler alınıyor...."</string> @@ -1055,6 +1112,9 @@ <item>"Kendinizi iyi hissetmiyorsanız diÄŸer insanları riske atmamak için iÅŸe gitmeyin. Belirtileriniz kötüleÅŸirse ilave SARS-CoV-2 testi yapılması gerekebilir."</item> </string-array> + <!-- XBUT Symptoms exact date button --> + <string name="symptoms_calendar_exact_date_button">"Tam tarih"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1083,7 +1143,7 @@ <!-- XTXT: error dialog - detailed text if there is an error during external navigation / external action --> <string name="errors_external_action">"Bu iÅŸlemi gerçekleÅŸtiremezsiniz. Lütfen yardım hattı ile iletiÅŸime geçin."</string> <!-- XTXT: error dialog - phone still needs Google Play Services or Google Mobile Services update --> - <string name="errors_google_update_needed">"Corona-Warn-App uygulamanız doÄŸru ÅŸekilde yüklendi ancak akıllı telefonunuzun iÅŸletim sisteminde \"COVID-19 maruz kalma bildirimleri\" hizmeti yok. Bu, Corona-Warn-App\'i kullanamayacağınız anlamına geliyor. Daha fazla bilgi için lütfen SSS sayfamıza bakın: https://www.coronawarn.app/en/faq/"</string> + <string name="errors_google_update_needed">"Corona-Warn-App uygulamanız doÄŸru ÅŸekilde yüklendi ancak akıllı telefonunuzun iÅŸletim sisteminde \"COVID-19 Maruz Kalma Bildirimleri Sistemi\" yok. Bu, Corona-Warn-App\'i kullanamayacağınız anlamına geliyor. Daha fazla bilgi için lütfen SSS sayfamıza bakın: https://www.coronawarn.app/en/faq/"</string> <!-- XTXT: error dialog - either Google API Error (10) or reached request limit per day --> <string name="errors_google_api_error">"Corona-Warn-App doÄŸru ÅŸekilde çalışıyor ancak mevcut risk durumunuzu güncelleyemiyoruz. Maruz kalma günlüğü aktif ve doÄŸru ÅŸekilde çalışıyor. Daha fazla bilgi için lütfen SSS sayfamıza bakın: https://www.coronawarn.app/en/faq/"</string> diff --git a/Corona-Warn-App/src/main/res/values/colors.xml b/Corona-Warn-App/src/main/res/values/colors.xml index ec54cc383d4eb0c5cbbb2e6f494338133c8ce141..b0246b90d5372420f43b7d242d32815a7ef2cc14 100644 --- a/Corona-Warn-App/src/main/res/values/colors.xml +++ b/Corona-Warn-App/src/main/res/values/colors.xml @@ -53,4 +53,11 @@ <color name="colorStableHairlineLight">#33FFFFFF</color> <color name="colorStableHairlineDark">#3317191A</color> + <!-- Calendar --> + <color name="colorCalendarSelectedDayBackground">#5D6F80</color> + <color name="colorCalendarTodayBorder">#007FAD</color> + <color name="colorCalendarTodayText">#007FAD</color> + <color name="colorCalendarMonthText">#DE000000</color> + <color name="colorCalendarLayoutFocusOn">#FF5D6F80</color> + <color name="colorCalendarLayoutFocusOff">#F5F5F5</color> </resources> diff --git a/Corona-Warn-App/src/main/res/values/dimens.xml b/Corona-Warn-App/src/main/res/values/dimens.xml index fb5edbf545e15f2e88acb152e7ad9c9c0967d61c..a411731f9aa6b3d473a92cb90143b7858f813c4e 100644 --- a/Corona-Warn-App/src/main/res/values/dimens.xml +++ b/Corona-Warn-App/src/main/res/values/dimens.xml @@ -123,4 +123,13 @@ <dimen name="match_constraint">0dp</dimen> <dimen name="no_padding">0dp</dimen> + + <!-- Calendar --> + <dimen name="calendar_header_height">72dp</dimen> + <dimen name="calendar_header_initial_radius">1dp</dimen> + <dimen name="calendar_header_top_radius">4dp</dimen> + <dimen name="calendar_header_bottom_radius">4dp</dimen> + <dimen name="calendar_layout_stroke">2dp</dimen> + <dimen name="calendar_day_size">40dp</dimen> + <dimen name="calendar_day_spacing">4dp</dimen> </resources> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 8ac46316b8176e0b3bbb9f6fc6d5c097cbc7bf9f..968491faafa62ac00cc81854fc2aeb1128bfed66 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -427,7 +427,7 @@ <!-- XHED: onboarding(tracing) - headline for consent information --> <string name="onboarding_tracing_headline_consent">"Declaration of Consent"</string> <!-- YTXT: onboarding(tracing) - body for consent information --> - <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the COVID-19 Exposure Logging functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="onboarding_tracing_body_consent">"To find out whether you have been in contact with an infected person and whether there is a risk that you yourself have been infected, you need to enable the App’s exposure logging feature. By tapping the “Enable†button, you agree to the enabling of the App’s exposure logging feature and the associated data processing."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"In order to use the App’s exposure logging feature, you will have to enable the \"Exposure Notifications\" functionality provided by Google on your smartphone and grant the Corona-Warn-App permission to use this."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"When exposure logging is enabled, your smartphone continuously generates and transmits random IDs via Bluetooth, which other Android or Apple smartphones in your vicinity can receive if exposure logging is also enabled on them. Your smartphone, in turn, receives the random IDs of the other smartphones. Your own random IDs and those received from other smartphones are recorded in the exposure log and stored there for 14 days."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To identify your risk of infection, the App loads a list – several times a day or on request – of the random IDs of all users who have told the App that they have been infected with the coronavirus. This list is then compared with the random IDs stored in the exposure log. If the App detects that you may have been in contact with an infected user, it will inform you of this and tell you that there is a risk that you are also infected. In this case, the App is also given access to other data stored in your smartphone’s exposure log (date, duration and Bluetooth signal strength of the contact)."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The Bluetooth signal strength is used to derive the physical distance (the stronger the signal, the smaller the distance). The App then analyzes this information in order to assess your likelihood of having been infected with the coronavirus and to give you recommendations for what to do next. This analysis is only performed locally on your smartphone. Apart from you, nobody (not even the RKI) will know whether you have been in contact with an infected person and what risk has been identified for you."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"To withdraw your consent to the exposure logging feature, you can disable the feature using the toggle switch in the App or delete the App. If you decide to use the exposure logging feature again, you can toggle the feature back on or reinstall the App. If you disable the exposure logging feature, the App will no longer check whether you have been in contact with an infected user. If you also wish to stop your device sending and receiving random IDs, you will need to disable COVID-19 Exposure Logging in your smartphone settings. Please note that your own random IDs and those received from other smartphones which are stored in the exposure log will not be deleted in the App. You can only permanently delete the data stored in the exposure log in your smartphone settings."<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"The App’s privacy notice (including an explanation of the data processing carried out for the exposure logging feature) can be found in the menu under “Data Privacy Informationâ€."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_button_next">"Activate Exposure Logging"</string> <!-- XTXT: onboarding(tracing) - dialog about tracing permission declined --> @@ -497,7 +497,7 @@ <string name="sixteen_title_text">"Age Limit: 16 and Up"</string> <!-- XACT: onboarding(sixteen) title --> - <string name="sixteen_description_text">"This app is intended for people who reside in Germany and who are at least 16 years of age."</string> + <string name="sixteen_description_text">"The use of this app is intended for persons who are at least 16 years of age and who reside in Germany."</string> <!-- #################################### @@ -543,9 +543,9 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_location_headline">"Allow location access"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled --> - <string name="settings_tracing_status_location_body">"Your location cannot be accessed. Google and/or Android requires access to your device\'s location to use Bluetooth."</string> + <string name="settings_tracing_status_location_body">"Activate your location services. Bluetooth Low Energy requires activated location services to calculate physical distances, but does not access your location. For further information, please see our FAQ page."</string> <!-- XTXT: settings(tracing) - explains user what to do on card if location is disabled: URL --> - <string name="settings_tracing_status_location_body_url"/> + <string name="settings_tracing_status_location_body_url">"https://www.coronawarn.app/en/faq/#android_location"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card - location --> <string name="settings_tracing_status_location_button">"Open Device Settings"</string> <!--XHED : settings(tracing) - headline on card about the current status and what to do --> @@ -724,7 +724,7 @@ <!-- NOTR: subtitle for legal information page, open contact form for languages other than English and German --> <string name="information_legal_subtitle_contact_form_non_en_de">"Contact Form in "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/en/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"English"</a>" or "<a href="https://www.rki.de/SharedDocs/Kontaktformulare/weitere/Corona-Warn-App/Corona-Warn-App_Integrator.html">"German"</a></string> <!-- XHED: Headline for legal information page, tax section --> - <string name="information_legal_headline_taxid">"VAT identification number"</string> + <string name="information_legal_headline_taxid">"VAT identification\nnumber"</string> <!-- YTXT: subtitle for legal information page, tax section --> <string name="information_legal_subtitle_taxid">"DE 165 893 430"</string> <!-- XACT: describes illustration --> @@ -744,14 +744,14 @@ <!-- XBUT: Positive button for generic web request error --> <string name="submission_error_dialog_web_generic_error_button_positive">"Back"</string> - <!-- XHED: Dialog title for already paired test error --> + <!-- XHED: Dialog title for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_title">"QR code is invalid"</string> - <!-- XMSG: Dialog body for already paired test error --> + <!-- XMSG: Dialog body for already paired test error: qr --> <string name="submission_error_dialog_web_test_paired_body">"The QR code is invalid or has been registered on another smartphone already. 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 COVID-19, you will be notified by the public health authority."</string> <!-- XHED: Dialog title for already paired test error: tan --> - <string name="submission_error_dialog_web_test_paired_title_tan" /> + <string name="submission_error_dialog_web_test_paired_title_tan">"TAN is invalid"</string> <!-- XMSG: Dialog body for already paired test via tan - error: tan --> - <string name="submission_error_dialog_web_test_paired_body_tan" /> + <string name="submission_error_dialog_web_test_paired_body_tan">"The TAN is invalid or has already been used. For further information, call the number listed under \"Request TAN\"."</string> <!-- XBUT: Positive button for already paired test error --> <string name="submission_error_dialog_web_test_paired_button_positive">"Back"</string> @@ -777,13 +777,13 @@ <string name="submission_error_dialog_web_tan_redeemed_button_positive">"OK"</string> <!-- XHED: Dialog title for keys submission process cancellation --> - <string name="submission_error_dialog_confirm_cancellation_title">"Wollen Sie wirklich abbrechen?"</string> + <string name="submission_error_dialog_confirm_cancellation_title">"Do you want to cancel?"</string> <!-- XMSG: Dialog body for keys submission process cancellation --> - <string name="submission_error_dialog_confirm_cancellation_body">"Ihre bisherigen Angaben werden nicht gespeichert."</string> + <string name="submission_error_dialog_confirm_cancellation_body">"Your entries will not be saved."</string> <!-- XBUT: Positive button for keys submission process cancellation --> - <string name="submission_error_dialog_confirm_cancellation_button_positive">"Ja"</string> + <string name="submission_error_dialog_confirm_cancellation_button_positive">"Yes"</string> <!-- XBUT: Negative button for keys submission process cancellation --> - <string name="submission_error_dialog_confirm_cancellation_button_negative">"Nein"</string> + <string name="submission_error_dialog_confirm_cancellation_button_negative">"No"</string> <!-- Permission Rationale Dialog --> <!-- XHED: Dialog headline QR Scan permission rationale --> @@ -886,9 +886,9 @@ <!-- XACT: Submission Tan page title --> <string name="submission_tan_accessibility_title">"TAN entry"</string> <!-- YTXT: Error text for the tan submission page --> - <string name="submission_tan_error">"Invalid TAN, please check your entry."</string> + <string name="submission_tan_error">"Invalid TAN. Please check your entry."</string> <!-- YTXT: Error text for the tan submission page (wrong characters) --> - <string name="submission_tan_character_error">"Invalid entry. Please check your entry."</string> + <string name="submission_tan_character_error">"Your entry contains invalid characters. Please check your entry."</string> <!-- Submission Intro --> <!-- XHED: Page title for menu at the start of the submission process --> @@ -948,9 +948,9 @@ <!-- XHED: Title for the privacy card--> <string name="submission_positive_other_warning_privacy_title">"Data Privacy"</string> <!-- YTXT: Body text for the privacy card--> - <string name="submission_positive_other_warning_privacy_body">"By tapping on “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacy Informationâ€."</string> + <string name="submission_positive_other_warning_privacy_body">"By tapping “Acceptâ€, you consent to the App sending your positive test result to the App’s server system along with your random IDs from the last 14 days, so that other App users who have enabled the exposure logging feature can be automatically notified that they may have been exposed to a risk of infection. The random IDs transmitted for this purpose do not contain any information that would allow conclusions to be drawn about your identity or your person. \n\nTransmitting your test result via the App is voluntary. You will not be penalized if you do not transmit your test result. Since it is not possible to trace or check whether and how you use the App, nobody but you will know whether you have transmitted the information that you are infected.\n\nYou can withdraw your consent at any time by deleting the App. This withdrawal of your consent will not affect the lawfulness of the processing carried out based on the consent prior to the withdrawal. Further information can be found in the menu under “Data Privacyâ€."</string> <!-- XBUT: other warning continue button --> - <string name="submission_positive_other_warning_button">"Next"</string> + <string name="submission_positive_other_warning_button">"Accept"</string> <!-- XACT: other warning - illustration description, explanation image --> <string name="submission_positive_other_illustration_description">"A device transmits an encrypted positive test diagnosis to the system."</string> @@ -991,7 +991,33 @@ <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Done"</string> <!-- XACT: submission finished - illustration description, explanation image --> - <string name="submission_done_illustration_description">"Everyone in the group is cheering because someone has shared the test result."</string> + <string name="submission_done_illustration_description">"Have you experienced one or more of the following symptoms in the past few days?"</string> + + <!-- Submission Symptoms --> + <!-- XHED: Page title for symptom screens --> + <string name="submission_symptom_title">"Symptoms"</string> + <!-- YTXT: headline text for initial symptom screen --> + <string name="submission_symptom_initial_headline">"Have you experienced one or more of the following symptoms in the past few days?"</string> + <!-- YTXT: Bullet points for symptoms --> + <string-array name="submission_symptom_symptom_bullets"> + <item>"Increased temperature or fever"</item> + <item>"Shortness of breath"</item> + <item>"Loss of sense of smell/taste"</item> + <item>"Cough"</item> + <item>"Runny nose"</item> + <item>"Sore throat"</item> + <item>"Headache and aching limbs"</item> + <item>"General weakness and exhaustion"</item> + </string-array> + <!-- XBUT: symptom initial screen yes button --> + <string name="submission_symptom_positive_button">"Yes"</string> + <!-- XBUT: symptom initial screen no button --> + <string name="submission_symptom_negative_button">"No"</string> + <!-- XBUT: symptom initial screen no information button --> + <string name="submission_symptom_no_information_button">"No comment"</string> + <!-- XBUT: symptom initial screen continue button --> + <string name="submission_symptom_further_button">"Next"</string> + <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> @@ -1022,6 +1048,22 @@ <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"In the second step, you register your test with your TAN in the app."</string> + <!-- Submission Symptom Calendar --> + <!-- XHED: Page title for calendar page in submission symptom flow --> + <string name="submission_symptom_calendar_title">"Start of Symptoms"</string> + <!-- XHED: Page headline for calendar page in symptom submission flow --> + <string name="submission_symptom_calendar_headline">"When did you first start to experience these symptoms? "</string> + <!-- YTXT: Body text for calendar page in symptom submission flow--> + <string name="submission_symptom_calendar_body">"Select the exact date in the calendar or, if you cannot remember the exact date, choose one of the other options."</string> + <!-- XBUT: symptom calendar screen less than 7 days button --> + <string name="submission_symptom_less_seven">"In the last 7 days"</string> + <!-- XBUT: symptom calendar screen 1-2 weeks button --> + <string name="submission_symptom_one_two_weeks">"1-2 weeks ago"</string> + <!-- XBUT: symptom calendar screen more than 2 weeks button --> + <string name="submission_symptom_more_two_weeks">"More than 2 weeks ago"</string> + <!-- XBUT: symptom calendar screen verify button --> + <string name="submission_symptom_verify">"No comment"</string> + <!-- Submission Status Card --> <!-- XHED: Page title for the various submission status: fetching --> <string name="submission_status_card_title_fetching">"Data being retrieved...."</string> @@ -1085,6 +1127,9 @@ <item>"Do not go to work if you feel unwell to ensure you do not put other people at risk. If your symptoms worsen, you might need a further SARS-CoV-2 test."</item> </string-array> + <!-- XBUT Symptoms exact date button --> + <string name="symptoms_calendar_exact_date_button">"Exact date"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1113,7 +1158,7 @@ <!-- XTXT: error dialog - detailed text if there is an error during external navigation / external action --> <string name="errors_external_action">"You cannot perform this action. Please contact the hotline."</string> <!-- XTXT: error dialog - phone still needs Google Play Services or Google Mobile Services update --> - <string name="errors_google_update_needed">"Your Corona-Warn-App is correctly installed, but the \"COVID-19 exposure notifications\" service is not available on your smartphone\'s operating system. This means that you cannot use the Corona-Warn-App. For further information, please see our FAQ page: https://www.coronawarn.app/en/faq/"</string> + <string name="errors_google_update_needed">"Your Corona-Warn-App is correctly installed, but the \"COVID-19 Exposure Notifications System\" is not available on your smartphone\'s operating system. This means that you cannot use the Corona-Warn-App. For further information, please see our FAQ page: https://www.coronawarn.app/en/faq/"</string> <!-- XTXT: error dialog - either Google API Error (10) or reached request limit per day --> <string name="errors_google_api_error">"The Corona-Warn-App is running correctly, but we cannot update your current risk status. Exposure logging remains active and is working correctly. For further information, please see our FAQ page: https://www.coronawarn.app/en/faq/"</string> diff --git a/Corona-Warn-App/src/main/res/values/styles.xml b/Corona-Warn-App/src/main/res/values/styles.xml index 106040033080d838608ef110e1ee9bb4e17dac4b..51dd9c9e93f2e565424502e1e487754f4d5dd475 100644 --- a/Corona-Warn-App/src/main/res/values/styles.xml +++ b/Corona-Warn-App/src/main/res/values/styles.xml @@ -140,11 +140,27 @@ <item name="android:textColor">@color/colorStableLight</item> </style> + <style name="selectionButton" parent="@style/Widget.AppCompat.Button.Borderless"> + <item name="android:padding">@dimen/card_padding</item> + <item name="android:gravity">left</item> + <item name="android:background">@drawable/card</item> + <item name="fontFamily">sans-serif-medium</item> + <item name="android:fontFamily">sans-serif-medium</item> + <item name="android:textStyle">normal</item> + <item name="android:textAllCaps">false</item> + <item name="android:textSize">20sp</item> + <item name="android:letterSpacing">0.0125</item> + </style> + + <style name="targetLayout"> + <item name="android:padding">@dimen/card_padding</item> + <item name="android:background">@drawable/card</item> + </style> + <style name="cardGrey"> <item name="android:background">@drawable/card_dark</item> </style> - <!-- #################################### Grey Body Background ###################################### --> @@ -285,4 +301,28 @@ <item name="android:textColor">@color/colorStableLight</item> <item name="android:textSize">14sp</item> </style> + + <!-- #################################### + Calendar + ###################################### --> + <style name="calendarMonthText"> + <item name="android:textColor">@color/colorCalendarMonthText</item> + <item name="android:textSize">@dimen/font_normal</item> + <item name="android:fontFamily">sans-serif-medium</item> + <item name="android:letterSpacing">0.1</item> + </style> + + <style name="calendarWeekDayNormal"> + <item name="android:textColor">@color/colorTextPrimary2</item> + <item name="android:textSize">@dimen/font_small</item> + <item name="android:fontFamily">sans-serif</item> + <item name="android:letterSpacing">0.4</item> + </style> + + <style name="calendarWeekDaySelected"> + <item name="android:textColor">@color/colorCalendarTodayText</item> + <item name="android:textSize">@dimen/font_small</item> + <item name="android:fontFamily">sans-serif-black</item> + <item name="android:letterSpacing">2</item> + </style> </resources> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..5a91e9b4eed50fdb33c859eef3bebe22b406e2dc --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt @@ -0,0 +1,76 @@ +package de.rki.coronawarnapp.nearby + +import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import java.io.File + +@Suppress("DEPRECATION") +class ENFClientTest : BaseTest() { + + @MockK + lateinit var googleENFClient: ExposureNotificationClient + + @MockK + lateinit var diagnosisKeyProvider: DiagnosisKeyProvider + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns true + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createClient() = ENFClient( + googleENFClient = googleENFClient, + diagnosisKeyProvider = diagnosisKeyProvider + ) + + @Test + fun `internal enf client is available as workaround`() { + val client = createClient() + client.internalClient shouldBe googleENFClient + } + + @Test + fun `provide diagnosis key call is forwarded to the right module`() { + val client = createClient() + val keyFiles = listOf(File("test")) + val configuration = mockk<ExposureConfiguration>() + val token = "123" + + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns true + runBlocking { + client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe true + } + + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns false + runBlocking { + client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe false + } + + coVerify(exactly = 2) { + diagnosisKeyProvider.provideDiagnosisKeys( + keyFiles, + configuration, + token + ) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2141680366571b7216d7d003b3874eb570a60160 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt @@ -0,0 +1,200 @@ +@file:Suppress("DEPRECATION") + +package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider + +import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.tasks.OnSuccessListener +import com.google.android.gms.tasks.Task +import de.rki.coronawarnapp.util.GoogleAPIVersion +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import java.io.File + +class DefaultDiagnosisKeyProviderTest : BaseTest() { + @MockK + lateinit var googleENFClient: ExposureNotificationClient + + @MockK + lateinit var googleAPIVersion: GoogleAPIVersion + + @MockK + lateinit var submissionQuota: SubmissionQuota + + @MockK + lateinit var exampleConfiguration: ExposureConfiguration + private val exampleKeyFiles = listOf(File("file1"), File("file2")) + private val exampleToken = "123e4567-e89b-12d3-a456-426655440000" + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + coEvery { submissionQuota.consumeQuota(any()) } returns true + + val taskResult = mockk<Task<Void>>() + every { taskResult.addOnSuccessListener(any()) } answers { + val listener = arg<OnSuccessListener<Nothing>>(0) + listener.onSuccess(null) + taskResult + } + every { taskResult.addOnFailureListener(any()) } returns taskResult + coEvery { googleENFClient.provideDiagnosisKeys(any(), any(), any()) } returns taskResult + + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createProvider() = DefaultDiagnosisKeyProvider( + googleAPIVersion = googleAPIVersion, + submissionQuota = submissionQuota, + enfClient = googleENFClient + ) + + @Test + fun `legacy key provision is used on older ENF versions`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns false + + val provider = createProvider() + + runBlocking { + provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) + } + + coVerify(exactly = 0) { + googleENFClient.provideDiagnosisKeys( + exampleKeyFiles, exampleConfiguration, exampleToken + ) + } + + coVerify(exactly = 1) { + googleENFClient.provideDiagnosisKeys( + listOf(exampleKeyFiles[0]), exampleConfiguration, exampleToken + ) + googleENFClient.provideDiagnosisKeys( + listOf(exampleKeyFiles[1]), exampleConfiguration, exampleToken + ) + submissionQuota.consumeQuota(2) + } + } + + @Test + fun `normal key provision is used on newer ENF versions`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true + + val provider = createProvider() + + runBlocking { + provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) + } + + coVerify(exactly = 1) { + googleENFClient.provideDiagnosisKeys(any(), any(), any()) + googleENFClient.provideDiagnosisKeys( + exampleKeyFiles, exampleConfiguration, exampleToken + ) + submissionQuota.consumeQuota(1) + } + } + + @Test + fun `passing an a null configuration leads to constructing a fallback from defaults`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true + + val provider = createProvider() + val fallback = ExposureConfiguration.ExposureConfigurationBuilder().build() + + runBlocking { + provider.provideDiagnosisKeys(exampleKeyFiles, null, exampleToken) + } + + coVerify(exactly = 1) { + googleENFClient.provideDiagnosisKeys(any(), any(), any()) + googleENFClient.provideDiagnosisKeys(exampleKeyFiles, fallback, exampleToken) + } + } + + @Test + fun `passing an a null configuration leads to constructing a fallback from defaults, legacy`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns false + + val provider = createProvider() + val fallback = ExposureConfiguration.ExposureConfigurationBuilder().build() + + runBlocking { + provider.provideDiagnosisKeys(exampleKeyFiles, null, exampleToken) + } + + coVerify(exactly = 1) { + googleENFClient.provideDiagnosisKeys( + listOf(exampleKeyFiles[0]), fallback, exampleToken + ) + googleENFClient.provideDiagnosisKeys( + listOf(exampleKeyFiles[1]), fallback, exampleToken + ) + submissionQuota.consumeQuota(2) + } + } + + @Test + fun `quota is consumed silenently`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true + coEvery { submissionQuota.consumeQuota(any()) } returns false + + val provider = createProvider() + + runBlocking { + provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) + } + + coVerify(exactly = 1) { + googleENFClient.provideDiagnosisKeys(any(), any(), any()) + googleENFClient.provideDiagnosisKeys( + exampleKeyFiles, exampleConfiguration, exampleToken + ) + submissionQuota.consumeQuota(1) + } + } + + @Test + fun `quota is consumed silently, legacy`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns false + coEvery { submissionQuota.consumeQuota(any()) } returns false + + val provider = createProvider() + + runBlocking { + provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) + } + + coVerify(exactly = 0) { + googleENFClient.provideDiagnosisKeys( + exampleKeyFiles, exampleConfiguration, exampleToken + ) + } + + coVerify(exactly = 1) { + googleENFClient.provideDiagnosisKeys( + listOf(exampleKeyFiles[0]), exampleConfiguration, exampleToken + ) + googleENFClient.provideDiagnosisKeys( + listOf(exampleKeyFiles[1]), exampleConfiguration, exampleToken + ) + submissionQuota.consumeQuota(2) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuotaTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuotaTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c5d7238a84bd7dea1318173e73841f09ee74bbdc --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuotaTest.kt @@ -0,0 +1,228 @@ +package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider + +import de.rki.coronawarnapp.nearby.ENFClientLocalData +import de.rki.coronawarnapp.util.TimeStamper +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.joda.time.Duration +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class SubmissionQuotaTest : BaseTest() { + @MockK + lateinit var enfData: ENFClientLocalData + + @MockK + lateinit var timeStamper: TimeStamper + + private var testStorageCurrentQuota = SubmissionQuota.DEFAULT_QUOTA + private var testStorageLastQuotaReset = Instant.parse("2020-08-01T01:00:00.000Z") + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { enfData.currentQuota = any() } answers { + testStorageCurrentQuota = arg(0) + Unit + } + every { enfData.currentQuota } answers { + testStorageCurrentQuota + } + every { enfData.lastQuotaResetAt } answers { + testStorageLastQuotaReset + } + every { enfData.lastQuotaResetAt = any() } answers { + testStorageLastQuotaReset = arg(0) + Unit + } + every { timeStamper.nowUTC } returns Instant.parse("2020-08-01T23:00:00.000Z") + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createQuota() = SubmissionQuota( + enfData = enfData, + timeStamper = timeStamper + ) + + @Test + fun `first init sets a sane default quota`() { + // The default lastQuotaReset is at 0L EPOCH Millis + testStorageLastQuotaReset = Instant.EPOCH + + val quota = createQuota() + + runBlocking { + quota.consumeQuota(5) shouldBe true + } + + coVerify { enfData.currentQuota = 20 } + + // Reset to 20, then consumed 5 + testStorageCurrentQuota shouldBe 15 + } + + @Test + fun `quota consumption return true if quota was available`() { + testStorageCurrentQuota shouldBe 20 + + val quota = createQuota() + + runBlocking { + quota.consumeQuota(10) shouldBe true + quota.consumeQuota(10) shouldBe true + quota.consumeQuota(10) shouldBe false + quota.consumeQuota(1) shouldBe false + } + + verify(exactly = 4) { timeStamper.nowUTC } + } + + @Test + fun `consumption of 0 quota is handled`() { + val quota = createQuota() + + runBlocking { + quota.consumeQuota(0) shouldBe true + quota.consumeQuota(20) shouldBe true + quota.consumeQuota(0) shouldBe true + quota.consumeQuota(1) shouldBe false + } + } + + @Test + fun `partial consumption is not possible`() { + testStorageCurrentQuota shouldBe 20 + + val quota = createQuota() + + runBlocking { + quota.consumeQuota(18) shouldBe true + quota.consumeQuota(1) shouldBe true + quota.consumeQuota(2) shouldBe false + } + } + + @Test + fun `quota consumption automatically fills up quota if possible`() { + val quota = createQuota() + + // Reset is at 00:00:00UTC, we trigger at 1 milisecond after midnight + val timeTravelTarget = Instant.parse("2020-12-24T00:00:00.001Z") + + runBlocking { + quota.consumeQuota(20) shouldBe true + quota.consumeQuota(20) shouldBe false + + every { timeStamper.nowUTC } returns timeTravelTarget + + quota.consumeQuota(20) shouldBe true + quota.consumeQuota(1) shouldBe false + } + + coVerify(exactly = 1) { enfData.currentQuota = 20 } + verify(exactly = 4) { timeStamper.nowUTC } + verify(exactly = 1) { enfData.lastQuotaResetAt = timeTravelTarget } + } + + @Test + fun `quota fill up is at midnight`() { + testStorageCurrentQuota = 20 + testStorageLastQuotaReset = Instant.parse("2020-12-24T23:00:00.000Z") + val startTime = Instant.parse("2020-12-24T23:59:59.998Z") + every { timeStamper.nowUTC } returns startTime + + val quota = createQuota() + + runBlocking { + quota.consumeQuota(20) shouldBe true + quota.consumeQuota(1) shouldBe false + + every { timeStamper.nowUTC } returns startTime.plus(1) + quota.consumeQuota(1) shouldBe false + + every { timeStamper.nowUTC } returns startTime.plus(2) + quota.consumeQuota(1) shouldBe false + + every { timeStamper.nowUTC } returns startTime.plus(3) + quota.consumeQuota(1) shouldBe true + + every { timeStamper.nowUTC } returns startTime.plus(4) + quota.consumeQuota(20) shouldBe false + + every { timeStamper.nowUTC } returns startTime.plus(3).plus(Duration.standardDays(1)) + quota.consumeQuota(20) shouldBe true + } + } + + @Test + fun `large time gaps are no issue`() { + val startTime = Instant.parse("2020-12-24T20:00:00.000Z") + + runBlocking { + every { timeStamper.nowUTC } returns startTime + val quota = createQuota() + quota.consumeQuota(17) shouldBe true + } + + runBlocking { + every { timeStamper.nowUTC } returns startTime.plus(Duration.standardDays(365)) + val quota = createQuota() + quota.consumeQuota(20) shouldBe true + quota.consumeQuota(1) shouldBe false + } + + runBlocking { + every { timeStamper.nowUTC } returns startTime.plus(Duration.standardDays(365 * 2)) + val quota = createQuota() + quota.consumeQuota(17) shouldBe true + } + runBlocking { + every { timeStamper.nowUTC } returns startTime.plus(Duration.standardDays(365 * 3)) + val quota = createQuota() + quota.consumeQuota(3) shouldBe true + quota.consumeQuota(17) shouldBe true + quota.consumeQuota(1) shouldBe false + } + } + + @Test + fun `reverse timetravel is handled `() { + testStorageLastQuotaReset = Instant.parse("2020-12-24T23:00:00.000Z") + val startTime = Instant.parse("2020-12-24T23:59:59.999Z") + every { timeStamper.nowUTC } returns startTime + + val quota = createQuota() + + runBlocking { + quota.consumeQuota(20) shouldBe true + quota.consumeQuota(1) shouldBe false + + // Go forward and get a reset + every { timeStamper.nowUTC } returns startTime.plus(Duration.standardHours(1)) + quota.consumeQuota(20) shouldBe true + quota.consumeQuota(1) shouldBe false + + // Go backwards and don't gain a reset + every { timeStamper.nowUTC } returns startTime.minus(Duration.standardHours(1)) + quota.consumeQuota(1) shouldBe false + + // Go forward again, but no new reset happens + every { timeStamper.nowUTC } returns startTime.plus(Duration.standardHours(1)) + quota.consumeQuota(1) shouldBe false + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/ScanResultTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/ScanResultTest.kt index 2f7e78179372d2c27c2d55694274a65f0a44486b..ee4cd09b339d00404b05056550bb3403939be060 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/ScanResultTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/ScanResultTest.kt @@ -23,7 +23,7 @@ class ScanResultTest { @Test fun containsValidGUID() { - //valid test + // valid test scanResult = QRScanResult("https://localhost/?$guid") scanResult.isValid shouldBe true @@ -51,4 +51,3 @@ class ScanResultTest { QRScanResult("https://localhost/?$guid").guid shouldBe guid } } - diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt index f1d222553e27a11b1f4dfbab4cbb86add4466170..4768cb8c0beb02c4c1bc6d820d5b2b6b44ca0740 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt @@ -6,6 +6,7 @@ import de.rki.coronawarnapp.http.WebRequestBuilder import de.rki.coronawarnapp.http.playbook.BackgroundNoise import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction import de.rki.coronawarnapp.util.formatter.TestResult import io.mockk.MockKAnnotations @@ -33,6 +34,8 @@ class SubmissionServiceTest { @MockK private lateinit var backgroundNoise: BackgroundNoise + private val symptoms = Symptoms(Symptoms.StartOf.OneToTwoWeeksAgo, Symptoms.Indication.POSITIVE) + @Before fun setUp() { MockKAnnotations.init(this) @@ -136,17 +139,17 @@ class SubmissionServiceTest { @Test(expected = NoRegistrationTokenSetException::class) fun submitExposureKeysWithoutRegistrationTokenFails() { runBlocking { - SubmissionService.asyncSubmitExposureKeys(listOf()) + SubmissionService.asyncSubmitExposureKeys(listOf(), symptoms) } } @Test fun submitExposureKeysSucceeds() { every { LocalData.registrationToken() } returns registrationToken - coEvery { SubmitDiagnosisKeysTransaction.start(registrationToken, any()) } just Runs + coEvery { SubmitDiagnosisKeysTransaction.start(registrationToken, any(), symptoms) } just Runs runBlocking { - SubmissionService.asyncSubmitExposureKeys(listOf()) + SubmissionService.asyncSubmitExposureKeys(listOf(), symptoms) } } @@ -162,5 +165,4 @@ class SubmissionServiceTest { LocalData.devicePairingSuccessfulTimestamp(0L) } } - } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculationsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculationsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..eb5801c317ef42a1d5f3fc3cde27787ef1a20227 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/ExposureKeyHistoryCalculationsTest.kt @@ -0,0 +1,100 @@ +package de.rki.coronawarnapp.submission + +import KeyExportFormat +import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class ExposureKeyHistoryCalculationsTest { + + private lateinit var instance: ExposureKeyHistoryCalculations + private lateinit var converter: KeyConverter + + @Before + fun setUp() { + converter = object : KeyConverter { + override fun toExternalFormat( + key: TemporaryExposureKey, + riskValue: Int + ) = + KeyExportFormat.TemporaryExposureKey.newBuilder() + .setRollingStartIntervalNumber(riskValue * 10) + .build() + } + instance = ExposureKeyHistoryCalculations(TransmissionRiskVectorDeterminator(), converter) + } + + @Test + fun test_limitKeyCount() { + val rollingStartIntervalNumber = 0 + createKey(rollingStartIntervalNumber) + Assert.assertEquals(0, instance.limitKeyCount(emptyList<String>()).size) + Assert.assertEquals( + 7, instance.limitKeyCount( + listOf( + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ) + ).size + ) + Assert.assertEquals( + 14, instance.limitKeyCount( + listOf( + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15" + ) + ).size + ) + } + + @Test + fun test_toSortedHistory() { + Assert.assertArrayEquals( + intArrayOf(8, 4, 3, 2, 1), instance.toSortedHistory( + listOf( + createKey(3), + createKey(8), + createKey(1), + createKey(2), + createKey(4) + ) + ).map { it.rollingStartIntervalNumber }.toTypedArray().toIntArray() + ) + } + + @Test + fun test_toExternalFormat() { + Assert.assertArrayEquals( + intArrayOf(10, 20), instance.toExternalFormat( + listOf( + createKey(0), + createKey(1) + ), + TransmissionRiskVector(intArrayOf(0, 1, 2)) + ).map { it.rollingStartIntervalNumber }.toTypedArray().toIntArray() + ) + } + + private fun createKey(rollingStartIntervalNumber: Int) = + TemporaryExposureKey.TemporaryExposureKeyBuilder() + .setRollingStartIntervalNumber(rollingStartIntervalNumber).build() +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminatorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..5c8a5fe29d41ae794f6ebbccf529a21b1fd723ae --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminatorTest.kt @@ -0,0 +1,28 @@ +package de.rki.coronawarnapp.submission + +import org.junit.Assert +import org.junit.Test + +class TransmissionRiskVectorDeterminatorTest { + + @Test + fun test_determine() { + Assert.assertArrayEquals( + intArrayOf(5, 6, 7, 7, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 1), + TransmissionRiskVectorDeterminator().determine( + Symptoms( + null, Symptoms.Indication.NO_INFORMATION + ) + ).raw + ) + } + + @Test + fun test_numberOfDays() { + Assert.assertEquals( + 4, + TransmissionRiskVectorDeterminator.numberOfDays( + 0, + 1000 * 3600 * (24 * 4 + 2))) + } +} 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 index 3a7e3e3d4dd2a19ec1b7c52d58454998eba2cd90..930461f6e870ce0033ab76e79a5000d133783e51 100644 --- 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 @@ -1,27 +1,30 @@ package de.rki.coronawarnapp.transaction -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.GoogleAPIVersion import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent +import io.mockk.MockKAnnotations import io.kotest.matchers.shouldBe import io.mockk.Runs +import io.mockk.clearAllMocks import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.coVerifyOrder import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.mockkObject -import io.mockk.unmockkAll import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test import org.joda.time.Instant import org.joda.time.LocalDate -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 @@ -32,13 +35,19 @@ import java.util.UUID */ class RetrieveDiagnosisKeysTransactionTest { - @Before + @MockK + lateinit var mockEnfClient: ENFClient + + @BeforeEach fun setUp() { + MockKAnnotations.init(this) + mockkObject(AppInjector) val appComponent = mockk<ApplicationComponent>().apply { every { transRetrieveKeysInjection } returns RetrieveDiagnosisInjectionHelper( TransactionCoroutineScope(), - GoogleAPIVersion() + GoogleAPIVersion(), + mockEnfClient ) } every { AppInjector.component } returns appComponent @@ -49,29 +58,22 @@ class RetrieveDiagnosisKeysTransactionTest { mockkObject(LocalData) coEvery { InternalExposureNotificationClient.asyncIsEnabled() } returns true - coEvery { - InternalExposureNotificationClient.asyncProvideDiagnosisKeys( - any(), - any(), - any() - ) - } returns mockk() - coEvery { - InternalExposureNotificationClient.getVersion() - } returns 17000000L + coEvery { ApplicationConfigurationService.asyncRetrieveExposureConfiguration() } returns mockk() every { LocalData.googleApiToken(any()) } just Runs every { LocalData.lastTimeDiagnosisKeysFromServerFetch() } returns Date() every { LocalData.lastTimeDiagnosisKeysFromServerFetch(any()) } just Runs every { LocalData.googleApiToken() } returns UUID.randomUUID().toString() - every { LocalData.googleAPIProvideDiagnosisKeysCallCount = any() } just Runs - every { LocalData.googleAPIProvideDiagnosisKeysCallCount } returns 0 - every { LocalData.nextTimeRateLimitingUnlocks = any() } just Runs - every { LocalData.nextTimeRateLimitingUnlocks } returns Instant.now() + } + + @AfterEach + fun cleanUp() { + clearAllMocks() } @Test - fun testTransactionNoFiles() { + fun `unsuccessful ENF submission`() { + coEvery { mockEnfClient.provideDiagnosisKeys(any(), any(), any()) } returns false val requestedCountries = listOf("DE") coEvery { RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"]( @@ -81,22 +83,24 @@ class RetrieveDiagnosisKeysTransactionTest { runBlocking { RetrieveDiagnosisKeysTransaction.start(requestedCountries) + } - coVerifyOrder { - RetrieveDiagnosisKeysTransaction["executeSetup"]() - RetrieveDiagnosisKeysTransaction["executeQuotaCalculation"]() - RetrieveDiagnosisKeysTransaction["executeRetrieveRiskScoreParams"]() - RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"]( - requestedCountries - ) - RetrieveDiagnosisKeysTransaction["executeFetchDateUpdate"](any<Date>()) - } + coVerifyOrder { + RetrieveDiagnosisKeysTransaction["executeSetup"]() + RetrieveDiagnosisKeysTransaction["executeRetrieveRiskScoreParams"]() + RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"]( + requestedCountries + ) + } + coVerify(exactly = 0) { + RetrieveDiagnosisKeysTransaction["executeFetchDateUpdate"](any<Date>()) } } @Test - fun testTransactionHasFiles() { + fun `successful submission`() { val file = Paths.get("src", "test", "resources", "keys.bin").toFile() + coEvery { mockEnfClient.provideDiagnosisKeys(listOf(file), any(), any()) } returns true val requestedCountries = listOf("DE") coEvery { @@ -107,21 +111,16 @@ class RetrieveDiagnosisKeysTransactionTest { runBlocking { RetrieveDiagnosisKeysTransaction.start(requestedCountries) + } - coVerifyOrder { - RetrieveDiagnosisKeysTransaction["executeSetup"]() - RetrieveDiagnosisKeysTransaction["executeQuotaCalculation"]() - RetrieveDiagnosisKeysTransaction["executeRetrieveRiskScoreParams"]() - RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"]( - requestedCountries - ) - RetrieveDiagnosisKeysTransaction["executeAPISubmission"]( - any<String>(), - listOf(file), - any<ExposureConfiguration>() - ) - RetrieveDiagnosisKeysTransaction["executeFetchDateUpdate"](any<Date>()) - } + coVerifyOrder { + RetrieveDiagnosisKeysTransaction["executeSetup"]() + RetrieveDiagnosisKeysTransaction["executeRetrieveRiskScoreParams"]() + RetrieveDiagnosisKeysTransaction["executeFetchKeyFilesFromServer"]( + requestedCountries + ) + mockEnfClient.provideDiagnosisKeys(listOf(file), any(), any()) + RetrieveDiagnosisKeysTransaction["executeFetchDateUpdate"](any<Date>()) } } @@ -129,9 +128,4 @@ class RetrieveDiagnosisKeysTransactionTest { fun `conversion from date to localdate`() { LocalDate.fromDateFields(Date(0)) shouldBe LocalDate.parse("1970-01-01") } - - @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 index 25d61722c61202975cab25cf1bcdea121ffe6fd0..6840c334425ae639fc8357eac807863ba3215bf0 100644 --- 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 @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.http.playbook.BackgroundNoise import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent import de.rki.coronawarnapp.worker.BackgroundWorkScheduler @@ -39,6 +40,8 @@ class SubmitDiagnosisKeysTransactionTest { private val authString = "authString" private val registrationToken = "123" + private val symptoms = Symptoms(Symptoms.StartOf.OneToTwoWeeksAgo, Symptoms.Indication.POSITIVE) + @Before fun setUp() { MockKAnnotations.init(this) @@ -72,7 +75,7 @@ class SubmitDiagnosisKeysTransactionTest { coEvery { webRequestBuilder.asyncSubmitKeysToServer(authString, listOf()) } just Runs runBlocking { - SubmitDiagnosisKeysTransaction.start(registrationToken, listOf()) + SubmitDiagnosisKeysTransaction.start(registrationToken, listOf(), symptoms) coVerifyOrder { webRequestBuilder.asyncSubmitKeysToServer(authString, listOf()) @@ -98,7 +101,7 @@ class SubmitDiagnosisKeysTransactionTest { } just Runs runBlocking { - SubmitDiagnosisKeysTransaction.start(registrationToken, listOf(key)) + SubmitDiagnosisKeysTransaction.start(registrationToken, listOf(key), symptoms) coVerifyOrder { webRequestBuilder.asyncSubmitKeysToServer(authString, any()) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/calendar/CalendarCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/calendar/CalendarCalculationTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ad8e47590f0c812119283aa3f66442855d432736 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/calendar/CalendarCalculationTest.kt @@ -0,0 +1,116 @@ +package de.rki.coronawarnapp.ui.calendar + +import io.kotest.matchers.shouldBe +import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormat +import org.junit.Test + +class CalendarCalculationTest { + + private var pattern = "dd.MM.yyyy" + private val formatter = DateTimeFormat.forPattern(pattern) + + @Test + fun calculateSameYearSameMonth() { + var input = "27.08.2020" + val dateTime = + DateTime.parse(input, DateTimeFormat.forPattern(pattern)) + val dates = CalendarCalculation().getDates(dateTime) + + // First day - 3 of August + dates.first().date.dayOfMonth shouldBe 3 + dates.first().date.monthOfYear shouldBe 8 + + // Last day - 30 of August + dates.last().date.dayOfMonth shouldBe 30 + dates.last().date.monthOfYear shouldBe 8 + + CalendarCalculation().getMonthText( + dates.first().date, + dates.last().date + ) shouldBe "August 2020" + } + + @Test + fun calculateSameYearDifferentMonth() { + var input = "15.09.2020" + val dateTime = + DateTime.parse(input, DateTimeFormat.forPattern(pattern)) + val dates = CalendarCalculation().getDates(dateTime) + + // First day - 24 of August + dates.first().date.dayOfMonth shouldBe 24 + dates.first().date.monthOfYear shouldBe 8 + + // Last day - 20 of September + dates.last().date.dayOfMonth shouldBe 20 + dates.last().date.monthOfYear shouldBe 9 + + CalendarCalculation().getMonthText( + dates.first().date, + dates.last().date + ) shouldBe "August - September 2020" + } + + @Test + fun calculateDifferentYearDifferentMonth() { + var input = "12.01.2021" + val dateTime = + DateTime.parse(input, DateTimeFormat.forPattern(pattern)) + val dates = CalendarCalculation().getDates(dateTime) + + // First day - 21 of December 2020 + dates.first().date.dayOfMonth shouldBe 21 + dates.first().date.monthOfYear shouldBe 12 + dates.first().date.year shouldBe 2020 + + // Last day - 17 of January 2021 + dates.last().date.dayOfMonth shouldBe 17 + dates.last().date.monthOfYear shouldBe 1 + dates.last().date.year shouldBe 2021 + + CalendarCalculation().getMonthText( + dates.first().date, + dates.last().date + ) shouldBe "December 2020 - January 2021" + } + + @Test + fun calculateEdgeCases() { + // new year + CalendarCalculation().getDates(DateTime.parse("27.12.2021", formatter)).apply { + // First day - 6 of December 2021 + first().date.dayOfMonth shouldBe 6 + first().date.monthOfYear shouldBe 12 + first().date.year shouldBe 2021 + + // Last day - 2 of January 2022 + last().date.dayOfMonth shouldBe 2 + last().date.monthOfYear shouldBe 1 + last().date.year shouldBe 2022 + + CalendarCalculation().getMonthText( + first().date, + last().date + ) shouldBe "December 2021 - January 2022" + } + + // leap year + CalendarCalculation().getDates(DateTime.parse("29.02.2024", formatter)).apply { + // First day - 5 of February 2024 + first().date.dayOfMonth shouldBe 5 + first().date.monthOfYear shouldBe 2 + first().date.year shouldBe 2024 + + // Last day - 2 of March 2024 + last().date.dayOfMonth shouldBe 3 + last().date.monthOfYear shouldBe 3 + last().date.year shouldBe 2024 + + CalendarCalculation().getMonthText( + first().date, + last().date + ) shouldBe "February - March 2024" + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt index 581282e966e84ea61492fcbb3dd4ad20e198ab2b..110d8cbeed2aef3b469c0355c3a848025f1faadf 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt @@ -40,7 +40,6 @@ internal class GoogleAPIVersionTest { runBlockingTest { classUnderTest.isAtLeast(GoogleAPIVersion.V16) shouldBe true } - } @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleQuotaCalculatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleQuotaCalculatorTest.kt deleted file mode 100644 index 995ed675179badf7985d26df0104a4593f192d00..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleQuotaCalculatorTest.kt +++ /dev/null @@ -1,293 +0,0 @@ -package de.rki.coronawarnapp.util - -import de.rki.coronawarnapp.storage.LocalData -import io.mockk.every -import io.mockk.mockkObject -import io.mockk.unmockkObject -import org.joda.time.DateTime -import org.joda.time.DateTimeUtils -import org.joda.time.DateTimeZone -import org.joda.time.Duration -import org.joda.time.Instant -import org.joda.time.chrono.GJChronology -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import testhelpers.BaseTest -import timber.log.Timber -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicLong - -internal class GoogleQuotaCalculatorTest : BaseTest() { - - private val timeInTest = DateTimeUtils.currentTimeMillis() - - private lateinit var classUnderTest: GoogleQuotaCalculator - private val nextTimeRateLimitingUnlocksInTesting = AtomicLong() - private val googleAPIProvideDiagnosisKeysCallCount = AtomicInteger() - - private val defaultIncrementByAmountInTest = 14 - private val defaultQuotaLimitInTest = 20 - - @BeforeEach - fun setUpClassUnderTest() { - classUnderTest = GoogleQuotaCalculator( - incrementByAmount = defaultIncrementByAmountInTest, - quotaLimit = defaultQuotaLimitInTest, - quotaResetPeriod = Duration.standardHours(24), - quotaTimeZone = DateTimeZone.UTC, - quotaChronology = GJChronology.getInstanceUTC() - ) - DateTimeUtils.setCurrentMillisFixed(timeInTest) - - // Since LocalData is simple to mock - mockkObject(LocalData) - every { LocalData.nextTimeRateLimitingUnlocks = any() } answers { - nextTimeRateLimitingUnlocksInTesting.set((this.arg(0) as Instant).millis) - } - every { LocalData.nextTimeRateLimitingUnlocks } answers { - Instant.ofEpochMilli(nextTimeRateLimitingUnlocksInTesting.get()) - } - every { LocalData.googleAPIProvideDiagnosisKeysCallCount = any() } answers { - googleAPIProvideDiagnosisKeysCallCount.set(this.arg(0)) - } - every { LocalData.googleAPIProvideDiagnosisKeysCallCount } answers { - googleAPIProvideDiagnosisKeysCallCount.get() - } - } - - @Test - fun `isAboveQuota false if called initially`() { - assertEquals(classUnderTest.hasExceededQuota, false) - } - - @Test - fun `isAboveQuota true if called above quota limit when calling with amount bigger than one`() { - for (callNumber in 1..5) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - if (callNumber > 1) { - assertEquals(true, aboveQuota) - } else { - assertEquals(false, aboveQuota) - } - } - } - - @Test - fun `getProgressTowardsQuota increases with calls to isAboveQuota but is stopped once increased above the quota`() { - var latestCallNumberWithoutLimiting = 1 - for (callNumber in 1..5) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - val expectedIncrement = callNumber * defaultIncrementByAmountInTest - if (expectedIncrement >= defaultQuotaLimitInTest) { - assertEquals( - (latestCallNumberWithoutLimiting + 1) * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - } else { - assertEquals( - callNumber * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - latestCallNumberWithoutLimiting = callNumber - } - } - } - - @Test - fun `getProgressTowardsQuota is reset and the quota is not recalculated but isAboveQuota should still be false`() { - var latestCallNumberWithoutLimiting = 1 - for (callNumber in 1..5) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - val expectedIncrement = callNumber * defaultIncrementByAmountInTest - if (expectedIncrement >= defaultQuotaLimitInTest) { - assertEquals( - (latestCallNumberWithoutLimiting + 1) * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - } else { - assertEquals( - callNumber * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - latestCallNumberWithoutLimiting = callNumber - } - } - - classUnderTest.resetProgressTowardsQuota(0) - assertEquals(false, classUnderTest.hasExceededQuota) - } - - @Test - fun `getProgressTowardsQuota is reset but the reset value is no multiple of incrementByAmount`() { - var latestCallNumberWithoutLimiting = 1 - for (callNumber in 1..5) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - val expectedIncrement = callNumber * defaultIncrementByAmountInTest - if (expectedIncrement >= defaultQuotaLimitInTest) { - assertEquals( - (latestCallNumberWithoutLimiting + 1) * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - } else { - assertEquals( - callNumber * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - latestCallNumberWithoutLimiting = callNumber - } - } - - assertThrows<IllegalArgumentException> { - classUnderTest.resetProgressTowardsQuota(defaultIncrementByAmountInTest + 1) - } - } - - @Test - fun `getProgressTowardsQuota is reset and the quota is not recalculated and the progress should update`() { - var latestCallNumberWithoutLimiting = 1 - for (callNumber in 1..5) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - val expectedIncrement = callNumber * defaultIncrementByAmountInTest - if (expectedIncrement >= defaultQuotaLimitInTest) { - assertEquals( - (latestCallNumberWithoutLimiting + 1) * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - } else { - assertEquals( - callNumber * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - latestCallNumberWithoutLimiting = callNumber - } - } - - val newProgressAfterReset = 14 - classUnderTest.resetProgressTowardsQuota(newProgressAfterReset) - assertEquals(false, classUnderTest.hasExceededQuota) - assertEquals(newProgressAfterReset, classUnderTest.getProgressTowardsQuota()) - } - - @Test - fun `getProgressTowardsQuota is reset and the quota is not recalculated and the progress throws an error because of too high newProgress`() { - var latestCallNumberWithoutLimiting = 1 - var progressBeforeReset: Int? = null - for (callNumber in 1..5) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - val expectedIncrement = callNumber * defaultIncrementByAmountInTest - if (expectedIncrement >= defaultQuotaLimitInTest) { - progressBeforeReset = - (latestCallNumberWithoutLimiting + 1) * defaultIncrementByAmountInTest - assertEquals( - (latestCallNumberWithoutLimiting + 1) * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - } else { - assertEquals( - callNumber * defaultIncrementByAmountInTest, - classUnderTest.getProgressTowardsQuota() - ) - latestCallNumberWithoutLimiting = callNumber - } - } - - val newProgressAfterReset = defaultQuotaLimitInTest + 1 - assertThrows<IllegalArgumentException> { - classUnderTest.resetProgressTowardsQuota(newProgressAfterReset) - } - assertEquals(true, classUnderTest.hasExceededQuota) - assertEquals( - (progressBeforeReset - ?: throw IllegalStateException("progressBeforeReset was not set during test")), - classUnderTest.getProgressTowardsQuota() - ) - } - - @Test - fun `isAboveQuota true if called above quota limit when calling with amount one`() { - classUnderTest = GoogleQuotaCalculator( - incrementByAmount = 1, - quotaLimit = 3, - quotaResetPeriod = Duration.standardHours(24), - quotaTimeZone = DateTimeZone.UTC, - quotaChronology = GJChronology.getInstanceUTC() - ) - for (callNumber in 1..15) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - if (callNumber > 3) { - assertEquals(true, aboveQuota) - } else { - assertEquals(false, aboveQuota) - } - } - } - - @Test - fun `isAboveQuota false if called above quota limit but next day resets quota`() { - for (callNumber in 1..5) { - classUnderTest.calculateQuota() - val aboveQuota = classUnderTest.hasExceededQuota - Timber.v("call number $callNumber above quota: $aboveQuota") - if (callNumber > 1) { - assertEquals(true, aboveQuota) - } else { - assertEquals(false, aboveQuota) - } - } - - // Day Change - val timeInTestAdvancedByADay = timeInTest + Duration.standardDays(1).millis - DateTimeUtils.setCurrentMillisFixed(timeInTestAdvancedByADay) - classUnderTest.calculateQuota() - val aboveQuotaAfterDayAdvance = classUnderTest.hasExceededQuota - Timber.v("above quota after day advance: $aboveQuotaAfterDayAdvance") - - assertEquals(false, aboveQuotaAfterDayAdvance) - } - - @Test - fun `test if isAfter is affected by Timezone to make sure we do not run into Shifting Errors`() { - val testTimeUTC = DateTime( - timeInTest, - DateTimeZone.UTC - ).withChronology(GJChronology.getInstanceUTC()) - val testTimeGMT = DateTime( - timeInTest, - DateTimeZone.forID("Etc/GMT+2") - ).withChronology(GJChronology.getInstanceUTC()) - - assertEquals(testTimeGMT, testTimeUTC) - assertEquals(testTimeGMT.millis, testTimeUTC.millis) - - val testTimeUTCAfterGMT = testTimeUTC.plusMinutes(1) - - assertEquals(true, testTimeUTCAfterGMT.isAfter(testTimeGMT)) - - val testTimeGMTAfterUTC = testTimeGMT.plusMinutes(1) - - assertEquals(true, testTimeGMTAfterUTC.isAfter(testTimeUTC)) - } - - @AfterEach - fun cleanup() { - DateTimeUtils.setCurrentMillisSystem() - unmockkObject(LocalData) - } -} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensionsTest.kt deleted file mode 100644 index 9ffe27e56ca4d9ca4c6e2243be2751397d72cfa8..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensionsTest.kt +++ /dev/null @@ -1,124 +0,0 @@ -package de.rki.coronawarnapp.util - -import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey -import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat -import org.hamcrest.CoreMatchers -import org.hamcrest.MatcherAssert -import org.junit.Test - -private const val DEFAULT_TRANSMISSION_RISK_LEVEL = 1 -private const val TRANSMISSION_RISK_DAY_0 = 5 -private const val TRANSMISSION_RISK_DAY_1 = 6 -private const val TRANSMISSION_RISK_DAY_2 = 8 -private const val TRANSMISSION_RISK_DAY_3 = 8 -private const val TRANSMISSION_RISK_DAY_4 = 8 -private const val TRANSMISSION_RISK_DAY_5 = 5 -private const val TRANSMISSION_RISK_DAY_6 = 3 -private const val TRANSMISSION_RISK_DAY_7 = 1 - -class ProtoFormatConverterExtensionsTest { - - @Test - fun areTransmissionRiskLevelsCorrectlyAssigned() { - - val key1 = byteArrayOf( - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f - ) - - val diagnosisKeyList = mutableListOf<TemporaryExposureKey>() - val numKeys = 13 - for (pos in 0 until numKeys) { - diagnosisKeyList.add( - TemporaryExposureKey.TemporaryExposureKeyBuilder() - .setKeyData(key1) - .setRollingStartIntervalNumber(pos * 144) - .setRollingPeriod(144) - .setTransmissionRiskLevel(0) - .build() - ) - } - - val transformedKeyList = diagnosisKeyList.transformKeyHistoryToExternalFormat() - .sortedWith(compareBy { it.rollingStartIntervalNumber }) - - MatcherAssert.assertThat( - transformedKeyList.size, - CoreMatchers.equalTo(numKeys) - ) - - val correctRiskLevels = arrayOf( - DEFAULT_TRANSMISSION_RISK_LEVEL, - DEFAULT_TRANSMISSION_RISK_LEVEL, - DEFAULT_TRANSMISSION_RISK_LEVEL, - DEFAULT_TRANSMISSION_RISK_LEVEL, - DEFAULT_TRANSMISSION_RISK_LEVEL, - DEFAULT_TRANSMISSION_RISK_LEVEL, - TRANSMISSION_RISK_DAY_7, - TRANSMISSION_RISK_DAY_6, - TRANSMISSION_RISK_DAY_5, - TRANSMISSION_RISK_DAY_4, - TRANSMISSION_RISK_DAY_3, - TRANSMISSION_RISK_DAY_2, - TRANSMISSION_RISK_DAY_1 - ) - - for (pos in 0 until numKeys) { - val key = transformedKeyList[pos] - MatcherAssert.assertThat( - key.transmissionRiskLevel, - CoreMatchers.equalTo(correctRiskLevels[pos]) - ) - MatcherAssert.assertThat( - key.rollingStartIntervalNumber, - CoreMatchers.equalTo(pos * 144) - ) - } - } - - @Test - fun areTransmissionRiskLevelsCorrectlyAssignedWithOnlyOneKey() { - - val key1 = byteArrayOf( - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f - ) - - val diagnosisKeyList = mutableListOf<TemporaryExposureKey>() - val numKeys = 1 - for (pos in 0 until numKeys) { - diagnosisKeyList.add( - TemporaryExposureKey.TemporaryExposureKeyBuilder() - .setKeyData(key1) - .setRollingStartIntervalNumber(pos * 144) - .setRollingPeriod(144) - .setTransmissionRiskLevel(0) - .build() - ) - } - - val transformedKeyList = diagnosisKeyList.transformKeyHistoryToExternalFormat() - .sortedWith(compareBy { it.rollingStartIntervalNumber }) - - MatcherAssert.assertThat( - transformedKeyList.size, - CoreMatchers.equalTo(numKeys) - ) - - val correctRiskLevels = arrayOf( - TRANSMISSION_RISK_DAY_1 - ) - - for (pos in 0 until numKeys) { - val key = transformedKeyList[pos] - MatcherAssert.assertThat( - key.transmissionRiskLevel, - CoreMatchers.equalTo(correctRiskLevels[pos]) - ) - MatcherAssert.assertThat( - key.rollingStartIntervalNumber, - CoreMatchers.equalTo(pos * 144) - ) - } - } -} diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt index 5f04e64c371d04d92ade5ef94b9dbcab8ca59c4a..db3450dbfac08368b08b90ffd21c8c49387e5d26 100644 --- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt +++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.lifecycle.SavedStateHandle import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction import de.rki.coronawarnapp.transaction.RiskLevelTransaction import io.kotest.matchers.shouldBe @@ -12,6 +13,7 @@ import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify import io.mockk.coVerifyOrder +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockkObject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -29,6 +31,7 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { @MockK lateinit var context: Context @MockK lateinit var savedStateHandle: SavedStateHandle + @MockK lateinit var enfClient: ENFClient @MockK lateinit var exposureNotificationClient: ExposureNotificationClient @MockK lateinit var keyCacheRepository: KeyCacheRepository @@ -42,6 +45,7 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { coEvery { RiskLevelTransaction.start() } returns Unit coEvery { keyCacheRepository.clear() } returns Unit + every { enfClient.internalClient } returns exposureNotificationClient } @AfterEach @@ -54,7 +58,7 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { handle = savedStateHandle, exampleArg = exampleArgs, context = context, - exposureNotificationClient = exposureNotificationClient, + enfClient = enfClient, keyCacheRepository = keyCacheRepository ) @@ -99,5 +103,4 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { vm.startLocalQRCodeScanEvent.value shouldBe Unit } - }