diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt index 6181fbc5e9f83af068a8b3f6c30e9613b5730186..e241f06989c437907a3ee4cc5878e0da50848a52 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt @@ -19,7 +19,7 @@ object DiaryData { val DATA_ITEMS = listOf( DayOverviewItem.Data( R.drawable.ic_contact_diary_location_item, - "Rewe", + "Supermarkt", Duration.standardMinutes(30), attributes = null, circumstances = null, @@ -27,7 +27,7 @@ object DiaryData { ), DayOverviewItem.Data( R.drawable.ic_contact_diary_person_item, - "Andrea Steinhauer", + "Erika Musterfrau", null, listOf( R.string.contact_diary_person_encounter_duration_below_15_min, @@ -89,7 +89,7 @@ object DiaryData { val PERSONS: List<DiaryPersonListItem> = listOf( DiaryPersonListItem( - item = DefaultContactDiaryPerson(fullName = "Andrea Steinhauer"), + item = DefaultContactDiaryPerson(fullName = "Erika Musterfrau"), personEncounter = DefaultContactDiaryPersonEncounter( contactDiaryPerson = DefaultContactDiaryPerson(fullName = ""), date = LocalDate.now(), @@ -106,7 +106,7 @@ object DiaryData { onCircumstanceInfoClicked = {} ), DiaryPersonListItem( - item = DefaultContactDiaryPerson(fullName = "Constantin Frenzel"), + item = DefaultContactDiaryPerson(fullName = "Max Mustermann"), personEncounter = DefaultContactDiaryPersonEncounter( contactDiaryPerson = DefaultContactDiaryPerson(fullName = ""), date = LocalDate.now() @@ -136,7 +136,6 @@ object DiaryData { phoneNumber = "+49151237865", emailAddress = "max.musterman@me.com" ), - DefaultContactDiaryPerson(fullName = "Erika Mustermann", emailAddress = "erika.mustermann@me.com"), - DefaultContactDiaryPerson(fullName = "John Doe") + DefaultContactDiaryPerson(fullName = "Erika Musterfrau", emailAddress = "erika.musterfrau@me.com") ) } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt index 4839c083c9b2aaffcffb39a8b06bc41c7e6dab57..b3ee5d1352b287a0b01341a90c74d77f3c76efd5 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt @@ -42,7 +42,8 @@ object HomeData { lastExposureDetectionTime = Instant.now(), lastEncounterAt = null, allowManualUpdate = false, - daysWithEncounters = 0 + daysWithEncounters = 0, + daysSinceInstallation = 4, ), onCardClick = {}, onUpdateClick = {} @@ -55,7 +56,8 @@ object HomeData { lastExposureDetectionTime = Instant.now(), lastEncounterAt = Instant.now(), allowManualUpdate = false, - daysWithEncounters = 1 + daysWithEncounters = 1, + daysSinceInstallation = 4 ), onCardClick = {}, onUpdateClick = {} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt index 292d1d2c64fa0004d0e3dc4e918cff094d7a617f..5f2f3a274e85e9c145fb7c1cab7cf55586869160 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt @@ -17,6 +17,7 @@ import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard import de.rki.coronawarnapp.storage.TracingRepository +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard @@ -67,6 +68,7 @@ class HomeFragmentTest : BaseUITest() { @MockK lateinit var statisticsProvider: StatisticsProvider @MockK lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler @MockK lateinit var appShortcutsHelper: AppShortcutsHelper + @MockK lateinit var tracingSettings: TracingSettings private lateinit var homeFragmentViewModel: HomeFragmentViewModel @@ -256,7 +258,8 @@ class HomeFragmentTest : BaseUITest() { cwaSettings = cwaSettings, statisticsProvider = statisticsProvider, deadmanNotificationScheduler = deadmanNotificationScheduler, - appShortcutsHelper = appShortcutsHelper + appShortcutsHelper = appShortcutsHelper, + tracingSettings = tracingSettings ) ) diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt index 7eeb02ee3d9649bea978adb941dacde0312778da..a0362781c44750b755b88ca6f51228a89fd8d598 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt @@ -4,6 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.nearby.TracingPermissionHelper +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import io.mockk.MockKAnnotations import io.mockk.Runs @@ -31,6 +32,7 @@ class OnboardingTracingFragmentTest : BaseUITest() { @MockK lateinit var interopRepo: InteroperabilityRepository @MockK lateinit var factory: TracingPermissionHelper.Factory + @MockK lateinit var tracingSettings: TracingSettings @Rule @JvmField @@ -47,7 +49,8 @@ class OnboardingTracingFragmentTest : BaseUITest() { OnboardingTracingFragmentViewModel( interoperabilityRepository = interopRepo, tracingPermissionHelperFactory = factory, - dispatcherProvider = TestDispatcherProvider() + dispatcherProvider = TestDispatcherProvider(), + tracingSettings = tracingSettings ) ) diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt index b5dcdc27b839085ae16cd320e6e7289bc0bf5057..a076477cbbad9c3e1ca82f5e789350f0d2599ea2 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt @@ -41,6 +41,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_INACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -61,6 +62,7 @@ object TracingData { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 0, + daysSinceInstallation = 4, lastEncounterAt = Instant.now() ) ), @@ -70,6 +72,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -90,6 +93,7 @@ object TracingData { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 1, + daysSinceInstallation = 4, lastEncounterAt = Instant.now() ) ), @@ -99,6 +103,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -119,6 +124,7 @@ object TracingData { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 2, + daysSinceInstallation = 4, lastEncounterAt = Instant.now() ) ), @@ -128,6 +134,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -153,6 +160,7 @@ object TracingData { ), BehaviorIncreasedRiskBox.Item, PeriodLoggedBox.Item( + daysSinceInstallation = 5, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsIncreasedRiskBox.Item( diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt index 6ca53f71453eed67437e683a2aa449027b609d08..49daaf6fa2da7d161058154d3607cca0196ca57c 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt @@ -7,7 +7,6 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory @@ -42,10 +41,10 @@ class DeltaOnboardingFragmentViewModel @AssistedInject constructor( ContactDiarySettings.OnboardingStatus.NOT_ONBOARDED } - fun isDeltaOnboardingDone() = LocalData.isInteroperabilityShownAtLeastOnce + fun isDeltaOnboardingDone() = settings.wasInteroperabilityShownAtLeastOnce fun setDeltaOboardinDone(value: Boolean) { - LocalData.isInteroperabilityShownAtLeastOnce = value + settings.wasInteroperabilityShownAtLeastOnce = value } @AssistedFactory diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt index 0212b6a3796ce9620ad366bddaf660bcc8427629..0218b306014d78f70d9cebd057cc2978cfbd7d66 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt @@ -8,8 +8,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.gson.Gson import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -17,7 +16,6 @@ import de.rki.coronawarnapp.util.serialization.BaseGson import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import timber.log.Timber @@ -26,7 +24,7 @@ import java.util.UUID class SubmissionTestFragmentViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val tekHistoryStorage: TEKHistoryStorage, - private val submissionRepository: SubmissionRepository, + private val submissionSettings: SubmissionSettings, tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory, @BaseGson baseGson: Gson ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { @@ -60,8 +58,7 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor( ) val errorEvents = SingleLiveEvent<Throwable>() - private val internalToken = MutableStateFlow(LocalData.registrationToken()) - val currentTestId = internalToken.asLiveData() + val currentTestId = submissionSettings.registrationToken.flow.asLiveData() val shareTEKsEvent = SingleLiveEvent<TEKExport>() @@ -85,13 +82,15 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor( .asLiveData(context = dispatcherProvider.Default) fun scrambleRegistrationToken() { - LocalData.registrationToken(UUID.randomUUID().toString()) - internalToken.value = LocalData.registrationToken() + submissionSettings.registrationToken.update { + UUID.randomUUID().toString() + } } fun deleteRegistrationToken() { - LocalData.registrationToken(null) - internalToken.value = LocalData.registrationToken() + submissionSettings.registrationToken.update { + null + } } fun updateStorage() { diff --git a/Corona-Warn-App/src/main/assets/privacy_de.html b/Corona-Warn-App/src/main/assets/privacy_de.html index a18e403654e0185e5b45020afef3904c9e100a7b..2c3f44c0c982c071f0c5f2bee6b767c86bb96649 100644 --- a/Corona-Warn-App/src/main/assets/privacy_de.html +++ b/Corona-Warn-App/src/main/assets/privacy_de.html @@ -118,7 +118,7 @@ </p> <p> Die App verzichtet daher auch grundsätzlich auf - jegliche Auswertung Ihres Nutzungsverhaltens durch Analyse-Tools. Nur wenn Sie ausdrücklich der + die Auswertung Ihres Nutzungsverhaltens durch Analyse-Tools. Nur wenn Sie ausdrücklich der freiwilligen Datenspende zustimmen, werden bestimmte Daten über Ihre Nutzung der App an das RKI übermittelt (siehe hierzu Punkt 5 e.). </p> @@ -284,81 +284,127 @@ Verfügung stellen können. </p> <h2> - e. Datenspende + e. Nutzungsdaten (Datenspende) </h2> <p> - Wenn Sie die Funktion Datenspende aktivieren, übermittelt die App unterschiedliche Daten über - Ihre App-Nutzung einmal täglich an das RKI (im Folgenden: Nutzungsdaten). Diese Nutzungsdaten - betreffen angezeigte Risiko-Begegnungen und Warnungen, durch Sie abgerufene Testergebnisse, ob - Sie andere Nutzer gewarnt haben sowie Angaben über das Betriebssystem Ihres Smartphones. Im - Einzelnen sind dies: + Wenn Sie die Datenspende aktivieren, übermittelt die App bestimmte Daten über Ihre App-Nutzung + einmal täglich an das RKI (im Folgenden: Nutzungsdaten). Diese Nutzungsdaten betreffen die von + der App angezeigten Risiko-Begegnungen, erhaltene und ausgelöste Warnungen, abgerufene + Testergebnisse sowie technische Angaben über das Betriebssystem Ihres Smartphones. Im Einzelnen + umfassen die Nutzungsdaten: + </p> <ul> - <li>Das Datum der Ãœbermittlung.</li> + <li>Das Datum der Datenspende (d. h. der Tag der Ãœbermittlung).</li> <li>Änderungen der Warnungshistorie im Vergleich zum Vortag.</li> - <li>Angaben dazu, welches Risiko Ihnen zum Zeitpunkt der Ãœbermittlung angezeigt wurde.</li> - <li>Angaben dazu, auf welcher Grundlage der Risikostatus in Zusammenhang mit einer Begegnung + <li>Welcher Risikostatus zum Zeitpunkt der Datenspende angezeigt wurde.</li> + <li>Angaben dazu, auf welcher Grundlage welcher Begegnungen der Risikostatus in Zusammenhang mit + einer Begegnung berechnet wurde. </li> + <li>Angaben zum Modell und der Version Ihres Smartphones und zur Version Ihrer App sowie dem + verwendeten Betriebssystem. + </li> </ul> <p> Wenn Sie ein Testergebnis über die App abgerufen haben: </p> <ul> - <li>Angaben dazu, ob Sie ein positives oder negatives Testergebnis über die App erhalten - haben. - </li> - <li>Angaben zum berechneten Risiko zum Zeitpunkt der Testregistrierung.</li> - <li>Angaben zum Zeitraum zwischen der letzten Begegnung mit erhöhtem Risiko und der - Testregistrierung. + <li>Ob es sich um ein positives oder negatives Testergebnis handelt.</li> + <li>Welches Risiko zum Zeitpunkt der Testregistrierung angezeigt wurde.</li> + <li>Wieviel Zeit seit der letzten Risiko-Begegnung und deren Anzeige in der App bis zur + Testregistrierung jeweils vergangen ist </li> - <li>Angaben zum Zeitraum zwischen der letzten Mitteilung eines erhöhten Risikos und der - Testregistrierung. + <li>Ob Sie die Funktion zum Auslösen einer Warnung gestartet haben und, falls ja, bis zu welchem + Schritt Sie dabei gekommen sind (z. B. bis zur Symptomabfrage). </li> - <li>Angaben dazu, ob Sie Ihr Testergebnis geteilt und andere gewarnt haben.</li> </ul> <p> - Wenn Sie andere über eine mögliche Risiko-Begegnung gewarnt haben: + Wenn Sie eine Warnung ausgelöst haben: </p> <ul> - <li>Angaben dazu, ob Sie die Schritte zur Warnung anderer abgebrochen haben./li> - <li>Angaben dazu, ob Sie Angaben zum Symptombeginn gemacht haben.</li> - <li>Angaben dazu, wann Sie Ihr Einverständnis in die Warnung anderer erteilt haben.</li> - <li>Angaben dazu, bis zu welcher Meldung im Rahmen der Warnung anderer Sie gekommen sind.</li> - <li>Angaben dazu, wie viele Stunden es gedauert hat, bis Sie Ihr Testergebnisses erhalten - haben. + <li>Ob Sie Angaben zum Symptombeginn gemacht haben.</li> + <li>Wann Sie Ihr Einverständnis zur Warnung anderer erteilt haben (vor oder nach der + Testregistrierung). </li> - <li>Angaben dazu, wie viele Tage seit der letzten Mitteilung eines hohen Risikos vergangen - sind. + <li>Ob Sie den Warnprozess bis zum Ende vollständig durchlaufen haben oder ob Sie den Prozess + vorher abgebrochen haben (etwa weil Sie nicht die Bestätigung der erfolgreichen Ãœbermittlung + Ihrer Daten abgewartet haben). </li> - <li>Angaben dazu, wie viele Stunden seit der Testregistrierung vergangen sind.</li> + <li>Wie viele Stunden es gedauert hat, bis Sie Ihr Testergebnis nach der Testregistrierung + erhalten haben. + </li> + <li>Wie viele Tage seit der letzten Mitteilung eines hohen Risikos vor Auslösen der Warnung + vergangen sind. + </li> + <li>Wie viele Stunden seit der Testregistrierung vergangen sind.</li> </ul> <p> - Sonstige Informationen: + Zusätzlich können Sie weitere freiwilligen Angaben zu Ihrer Region sowie zu Ihrer Altersgruppe + machen, die zusammen mit den Nutzungsdaten an das RKI übermittelt werden. +</p> +<p> + Das RKI wird die Nutzungsdaten und eventuelle optionale Angaben zu anonymisierten Statistiken + zusammenfassen und auswerten, um die Wirksamkeit und Funktionsweise der App zu bewerten und + Rückschlüsse auf das Pandemiegeschehen zu ziehen. +</p> +<p> + Die Teilnahme an der Datenspende ist freiwillig. Die Aktivierung der Datenspende setzt die + Bestätigung der Echtheit Ihrer App voraus (Beachten Sie bitte die weiteren Informationen hierzu + unter Punkt 5 h. und Punkt 11). +</p> +<h2> + f. Inhalte der Fehlerberichte +</h2> +<p> + Um den technischen Support der App bei der Fehleranalyse zu unterstützen, können Sie in der App + einen Fehlerbericht aufzeichnen. Wenn Sie die Aufzeichnung des Fehlerberichts starten, werden </p> <ul> - <li>Angaben zum Modell und der Version Ihres Smartphones und zur Version Ihrer App sowie dem - verwendeten Betriebssystem. + <li>Ihre Bedienschritte in der App,</li> + <li>die technischen Schritte und Abläufe sowie Statusmeldungen + <ul> + <li>zur Risiko-Ermittlung (z.B. zur Funktionsweise der Verarbeitung der Begegnungsdaten, + der Berechnung des Ansteckungsrisikos, der Aktualisierung der Positiv-Listen, der + Anzeige des errechneten Risikostatus), + </li> + <li>zum Abruf und der Anzeige von Testergebnissen und</li> + <li>zu möglichen Vorgängen zur Warnung Anderer (z.B. die Berechnung von + Ãœbertragungsrisiko-Werten und die technische Bereitstellung Ihrer Zufalls-IDs durch + Ihr Smartphone) + </li> + </ul> </li> </ul> <p> - Zusätzlich können Sie weitere freiwilligen Angaben zu Ihrer Region sowie zu Ihrer Altersgruppe - machen, die zusammen mit den Nutzungsdaten an das RKI übermittelt werden. + umfassend aufgezeichnet und auf Ihrem Smartphone gespeichert. Im Fehlerbericht können auch + Gesundheitsdaten enthalten sein, weil auch die technischen Schritte und Abläufe im Zusammenhang + mit der Erkennung einer Risiko-Begegnung aufgezeichnet werden. </p> <p> - Das RKI wird die Nutzungsdaten und weiteren freiwilligen Angaben zu Statistiken zusammenfassen - und auswerten, um die Wirksamkeit und Funktionsweise der App zu bewerten und Rückschlüsse auf - das Pandemiegeschehen zu ziehen. + Der Fehlerbericht enthält jedoch keine Informationen über QR-Codes für die Testregistrierung, + das in Ihrer App gespeicherte Token (siehe dazu oben Punkt 6 b. unter „Testergebnis abrufen“) + und Einträge in Ihrem Kontakt-Tagebuch. Der Fehlerbericht enthält auch nicht Ihren Namen oder + andere Angaben, mit denen Sie vom RKI identifiziert werden können. </p> <p> - Die Nutzung der Datenspende ist freiwillig. Sie entscheiden selbst darüber, ob Sie die - Datenspende aktivieren und Nutzungsdaten und die weiteren freiwilligen Angaben an das RKI - übermittelt werden sollen. Die Aktivierung der Datenspende setzt die Bestätigung der Echtheit - Ihrer App voraus (Beachten Sie bitte die weitere Informationen hierzu unter Punkt 5 g. und Punkt - 11). + Sie können die Aufzeichnung des Fehlerberichts jederzeit stoppen und den Fehlerbericht löschen. + Wenn Sie sich entscheiden, den Fehlerbericht mit dem RKI zu teilen, erhalten Sie über die App + eine Kennung zu Ihrem Fehlerbericht (Fehlerbericht-ID). Mit der Fehlerbericht-ID ermöglichen Sie + dem RKI die Zuordnung Ihres Fehlerberichts zu weiteren Informationen, die Sie dem technischen + Support gesondert mitteilen, z.B. wenn Sie noch eine Fehlerbeschreibung per E-Mail bereitstellen + möchten. Wenn Sie dem technischen Support Ihre Fehlerbericht-ID mitteilen, kann anhand dieser + weiteren Informationen möglicherweise eine Verbindung zu Ihrer Person hergestellt werden. +</p> +<p> + Die Erstellung und Ãœbersendung eines Fehlerberichts an das RKI ist freiwillig. Sie entscheiden + selbst darüber, ob Sie einen Fehlerbericht aufzeichnen möchten und diesen an den technischen + Support der App übersenden. Die Ãœbersendung des Fehlerberichts setzt die Bestätigung der + Echtheit Ihrer App voraus (Beachten Sie bitte die weiteren Informationen hierzu unter Punkt 5 h. + und Punkt 11). </p> <h2> - f. Teilnahme an einer Befragung + g. Teilnahme an einer Befragung </h2> <p> Einigen Nutzern wird in der App die Teilnahme an einer Befragung des RKI angeboten. In der Regel @@ -376,22 +422,24 @@ (Beachten Sie bitte die weitere Informationen hierzu unter Punkt 5 g. und Punkt 11). </p> <h2> - g. Bestätigung der Echtheit Ihrer App + h. Bestätigung der Echtheit Ihrer App </h2> <p> Einige Funktionen der App setzen voraus, dass vorab die Echtheit Ihrer App geprüft und gegenüber - dem RKI bestätigt wird. Dazu wird eine Funktion des Betriebssystems Ihres Smartphones genutzt. - Ihr Smartphone erzeugt hierbei eine eindeutige Kennung und sendet diese an den Anbieter Ihres - Betriebssystems (wenn Sie ein Android-Smartphone verwenden, werden Daten an Google übermittelt; - wenn Sie ein iPhone verwenden, werden Daten an Apple übermittelt). Die Kennung enthält - Informationen über die Version Ihres Smartphones und die Version der App. Möglicherweise kann - der Anbieter Ihres Betriebssystems anhand der Kennung auf Ihre Identität schließen und - nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat. Weitere Angaben - aus der App, z.B. Begegnungsdaten, erhält der Anbieter Ihres Betriebssystems nicht. Die Anbieter - des Betriebssystems nutzen die Kennung, um die Echtheit Ihrer App gegenüber dem RKI zu - bestätigen. Die Nutzung der Funktion zur Bestätigung der Echtheit ist freiwillig. Wenn Sie mit - der Bestätigung der Echtheit Ihrer App nicht einverstanden sind, kann es jedoch sein, dass Ihnen - andere Funktionen der App nicht zur Verfügung stehen. + dem RKI bestätigt wird. Die Echtheitsprüfung dient insbesondere dazu, festzustellen, ob Sie eine + manipulierte oder gefälschte („unechte“) Version der App verwenden. Für die Echtheitsprüfung + wird eine Funktion des Betriebssystems genutzt. Ihr Smartphone erzeugt dabei eine eindeutige + Kennung und sendet diese an den Hersteller Ihres Betriebssystems (wenn Sie ein + Android-Smartphone verwenden, werden Daten an Google übermittelt; wenn Sie ein iPhone verwenden, + werden Daten an Apple übermittelt). Die Kennung enthält Informationen über die Version Ihres + Smartphones und die Version der App. Möglicherweise kann der Anbieter Ihres Betriebssystems + anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung + Ihres Smartphones stattgefunden hat. Weitere Angaben aus der App, z.B. Begegnungsdaten, erhält + der Anbieter Ihres Betriebssystems nicht. Die Anbieter des Betriebssystems nutzen die Kennung, + um die Echtheit Ihrer App gegenüber dem RKI zu bestätigen. Die Nutzung der Funktion zur + Bestätigung der Echtheit ist freiwillig. Wenn Sie mit der Bestätigung der Echtheit Ihrer App + nicht einverstanden sind, kann es jedoch sein, dass Ihnen andere Funktionen der App nicht zur + Verfügung stehen. </p> <h1> 6. Wofür werden Ihre Daten verarbeitet? @@ -624,7 +672,7 @@ f. Datenspende </h2> <p> - Die Datenspende ist eine Zusatzfunktion der App. Die im Rahmen der Datenspende an das RKI + Die Datenspende ist eine Zusatzfunktionen der App. Die im Rahmen der Datenspende an das RKI übermittelten Nutzungsdaten und weiteren freiwilligen Angaben dienen der Bewertung der Wirksamkeit der App und sie werden ausgewertet, um folgende Verbesserungen zu ermöglichen: </p> @@ -662,7 +710,24 @@ oder wen Sie getroffen haben. </p> <h2> - g. Befragungen + g. Fehlerberichte +</h2> +<p> + Das RKI ist bemüht, eine fehlerfreie App anzubieten. Aufgrund der großen Anzahl verschiedener + Systeme kann dies jedoch nicht immer gewährleistet werden. Um den technischen Support der App + bei der Fehleranalyse zu unterstützen, können Sie den in Ihrer App erstellten Fehlerbericht an + das RKI übermitteln. Das RKI wird den Fehlerbericht auswerten, um die Ursache der in Ihrer App + auftretenden Fehler erkennen und beseitigen zu können. +</p> +<p> + Die Fehlerberichte werden ohne jeden Zusammenhang mit Ihrem Namen oder Ihrer Identität + vorübergehend gespeichert und im Rahmen der Fehleranalyse ausgewertet. Das RKI erfährt also + nicht, wer Sie sind oder wen Sie getroffen haben. Beachten Sie bitte, dass sich Hinweise auf + Ihre Identität ergeben können, wenn Sie dem technischen Support Ihre Fehlerbericht-ID unter + Offenlegung Ihrer Identität (z. B. per E-Mail) mitteilen. +</p> +<h2> + h. Befragungen </h2> <p> Befragungen finden auf einer Webseite außerhalb der App statt, auf die Sie weitergeleitet @@ -671,7 +736,7 @@ Befragung auf der Befragungs-Webseite beschrieben. </p> <h2> - h. Bestätigung der Echtheit Ihrer App + i. Bestätigung der Echtheit Ihrer App </h2> <p> Zur Bestätigung der Echtheit Ihrer App wird eine Funktion des Betriebssystems Ihres Smartphones @@ -692,43 +757,61 @@ Damit auch Nutzer von den offiziellen Corona-Apps anderer Länder gewarnt werden, hat das RKI zusammen mit mehreren in anderen Ländern für Gesundheitsaufgaben zuständigen amtlichen Stellen und Behörden (im - Folgenden: <strong>Gesundheitsbehörden</strong>) einen zentralen Warnserver + Folgenden: <strong>Gesundheitsbehörden</strong>) zentrale Warnserver zum länderübergreifenden Austausch von Warnungen (im Folgenden: - <strong>Austausch-Server</strong>) eingerichtet. Der Austausch-Server nutzt - die digitale Infrastruktur des zwischen den Mitgliedsstaaten eingerichteten - Netzwerks für elektronische Gesundheitsdienste. + <strong>Austausch-Server</strong>) eingerichtet. </p> +<ul> + <li>Der Austausch-Server der teilnehmenden Länder unter den europäischen Mitgliedstaaten nutzt + die digitale Infrastruktur des zwischen den Mitgliedsstaaten und der EU eingerichteten + Netzwerks für elektronische Gesundheitsdienste. + </li> + <li>Damit auch Warnungen zwischen Nutzern der Schweizer Corona-App und der Corona-Warn-App + möglich sind, betreibt das RKI zudem gemeinsam mit der Schweiz (Bundesamt für Gesundheit der + Schweizerischen Eidgenossenschaft) einen weiteren Austausch-Server. + </li> +</ul> <p> - Die nationalen Serversysteme der am Austausch-Server angebundenen - Corona-Apps übermitteln ihre eigenen Positiv-Listen regelmäßig an den - Austausch-Server und erhalten die Positiv-Listen der anderen Länder. + Die nationalen Serversysteme der an den Austausch-Servern angebundenen Corona-Apps übermitteln + ihre eigenen Positiv-Listen regelmäßig an die Austausch-Server und erhalten die Positiv-Listen + der anderen Länder. </p> <p> - Das Serversystem führt die erhaltenen Positiv-Listen mit der eigenen - Positiv-Liste zusammen, so dass die Risiko-Ermittlung auch - Risiko-Begegnungen mit Nutzern einer anderen Corona-App berücksichtigen - kann (siehe Ziffer 6 c.). Die anderen teilnehmenden Länder verfahren + Das jeweilige Serversystem führt die erhaltenen Positiv-Listen mit der eigenen Positiv-Liste + zusammen, so dass die Risiko-Ermittlung auch Risiko-Begegnungen mit Nutzern einer anderen + Corona-App berücksichtigen kann (siehe Ziffer 6 c.). Die anderen teilnehmenden Länder verfahren entsprechend mit den vom RKI bereitgestellten Positiv-Listen. </p> <p> - Am gemeinsam betriebenen Austausch-Server können nur Länder teilnehmen, - deren Corona-Apps zueinander kompatibel sind und die ein vergleichbar hohes - Datenschutzniveau gewährleisten. Dies setzt insbesondere voraus, dass die - Corona-Apps der teilnehmenden Länder ebenfalls das - COVID-19-Benachrichtigungssystem nutzen, von der jeweiligen nationalen - Gesundheitsbehörde zugelassen sind und die Privatsphäre ihrer Nutzer - wahren. Die technischen und organisatorischen Einzelheiten der - Zusammenarbeit werden in einem Beschluss der EU-Kommission festgelegt - (Durchführungsbeschluss (EU) 2020/1023 vom 15. Juli 2020, abrufbar unter - https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj). -</p> -<p> - Für die Verarbeitung der in den Positiv-Listen enthaltenen Angaben - (Zufalls-IDs und eventuelle Angaben zum Symptombeginn) auf dem - Austausch-Server zur Ermöglichung der länderübergreifenden - Risiko-Ermittlung und Warnung ist das RKI mit den jeweils zuständigen - Gesundheitsbehörden der teilnehmenden Länder gemeinsam verantwortlich. + An den Austausch-Servern können nur Länder teilnehmen, deren Corona-Apps zueinander kompatibel + sind und die ein vergleichbar hohes Datenschutzniveau gewährleisten. Dies setzt insbesondere + voraus, dass die Corona-Apps der teilnehmenden Länder ebenfalls das + COVID-19-Benachrichtigungssystem nutzen, von der jeweiligen nationalen Gesundheitsbehörde + zugelassen sind und die Privatsphäre ihrer Nutzer wahren. </p> +<ul> + <li>Die technischen und organisatorischen Einzelheiten der Zusammenarbeit betreffend den + innerhalb der EU betriebenen Austausch-Server werden in einem Beschluss der EU-Kommission + festgelegt (Durchführungsbeschluss (EU) 2020/1023 vom 15. Juli 2020, abrufbar unter <a + href="https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj"> + https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj + </a>. Für die Verarbeitung der in den Positiv-Listen enthaltenen Angaben (Zufalls-IDs und + eventuelle Angaben zum Symptombeginn) auf den Austausch-Servern zur Ermöglichung der + länderübergreifenden Risiko-Ermittlung und Warnung ist das RKI danach mit den jeweils + zuständigen Gesundheitsbehörden der teilnehmenden Länder gemeinsam verantwortlich. + </li> + <li> + Der Betrieb und der Datenaustausch des gemeinsam mit der Schweiz betriebenen + Austausch-Servers ist in einer individuellen Vereinbarung des RKI mit der Schweiz geregelt, + abrufbar unter <a + href="https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/WarnApp/Warn_App.html"> + https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/WarnApp/Warn_App.html</a>. + Danach erfolgt der technische Betrieb des Austausch-Servers durch das Bundesamt für + Gesundheit der Schweizerischen Eidgenossenschaft. Für die Speicherung, Bereitstellung und + spätere Löschung der Positiv-Listen ist das RKI gemeinsam mit dem Bundesamt für Gesundheit + der Schweizerischen Eidgenossenschaft verantwortlich. + </li> +</ul> <p> Bitte beachten Sie, dass sich die Liste der teilnehmenden Länder ändern kann. Die aktuelle Liste mit Angaben zu den jeweils verantwortlichen @@ -869,7 +952,15 @@ übermittelt werden, werden nach 180 Tagen gelöscht. </p> <h2> - d. Bestätigung der Echtheit Ihrer App + d. Fehlerberichte +</h2> +<p> + Sie können einen aufgezeichneten Fehlerbericht auf Ihrem Smartphone jederzeit löschen. + Fehlerberichte, die Sie an den technischen Support übersandt haben, werden spätestens nach 14 + Tagen gelöscht. +</p> +<h2> + e. Bestätigung der Echtheit Ihrer App </h2> <p> Die Kennungen, die Ihr Smartphone zur Bestätigung der Echtheit Ihrer App erzeugt, werden nach 30 @@ -879,19 +970,19 @@ 10. An wen werden Ihre Daten weitergegeben? </h1> <p> - Wenn Sie andere Nutzer über die App warnen, werden Ihr Testergebnis (in - Form Ihrer Zufalls-IDs der letzten 14 Tage) sowie optionale Angaben zum - Symptombeginn an die jeweils verantwortlichen Gesundheitsbehörden der am - Austausch-Server teilnehmenden Länder und von dort an die Serversysteme der - an den länderübergreifenden Warnungen teilnehmenden Corona-Apps - weitergegeben. Die Serversysteme der nationalen Corona-Apps verteilen diese - Informationen dann als Bestandteil der Positiv-Listen an ihre jeweiligen - eigenen Nutzer. + Wenn Sie andere Nutzer über die App warnen, werden Ihr Testergebnis (in Form Ihrer Zufalls-IDs + der letzten 14 Tage) sowie optionale Angaben zum Symptombeginn an die jeweils verantwortlichen + Gesundheitsbehörden der an den Austausch-Servern teilnehmenden Länder und von dort an die + Serversysteme der an den länderübergreifenden Warnungen teilnehmenden Corona-Apps weitergegeben. + Die Serversysteme der nationalen Corona-Apps verteilen diese Informationen dann als Bestandteil + der Positiv-Listen an ihre jeweiligen eigenen Nutzer. </p> <p> - Mit dem Betrieb und der Wartung des gemeinsam betriebenen Warnsystems haben - die zuständigen nationalen Gesundheitsbehörden der teilnehmenden Länder die - EU-Kommission als Auftragsverarbeiter beauftragt. + Mit dem Betrieb und der Wartung des gemeinsam betriebenen Austausch-Servers der teilnehmenden + EU-Länder haben die jeweils zuständigen nationalen Gesundheitsbehörden die EU-Kommission als + Auftragsverarbeiter beauftragt. Der Austausch-Server für länderübergreifende Warnungen zwischen + der Corona-Warn-App und der schweizerischen Corona-App wird vom Bundesamt für Gesundheit der + Schweizerischen Eidgenossenschaft in Abstimmung mit dem RKI betrieben und gewartet. </p> <p> Mit dem Betrieb und der Wartung eines Teils der technischen Infrastruktur @@ -911,22 +1002,22 @@ 11. Werden Ihre Daten in Länder außerhalb der EU übermittelt? </h1> <p> - Wenn Sie eine Warnung auslösen, können die aktuellen Positiv-Listen - unabhängig vom Aufenthaltsort des Nutzers (etwa im Urlaub oder auf - Geschäftsreise) abgerufen werden. -</p> -<p> - Zudem kann es im Rahmen der Bestätigung der Echtheit Ihrer App zu einer Ãœbermittlung von Daten - in ein Land außerhalb der EU kommen. Die von Ihrem Smartphone erzeugte Kennung, die - Informationen über die Version Ihres Smartphones und der App enthält, wird an den - Betriebssystemanbieter Ihres Smartphones (Apple oder Google) übermittelt. Dabei kann es auch zu - einer Datenübermittlung in Drittländer, insbesondere in die USA, kommen. Dort besteht - möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre - europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht - die Möglichkeit, dass Sicherheitsbehörden im Drittland auf die übermittelten Daten beim - Betriebssystemanbieter zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen - Informationen verknüpfen. Dies betrifft jedoch nur die übermittelte Kennung. Weitere Angaben aus - der App, beispielsweise Begegnungsdaten, sind davon nicht erfasst. + Wenn Sie eine Warnung auslösen, werden Ihre Zufalls-IDs auch in die Schweiz zu dem vom RKI + gemeinsam mit dem Bundesamt für Gesundheit der Schweizerischen Eidgenossenschaft betriebenen + Austausch-Server übermittelt. Für die Schweiz hat die EU einen Angemessenheitsbeschluss + erlassen, in dem die Angemessenheit des Datenschutzniveaus festgestellt wird (Art. 45 DSGVO). + Daneben können die aktuellen Positiv-Listen unabhängig vom Aufenthaltsort des Nutzers (etwa im + Urlaub oder auf Geschäftsreise) abgerufen werden. Zudem kann es im Rahmen der Bestätigung der + Echtheit Ihrer App zu einer Ãœbermittlung von Daten in ein Land außerhalb der EU kommen. Die von + Ihrem Smartphone erzeugte Kennung, die Informationen über die Version Ihres Smartphones und der + App enthält, wird an den Betriebssystemanbieter Ihres Smartphones (Apple oder Google) + übermittelt. Dabei kann es auch zu einer Datenübermittlung in Drittländer, insbesondere in die + USA, kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes + Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt + werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland auf die + übermittelten Daten beim Betriebssystemanbieter zugreifen und diese auswerten, beispielsweise + indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft jedoch nur die übermittelte + Kennung. Weitere Angaben aus der App, beispielsweise Begegnungsdaten, sind davon nicht erfasst. </p> <p> Im Ãœbrigen werden die von der App @@ -1011,7 +1102,18 @@ aktivieren. </p> <h2> - e. Einverständnis „Befragung“ + e. Einverständnis „Fehlerberichte“ +</h2> +<p> + Ihr Einverständnis zur Analyse von bereits an das RKI übermittelten Fehlerberichten können Sie + zurücknehmen, indem Sie dem technischen Support unter Nennung Ihrer Fehlerbericht-ID mitteilen, + dass Sie die Analyse des Fehlerberichts nicht mehr wünschen. Ihr Fehlerbericht wird dann + gelöscht. Bitte beachten Sie, dass das RKI dabei Ihre Identität erfahren kann. Wenn Sie dem + technischen Support Ihre Fehlerbericht-ID nicht mitteilen, wird der übermittelte Fehlerbericht + automatisch nach 14 Tagen gelöscht. +</p> +<h2> + f. Einverständnis „Befragung“ </h2> <p> Ihr Einverständnis zur Teilnahme an einer Befragung des RKI erteilen Sie nicht in der App, @@ -1019,7 +1121,7 @@ beschrieben, wie Sie Ihr Einverständnis zurücknehmen können. </p> <h2> - f. Einverständnis „Bestätigung der Echtheit Ihrer App“ + g. Einverständnis „Bestätigung der Echtheit Ihrer App“ </h2> <p> Wenn Sie Ihr Einverständnis zur Bestätigung der Echtheit Ihrer App zurücknehmen, hat dies keine @@ -1059,11 +1161,10 @@ </li> </ul> <p> - Diese Datenschutzrechte stehen Ihnen entsprechend auch gegenüber den für - die Datenverarbeitung verantwortlichen Gesundheitsbehörden der am - Austausch-Server teilnehmenden Länder zu, sofern Sie Ihre Zufalls-IDs der - letzten Tage zur Warnung Ihrer Mitmenschen übermittelt haben (siehe Punkt - 7). + Diese Datenschutzrechte stehen Ihnen entsprechend auch gegenüber den für die Datenverarbeitung + verantwortlichen Gesundheitsbehörden der an den Austausch-Servern teilnehmenden Länder zu, + sofern Sie Ihre Zufalls-IDs der letzten Tage zur Warnung Ihrer Mitmenschen übermittelt haben + (siehe Punkt 7). </p> <p> Bitte beachten Sie, dass die vorgenannten Datenschutzrechte nur erfüllt @@ -1089,5 +1190,5 @@ datenschutz@rki.de. </p> <p> - Stand: 01.03.2021 + Stand: 24.03.2021 </p> diff --git a/Corona-Warn-App/src/main/assets/privacy_en.html b/Corona-Warn-App/src/main/assets/privacy_en.html index 379e081aabfe86cd3b2b0672daa6c3597c40ddbf..3b226d6e8408ca94f3489267d9fe1213dabe680b 100644 --- a/Corona-Warn-App/src/main/assets/privacy_en.html +++ b/Corona-Warn-App/src/main/assets/privacy_en.html @@ -115,7 +115,7 @@ location or other personal details. </p> <p> - In principle, the app does not therefore use any analysis tools to evaluate the way you use it. + In principle, the app does not therefore use analysis tools to evaluate the way you use it. Only if you expressly agree to voluntarily share data (see Section 5 e.), will certain data about your use of the app be transmitted to the RKI. </p> @@ -276,76 +276,117 @@ tracing purposes, and how you can provide it. </p> <h2> - e. Data sharing + e. Usage data (data sharing) </h2> <p> - If you enable the data sharing feature, the app will transmit various data about your use of - the app (hereinafter referred to as usage data) once a day to the RKI. This usage data concerns - possible exposures and warnings that have been displayed to you, test results you have - retrieved, and whether you have warned other users, and information about your smartphone’s - operating system. Specifically, this means: + If you enable data sharing, the app will transmit certain data about your use of the app + (hereinafter referred to as usage data) once a day to the RKI. This usage data concerns the + possible exposures displayed by the app, warnings that have been received and triggered, test + results that have been retrieved, and technical information about your smartphone’s operating + system. Specifically, the usage data includes: </p> <ul> - <li>The date of transmission</li> + <li>The date when you shared the data (i.e. the date of transmission)</li> <li>Changes to the warning history compared to the previous day</li> - <li>Information about what risk was shown to you at the time of transmission</li> - <li>Information about the basis on which the risk status was calculated in connection with an - encounter + <li>What risk status was shown to you at the time when you used the data sharing feature</li> + <li>Information about which encounters served as a basis on which to calculate the risk status + </li> + <li>Information about the model and version of your smartphone and the version of your app as + well as the operating system you are using. </li> </ul> <p> If you retrieved a test result via the app: </p> <ul> - <li>Information about whether you received a positive or negative test result via the app</li> - <li>Information about the calculated risk at the time of test registration</li> - <li>Information about the period between your last encounter involving an elevated risk and when - the test was registered + <li>Whether the test result was positive or negative</li> + <li>What risk was shown to you at the time when you registered the test</li> + <li>How much time has passed since the last possible exposure and its display in the app until + the relevant test registration </li> - <li>Information about the period between the last notification of an elevated risk and when the - test was registered + <li>Whether you have used the feature for warning others and, if so, which step you reached in + the process (e.g. the part that asks about your symptoms) </li> - <li>Information about whether you have shared your test result and warned others.</li> </ul> <p> - If you have warned others about a possible exposure: + If you have used the warning feature: </p> <ul> - <li>Information about whether you cancelled the procedure to warn others</li> - <li>Information about whether you provided information about the onset of symptoms</li> - <li>Information about when you gave your consent to warn others</li> - <li>Information about how far you got in the procedure to warn others</li> - <li>Information about how many hours it took before you received your test result</li> - <li>Information about how many days have passed since the last notification of an elevated - risk + <li>Whether you provided information about the onset of symptoms</li> + <li>When you gave your consent to warn others (before or after registering the test)</li> + <li>Whether you completed the entire warning process or whether you aborted the process before + the end (for example, because you did not wait for confirmation that your data had been + successfully transmitted) + </li> + <li>How many hours it took before you received your test result after registering your test</li> + <li>How many days passed since the last notification of an elevated risk before the warning + feature was used + </li> + <li>How many hours have passed since the test was registered. </li> - <li>Information about how many hours have passed since the test was registered.</li> </ul> <p> - Other information: + In addition, you can provide further optional information about your region and age group, which + will be transmitted to the RKI together with the usage data. +</p> +<p> + The RKI will compile the usage data and any optional information into anonymised statistics and + analyse it to assess the effectiveness and functioning of the app, and draw conclusions + regarding the pandemic. +</p> +<p> + Participation in data sharing is voluntary. To enable the data sharing feature, the authenticity + of your app first needs to be confirmed (please note the further information about this under + Sections 5 h. and 11). +</p> +<h2> + f. Contents of the error reports +</h2> +<p> + To assist the app’s technical support team with error analysis, you can record an error report + in the app. When you start recording the error report, a comprehensive record is made of </p> <ul> - <li>Information about the model and version of your smartphone and the version of your app as - well as the operating system you are using. - </li> + <li>the steps you take in the app,</li> + <li>the technical steps and processes as well as status messages involving + <ul> + <li>exposure logging (e.g. involving the functioning of the processing of exposure data, + the calculation of the risk of infection, the updating of the positive lists, the + display of the calculated risk status), + </li> + <li>the retrieval and display of test results, and</li> + <li>possible processes for warning others (e.g. the calculation of transmission risk + values and the technical provision of your random IDs by your smartphone) + </li> + </ul> </ul> <p> - In addition, you can provide further voluntary information about your region and age group, - which will be transmitted to the RKI together with the usage data. + and stored on your smartphone. The error report may also contain health data, because the + technical steps and processes related to the detection of a possible exposure are also recorded. +</p> +<p> + However, the error report does not contain information about QR codes for test registration, the + token stored in your app (see “Retrieving a test result†in Section 6 b. above) and entries in + your contact journal. Furthermore, the error report does not contain your name or other + information with which the RKI can identify you. </p> <p> - The RKI will compile the usage data and other voluntary information into statistics and analyse - it to assess the effectiveness and functioning of the app, and draw conclusions regarding the - pandemic. + You can stop recording the error report and erase the error report at any time. If you choose to + share the error report with the RKI, you will receive an identifier for your error report (error + report ID) via the app. The error report ID allows the RKI to assign your error report to + further information that you provide separately to the technical support team, e.g. if you also + wish to provide a description of the error by email. If you provide your error report ID to the + technical support team, it may be possible to establish a link to you based on this further + information. </p> <p> - Using the data sharing feature is voluntary. You decide yourself whether you want to enable the - data sharing feature and whether usage data and other voluntary information should be - transmitted to the RKI. To enable the data sharing feature, the authenticity of your app first - needs to be confirmed (please note the further information about this in Sections 5 g. and 11). + Creating and sending an error report to the RKI is voluntary. You decide yourself whether you + want to record an error report and send it to the app’s technical support team. To send the + error report, the authenticity of your app first needs to be confirmed (please note the further + information about this under Sections 5 h. and 11). </p> <h2> - f. Participation in a survey + g. Participation in a survey </h2> <p> Some app users are offered to participate in a survey by the RKI. This offer to participate in @@ -362,17 +403,18 @@ information about this in Sections 5 g. and 11). </p> <h2> - g. Confirmation of the authenticity of your app + h. Confirmation of the authenticity of your app </h2> <p> Before you can use some of the app’s features, the authenticity of your app first needs to be - checked and confirmed to the RKI. This process uses a feature of your smartphone’s operating - system. Your smartphone generates a unique identifier and sends it to your operating system - provider (if you use an Android smartphone, data is transmitted to Google; if you use an iPhone, - data is transmitted to Apple). The identifier contains information about the version of your - smartphone and the version of the app. It is possible for your operating system provider to - infer your identity from the identifier and learn that your smartphone has been authenticated. - Your operating system provider does not receive any other + checked and confirmed to the RKI. Specifically, this authentication serves to determine whether + you are using a manipulated or counterfeit (“fakeâ€) version of the app. The authentication uses + a feature of your operating system. Your smartphone generates a unique identifier and sends it + to your operating system provider (if you use an Android smartphone, data is transmitted to + Google; if you use an iPhone, data is transmitted to Apple). The identifier contains information + about the version of your smartphone and the version of the app. It is possible for your + operating system provider to infer your identity from the identifier and learn that your + smartphone has been authenticated. Your operating system provider does not receive any other information, such as exposure data, from the app. Your operating system provider will use the identifier to confirm the authenticity of your app to the RKI. Using the feature for confirming the authenticity of your app is voluntary. However, if you do not agree to having the @@ -641,7 +683,23 @@ have met. </p> <h2> - g. Surveys + g. Error reports +</h2> +<p> + The RKI strives to offer a bug-free app. However, due to the large number of different systems + involved, this cannot always be guaranteed. To assist the app’s technical support team with + error analysis, you can send the error report that has been recorded in your app to the RKI. The + RKI will analyse the error report in order to be able to identify and eliminate the cause of the + errors that occur in your app. +</p> +<p> + For the error analysis, the error reports will be temporarily stored and analysed without any + connection to your name or identity. This means the RKI will not find out who you are or who you + have met. Please note that if you provide your error report ID to the technical support team + (e.g. by email), this may reveal information about your identity. +</p> +<h2> + h. Surveys </h2> <p> The surveys take place on a website outside of the app, which you will be redirected to. The app @@ -649,7 +707,7 @@ survey are described in the information about the survey on the survey website. </p> <h2> - h. Confirmation of the authenticity of your app + i. Confirmation of the authenticity of your app </h2> <p> A feature of your smartphone’s operating system is used to confirm the authenticity of your app. @@ -668,19 +726,27 @@ To ensure that users are also warned by the official coronavirus apps of other countries, the RKI, together with several official healthcare bodies and authorities in other countries (hereinafter referred to as <strong>health - authorities</strong>) has set up a central warning server + authorities</strong>) has set up central warning servers for sharing warnings between countries (hereinafter referred to as the <strong>exchange - server</strong>). The exchange server uses the digital - infrastructure of the eHealth Network established between the Member - States. + server</strong>). </p> +<ul> + <li>The exchange server of the participating countries among European Member States uses the + digital infrastructure of the eHealth Network established between the Member States and the + EU. + </li> + <li>In order to also enable warnings between users of the Swiss coronavirus app and the + Corona-Warn-App, the RKI also operates another exchange server together with Switzerland + (Federal Office of Public Health of the Swiss Confederation). + </li> +</ul> <p> The national server systems of the coronavirus apps connected to the exchange server regularly transmit their own positive lists to the exchange server and receive the positive lists of the other countries. </p> <p> - The server system merges the positive lists received in this way with its + Each server system merges the positive lists received in this way with its own positive list, which allows the exposure logging feature to also take into account possible exposures involving users of another coronavirus app (see point 6 c.) The other participating countries proceed in the same way @@ -689,22 +755,34 @@ <p> Only countries whose coronavirus apps are compatible with each other and which guarantee a comparably high level of data protection can participate - in the joint exchange server. In particular, this requires that the + in the exchange servers. In particular, this requires that the coronavirus apps of the participating countries also use the COVID-19 exposure notification system, are approved by the respective national - health authorities, and respect the privacy of their users. The technical - and organisational details of this cooperation are laid down in an EU - Commission Decision (Commission Implementing Decision (EU) 2020/1023 of 15 - July 2020, which is available at https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj). -</p> -<p> - Together with the respective competent health authorities of the - participating countries, the RKI is a joint controller under data - protection law, meaning it is responsible for processing the information - contained on the positive lists (random IDs and, if applicable, information - about the onset of symptoms) on the exchange server in order to enable the - transnational exposure logging and warning system. + health authorities, and respect the privacy of their users. </p> +<ul> + <li>The technical and organisational details of this cooperation concerning the exchange server + operated within the EU are laid down in an EU Commission Decision (Commission Implementing + Decision (EU) 2020/1023 of 15 July 2020, which is available at <a + href="https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj"> + https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj</a>). Under it, together with the + respective competent health authorities of the participating countries, the RKI is a joint + controller under data protection law, meaning it is responsible for processing the + information contained on the positive lists (random IDs and, if applicable, information + about the onset of symptoms) on the exchange servers in order to enable the transnational + exposure logging and warning system. + </li> + <li>The operation and data exchange of the joint exchange server with Switzerland is regulated + in an individual agreement between the RKI and Switzerland, available at <a + href="https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/WarnApp/Warn_App.html"> + https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/WarnApp/Warn_App.html</a>. + Under the agreement, the technical operation of the exchange server is carried out by the + Federal Office of Public Health of the Swiss Confederation. Together with the Federal Office + of Public Health of the Swiss Confederation, the RKI is a joint controller under data + protection law, meaning it is responsible for the storage, provision and subsequent erasure + of the positive lists. + </li> +</ul> <p> Please note that the list of participating countries is subject to change. The current list, with details of the competent health authorities in each @@ -840,7 +918,14 @@ will be deleted after 180 days. </p> <h2> - d. Confirmation of the authenticity of your app + d. Error reports +</h2> +<p> + You can erase a recorded error report on your smartphone at any time. Error reports that you + have sent to the technical support team will be erased after 14 days at the latest +</p> +<h2> + e. Confirmation of the authenticity of your app </h2> <p> The identifier generated by your smartphone to confirm the authenticity of your app will be @@ -861,9 +946,11 @@ lists. </p> <p> - The competent national health authorities in the participating countries - have commissioned the EU Commission, as data processor, to operate and - maintain the joint warning system. + The competent national health authorities have commissioned the EU Commission, as data + processor, to operate and maintain the joint exchange server of the participating EU countries. + The exchange server for transnational warnings between the Corona-Warn-App and the Swiss + coronavirus app is operated and maintained by the Federal Office of Public Health of the Swiss + Confederation in consultation with the RKI. </p> <p> The RKI has commissioned T-Systems International GmbH and SAP Deutschland @@ -885,9 +972,12 @@ 11. Is your data transferred to countries outside the EU? </h1> <p> - If you activate the warning feature, please note that users can retrieve - the latest positive lists regardless of where they are (even if they are - abroad on holiday or on a business trip, for example). + If you activate the warning feature, your random IDs will also be transmitted to Switzerland to + the exchange server that is operated by the RKI together with the Federal Office of Public + Health of the Swiss Confederation. The EU has issued an adequacy decision for Switzerland, which + determines the adequacy of the level of data protection in the country (Art. 45 GDPR). In + addition, please note that users can retrieve the latest positive lists regardless of where they + are (even if they are abroad on holiday or on a business trip, for example). </p> <p> In addition, the confirmation of the authenticity of your app may involve the transfer of data @@ -979,7 +1069,17 @@ donation again, you can re-enable the feature in the settings. </p> <h2> - e. Consent to “survey participation†+ e. Consent to “error reports†+</h2> +<p> + You can withdraw your consent to the analysis of error reports already submitted to the RKI by + informing the technical support team that you no longer wish to have the error report analysed, + stating your error report ID. Your error report will then be erased. Please note that the RKI + may learn your identity in the process. If you do not provide your error report ID to the + technical support team, the submitted error report will be automatically erased after 14 days. +</p> +<h2> + f. Consent to “survey participation†</h2> <p> You do not give your consent to participate in an RKI survey in the app, but via the website on @@ -987,7 +1087,7 @@ your consent. </p> <h2> - f. Consent to “confirmation of the authenticity of your app†+ g. Consent to “confirmation of the authenticity of your app†</h2> <p> If you withdraw your consent to the confirmation of your app’s authenticity, this will not @@ -1049,5 +1149,5 @@ 13353 Berlin, or by emailing datenschutz@rki.de. </p> <p> - Last amended: 01 March 2021 + Last amended: 24 March 2021 </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 31ca70cf54ecdba84986828ae501ae4a07808ff6..6375f658126b2988ac8924450d2182d1abcd093b 100644 --- a/Corona-Warn-App/src/main/assets/privacy_tr.html +++ b/Corona-Warn-App/src/main/assets/privacy_tr.html @@ -118,10 +118,9 @@ bilgilerinizi öğrenmesine olanak tanıyan verileri toplamamaktadır. </p> <p> - Dolayısıyla Uygulama esas itibariyle, analiz araçları üzerinden kullanıcı davranışınızın - herhangi bir deÄŸerlendirmesini yapmaz. Sadece isteÄŸe baÄŸlı veri bağışına açıkça onay vermeniz - durumunda, Uygulama kullanımınıza iliÅŸkin belirli veriler, RKI’ye aktarılacaktır (bkz. Madde 5 - e.). + Dolayısıyla Uygulama, prensip olarak analiz araçları üzerinden kullanıcı davranışınızın herhangi + bir deÄŸerlendirmesini yapmaz. Sadece isteÄŸe baÄŸlı veri bağışına açıkça onay vermeniz durumunda, + Uygulama kullanımınıza iliÅŸkin belirli veriler, RKI’ye aktarılacaktır (bkz. Madde 5 e.). </p> <p> Uygulama tarafından iÅŸlenen veriler aÅŸağıdaki kategorilerde @@ -274,76 +273,128 @@ çıkarsa ve saÄŸlık kurumu sizden temas takibinde yardım etmenizi rica ederse, saÄŸlık kurumuna gerekli bilgileri hızlı bir ÅŸekilde aktarabilirsiniz. </p> -Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas güncesine nasıl kaydedileceÄŸine siz karar verirsiniz. Bu açıdan veri giriÅŸlerinizden de siz sorumlusunuz. Dolayısıyla temas güncesine eklediÄŸiniz kiÅŸilerin mahremiyetine lütfen saygı gösterin. Bu bilgiler, üçüncü taraflara aktarılmamalı ve güvenli olmayan iletiÅŸim kanalları üzerinden aktarımı yapılmamalıdır. Yetkili saÄŸlık kurumu, temas baÄŸlantılarınızı takip etmek isterse, sizden hangi bilgilere gerek duyduÄŸunu ve sizin bunları nasıl saÄŸlayacağınızı size söyleyecektir. <p> + Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas güncesine nasıl + kaydedileceÄŸine siz karar verirsiniz. Bu açıdan veri giriÅŸlerinizden de siz sorumlusunuz. + Dolayısıyla temas güncesine eklediÄŸiniz kiÅŸilerin mahremiyetine lütfen saygı gösterin. Bu + bilgiler, üçüncü taraflara aktarılmamalı ve güvenli olmayan iletiÅŸim kanalları üzerinden + aktarımı yapılmamalıdır. Yetkili saÄŸlık kurumu, temas baÄŸlantılarınızı takip etmek isterse, + sizden hangi bilgilere gerek duyduÄŸunu ve sizin bunları nasıl saÄŸlayacağınızı size + söyleyecektir. </p> <h2> e. Veri bağışı </h2> <p> - Veri bağışı iÅŸlevini etkinleÅŸtirdiÄŸinizde Uygulama, Uygulama kullanımınıza iliÅŸkin çeÅŸitli - verileri günde bir kez RKI’ye aktarır (bundan böyle: Kullanım verileri). Bu kullanım verileri, - görüntülenen riskli karşılaÅŸmalar ve uyarılar, size gönderilen test sonuçları, diÄŸer - kullanıcıları uyarıp uyarmadığınız ve akıllı telefonunuzun iÅŸletim sistemine iliÅŸkin verilerdir. - Ayrıntılı olarak bunlar: + Veri bağışını etkinleÅŸtirdiÄŸinizde Uygulama, Uygulama kullanımınıza iliÅŸkin belirli verileri + günde bir kez RKI’ye aktarır (bundan böyle: Kullanım verileri). Bu kullanım verileri, Uygulama + tarafından görüntülenen riskli karşılaÅŸmalar, alınan ve tetiklenen uyarılar, alınan test + sonuçları ve akıllı telefonunuzun iÅŸletim sistemine iliÅŸkin verilerdir. Kullanım verileri + ayrıntılarıyla ÅŸunlardır: + </p> <ul> - <li>Veri aktarımının tarihi.</li> + <li>Veri bağışının tarihi (yani aktarımın günü)</li> <li>Uyarı geçmiÅŸindeki önceki güne göre deÄŸiÅŸiklikler.</li> - <li>Veri aktarımı sırasında size görüntülenen risklere iliÅŸkin bilgiler.</li> - <li>Bir karşılaÅŸmada risk durumunun hesaplama temeline iliÅŸkin bilgiler.</li> + <li>Veri bağışı sırasında hangi risk durumunun görüntülendiÄŸi.</li> + <li>KarşılaÅŸmaların risk durumlarının hesaplama temeline iliÅŸkin veriler.</li> + <li>Akıllı telefonunuzun modeli ve sürümü ile Uygulamanızın sürümü ve kullanılan iÅŸletim + sistemine iliÅŸkin bilgiler. + </li> </ul> <p> Uygulama üzerinden test sonucunuzu aldıysanız: </p> <ul> - <li>Uygulama üzerinden pozitif veya negatif bir test sonucu alıp almadığınıza iliÅŸkin - bilgiler. + <li>Test sonucunun pozitif mi yoksa negatif mi olduÄŸu. + </li> + <li>Test kaydı sırasında hangi riskin görüntülendiÄŸi.</li> + <li>Son riskli karşılaÅŸmadan ve bunun Uygulamada görüntülenmesinden test kaydına kadar geçen + süre. </li> - <li>Test kaydı sırasında hesaplanan risk hakkında bilgiler.</li> - <li>En son yüksek riskli karşılaÅŸma ile test kaydı arasındaki süreye iliÅŸkin bilgiler.</li> - <li>En son yüksek risk bildirimi ile test kaydı arasındaki süreye iliÅŸkin bilgiler.</li> - <li>Test sonucunuzu paylaşıp paylaÅŸmadığınız ve baÅŸkalarını uyarıp uyarmadığınız hakkında - bilgiler. + <li>Bir uyarıyı tetiklemek için iÅŸlevi baÅŸlatıp baÅŸlatmadığınız ve baÅŸlattıysanız, bu süreçte + hangi adıma kadar geldiÄŸiniz (örn. semptom sorgusuna kadar gelmeniz). </li> </ul> <p> - BaÅŸkalarını olası bir riskle karşılaÅŸma konusunda uyardıysanız: + Bir uyarı tetiklediyseniz: </p> <ul> - <li>BaÅŸkalarını uyarma adımlarını iptal edip etmediÄŸiniz hakkında bilgiler.</li> - <li>Semptomların baÅŸlangıcına iliÅŸkin bildirimde bulunmanıza iliÅŸkin bilgiler.</li> - <li>BaÅŸkalarını uyarmak için ne zaman rıza göstermenize iliÅŸkin bilgiler.</li> - <li>BaÅŸkalarını uyarma kapsamında hangi mesaja kadar geldiÄŸinize dair bilgiler.</li> - <li>Test sonucunuza eriÅŸiminizin kaç saat sürdüğüne iliÅŸkin bilgiler.</li> - <li>En son yüksek risk bildiriminden bu yana geçen gün sayısına iliÅŸkin bilgiler.</li> - <li>Test kaydından sonra geçen saat sayısına iliÅŸkin bilgiler.</li> + <li>Semptomların baÅŸlangıcı hakkında bilgi verip vermediÄŸiniz.</li> + <li>BaÅŸkalarını uyarmak için ne zaman rıza verdiÄŸiniz (test kaydından önce veya sonra).</li> + <li>Uyarı sürecinin tamamını geçip geçmediÄŸiniz veya uyarı iÅŸlemini daha önce yarıda bırakılıp + bırakmadığınız (örneÄŸin verilerinizin baÅŸarıyla aktarıldığına iliÅŸkin onayı bekleyip + beklemediÄŸiniz gibi). + </li> + <li>Test kaydınız ile test sonucunuza eriÅŸiminiz arasında kaç saat geçtiÄŸine iliÅŸkin bilgi.</li> + <li>Yüksek risk bildiriminden uyarının tetiklenmesine kadar geçen gün sayısı.</li> + <li>Test kaydından sonra geçen saat sayısı.</li> </ul> <p> - DiÄŸer veriler: + Ayrıca kullanım verileriyle birlikte RKI’ye aktarılmak üzere bölgeniz ve yaÅŸ grubunuza iliÅŸkin + diÄŸer opsiyonel verileri de saÄŸlayabilirsiniz. +</p> +<p> + RKI, Uygulamanın etki gücünü ve iÅŸlevselliÄŸini deÄŸerlendirmek ve pandemi hakkında yeni + çıkarımlar elde etmek için, kullanım verilerini olası opsiyonel veriler ile birleÅŸtirecek ve + anonimleÅŸtirilmiÅŸ istatistikler olarak deÄŸerlendirecektir +</p> +<p> + Veri bağışına katılım, gönüllü olarak gerçekleÅŸmektedir. Veri bağışının etkinleÅŸtirilmesi, + Uygulamanızın orijinalliÄŸinin doÄŸrulanmasını gerektirir (bu konuyla ilgili daha fazla bilgi için + bkz. Madde 5 h. ve 11.). +</p> +<h2> + f. Hata raporlarının içerikleri +</h2> +<p> + Hata analizi ile ilgili olarak Uygulama teknik desteÄŸine yardımcı olmak için Uygulamanızda hata + raporu kaydedebilirsiniz. Hata raporunu kaydetmeye baÅŸladığınızda, </p> <ul> - <li>Akıllı telefonunuzun modeli ve sürümü ile Uygulamanızın sürümü ve kullanılan iÅŸletim - sistemine iliÅŸkin bilgiler. + <li>Uygulamadaki kullanım adımlarınız,</li> + <li>teknik adımlar ve süreçler ile durum raporları + <ul> + <li>risk deÄŸerlendirmesi (örneÄŸin, maruz kalma verilerinin nasıl iÅŸlendiÄŸi, enfeksiyon + riskinin hesaplanması, pozitif listelerin güncellenmesi, hesaplanan risk durumunun + görüntülenmesi), + </li> + <li>test sonuçlarını çaÄŸrılması ve görüntülenmesi ve</li> + <li>baÅŸkalarını uyarmak için olası prosedürler (örneÄŸin akıllı telefonunuz tarafından + taşıma riski deÄŸerlerinin hesaplanması ve rastgele kimlik numaralarınızın tekniksel + açıdan saÄŸlanması) + </li> + </ul> </li> </ul> <p> - Ayrıca kullanım verileriyle birlikte RKI’ye aktarılmak üzere bölgeniz ve yaÅŸ grubunuza iliÅŸkin - diÄŸer isteÄŸe baÄŸlı verileri de saÄŸlayabilirsiniz. + akıllı telefonunuzda kapsamlı bir ÅŸekilde kaydedilir ve saklanır. Bir hata raporu saÄŸlık + verileri de içerebilir, çünkü bir riskli karşılaÅŸmanın saptanmasına yönelik teknik adımlar ve + süreçler de kaydedilmektedir. </p> <p> - RKI, Uygulamanın etki gücünü ve iÅŸlevselliÄŸini deÄŸerlendirmek ve pandemi hakkında yeni - çıkarımlar elde etmek için, kullanım verilerini diÄŸer veriler ile birleÅŸtirecek ve istatistikler - olarak deÄŸerlendirecektir. + Ancak hata raporu, test kaydı QR kodlarını, Uygulamanızda depolanan belirteç ve temas + günlüğünüzdeki veri giriÅŸlerine iliÅŸkin herhangi bir bilgi içermez. Hata raporu, adınızı ve RKI + tarafından kimliÄŸinizin tanımlanmasını saÄŸlayacak diÄŸer verileri içermez. +</p> +<p> + Ä°stediÄŸiniz zaman hata raporunu kaydını durdurabilir ve hata raporunu silebilirsiniz. Hata + raporunu RKI ile paylaÅŸmaya karar verdiÄŸinizde, size Uygulama üzerinden hata raporu bir kimlik + kodu (hata raporu kimlik numarası) gönderilir. Hata raporu kimlik numarası üzerinden RKI, hata + raporunuzu, teknik desteÄŸe örneÄŸin hatanın bir açıklamasını e-posta ile ilettiÄŸinizde ayrı + olarak saÄŸladığınız diÄŸer bilgilere atayabilir. Hata raporu kimlik numaranızı teknik desteÄŸe + aktardığınızda, onların diÄŸer bazı veriler aracılığıyla sizin kimliÄŸinize baÄŸlantı kurması + mümkün olabilir. </p> <p> - Veri bağışının kullanılması isteÄŸe baÄŸlıdır. Veri bağışının etkinleÅŸtirilmesine ve kullanım - verilerinin diÄŸer isteÄŸe baÄŸlı veriler ile birlikte RKI’ye aktarılmasına, siz kendiniz karar - verirsiniz. Veri bağışının etkinleÅŸtirilmesi, Uygulamanızın orijinalliÄŸinin doÄŸrulanmasını - gerektirir (bu konuyla ilgili daha fazla bilgi için bkz. Madde 5 g. ve 11.). + Bir hata raporunun oluÅŸturulması ve RKI’ye gönderimi isteÄŸe baÄŸlı gerçekleÅŸir. Bir hata raporu + kaydetmek ve bunu Uygulama teknik desteÄŸe göndermek isteyip istemediÄŸinize siz kendiniz karar + verirsiniz. Veri bağışının gönderiminin etkinleÅŸtirilmesi, Uygulamanızın orijinalliÄŸinin + doÄŸrulanmasını gerektirir (bu konuyla ilgili daha fazla bilgi için bkz. Madde 5 h. ve 11.). </p> + <h2> - f. Ankete katılım + g. Ankete katılım </h2> <p> Uygulamada bazı kullanıcılara bir RKI anketine katılma olanağı verilir. Ankete katılma teklifi @@ -356,24 +407,26 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g Ankete katılım isteÄŸe baÄŸlıdır. Bir ankete katılmaya ve kullanım verilerinin RKI’ye aktarılmasına, siz kendiniz karar verirsiniz. Bu anketler, yönlendirileceÄŸiniz Uygulama dışındaki bir web sitesinde gerçekleÅŸtirilir. Ankete katılım, Uygulamanızın orijinalliÄŸinin - doÄŸrulanmasını gerektirir (bu konuyla ilgili daha fazla bilgi için bkz. Madde 5 g. ve 11.). + doÄŸrulanmasını gerektirir (bu konuyla ilgili daha fazla bilgi için bkz. Madde 5 h. ve 11.). </p> <h2> - g. Uygulamanızın orijinalliÄŸinin doÄŸrulanması + h. Uygulamanızın orijinalliÄŸinin doÄŸrulanması </h2> <p> Uygulamadaki bazı iÅŸlevler, Uygulamanızın orijinalliÄŸinin önceden kontrol edilmesini ve RKI için - onaylanmasını gerektirir. Bunun için akıllı telefonunuzun iÅŸletim sisteminin bir iÅŸlevinden - yararlanılır. Bu baÄŸlamda akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve - bunu iÅŸletim sisteminizin saÄŸlayıcısına gönderir (bir Android akıllı telefon kullanıyorsanız, bu - veriler Google’a, iPhone kullanıyorsanız Apple’a aktarılır). Bu kimlik kodu, akıllı - telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Ä°ÅŸletim sisteminizin saÄŸlayıcısı, - kimlik kodunuz üzerinden kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik - kontrolünün yapıldığını anlayabilir. Uygulamadan karşılaÅŸma verileri gibi baÅŸka bilgiler Ä°ÅŸletim - sisteminizin saÄŸlayıcısına gönderilmez. Ä°ÅŸletim sisteminin saÄŸlayıcısı, Uygulamanızın orijinal - olduÄŸunu RKI’ye doÄŸrulamak için bu kimlik kodunu kullanır. OrijinalliÄŸinin doÄŸrulanmasını - saÄŸlayan bu iÅŸlevin kullanılması isteÄŸe baÄŸlıdır. Ancak Uygulamanızın orijinalliÄŸinin - doÄŸrulanmasını kabul etmezseniz, Uygulamanın baÅŸka bazı iÅŸlevleri sizin için sunulmayabilir. + onaylanmasını gerektirir. Orijinal ürün kontrolü, özellikle Uygulamanın manipüle edilmiÅŸ veya + tahrif edilmiÅŸ (“sahteâ€) bir sürümünü kullanıp kullanmadığınızı belirlemek içindir. Orijinal + ürün kontrolü için iÅŸletim sisteminizin bir iÅŸlevinden yararlanılır. Bu baÄŸlamda akıllı + telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve bunu iÅŸletim sisteminizin + üreticisine gönderir (bir Android akıllı telefon kullanıyorsanız, bu veriler Google’a, iPhone + kullanıyorsanız Apple’a aktarılır). Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama + hakkında veriler içerir. Ä°ÅŸletim sisteminizin saÄŸlayıcısı, muhtemelen kimlik kodunuz üzerinden + kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik kontrolünün yapıldığını + anlayabilir. Uygulamadan karşılaÅŸma verileri gibi baÅŸka bilgiler Ä°ÅŸletim sisteminizin + saÄŸlayıcısına gönderilmez. Ä°ÅŸletim sisteminin saÄŸlayıcısı, Uygulamanızın orijinal olduÄŸunu + RKI’ye doÄŸrulamak için bu kimlik kodunu kullanır. OrijinalliÄŸinin doÄŸrulanmasını saÄŸlayan bu + iÅŸlevin kullanılması isteÄŸe baÄŸlıdır. Ancak Uygulamanızın orijinalliÄŸinin doÄŸrulanmasını kabul + etmezseniz, Uygulamanın baÅŸka bazı iÅŸlevleri sizin için sunulmayabilir. </p> <h1> 6. Verileriniz niçin iÅŸleniyor? @@ -637,6 +690,23 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g olmaksızın saklanır ve deÄŸerlendirilir. RKI, kim olduÄŸunuzu veya kiminle karşılaÅŸtığınızı öğrenmez. </p> +<h2> + g. Hata raporları +</h2> +<p> + RKI, hatasız bir Uygulama sunmayı hedeflemektedir. Ancak, çok sayıda farklı sistem nedeniyle bu + hedefe her zaman ulaşılmayabilmektedir. Hata analizi ile ilgili olarak Uygulama teknik desteÄŸine + yardımcı olmak için, Uygulamanızda hata raporu oluÅŸturup RKI’ye aktarabilirsiniz. RKI, + Uygulamanızda ortaya çıkan hataların nedenlerini belirlemek ve bunları gidermek için hata + raporunu deÄŸerlendirmeye alacaktır. +</p> +<p> + Bu hata raporları, adınız ve kimliÄŸiniz ile herhangi bir baÄŸlam olmaksızın geçici olarak + saklanır ve hata analizi kapsamında deÄŸerlendirilir. RKI, kim olduÄŸunuzu veya kiminle + karşılaÅŸtığınızı öğrenmez. Ancak hata raporunu teknik desteÄŸe iletirken, hata raporu kimlik + numarasını ifÅŸa ederseniz (örn. e-posta yoluyla), bu iÅŸlemin kimliÄŸiniz hakkında ipuçları ortaya + çıkarabileceÄŸini göz önünde bulundurun. +</p> <h2> g. Anketler </h2> @@ -661,45 +731,60 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g 7. Sınır ötesi uyarı sistemi nasıl çalışır? </h1> <p> - DiÄŸer ülkelerdeki resmi Korona uygulamalarının kullanıcılarının da - uyarılmasını için RKI, bu ülkelerdeki sorumlu merciler ve kurumlar (bundan - böyle: <strong>saÄŸlık kurumları</strong>) ile birlikte uyarı mesajlarının - sınır ötesi deÄŸiÅŸimine yönelik merkezi bir uyarı sunucusu (bundan böyle: <strong>veri deÄŸiÅŸim - sunucusu</strong>) kurmuÅŸtur. Veri deÄŸiÅŸim sunucusu, - elektronik saÄŸlık hizmetleri için üye devletler arasında kurulmuÅŸ olan ağın - dijital altyapısını kullanır. -</p> -<p> - Veri deÄŸiÅŸim sunucusuna baÄŸlı Korona uygulamalarının ulusal sunucu - sistemleri, kendi pozitif listelerini düzenli olarak veri deÄŸiÅŸim - sunucusuna iletir ve ondan diÄŸer ülkelerin pozitif listelerini alır. + DiÄŸer ülkelerdeki resmi Korona uygulamalarının kullanıcılarının da uyarılmasını için RKI, bu + ülkelerdeki sorumlu merciler ve kurumlar (bundan böyle: <strong>saÄŸlık kurumları</strong>) ile + birlikte uyarı mesajlarının sınır ötesi deÄŸiÅŸimine yönelik merkezi uyarı sunucuları(bundan + böyle: <strong>veri deÄŸiÅŸim sunucusu</strong>) kurmuÅŸtur. Veri deÄŸiÅŸim sunucusu, elektronik + saÄŸlık hizmetleri için üye devletler arasında kurulmuÅŸ olan ağın dijital altyapısını kullanır. </p> +<ul> + <li>Avrupa BirliÄŸi üye devletlerinden katılımcı ülkelerin veri deÄŸiÅŸim sunucusu, üye devletler + ile AB arasındaki elektronik saÄŸlık hizmetleri iletiÅŸim ağının dijital altyapısını kullanır. + </li> + <li>RKI bu deÄŸiÅŸim sunucusu dışında ayrıca, Ä°sviçre Corona-App ve Corona-Warn-App kullanıcıları + arasında da uyarıların iletilebilmesi için, Ä°sviçre (Ä°sviçre Konfederasyonu Federal SaÄŸlık + Dairesi) ile birlikte baÅŸka bir deÄŸiÅŸim sunucusu iÅŸletmektedir. + </li> +</ul> <p> - Sunucu sistemi, aldığı pozitif listeleri kendi pozitif listesiyle - birleÅŸtirir, bu sayede risk deÄŸerlendirmesinde, diÄŸer Korona - uygulamalarının kullanıcılarıyla olan riskli karşılaÅŸmalar da dikkate - alınabilir (bkz. Madde 6 c.). DiÄŸer katılımcı ülkeler, RKI tarafından - saÄŸlanan pozitif listelerle aynı yöntemi kullanarak çalışmaktadır. + Veri deÄŸiÅŸim sunucularına baÄŸlı Korona uygulamalarının ulusal sunucu sistemleri, kendi pozitif + listelerini düzenli olarak veri deÄŸiÅŸim sunucularına iletir ve onlardan diÄŸer ülkelerin pozitif + listelerini alır. </p> <p> - OrtaklaÅŸa iÅŸletilen veri deÄŸiÅŸim sunucusuna, yalnızca ulusal Korona - uygulamalarının uyumlu olduÄŸu ve aynı yüksek düzeyde veri gizliliÄŸi - güvencesi veren ülkelerin katılmasına izin verilmektedir. Bunun için, - özellikle, katılımcı ülkelerin Korona uygulamalarının da COVID-19 bildirim - sistemini kullanması, ilgili ulusal saÄŸlık kurumları tarafından onaylanmış - olması ve ayrıca kullanıcılarının gizliliÄŸinin korunması zorunludur. - Ä°ÅŸbirliÄŸinin teknik ve organizasyonel ayrıntıları AB Komisyonunun bir - kararında belirlenmiÅŸtir (15 Temmuz 2020 tarihli Uygulama Kararı (AB) - 2020/1023, bu karara https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj) adresinden - eriÅŸebilirsiniz. + Ä°lgili sunucu sistemi, aldığı pozitif listeleri kendi pozitif listesiyle birleÅŸtirir, bu sayede + risk deÄŸerlendirmesinde, diÄŸer Korona uygulamalarının kullanıcılarıyla olan riskli karşılaÅŸmalar + da dikkate alınabilir (bkz. Madde 6 c.). DiÄŸer katılımcı ülkeler, RKI tarafından saÄŸlanan + pozitif listelerle aynı yöntemi kullanarak çalışmaktadır. </p> <p> - RKI ve katılımcı ülkelerin yetkili saÄŸlık kurumları, sınır ötesi risk - deÄŸerlendirmesi ve uyarısı saÄŸlamak üzere veri deÄŸiÅŸim sunucusunda bulunan - pozitif listelerde yer alan verilerin (rastgele kimlik numaraları ve - semptomların baÅŸlangıcına iliÅŸkin olası bilgiler) iÅŸlenmesinden müştereken - sorumludur. + Veri deÄŸiÅŸim sunucularına, yalnızca ulusal Korona uygulamalarının uyumlu olduÄŸu ve aynı yüksek + düzeyde veri gizliliÄŸi güvencesi veren ülkelerin katılmasına izin verilmektedir. Bunun için, + özellikle, katılımcı ülkelerin Korona uygulamalarının da COVID-19 bildirim sistemini kullanması, + ilgili ulusal saÄŸlık kurumları tarafından onaylanmış olması ve ayrıca kullanıcılarının + gizliliÄŸinin korunması zorunludur. </p> +<ul> + <li>AB içinde iÅŸletilen veri deÄŸiÅŸim sunucusuna iliÅŸkin iÅŸ birliÄŸinin teknik ve organizasyonel + ayrıntıları AB Komisyonunun bir kararında belirlenmiÅŸtir (15 Temmuz 2020 tarihli Uygulama + Kararı (AB) 2020/1023, bu karara <a + href="https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj"> + https://eur-lex.europa.eu/eli/dec_impl/2020/1023/oj + </a>. adresinden eriÅŸebilirsiniz. RKI ve katılımcı ülkelerin yetkili saÄŸlık kurumları, sınır + ötesi risk deÄŸerlendirmesi ve uyarısı saÄŸlamak üzere veri deÄŸiÅŸim sunucularında bulunan + pozitif listelerde yer alan verilerin (rastgele kimlik numaraları ve semptomların + baÅŸlangıcına iliÅŸkin olası bilgiler) iÅŸlenmesinden müştereken sorumludur. + </li> + <li> + Ä°sviçre ile ortaklaÅŸa iÅŸletilen veri deÄŸiÅŸim sunucusunun çalışması ve veri alışveriÅŸi, RKI + ve Ä°sviçre arasında özel bir anlaÅŸmada düzenlenmekte olup, ayrıntıları <a + href="https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/WarnApp/Warn_App.html"> + https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/WarnApp/Warn_App.html</a> + adresinden eriÅŸilebilir. Veri deÄŸiÅŸim sunucusunun teknik iÅŸletimi, Ä°sviçre Federal SaÄŸlık + Dairesi tarafından gerçekleÅŸtirilmektedir. Pozitif listelerin saklanması, saÄŸlanması ve daha + sonra silinmesinden, RKI ve Ä°sviçre Federal Halk SaÄŸlığı Dairesi müştereken sorumludur. + </li> +</ul> <p> Sisteme katılımcı ülkeler listesinin deÄŸiÅŸebileceÄŸini, lütfen göz önünde bulundurun. Ä°lgili sorumlu saÄŸlık kurumları hakkında bilgilerin bulunduÄŸu @@ -835,7 +920,14 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g Veri bağışı kapsamında RKI’ye aktarılan kullanım verileri ve diÄŸer isteÄŸe baÄŸlı veriler 180 gün sonra silinir.</p> <h2> - d. Uygulamanızın orijinalliÄŸinin doÄŸrulanması + d. Hata raporları +</h2> +<p> + Akıllı telefonunuzda kaydedilmiÅŸ bir hata raporunu istediÄŸiniz zaman silebilirsiniz. Teknik + desteÄŸe göndermiÅŸ olduÄŸunuz hata raporları ise en geç 14 gün sonra silinecektir. +</p> +<h2> + e. Uygulamanızın orijinalliÄŸinin doÄŸrulanması </h2> <p> Uygulamanızın orijinalliÄŸini doÄŸrulamak için akıllı telefonunuzun oluÅŸturduÄŸu kimlik kodları, @@ -845,54 +937,51 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g 10. Verileriniz kime aktarılır? </h1> <p> - Uygulama aracılığıyla diÄŸer kullanıcıları uyardığınızda, test sonucunuz - <br/> - son 14 güne ait rastgele kimlik numaralarınız halinde ve semptomların - baÅŸlangıcına iliÅŸkin isteÄŸe baÄŸlı bilgiler, veri deÄŸiÅŸim sunucusuna katılan - ülkelerin sorumlu saÄŸlık kurumlarına gönderilir ve oradan da sınır ötesi - uyarı sistemine katılan resmi Korona uygulamalarının sunucu sistemlerine - aktarılır. Ulusal Korona uygulamalarının sunucu sistemleri, bu verileri - pozitif listelerin bir ögesi olarak kendi kullanıcılarına dağıtır. + Uygulama aracılığıyla diÄŸer kullanıcıları uyardığınızda, test sonucunuz son 14 güne ait rastgele + kimlik numaralarınız halinde ve semptomların baÅŸlangıcına iliÅŸkin isteÄŸe baÄŸlı bilgiler, veri + deÄŸiÅŸim sunucularına katılan ülkelerin sorumlu saÄŸlık kurumlarına gönderilir ve oradan da sınır + ötesi uyarı sistemine katılan resmi Korona uygulamalarının sunucu sistemlerine aktarılır. Ulusal + Korona uygulamalarının sunucu sistemleri, bu verileri pozitif listelerin bir ögesi olarak kendi + kullanıcılarına dağıtır. + </p> <p> - Katılımcı ülkelerin yetkili ulusal saÄŸlık kurumları, ortaklaÅŸa iÅŸletilen - uyarı sisteminin iÅŸletimi ve bakımı için AB Komisyonunu iÅŸletici kuruluÅŸ - olarak görevlendirmiÅŸtir. + Katılımcı AB ülkelerinin yetkili ulusal saÄŸlık kurumları, ortaklaÅŸa iÅŸletilen uyarı sisteminin + iÅŸletimi ve bakımı için AB Komisyonuna iÅŸletici kuruluÅŸ olarak yetki vermiÅŸtir. Corona-Warn-App + ile Ä°sviçre Corona-App arasında gerçekleÅŸen sınır ötesi uyarılar ile ilgili olarak veri deÄŸiÅŸim + sunucusu, RKI ile koordineli olarak Ä°sviçre Federal SaÄŸlık Dairesi tarafından iÅŸletilir ve + bakımı yapılır. </p> <p> - RKI, Uygulamanın teknik altyapısının bir bölümünün (örn. sunucu sistemleri, - yardım hattı) iÅŸletimi ve bakımı için, iÅŸletici kuruluÅŸ olarak T-Systems - International GmbH ve SAP Deutschland SE & Co. KG firmalarını - görevlendirmiÅŸtir. Bu firmalar, aynı zamanda AB Komisyonu tarafından, - katılımcı ülkelerin ortaklaÅŸa iÅŸletilen uyarı sisteminin teknik yapısının - temini ve idaresi için alt iÅŸleyici olarak görevlendirilmektedir. -</p> -<p> - Sözü gelmiÅŸken, RKI, Uygulamanın kullanımıyla baÄŸlantılı olarak toplanan - verilerinizi, yalnızca RKI’nin yasalar tarafından yükümlü kılınması veya - Uygulamanın teknik altyapısına bir saldırı olması durumunda bir yasal - takibat veya cezai kovuÅŸturma için ifÅŸa edilmesi gerekli olduÄŸu takdirde - üçüncü taraflara aktarır. DiÄŸer durumlarda RKI tarafından veri aktarımı gerçekleÅŸmez. + RKI, Uygulamanın teknik altyapısının bir bölümünün (örn. sunucu sistemleri, yardım hattı) + iÅŸletimi ve bakımı için, iÅŸletici kuruluÅŸ olarak T-Systems International GmbH ve SAP Deutschland + SE & Co. KG firmalarını görevlendirmiÅŸtir. Bu firmalar, aynı zamanda AB Komisyonu tarafından, + katılımcı ülkelerin ortaklaÅŸa iÅŸletilen uyarı sisteminin teknik yapısının temini ve idaresi için + alt iÅŸleyici olarak görevlendirilmektedir. Sözü gelmiÅŸken, RKI, Uygulamanın kullanımıyla + baÄŸlantılı olarak toplanan verilerinizi, yalnızca RKI’nin yasalar tarafından yükümlü kılınması + veya Uygulamanın teknik altyapısına bir saldırı olması durumunda bir yasal takibat veya cezai + kovuÅŸturma için ifÅŸa edilmesi gerekli olduÄŸu takdirde üçüncü taraflara aktarır. DiÄŸer durumlarda + RKI tarafından veri aktarımı gerçekleÅŸmez. </p> <h1> 11. Verileriniz AB dışındaki ülkelere aktarılacak mı? </h1> <p> - Bir uyarı tetiklediÄŸinizde, güncel pozitif listeler kullanıcının konumundan - bağımsız olarak (örneÄŸin tatilde veya iÅŸ gezisinde) çaÄŸrılır. -</p> -<p> - Ayrıca, Uygulamanızın orijinalliÄŸinin doÄŸrulanması kapsamında verilerin AB dışındaki bir ülkeye - aktarılması söz konusu olabilir. Akıllı telefonunuz tarafından oluÅŸturulan ve akıllı - telefonunuzun ve Uygulamanın sürümüyle ilgili bilgileri içeren kimlik kodu, akıllı telefonunuzun - iÅŸletim sisteminin saÄŸlayıcısına (Apple veya Google) aktarılır. Bu süreçte verilerin üçüncü bir - ülkeye, özellikle ABD’ye aktarılması da söz konusu olabilir. Orada Avrupa hukukuna karşılık - gelen kiÅŸisel verilerin koruma seviyesi bulunmayabilir ve Avrupa’daki veri koruma haklarınız - uygulanmayabilir. Bu baÄŸlamda özellikle güvenlik makamlarının iÅŸletim sistemi saÄŸlayıcısına - aktarılan bu verilere eriÅŸme ve örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları - deÄŸerlendirmeye alma olasılığı bulunmaktadır. Ancak bu, yalnızca aktarılan kimlik kodlarını - etkiler. KarşılaÅŸma verileri gibi Uygulamadaki diÄŸer veriler bu prosedüre dahil edilmez. -</p> + Bir uyarıyı tetiklediÄŸinizde, rastgele kimlik numaralarınız, Ä°sviçre Federal SaÄŸlık Dairesi ile + birlikte RKI tarafından iÅŸletilen Ä°sviçre’deki veri deÄŸiÅŸim sunucusuna da gönderilir. AB, + Ä°sviçre veri koruma seviyesinin yeterliliÄŸinin belirlendiÄŸi bir yeterlilik kararı çıkarmıştır + (GVKT madde 45). Bundan baÅŸka güncel pozitif listeler, kullanıcının konumundan bağımsız olarak + (örneÄŸin tatilde veya iÅŸ gezisinde) çaÄŸrılır. Ayrıca, Uygulamanızın orijinalliÄŸinin doÄŸrulanması + kapsamında verilerin AB dışındaki bir ülkeye aktarılması söz konusu olabilir. Akıllı telefonunuz + tarafından oluÅŸturulan ve akıllı telefonunuzun ve Uygulamanın sürümüyle ilgili bilgileri içeren + kimlik kodu, akıllı telefonunuzun iÅŸletim sisteminin saÄŸlayıcısına (Apple veya Google) + aktarılır. Bu süreçte verilerin üçüncü ülkelere, özellikle ABD’ye aktarılması da söz konusu + olabilir. Orada muhtemelen Avrupa hukuku standartlarına uygun kiÅŸisel verilerin koruma seviyesi + bulunmayabilir ve Avrupa’daki veri koruma haklarınız uygulanmayabilir. Bu baÄŸlamda özellikle + üçüncü ülke güvenlik makamlarının iÅŸletim sistemi saÄŸlayıcısına aktarılan bu verilere eriÅŸme ve + örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları deÄŸerlendirmeye alma olasılığı + bulunmaktadır. Ancak bu, yalnızca aktarılan kimlik kodlarını etkiler. KarşılaÅŸma verileri gibi + Uygulamadaki diÄŸer veriler bu prosedüre dahil edilmez.</p> <p> Ancak Uygulama tarafından aktarılan veriler, sadece Almanya’daki sunucularda veya bir diÄŸer AB (veya Avrupa Ekonomik Alanı) ülkesindeki sunucularda iÅŸlenir, dolayısıyla Genel Veri Koruma Tüzüğünün (GVKT) katı gerekliliklerine tabi @@ -971,6 +1060,17 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g ve diÄŸer isteÄŸe baÄŸlı verileri günlük olarak RKI’ye aktarmayacaktır. Daha sonra veri bağışına yeniden izin vermek isterseniz, ilgili iÅŸlevi ayarlardan yeniden etkinleÅŸtirebilirsiniz. </p> +<h2> + e. “Hata raporları†rıza beyanı +</h2> +<p> + Daha önceden RKI’ye gönderdiÄŸiniz hata raporlarının analizi için olan rıza beyanınızı geri + çekebilirsiniz, bunun için teknik desteÄŸe hata raporu kimlik numaranızı belirterek, hata + raporunun artık analiz edilmesini istemediÄŸinizi bildirmeniz yeterlidir. Bunun üzerine hata + raporunuz silinir. RKI’nin bu prosedürde kimliÄŸinizi öğrenebileceÄŸini göz önünde bulundurun. + Hata raporu kimlik numaranızı teknik destek aktarmazsanız, gönderilen hata raporu 14 gün sonra + otomatik olarak silinecektir. +</p> <h2> e. “Anket†rıza beyanı </h2> @@ -1042,5 +1142,5 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g veya e-posta yoluyla: datenschutz@rki.de. </p> <p> - Baskı 01.03.2021 + Baskı 24.03.2021 </p> \ No newline at end of file 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 f797cf5b89cd738c7f47d106b854ef1f2ec08064..d57407ef4e48d7f63255544a925d6fdae4e261a8 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 @@ -21,7 +21,9 @@ import de.rki.coronawarnapp.exception.reporting.ErrorReportReceiver import de.rki.coronawarnapp.exception.reporting.ReportingConstants.ERROR_REPORT_LOCAL_BROADCAST_CHANNEL import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.risk.RiskLevelChangeDetector -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.storage.OnboardingSettings +import de.rki.coronawarnapp.storage.preferences.EncryptedPreferencesMigration import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.util.CWADebug @@ -29,6 +31,7 @@ import de.rki.coronawarnapp.util.WatchdogService import de.rki.coronawarnapp.util.device.ForegroundState import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent +import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -57,6 +60,9 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { @Inject lateinit var notificationHelper: NotificationHelper @Inject lateinit var deviceTimeHandler: DeviceTimeHandler @Inject lateinit var autoSubmission: AutoSubmission + @Inject lateinit var submissionSettings: SubmissionSettings + @Inject lateinit var onboardingSettings: OnboardingSettings + @Inject lateinit var encryptedPreferencesMigration: EncryptedPreferencesMigration @LogHistoryTree @Inject lateinit var rollingLogHistory: Timber.Tree @@ -70,6 +76,10 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { CWADebug.initAfterInjection(component) + encryptedPreferencesMigration.doMigration() + + BackgroundWorkScheduler.init(component) + Timber.plant(rollingLogHistory) Timber.v("onCreate(): WorkManager setup done: $workManager") @@ -87,8 +97,8 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { .onEach { isAppInForeground = it } .launchIn(GlobalScope) - if (LocalData.onboardingCompletedTimestamp() != null) { - if (!LocalData.isAllowedToSubmitDiagnosisKeys()) { + if (onboardingSettings.isOnboarded) { + if (!submissionSettings.isAllowedToSubmitKeys) { deadmanNotificationScheduler.schedulePeriodic() } contactDiaryWorkScheduler.schedulePeriodic() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt index 70cf264d396708e97412548538bd50aafa167d42..a090b59fadb6860df156d971cc1056565ec12163 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt @@ -3,9 +3,12 @@ package de.rki.coronawarnapp.bugreporting import dagger.Module import dagger.Provides import dagger.Reusable +import dagger.multibindings.IntoSet import de.rki.coronawarnapp.bugreporting.censors.BugCensor +import de.rki.coronawarnapp.bugreporting.censors.DiaryEncounterCensor import de.rki.coronawarnapp.bugreporting.censors.DiaryLocationCensor import de.rki.coronawarnapp.bugreporting.censors.DiaryPersonCensor +import de.rki.coronawarnapp.bugreporting.censors.DiaryVisitCensor import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor import de.rki.coronawarnapp.bugreporting.censors.RegistrationTokenCensor import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLoggerScope @@ -22,7 +25,6 @@ import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.protobuf.ProtoConverterFactory -import timber.log.Timber import javax.inject.Singleton @Module @@ -67,19 +69,27 @@ class BugReportingSharedModule { @Provides fun scope(): CoroutineScope = DebugLoggerScope - @Singleton @Provides - fun censors( - registrationTokenCensor: RegistrationTokenCensor, - diaryPersonCensor: DiaryPersonCensor, - diaryLocationCensor: DiaryLocationCensor, - qrCodeCensor: QRCodeCensor - ): List<BugCensor> = listOf( - registrationTokenCensor, - diaryPersonCensor, - diaryLocationCensor, - qrCodeCensor - ).also { - Timber.d("Loaded BugCensors: %s", it) - } + @IntoSet + fun registrationTokenCensor(censor: RegistrationTokenCensor): BugCensor = censor + + @Provides + @IntoSet + fun qrCodeCensor(censor: QRCodeCensor): BugCensor = censor + + @Provides + @IntoSet + fun diaryPersonCensor(censor: DiaryPersonCensor): BugCensor = censor + + @Provides + @IntoSet + fun diaryEncounterCensor(censor: DiaryEncounterCensor): BugCensor = censor + + @Provides + @IntoSet + fun diaryLocationCensor(censor: DiaryLocationCensor): BugCensor = censor + + @Provides + @IntoSet + fun diaryVisitCensor(censor: DiaryVisitCensor): BugCensor = censor } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt index 1540f5b4d283703db2e55574aec1fdcaf7942e69..8be45d08b70e1fcf0dec7a11becd81b00a5cafaa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt @@ -8,4 +8,38 @@ interface BugCensor { * If there is something to censor a new log line is returned, otherwise returns null */ suspend fun checkLog(entry: LogLine): LogLine? + + companion object { + fun withValidName(name: String?, action: (String) -> Unit): Boolean { + if (name.isNullOrBlank()) return false + if (name.length < 3) return false + action(name) + return true + } + + fun withValidEmail(email: String?, action: (String) -> Unit): Boolean { + if (email.isNullOrBlank()) return false + if (email.length < 6) return false + action(email) + return true + } + + fun withValidPhoneNumber(number: String?, action: (String) -> Unit): Boolean { + if (number.isNullOrBlank()) return false + if (number.length < 4) return false + action(number) + return true + } + + fun withValidComment(comment: String?, action: (String) -> Unit): Boolean { + if (comment.isNullOrBlank()) return false + if (comment.length < 3) return false + action(comment) + return true + } + + fun LogLine.toNewLogLineIfDifferent(newMessage: String): LogLine? { + return if (newMessage != message) copy(message = newMessage) else null + } + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensor.kt new file mode 100644 index 0000000000000000000000000000000000000000..b8d3934117675dc62e9e8d29f607b42fcb172f36 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensor.kt @@ -0,0 +1,47 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidComment +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +@Reusable +class DiaryEncounterCensor @Inject constructor( + @DebuggerScope debugScope: CoroutineScope, + diary: ContactDiaryRepository +) : BugCensor { + + private val encounters by lazy { + diary.personEncounters.stateIn( + scope = debugScope, + started = SharingStarted.Lazily, + initialValue = null + ).filterNotNull() + } + + override suspend fun checkLog(entry: LogLine): LogLine? { + val encountersNow = encounters.first().filter { !it.circumstances.isNullOrBlank() } + + if (encountersNow.isEmpty()) return null + + val newMessage = encountersNow.fold(entry.message) { orig, encounter -> + var wip = orig + + withValidComment(encounter.circumstances) { + wip = wip.replace(it, "Encounter#${encounter.id}/Circumstances") + } + + wip + } + + return entry.toNewLogLineIfDifferent(newMessage) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensor.kt index 61dc4cc8d637eb73fcb54388058daf3eab9ce3fe..24bf031cf63dff35797f0a0049b3fd133ce2ca8a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensor.kt @@ -1,10 +1,13 @@ package de.rki.coronawarnapp.bugreporting.censors import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidEmail +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidPhoneNumber import de.rki.coronawarnapp.bugreporting.debuglog.LogLine import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository -import de.rki.coronawarnapp.util.CWADebug import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filterNotNull @@ -31,14 +34,22 @@ class DiaryLocationCensor @Inject constructor( if (locationsNow.isEmpty()) return null - var newMessage = locationsNow.fold(entry.message) { oldMsg, location -> - oldMsg.replace(location.locationName, "Location#${location.locationId}") - } + val newMessage = locationsNow.fold(entry.message) { orig, location -> + var wip = orig + + withValidName(location.locationName) { + wip = wip.replace(it, "Location#${location.locationId}/Name") + } + withValidEmail(location.emailAddress) { + wip = wip.replace(it, "Location#${location.locationId}/EMail") + } + withValidPhoneNumber(location.phoneNumber) { + wip = wip.replace(it, "Location#${location.locationId}/PhoneNumber") + } - if (CWADebug.isDeviceForTestersBuild) { - newMessage = entry.message + wip } - return entry.copy(message = newMessage) + return entry.toNewLogLineIfDifferent(newMessage) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensor.kt index 591c8db2cfc412e096e96393ac74ddc6e6669650..42341a8b5fd687da53a5e63ed7e93ab99477f28c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensor.kt @@ -1,10 +1,13 @@ package de.rki.coronawarnapp.bugreporting.censors import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidEmail +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidPhoneNumber import de.rki.coronawarnapp.bugreporting.debuglog.LogLine import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository -import de.rki.coronawarnapp.util.CWADebug import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filterNotNull @@ -31,14 +34,22 @@ class DiaryPersonCensor @Inject constructor( if (personsNow.isEmpty()) return null - var newMessage = personsNow.fold(entry.message) { oldMsg, person -> - oldMsg.replace(person.fullName, "Person#${person.personId}") - } + val newMessage = personsNow.fold(entry.message) { orig, person -> + var wip = orig + + withValidName(person.fullName) { + wip = wip.replace(it, "Person#${person.personId}/Name") + } + withValidEmail(person.emailAddress) { + wip = wip.replace(it, "Person#${person.personId}/EMail") + } + withValidPhoneNumber(person.phoneNumber) { + wip = wip.replace(it, "Person#${person.personId}/PhoneNumber") + } - if (CWADebug.isDeviceForTestersBuild) { - newMessage = entry.message + wip } - return entry.copy(message = newMessage) + return entry.toNewLogLineIfDifferent(newMessage) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensor.kt new file mode 100644 index 0000000000000000000000000000000000000000..ff0c63cf1100805d6a36e6ce74efff917445e4cc --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensor.kt @@ -0,0 +1,46 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +@Reusable +class DiaryVisitCensor @Inject constructor( + @DebuggerScope debugScope: CoroutineScope, + diary: ContactDiaryRepository +) : BugCensor { + + private val visits by lazy { + diary.locationVisits.stateIn( + scope = debugScope, + started = SharingStarted.Lazily, + initialValue = null + ).filterNotNull() + } + + override suspend fun checkLog(entry: LogLine): LogLine? { + val visitsNow = visits.first().filter { !it.circumstances.isNullOrBlank() } + + if (visitsNow.isEmpty()) return null + + val newMessage = visitsNow.fold(entry.message) { orig, visit -> + var wip = orig + + BugCensor.withValidComment(visit.circumstances) { + wip = wip.replace(it, "Visit#${visit.id}/Circumstances") + } + + wip + } + + return entry.toNewLogLineIfDifferent(newMessage) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensor.kt index 208ac88117faf57f908ffa012132c8111bbaab80..b85888d1d02ba6fb39e379e7740c1fb68aefe39a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensor.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.bugreporting.censors import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent import de.rki.coronawarnapp.bugreporting.debuglog.LogLine import de.rki.coronawarnapp.util.CWADebug import javax.inject.Inject @@ -13,17 +14,18 @@ class QRCodeCensor @Inject constructor() : BugCensor { val guid = lastGUID ?: return null if (!entry.message.contains(guid)) return null - var newMessage = entry.message.replace(guid, PLACEHOLDER + guid.takeLast(4)) - - if (CWADebug.isDeviceForTestersBuild) { - newMessage = entry.message + val newMessage = if (CWADebug.isDeviceForTestersBuild) { + entry.message.replace(guid, PLACEHOLDER_TESTER + guid.takeLast(27)) + } else { + entry.message.replace(guid, PLACEHOLDER + guid.takeLast(4)) } - return entry.copy(message = newMessage) + return entry.toNewLogLineIfDifferent(newMessage) } companion object { var lastGUID: String? = null + private const val PLACEHOLDER_TESTER = "########-" private const val PLACEHOLDER = "########-####-####-####-########" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt index 590df2a5aebfe0b81ac7292a61f37cae74bab5ea..81f2bece36227c378fb5c212b3a27171c2598a64 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt @@ -1,27 +1,31 @@ package de.rki.coronawarnapp.bugreporting.censors import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent import de.rki.coronawarnapp.bugreporting.debuglog.LogLine -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.CWADebug import javax.inject.Inject @Reusable -class RegistrationTokenCensor @Inject constructor() : BugCensor { +class RegistrationTokenCensor @Inject constructor( + private val submissionSettings: SubmissionSettings +) : BugCensor { override suspend fun checkLog(entry: LogLine): LogLine? { - val token = LocalData.registrationToken() ?: return null + val token = submissionSettings.registrationToken.value ?: return null if (!entry.message.contains(token)) return null - var newMessage = entry.message.replace(token, PLACEHOLDER + token.takeLast(4)) - - if (CWADebug.isDeviceForTestersBuild) { - newMessage = entry.message + val newMessage = if (CWADebug.isDeviceForTestersBuild) { + entry.message.replace(token, PLACEHOLDER_TESTER + token.takeLast(27)) + } else { + entry.message.replace(token, PLACEHOLDER + token.takeLast(4)) } - return entry.copy(message = newMessage) + return entry.toNewLogLineIfDifferent(newMessage) } companion object { + private const val PLACEHOLDER_TESTER = "########-" private const val PLACEHOLDER = "########-####-####-####-########" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt index 57f7e38a498fc264bd3d577753690e6c9bc0ea09..698f5b68c658e6a27e39089550f8c42b1997ab8b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt @@ -90,6 +90,7 @@ class DebugLogger( Timber.tag(TAG).i("setInjectionIsReady()") component.inject(this) isDaggerReady = true + Timber.tag(TAG).d("Censors loaded: %s", bugCensors) } suspend fun start(): Unit = mutex.withLock { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerBase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerBase.kt index ee056fbd562c4e613e63aac0f8fd60173f38249b..0a152bfd5f48e453bc79bf6474bc3d119da47db4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerBase.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerBase.kt @@ -8,5 +8,5 @@ import javax.inject.Inject */ @Suppress("UnnecessaryAbstractClass") abstract class DebugLoggerBase { - @Inject internal lateinit var bugCensors: dagger.Lazy<List<BugCensor>> + @Inject internal lateinit var bugCensors: dagger.Lazy<Set<BugCensor>> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt index 41f63891d30d37f5c88b944ffb680da7b35a4bef..0adf631f1500b23fb6dd6510eead37f5d0ff9544 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogFragment.kt @@ -8,6 +8,7 @@ import android.text.format.Formatter import android.view.View import android.widget.Toast import androidx.core.view.isGone +import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import de.rki.coronawarnapp.R @@ -23,6 +24,8 @@ import de.rki.coronawarnapp.util.ui.setGone import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import org.joda.time.Duration +import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject @@ -101,8 +104,8 @@ class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), Auto vm.events.observe2(this) { when (it) { - DebugLogViewModel.Event.ShowLogDeletedConfirmation -> { - showLogDeletionConfirmation() + DebugLogViewModel.Event.ShowLogDeletionRequest -> { + showLogDeletionRequest() } DebugLogViewModel.Event.NavigateToPrivacyFragment -> { doNavigate( @@ -138,7 +141,25 @@ class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), Auto } vm.logUploads.observe2(this@DebugLogFragment) { - binding.debugLogHistoryContainer.setGone(it.logs.isEmpty()) + val lastLog = it.logs.lastOrNull()?.uploadedAt + + binding.debugLogHistoryContainer.setGone(lastLog == null) + + val now = Instant.now() + + if (lastLog != null && Duration(lastLog, now).standardSeconds < 3) { + binding.scrollview.fullScroll(NestedScrollView.FOCUS_DOWN) + + binding.debugLogHistoryContainer.apply { + postOnAnimationDelayed( + { + isPressed = true + postOnAnimationDelayed({ isPressed = false }, 250) + }, + 250 + ) + } + } } binding.debugLogHistoryContainer.setOnClickListener { vm.onIdHistoryPress() } } @@ -151,10 +172,14 @@ class DebugLogFragment : Fragment(R.layout.bugreporting_debuglog_fragment), Auto ) } - private fun showLogDeletionConfirmation() { + private fun showLogDeletionRequest() { MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.debugging_debuglog_stop_confirmation_title) setMessage(R.string.debugging_debuglog_stop_confirmation_message) - setPositiveButton(android.R.string.yes) { _, _ -> } + setPositiveButton(R.string.debugging_debuglog_stop_confirmation_confirmation_button) { _, _ -> + vm.stopAndDeleteDebugLog() + } + setNegativeButton(R.string.debugging_debuglog_stop_confirmation_discard_button) { _, _ -> /* dismiss */ } }.show() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt index ec5b7e32885e1c8442c4ea6db27388008d247144..d9793e597bc6606b636e4d87e4eed7be0b0578fe 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt @@ -65,8 +65,7 @@ class DebugLogViewModel @AssistedInject constructor( fun onToggleRecording() = launchWithProgress { if (debugLogger.isLogging.value) { - debugLogger.stop() - events.postValue(Event.ShowLogDeletedConfirmation) + events.postValue(Event.ShowLogDeletionRequest) } else { if (debugLogger.storageCheck.isLowStorage(forceCheck = true)) { Timber.d("Low storage, not starting logger.") @@ -86,6 +85,14 @@ class DebugLogViewModel @AssistedInject constructor( } } + fun stopAndDeleteDebugLog() { + launchWithProgress { + if (debugLogger.isLogging.value) { + debugLogger.stop() + } + } + } + fun onStoreLog() = launchWithProgress(finishProgressAction = false) { Timber.d("storeLog()") val snapshot = logSnapshotter.snapshot() @@ -147,7 +154,7 @@ class DebugLogViewModel @AssistedInject constructor( object NavigateToPrivacyFragment : Event() object NavigateToUploadFragment : Event() object NavigateToUploadHistory : Event() - object ShowLogDeletedConfirmation : Event() + object ShowLogDeletionRequest : Event() object ShowLowStorageDialog : Event() data class ShowLocalExportError(val error: Throwable) : Event() data class Error(val error: Throwable) : Event() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/DebugLogUploadFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/DebugLogUploadFragment.kt index 6aac39aad2378643f4bd87797f38c2fb34e66bb8..af206680ac14c808d620a1227eca3d8d2b5090d9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/DebugLogUploadFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/DebugLogUploadFragment.kt @@ -34,7 +34,7 @@ class DebugLogUploadFragment : Fragment(R.layout.bugreporting_debuglog_upload_fr vm.onUploadLog() } - debugLogSharePrivacyInformation.setOnClickListener { + debugLogPrivacyInformation.setOnClickListener { vm.onPrivacyButtonPress() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiV1.kt index ef9ea5988795eccfa701d6bd790727d7c3ab69f7..8ae2ecc70a4d196c0179b1a82fe3d65dba89e32b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiV1.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiV1.kt @@ -15,7 +15,7 @@ interface LogUploadAuthApiV1 { @SerializedName("errorCode") val errorCode: String? ) - @POST("version/v1/android/log") + @POST("version/v1/android/els") suspend fun authOTP( @Body requestBody: ElsOtpRequestAndroid.ELSOneTimePasswordRequestAndroid ): AuthResponse diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt index 8a0a77ce39de5095615995934125dbd28da67e2c..121f7f19ff5c474b695211afb0405691d5f91f33 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.server.protocols.internal.ppdd.ElsOtp import de.rki.coronawarnapp.server.protocols.internal.ppdd.ElsOtpRequestAndroid +import de.rki.coronawarnapp.util.CWADebug import kotlinx.coroutines.flow.first import org.joda.time.Instant import timber.log.Timber @@ -26,6 +27,11 @@ class LogUploadAuthorizer @Inject constructor( suspend fun getAuthorizedOTP(otp: UUID = UUID.randomUUID()): LogUploadOtp { Timber.tag(TAG).d("getAuthorizedOTP() trying to authorize %s", otp) + // TODO ¯\_(ツ)_/¯ + if (!CWADebug.isDeviceForTestersBuild) { + throw UnsupportedOperationException() + } + val elsOtp = ElsOtp.ELSOneTimePassword.newBuilder().apply { setOtp(otp.toString()) }.build() @@ -52,7 +58,13 @@ class LogUploadAuthorizer @Inject constructor( Timber.tag(TAG).v("Auth response received: %s", it) } - return LogUploadOtp(otp = otp.toString(), expirationDate = Instant.parse(authResponse.expirationDate)).also { + val expirationDate = if (authResponse.expirationDate.isNotEmpty()) { + Instant.parse(authResponse.expirationDate) + } else { + Instant.EPOCH + } + + return LogUploadOtp(otp = otp.toString(), expirationDate = expirationDate).also { Timber.tag(TAG).d("%s created", it) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt index 97949918f02c3590afca4dd76ab5fb8b3ae11f88..4c273c3bf87907bf9a2779923cade024be725462 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt @@ -13,7 +13,7 @@ import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaDataRequestAndroid -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.Flow @@ -21,7 +21,6 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeout import org.joda.time.Hours -import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -36,7 +35,8 @@ class Analytics @Inject constructor( private val donorModules: Set<@JvmSuppressWildcards DonorModule>, private val settings: AnalyticsSettings, private val logger: LastAnalyticsSubmissionLogger, - private val timeStamper: TimeStamper + private val timeStamper: TimeStamper, + private val onboardingSettings: OnboardingSettings ) { private val submissionLockoutMutex = Mutex() @@ -173,7 +173,7 @@ class Analytics @Inject constructor( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun stopDueToTimeSinceOnboarding(): Boolean { - val onboarding = LocalData.onboardingCompletedTimestamp()?.let { Instant.ofEpochMilli(it) } ?: return true + val onboarding = onboardingSettings.onboardingCompletedTimestamp ?: return true return onboarding.plus(Hours.hours(ONBOARDING_DELAY_HOURS).toStandardDuration()).isAfter(timeStamper.nowUTC) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt index 4a2053feca5bf156a479f8fb6085c4f70e80faf2..34f8be0bb2a6df2029de72f0de6a2888206da592 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonor.kt @@ -6,7 +6,7 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettin import de.rki.coronawarnapp.risk.RiskLevelSettings import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.formatter.TestResult import kotlinx.coroutines.flow.first @@ -22,6 +22,7 @@ class TestResultDonor @Inject constructor( private val riskLevelSettings: RiskLevelSettings, private val riskLevelStorage: RiskLevelStorage, private val timeStamper: TimeStamper, + private val submissionSettings: SubmissionSettings ) : DonorModule { override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution { @@ -31,7 +32,7 @@ class TestResultDonor @Inject constructor( return TestResultMetadataNoContribution } - val timestampAtRegistration = LocalData.initialTestResultReceivedTimestamp() + val timestampAtRegistration = submissionSettings.initialTestResultReceivedAt if (timestampAtRegistration == null) { Timber.d("Skipping TestResultMetadata donation timestampAtRegistration isn't found") @@ -43,8 +44,7 @@ class TestResultDonor @Inject constructor( .analytics .hoursSinceTestRegistrationToSubmitTestResultMetadata - val registrationTime = Instant.ofEpochMilli(timestampAtRegistration) - val hoursSinceTestRegistrationTime = Duration(registrationTime, timeStamper.nowUTC).standardHours.toInt() + val hoursSinceTestRegistrationTime = Duration(timestampAtRegistration, timeStamper.nowUTC).standardHours.toInt() val isDiffHoursMoreThanConfigHoursForPendingTest = hoursSinceTestRegistrationTime >= configHours val testResultAtRegistration = @@ -53,7 +53,7 @@ class TestResultDonor @Inject constructor( val daysSinceMostRecentDateAtRiskLevelAtTestRegistration = calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( riskLevelSettings.lastChangeCheckedRiskLevelTimestamp, - registrationTime + timestampAtRegistration ) val riskLevelAtRegistration = testResultDonorSettings.riskLevelAtTestRegistration.value @@ -62,7 +62,7 @@ class TestResultDonor @Inject constructor( if (riskLevelAtRegistration == PpaData.PPARiskLevel.RISK_LEVEL_LOW) { DEFAULT_HOURS_SINCE_HIGH_RISK_WARNING } else { - calculatedHoursSinceHighRiskWarning(registrationTime) + calculatedHoursSinceHighRiskWarning(timestampAtRegistration) } return when { @@ -87,7 +87,7 @@ class TestResultDonor @Inject constructor( */ testResultAtRegistration.isFinal -> finalTestMetadataDonation( - registrationTime, + timestampAtRegistration, testResultAtRegistration, daysSinceMostRecentDateAtRiskLevelAtTestRegistration, hoursSinceHighRiskWarningAtTestRegistration diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt index b545c807ec7d29864850a5c770e6ffd08100da77..66ecae8a8c6a1d467ce314b3c71db8ab90ef3893 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt @@ -8,7 +8,7 @@ import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.risk.RollbackItem -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException import de.rki.coronawarnapp.task.TaskFactory @@ -33,7 +33,8 @@ class DownloadDiagnosisKeysTask @Inject constructor( private val appConfigProvider: AppConfigProvider, private val keyPackageSyncTool: KeyPackageSyncTool, private val timeStamper: TimeStamper, - private val settings: DownloadDiagnosisKeysSettings + private val settings: DownloadDiagnosisKeysSettings, + private val submissionSettings: SubmissionSettings ) : Task<DownloadDiagnosisKeysTask.Progress, Task.Result> { private val internalProgress = ConflatedBroadcastChannel<Progress>() @@ -113,7 +114,7 @@ class DownloadDiagnosisKeysTask @Inject constructor( // remember version code of this execution for next time settings.updateLastVersionCodeToCurrent() - if (LocalData.isAllowedToSubmitDiagnosisKeys()) { + if (submissionSettings.isAllowedToSubmitKeys) { Timber.tag(TAG).i("task aborted, positive test result") return object : Task.Result {} } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt index 8dbdda01a81613628f5c0ad3173d64397918f5d0..b145c1dca4989fa25452abc6189337080263a5f3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt @@ -6,6 +6,12 @@ import de.rki.coronawarnapp.BuildConfig @Suppress("MayBeConstant") object BuildConfigWrap { + val DEBUG: Boolean = BuildConfig.DEBUG + val FLAVOR: String = BuildConfig.FLAVOR + val BUILD_TYPE: String = BuildConfig.BUILD_TYPE + + val GIT_COMMIT_SHORT_HASH: String = BuildConfig.GIT_COMMIT_SHORT_HASH + val ENVIRONMENT_JSONDATA = BuildConfig.ENVIRONMENT_JSONDATA val ENVIRONMENT_TYPE_DEFAULT = BuildConfig.ENVIRONMENT_TYPE_DEFAULT diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/installTime/InstallTimeProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/installTime/InstallTimeProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..9aa76387459176aa9471fe55a2435637f28f48e4 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/installTime/InstallTimeProvider.kt @@ -0,0 +1,19 @@ +package de.rki.coronawarnapp.installTime + +import android.content.Context +import de.rki.coronawarnapp.util.TimeAndDateExtensions.roundUpMsToDays +import de.rki.coronawarnapp.util.di.AppContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class InstallTimeProvider @Inject constructor( + @AppContext private val context: Context +) { + private val installTime: Long = context + .packageManager + .getPackageInfo(context.packageName, 0) + .firstInstallTime + + val daysSinceInstallation: Long get() = (System.currentTimeMillis() - installTime).roundUpMsToDays() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt index b571c7f7f8b45e1fc65f5649fd00afa618bf62b1..e335d18c8d841cfbacf6813521731ef5a3908e3f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt @@ -12,8 +12,6 @@ import javax.inject.Inject /** * For general app related values, * e.g. "Has dialog been shown", as "OnBoarding been shown?" - * In future refactoring it should contain all values - * from **[de.rki.coronawarnapp.storage.LocalData]** that don't fit more specific classes. */ class CWASettings @Inject constructor( @AppContext val context: Context @@ -27,6 +25,10 @@ class CWASettings @Inject constructor( get() = prefs.getBoolean(PKEY_DEVICE_TIME_INCORRECT_ACK, false) set(value) = prefs.edit { putBoolean(PKEY_DEVICE_TIME_INCORRECT_ACK, value) } + var wasInteroperabilityShownAtLeastOnce: Boolean + get() = prefs.getBoolean(PKEY_INTEROPERABILITY_SHOWED_AT_LEAST_ONCE, false) + set(value) = prefs.edit { putBoolean(PKEY_INTEROPERABILITY_SHOWED_AT_LEAST_ONCE, value) } + var firstReliableDeviceTime: Instant get() = Instant.ofEpochMilli(prefs.getLong(PKEY_DEVICE_TIME_FIRST_RELIABLE, 0L)) set(value) = prefs.edit { putLong(PKEY_DEVICE_TIME_FIRST_RELIABLE, value.millis) } @@ -42,6 +44,20 @@ class CWASettings @Inject constructor( ).let { raw -> ConfigData.DeviceTimeState.values().single { it.key == raw } } set(value) = prefs.edit { putString(PKEY_DEVICE_TIME_LAST_STATE_CHANGE_STATE, value.key) } + var numberOfRemainingSharePositiveTestResultReminders: Int + get() = prefs.getInt(PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT, Int.MIN_VALUE) + set(value) = prefs.edit { putInt(PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT, value) } + + val isNotificationsRiskEnabled = prefs.createFlowPreference( + key = PKEY_NOTIFICATIONS_RISK_ENABLED, + defaultValue = false + ) + + val isNotificationsTestEnabled = prefs.createFlowPreference( + key = PKEY_NOTIFICATIONS_TEST_ENABLED, + defaultValue = false + ) + val lastChangelogVersion = prefs.createFlowPreference( key = LAST_CHANGELOG_VERSION, defaultValue = DEFAULT_APP_VERSION @@ -53,9 +69,13 @@ class CWASettings @Inject constructor( companion object { private const val PKEY_DEVICE_TIME_INCORRECT_ACK = "devicetime.incorrect.acknowledged" + private const val PKEY_INTEROPERABILITY_SHOWED_AT_LEAST_ONCE = "interoperability.showed" private const val PKEY_DEVICE_TIME_FIRST_RELIABLE = "devicetime.correct.first" private const val PKEY_DEVICE_TIME_LAST_STATE_CHANGE_TIME = "devicetime.laststatechange.timestamp" private const val PKEY_DEVICE_TIME_LAST_STATE_CHANGE_STATE = "devicetime.laststatechange.state" + private const val PKEY_NOTIFICATIONS_RISK_ENABLED = "notifications.risk.enabled" + private const val PKEY_NOTIFICATIONS_TEST_ENABLED = "notifications.test.enabled" + private const val PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT = "testresults.count" private const val LAST_CHANGELOG_VERSION = "update.changelog.lastversion" private const val DEFAULT_APP_VERSION = 1L } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt index c726fe9f59792f8052772dc24dbf66c57a6bdeab..1c3ad7c8c64bb948aa51c69a4c3123f1922673a4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt @@ -6,7 +6,7 @@ import androidx.annotation.VisibleForTesting import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.util.coroutine.AppScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first @@ -16,6 +16,7 @@ import timber.log.Timber class TracingPermissionHelper @AssistedInject constructor( @Assisted private val callback: Callback, private val enfClient: ENFClient, + private val tracingSettings: TracingSettings, @AppScope private val scope: CoroutineScope ) { @@ -73,11 +74,7 @@ class TracingPermissionHelper @AssistedInject constructor( return true } - private fun isConsentGiven(): Boolean { - val firstTracingActivationAt = LocalData.initialTracingActivationTimestamp() - Timber.tag(TAG).v("isConsentGiven(): First tracing activationat: %d", firstTracingActivationAt) - return firstTracingActivationAt != null - } + private fun isConsentGiven(): Boolean = tracingSettings.isConsentGiven interface Callback { fun onUpdateTracingStatus(isTracingEnabled: Boolean) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatus.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatus.kt index 208465596fcc7d74c6eaf67970ed7232ac0c3197..e8c1e9f7d7ebfb442c6aa05bb584990db24ac6f6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatus.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatus.kt @@ -6,7 +6,7 @@ import com.google.android.gms.nearby.exposurenotification.ExposureNotificationCl import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.flow.shareLatest import kotlinx.coroutines.CancellationException @@ -30,6 +30,7 @@ import kotlin.coroutines.suspendCoroutine @Singleton class DefaultTracingStatus @Inject constructor( private val client: ExposureNotificationClient, + private val tracingSettings: TracingSettings, @AppScope val scope: CoroutineScope ) : TracingStatus { @@ -72,14 +73,15 @@ class DefaultTracingStatus @Inject constructor( client.start() .addOnSuccessListener { cont.resume(it) } .addOnFailureListener { cont.resumeWithException(it) } + .also { + tracingSettings.isConsentGiven = true + } } private suspend fun asyncStop() = suspendCoroutine<Void> { cont -> client.stop() .addOnSuccessListener { cont.resume(it) } .addOnFailureListener { cont.resumeWithException(it) } - }.also { - LocalData.lastNonActiveTracingTimestamp(System.currentTimeMillis()) } override val isTracingEnabled: Flow<Boolean> = flow { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ShareTestResultNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ShareTestResultNotificationService.kt index f18af548a82fda119acb5f6380691e98e73c6d72..ad5dac0ac493edcfc99e8d4ee4edf84f08ddad6b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ShareTestResultNotificationService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ShareTestResultNotificationService.kt @@ -3,11 +3,11 @@ package de.rki.coronawarnapp.notification import android.content.Context import androidx.navigation.NavDeepLinkBuilder import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_ID import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_INITIAL_OFFSET import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_INTERVAL import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppContext @@ -17,13 +17,14 @@ import javax.inject.Inject class ShareTestResultNotificationService @Inject constructor( @AppContext private val context: Context, private val timeStamper: TimeStamper, - private val notificationHelper: NotificationHelper + private val notificationHelper: NotificationHelper, + private val cwaSettings: CWASettings ) { fun scheduleSharePositiveTestResultReminder() { - if (LocalData.numberOfRemainingSharePositiveTestResultReminders < 0) { + if (cwaSettings.numberOfRemainingSharePositiveTestResultReminders < 0) { Timber.v("Schedule positive test result notification") - LocalData.numberOfRemainingSharePositiveTestResultReminders = POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT + cwaSettings.numberOfRemainingSharePositiveTestResultReminders = POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT notificationHelper.scheduleRepeatingNotification( timeStamper.nowUTC.plus(POSITIVE_RESULT_NOTIFICATION_INITIAL_OFFSET), POSITIVE_RESULT_NOTIFICATION_INTERVAL, @@ -35,8 +36,8 @@ class ShareTestResultNotificationService @Inject constructor( } fun showSharePositiveTestResultNotification(notificationId: Int) { - if (LocalData.numberOfRemainingSharePositiveTestResultReminders > 0) { - LocalData.numberOfRemainingSharePositiveTestResultReminders -= 1 + if (cwaSettings.numberOfRemainingSharePositiveTestResultReminders > 0) { + cwaSettings.numberOfRemainingSharePositiveTestResultReminders -= 1 val pendingIntent = NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_graph) .setComponentName(MainActivity::class.java) @@ -61,7 +62,7 @@ class ShareTestResultNotificationService @Inject constructor( fun resetSharePositiveTestResultNotification() { cancelSharePositiveTestResultNotification() - LocalData.numberOfRemainingSharePositiveTestResultReminders = Int.MIN_VALUE + cwaSettings.numberOfRemainingSharePositiveTestResultReminders = Int.MIN_VALUE Timber.v("Positive test result notification counter has been reset") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt index 9a9c3412fce65d9c1f45ae41135afd5335e5a436..9b5995b38cb1e31beb0f151aec5de801d971c95f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.notification import android.content.Context import androidx.navigation.NavDeepLinkBuilder import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.util.device.ForegroundState import de.rki.coronawarnapp.util.di.AppContext @@ -17,13 +17,14 @@ class TestResultAvailableNotificationService @Inject constructor( @AppContext private val context: Context, private val foregroundState: ForegroundState, private val navDeepLinkBuilderProvider: Provider<NavDeepLinkBuilder>, - private val notificationHelper: NotificationHelper + private val notificationHelper: NotificationHelper, + private val cwaSettings: CWASettings ) { suspend fun showTestResultAvailableNotification(testResult: TestResult) { if (foregroundState.isInForeground.first()) return - if (!LocalData.isNotificationsTestEnabled) { + if (!cwaSettings.isNotificationsTestEnabled.value) { Timber.i("Don't show test result available notification because user doesn't want to be informed") return } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt index 608c329c993ddcb7a6a263aa2df4f8a81b0814ed..cce89d6506f7ccdb7fb2bc872f11f5a76aa5991e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/BackgroundNoise.kt @@ -1,35 +1,24 @@ package de.rki.coronawarnapp.playbook -import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.util.di.AppInjector +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.worker.BackgroundConstants import de.rki.coronawarnapp.worker.BackgroundWorkScheduler +import javax.inject.Inject +import javax.inject.Singleton import kotlin.random.Random -class BackgroundNoise { - companion object { - @Volatile - private var instance: BackgroundNoise? = null - - fun getInstance(): BackgroundNoise { - return instance ?: synchronized(this) { - instance ?: BackgroundNoise().also { - instance = it - } - } - } - } - +@Singleton +class BackgroundNoise @Inject constructor( + private val submissionSettings: SubmissionSettings, private val playbook: Playbook - get() = AppInjector.component.playbook - +) { fun scheduleDummyPattern() { if (BackgroundConstants.NUMBER_OF_DAYS_TO_RUN_PLAYBOOK > 0) BackgroundWorkScheduler.scheduleBackgroundNoisePeriodicWork() } suspend fun foregroundScheduleCheck() { - if (LocalData.isAllowedToSubmitDiagnosisKeys()) { + if (submissionSettings.isAllowedToSubmitKeys) { val chance = Random.nextFloat() * 100 if (chance < DefaultPlaybook.PROBABILITY_TO_EXECUTE_PLAYBOOK_ON_APP_OPEN) { playbook.dummy() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt index a746ab02146edcda3bf436f4f485e7cd93b318ed..8a6fe31799602ee50ff2aabe8522054b497c6d00 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt @@ -8,7 +8,8 @@ import de.rki.coronawarnapp.datadonation.survey.Surveys import de.rki.coronawarnapp.notification.NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.device.ForegroundState import de.rki.coronawarnapp.util.di.AppContext @@ -22,6 +23,7 @@ import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject +@Suppress("LongParameterList") class RiskLevelChangeDetector @Inject constructor( @AppContext private val context: Context, @AppScope private val appScope: CoroutineScope, @@ -30,7 +32,9 @@ class RiskLevelChangeDetector @Inject constructor( private val notificationManagerCompat: NotificationManagerCompat, private val foregroundState: ForegroundState, private val notificationHelper: NotificationHelper, - private val surveys: Surveys + private val surveys: Surveys, + private val submissionSettings: SubmissionSettings, + private val tracingSettings: TracingSettings ) { fun launch() { @@ -64,7 +68,7 @@ class RiskLevelChangeDetector @Inject constructor( Timber.d("Last state was $oldRiskState and current state is $newRiskState") - if (hasHighLowLevelChanged(oldRiskState, newRiskState) && !LocalData.submissionWasSuccessful()) { + if (hasHighLowLevelChanged(oldRiskState, newRiskState) && !submissionSettings.isSubmissionSuccessful) { Timber.d("Notification Permission = ${notificationManagerCompat.areNotificationsEnabled()}") if (!foregroundState.isInForeground.first()) { @@ -80,7 +84,7 @@ class RiskLevelChangeDetector @Inject constructor( } if (oldRiskState == RiskState.INCREASED_RISK && newRiskState == RiskState.LOW_RISK) { - LocalData.isUserToBeNotifiedOfLoweredRiskLevel = true + tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel.update { true } Timber.d("Risk level changed LocalData is updated. Current Risk level is $newRiskState") surveys.resetSurvey(Surveys.Type.HIGH_RISK_ENCOUNTER) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt index 058d658c79fd84cf764a8176046b2baf158f8a8b..0bdfeb13edd4bfc6c51bd722fbc0ebd21d0cdaf5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt @@ -15,7 +15,7 @@ import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetec import de.rki.coronawarnapp.risk.RiskLevelResult.FailureReason import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException import de.rki.coronawarnapp.task.TaskFactory @@ -45,6 +45,7 @@ class RiskLevelTask @Inject constructor( private val appConfigProvider: AppConfigProvider, private val riskLevelStorage: RiskLevelStorage, private val keyCacheRepository: KeyCacheRepository, + private val submissionSettings: SubmissionSettings, private val analyticsExposureWindowCollector: AnalyticsExposureWindowCollector ) : Task<DefaultProgress, RiskLevelTaskResult> { @@ -82,7 +83,7 @@ class RiskLevelTask @Inject constructor( Timber.d("The current time is %s", it) } - if (LocalData.isAllowedToSubmitDiagnosisKeys()) { + if (submissionSettings.isAllowedToSubmitKeys) { Timber.i("Positive test result, skip risk calculation") return RiskLevelTaskResult( calculatedAt = nowUTC, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/EncryptedPreferences.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/EncryptedPreferences.kt deleted file mode 100644 index 63733ba49fa6471e67ab037f2835dbcf677a8381..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/EncryptedPreferences.kt +++ /dev/null @@ -1,8 +0,0 @@ -package de.rki.coronawarnapp.storage - -import javax.inject.Qualifier - -@Qualifier -@MustBeDocumented -@Retention(AnnotationRetention.RUNTIME) -annotation class EncryptedPreferences 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 deleted file mode 100644 index a5cb1011cb3155c2f2275c19f7a7b6addd8253e4..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt +++ /dev/null @@ -1,448 +0,0 @@ -package de.rki.coronawarnapp.storage - -import android.content.SharedPreferences -import androidx.core.content.edit -import de.rki.coronawarnapp.CoronaWarnApplication -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.util.security.SecurityHelper.globalEncryptedSharedPreferencesInstance -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import timber.log.Timber - -/** - * LocalData is responsible for all access to the shared preferences. Each preference is accessible - * by a string which is stored in strings.xml. - * - * @see SharedPreferences - */ -object LocalData { - - private const val PREFERENCE_INTEROPERABILITY_IS_USED_AT_LEAST_ONCE = - "preference_interoperability_is_used_at_least_once" - - private const val PREFERENCE_HAS_RISK_STATUS_LOWERED = - "preference_has_risk_status_lowered" - - /**************************************************** - * ONBOARDING DATA - ****************************************************/ - - /** - * Gets the boolean if the user has completed the onboarding - * from the EncryptedSharedPrefs - * - * @return boolean if user is onboarded - */ - fun isOnboarded(): Boolean = getSharedPreferenceInstance().getBoolean( - CoronaWarnApplication.getAppContext().getString(R.string.preference_onboarding_completed), - false - ) - - /** - * Sets the boolean if the user has completed the onboarding - * from the EncryptedSharedPrefs - * - * @param value boolean if onboarding was completed - */ - fun isOnboarded(value: Boolean) = getSharedPreferenceInstance().edit(true) { - putBoolean( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_onboarding_completed), - value - ) - } - - /** - * Gets the time when the user has completed the onboarding - * from the EncryptedSharedPrefs - * - * @return - */ - fun onboardingCompletedTimestamp(): Long? { - val timestamp = getSharedPreferenceInstance().getLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_onboarding_completed_timestamp), - 0L - ) - - if (timestamp == 0L) return null - return timestamp - } - - /** - * Sets the time when the user has completed the onboarding - * from the EncryptedSharedPrefs - * @param value - */ - fun onboardingCompletedTimestamp(value: Long) = getSharedPreferenceInstance().edit(true) { - putLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_onboarding_completed_timestamp), - value - ) - } - - /** - * Gets the boolean if the user has received the background warning - * from the EncryptedSharedPrefs - * - * @return boolean if background warning was shown - */ - fun isBackgroundCheckDone(): Boolean = getSharedPreferenceInstance().getBoolean( - CoronaWarnApplication.getAppContext().getString(R.string.preference_background_check_done), - false - ) - - /** - * Sets the boolean if the user has received the background warning - * from the EncryptedSharedPrefs - * - * @param value boolean if background warning was shown - */ - fun isBackgroundCheckDone(value: Boolean) = getSharedPreferenceInstance().edit(true) { - putBoolean( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_background_check_done), - value - ) - } - /**************************************************** - * TRACING DATA - ****************************************************/ - - /** - * Gets the initial timestamp when tracing was activated for the first time in ms - * - * @return timestamp in ms - */ - fun initialTracingActivationTimestamp(): Long? { - val timestamp = getSharedPreferenceInstance().getLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_initial_tracing_activation_time), - 0L - ) - - if (timestamp == 0L) return null - return timestamp - } - - /** - * Sets the initial timestamp when tracing was activated for the first time in ms - * - * @param value timestamp in ms - */ - fun initialTracingActivationTimestamp(value: Long) = - getSharedPreferenceInstance().edit(true) { - putLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_initial_tracing_activation_time), - value - ) - } - - /** - * Gets the timestamp when the user stopped Exposure Notification tracing the last time - * from the EncryptedSharedPrefs - * - * @return timestamp in ms - */ - fun lastNonActiveTracingTimestamp(): Long? { - val timestamp = getSharedPreferenceInstance().getLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_last_non_active_tracing_timestamp), - 0L - ) - if (timestamp == 0L) return null - return timestamp - } - - /** - * Sets the timestamp when the user stopped Exposure Notification tracing the last time - * from the EncryptedSharedPrefs - * - * @param value timestamp in ms - */ - fun lastNonActiveTracingTimestamp(value: Long?) = getSharedPreferenceInstance().edit(true) { - putLong( - CoronaWarnApplication.getAppContext().getString( - R.string.preference_last_non_active_tracing_timestamp - ), - value ?: 0L - ) - } - - /** - * Sets the total amount of time the tracing was not active - * from the EncryptedSharedPrefs - * - * @param value timestamp in ms - */ - fun totalNonActiveTracing(value: Long?) { - getSharedPreferenceInstance().edit(true) { - putLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_total_non_active_tracing), - value ?: 0L - ) - } - } - - /** - * Gets the total amount of time the tracing was not active - * from the EncryptedSharedPrefs - * - * @return timestamp in ms - */ - fun totalNonActiveTracing(): Long { - return getSharedPreferenceInstance().getLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_total_non_active_tracing), - 0L - ) - } - - /** - - * Gets the timestamp when the Background Polling Began initially - * from the EncryptedSharedPrefs - * - * @return timestamp in ms - */ - fun initialPollingForTestResultTimeStamp(): Long { - return getSharedPreferenceInstance().getLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_polling_test_result_started), - 0L - ) - } - - /** - * Sets the timestamp when the Background Polling Began initially - * from the EncryptedSharedPrefs - * - * @param value timestamp in ms - */ - fun initialPollingForTestResultTimeStamp(value: Long) = - getSharedPreferenceInstance().edit(true) { - putLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_polling_test_result_started), - value - ) - } - - /** - - * Gets the flag if notification is executed on Status Change - * from the EncryptedSharedPrefs - * - * @return boolean - */ - fun isTestResultAvailableNotificationSent(): Boolean { - return getSharedPreferenceInstance().getBoolean( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_test_result_notification), - false - ) - } - - /** - * Sets the flag if notification is executed on Status Change - * from the EncryptedSharedPrefs - * - * @param value boolean - */ - fun isTestResultAvailableNotificationSent(value: Boolean) = - getSharedPreferenceInstance().edit(true) { - putBoolean( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_test_result_notification), - value - ) - } - - /** - * Sets a boolean depending whether the risk level decreased or not. - */ - private val isUserToBeNotifiedOfLoweredRiskLevelFlowInternal by lazy { - MutableStateFlow(isUserToBeNotifiedOfLoweredRiskLevel) - } - val isUserToBeNotifiedOfLoweredRiskLevelFlow: Flow<Boolean> by lazy { - isUserToBeNotifiedOfLoweredRiskLevelFlowInternal - } - var isUserToBeNotifiedOfLoweredRiskLevel: Boolean - get() = getSharedPreferenceInstance().getBoolean( - PREFERENCE_HAS_RISK_STATUS_LOWERED, - false - ) - set(value) = getSharedPreferenceInstance() - .edit(commit = true) { putBoolean(PREFERENCE_HAS_RISK_STATUS_LOWERED, value) } - .also { isUserToBeNotifiedOfLoweredRiskLevelFlowInternal.value = value } - - /**************************************************** - * SETTINGS DATA - ****************************************************/ - - private const val PKEY_NOTIFICATIONS_RISK_ENABLED = "preference_notifications_risk_enabled" - - private val isNotificationsRiskEnabledFlowInternal by lazy { - MutableStateFlow(isNotificationsRiskEnabled) - } - val isNotificationsRiskEnabledFlow: Flow<Boolean> by lazy { - isNotificationsRiskEnabledFlowInternal - } - var isNotificationsRiskEnabled: Boolean - get() = getSharedPreferenceInstance().getBoolean(PKEY_NOTIFICATIONS_RISK_ENABLED, true) - set(value) = getSharedPreferenceInstance().edit(true) { - putBoolean(PKEY_NOTIFICATIONS_RISK_ENABLED, value) - isNotificationsRiskEnabledFlowInternal.value = value - } - - private const val PKEY_NOTIFICATIONS_TEST_ENABLED = "preference_notifications_test_enabled" - private val isNotificationsTestEnabledFlowInternal by lazy { - MutableStateFlow(isNotificationsTestEnabled) - } - val isNotificationsTestEnabledFlow: Flow<Boolean> by lazy { - isNotificationsTestEnabledFlowInternal - } - var isNotificationsTestEnabled: Boolean - get() = getSharedPreferenceInstance().getBoolean(PKEY_NOTIFICATIONS_TEST_ENABLED, true) - set(value) = getSharedPreferenceInstance().edit(true) { - putBoolean(PKEY_NOTIFICATIONS_TEST_ENABLED, value) - isNotificationsTestEnabledFlowInternal.value = value - } - - private const val PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT = "preference_positive_test_result_reminder_count" - var numberOfRemainingSharePositiveTestResultReminders: Int - get() = getSharedPreferenceInstance().getInt(PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT, Int.MIN_VALUE) - set(value) = getSharedPreferenceInstance().edit(true) { - putInt(PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT, value) - } - - /**************************************************** - * SUBMISSION DATA - ****************************************************/ - - private const val PREFERENCE_REGISTRATION_TOKEN = "preference_registration_token" - - /** - * Gets the registration token that is needed for the submission process - * - * @return the registration token - */ - fun registrationToken(): String? = getSharedPreferenceInstance() - .getString(PREFERENCE_REGISTRATION_TOKEN, null) - - /** - * Sets the registration token that is needed for the submission process - * - * @param value registration token as string - */ - fun registrationToken(value: String?) { - getSharedPreferenceInstance().edit(true) { - putString(PREFERENCE_REGISTRATION_TOKEN, value) - } - } - - fun initialTestResultReceivedTimestamp(value: Long) = - getSharedPreferenceInstance().edit(true) { - putLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_initial_result_received_time), - value - ) - } - - fun initialTestResultReceivedTimestamp(): Long? { - val timestamp = getSharedPreferenceInstance().getLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_initial_result_received_time), - 0L - ) - - if (timestamp == 0L) return null - return timestamp - } - - fun devicePairingSuccessfulTimestamp(value: Long) = - getSharedPreferenceInstance().edit(true) { - putLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_device_pairing_successful_time), - value - ) - } - - fun devicePairingSuccessfulTimestamp(): Long { - return getSharedPreferenceInstance().getLong( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_device_pairing_successful_time), - 0L - ) - } - - fun numberOfSuccessfulSubmissions(value: Int) = - getSharedPreferenceInstance().edit(true) { - putInt( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_number_successful_submissions), - value - ) - } - - private fun numberOfSuccessfulSubmissions(): Int { - return getSharedPreferenceInstance().getInt( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_number_successful_submissions), - 0 - ) - } - - fun submissionWasSuccessful(): Boolean = numberOfSuccessfulSubmissions() >= 1 - - fun isAllowedToSubmitDiagnosisKeys(isAllowedToSubmitDiagnosisKeys: Boolean) { - getSharedPreferenceInstance().edit(true) { - putBoolean( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_is_allowed_to_submit_diagnosis_keys), - isAllowedToSubmitDiagnosisKeys - ) - } - } - - fun isAllowedToSubmitDiagnosisKeys(): Boolean { - return getSharedPreferenceInstance().getBoolean( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_is_allowed_to_submit_diagnosis_keys), - false - ) - } - - /**************************************************** - * ENCRYPTED SHARED PREFERENCES HANDLING - ****************************************************/ - - private fun getSharedPreferenceInstance(): SharedPreferences = globalEncryptedSharedPreferencesInstance - - /**************************************************** - * INTEROPERABILITY - ****************************************************/ - - var isInteroperabilityShownAtLeastOnce: Boolean - get() { - return getSharedPreferenceInstance().getBoolean( - PREFERENCE_INTEROPERABILITY_IS_USED_AT_LEAST_ONCE, - false - ) - } - set(value) { - getSharedPreferenceInstance().edit(true) { - putBoolean(PREFERENCE_INTEROPERABILITY_IS_USED_AT_LEAST_ONCE, value) - } - } - - fun clear() { - // If you make use of a FlowPreference, you need to manually clear it here - Timber.w("LocalData.clear()") - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/OnboardingSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/OnboardingSettings.kt new file mode 100644 index 0000000000000000000000000000000000000000..1f22c31b71311e5ad2a7b7e08a053151da93f8ae --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/OnboardingSettings.kt @@ -0,0 +1,40 @@ +package de.rki.coronawarnapp.storage + +import android.content.Context +import androidx.core.content.edit +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.preferences.clearAndNotify +import org.joda.time.Instant +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class OnboardingSettings @Inject constructor( + @AppContext private val context: Context +) { + private val prefs by lazy { + context.getSharedPreferences("onboarding_localdata", Context.MODE_PRIVATE) + } + + var onboardingCompletedTimestamp: Instant? + get() = prefs.getLong(ONBOARDING_COMPLETED_TIMESTAMP, 0L).let { + if (it != 0L) { + Instant.ofEpochMilli(it) + } else null + } + set(value) = prefs.edit { putLong(ONBOARDING_COMPLETED_TIMESTAMP, value?.millis ?: 0L) } + + val isOnboarded: Boolean + get() = onboardingCompletedTimestamp != null + + var isBackgroundCheckDone: Boolean + get() = prefs.getBoolean(BACKGROUND_CHECK_DONE, false) + set(value) = prefs.edit { putBoolean(BACKGROUND_CHECK_DONE, value) } + + fun clear() = prefs.clearAndNotify() + + companion object { + private const val ONBOARDING_COMPLETED_TIMESTAMP = "onboarding.done.timestamp" + private const val BACKGROUND_CHECK_DONE = "onboarding.background.checked" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt index 197617c59e7036b4798387deadc399e128f48973..49488cc0e03fd3dd3dcb9f3add4fcc5edb6b154d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt @@ -32,7 +32,6 @@ import javax.inject.Singleton * The Tracing Repository refreshes and triggers all tracing relevant data. Some functions get their * data directly from the Exposure Notification, others consume the shared preferences. * - * @see LocalData * @see InternalExposureNotificationClient */ @Singleton diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingSettings.kt new file mode 100644 index 0000000000000000000000000000000000000000..63f4739e96fc9ebde92732f85661316e1d373638 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingSettings.kt @@ -0,0 +1,49 @@ +package de.rki.coronawarnapp.storage + +import android.content.Context +import androidx.core.content.edit +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.preferences.clearAndNotify +import de.rki.coronawarnapp.util.preferences.createFlowPreference +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TracingSettings @Inject constructor(@AppContext private val context: Context) { + + private val prefs by lazy { + context.getSharedPreferences("tracing_settings", Context.MODE_PRIVATE) + } + + var isConsentGiven: Boolean + get() = prefs.getBoolean(TRACING_ACTIVATION_TIMESTAMP, false) + set(value) = prefs.edit(true) { + putBoolean(TRACING_ACTIVATION_TIMESTAMP, value) + } + + var initialPollingForTestResultTimeStamp: Long + get() = prefs.getLong(TRACING_POOLING_TIMESTAMP, 0L) + set(value) = prefs.edit(true) { + putLong(TRACING_POOLING_TIMESTAMP, value) + } + + var isTestResultAvailableNotificationSent: Boolean + get() = prefs.getBoolean(TEST_RESULT_NOTIFICATION_SENT, false) + set(value) = prefs.edit(true) { + putBoolean(TEST_RESULT_NOTIFICATION_SENT, value) + } + + val isUserToBeNotifiedOfLoweredRiskLevel = prefs.createFlowPreference( + key = LOWERED_RISK_LEVEL, + defaultValue = false + ) + + fun clear() = prefs.clearAndNotify() + + companion object { + const val TRACING_POOLING_TIMESTAMP = "tracing.pooling.timestamp" + const val TRACING_ACTIVATION_TIMESTAMP = "tracing.activation.timestamp" + const val TEST_RESULT_NOTIFICATION_SENT = "test.notification.sent" + const val LOWERED_RISK_LEVEL = "notification.risk.lowered" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt index fb07dcbaef0e0ae6af55d59abc0e4393b00b9f8c..b17572eb8e5af1ab8d57d912c1de09249705b49e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.storage.interoperability import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.ui.Country import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -12,7 +12,8 @@ import javax.inject.Singleton @Singleton class InteroperabilityRepository @Inject constructor( - private val appConfigProvider: AppConfigProvider + private val appConfigProvider: AppConfigProvider, + private val settings: CWASettings ) { val countryList = appConfigProvider.currentConfig @@ -39,6 +40,6 @@ class InteroperabilityRepository @Inject constructor( } fun saveInteroperabilityUsed() { - LocalData.isInteroperabilityShownAtLeastOnce = true + settings.wasInteroperabilityShownAtLeastOnce = true } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..45b15c4d4b07e2cfc3fe60a6c82221d300dfbca1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesHelper.kt @@ -0,0 +1,51 @@ +package de.rki.coronawarnapp.storage.preferences + +import android.content.SharedPreferences +import android.content.pm.ApplicationInfo +import androidx.core.content.edit +import de.rki.coronawarnapp.exception.CwaSecurityException +import de.rki.coronawarnapp.util.security.EncryptedPreferencesFactory +import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.security.SecurityConstants +import java.io.File +import javax.inject.Inject + +class EncryptedPreferencesHelper @Inject constructor( + private val applicationInfo: ApplicationInfo, + factory: EncryptedPreferencesFactory, + encryptionErrorResetTool: EncryptionErrorResetTool +) { + + private val encryptedPreferencesFile by lazy { + File(applicationInfo.dataDir) + .resolve("shared_prefs/${SecurityConstants.ENCRYPTED_SHARED_PREFERENCES_FILE}.xml") + } + + val encryptedSharedPreferencesInstance: SharedPreferences? by lazy { + withSecurityCatch { + try { + if (encryptedPreferencesFile.exists()) { + factory.create(SecurityConstants.ENCRYPTED_SHARED_PREFERENCES_FILE) + } else { + null + } + } catch (e: Exception) { + encryptionErrorResetTool.isResetNoticeToBeShown = true + null + } + } + } + + fun clean() { + encryptedSharedPreferencesInstance?.edit(true) { + clear() + } + encryptedPreferencesFile.delete() + } + + private fun <T> withSecurityCatch(doInCatch: () -> T) = try { + doInCatch.invoke() + } catch (e: Exception) { + throw CwaSecurityException(e) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesMigration.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesMigration.kt new file mode 100644 index 0000000000000000000000000000000000000000..cda45aade8aba1a42b5211b52bbfab33d807f92e --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesMigration.kt @@ -0,0 +1,167 @@ +package de.rki.coronawarnapp.storage.preferences + +import android.content.Context +import android.content.SharedPreferences +import android.database.sqlite.SQLiteDatabase +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.storage.OnboardingSettings +import de.rki.coronawarnapp.storage.TracingSettings +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toInstantOrNull +import de.rki.coronawarnapp.util.di.AppContext +import org.joda.time.Instant +import timber.log.Timber +import javax.inject.Inject + +class EncryptedPreferencesMigration @Inject constructor( + @AppContext private val context: Context, + private val encryptedPreferencesHelper: EncryptedPreferencesHelper, + private val cwaSettings: CWASettings, + private val submissionSettings: SubmissionSettings, + private val tracingSettings: TracingSettings, + private val onboardingSettings: OnboardingSettings +) { + + fun doMigration() { + Timber.d("Migration start") + try { + copyData() + cleanData() + } catch (e: Exception) { + Timber.e(e, "Migration was not successful") + } + try { + dropDatabase() + } catch (e: Exception) { + Timber.e(e, "Database removing was not successful") + } + Timber.d("Migration finish") + } + + private fun copyData() { + val encryptedSharedPreferences = encryptedPreferencesHelper.encryptedSharedPreferencesInstance ?: return + Timber.d("EncryptedPreferences are available") + SettingsLocalData(encryptedSharedPreferences).apply { + cwaSettings.wasInteroperabilityShownAtLeastOnce = wasInteroperabilityShown() + cwaSettings.isNotificationsRiskEnabled.update { isNotificationsRiskEnabled() } + cwaSettings.isNotificationsTestEnabled.update { isNotificationsTestEnabled() } + cwaSettings.numberOfRemainingSharePositiveTestResultReminders = + numberOfRemainingSharePositiveTestResultReminders() + } + + OnboardingLocalData(encryptedSharedPreferences).apply { + onboardingSettings.onboardingCompletedTimestamp = onboardingCompletedTimestamp()?.let { + Instant.ofEpochMilli(it) + } + onboardingSettings.isBackgroundCheckDone = isBackgroundCheckDone() + } + + TracingLocalData(encryptedSharedPreferences).apply { + tracingSettings.initialPollingForTestResultTimeStamp = initialPollingForTestResultTimeStamp() + tracingSettings.isTestResultAvailableNotificationSent = isTestResultAvailableNotificationSent() + tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel.update { isUserToBeNotifiedOfLoweredRiskLevel() } + tracingSettings.isConsentGiven = initialTracingActivationTimestamp() != 0L + } + + SubmissionLocalData(encryptedSharedPreferences).apply { + submissionSettings.registrationToken.update { + registrationToken() + } + submissionSettings.initialTestResultReceivedAt = initialTestResultReceivedTimestamp().toInstantOrNull() + submissionSettings.devicePairingSuccessfulAt = devicePairingSuccessfulTimestamp().toInstantOrNull() + submissionSettings.isSubmissionSuccessful = numberOfSuccessfulSubmissions() >= 1 + submissionSettings.isAllowedToSubmitKeys = isAllowedToSubmitDiagnosisKeys() + } + } + + private fun cleanData() { + encryptedPreferencesHelper.clean() + } + + private fun dropDatabase() { + val file = context.getDatabasePath("coronawarnapp-db") + if (file.exists()) { + Timber.d("Removing database $file") + SQLiteDatabase.deleteDatabase(file) + } + } + + private class SettingsLocalData(private val sharedPreferences: SharedPreferences) { + + fun wasInteroperabilityShown() = sharedPreferences.getBoolean(PREFERENCE_INTEROPERABILITY_WAS_USED, false) + + fun isNotificationsRiskEnabled(): Boolean = sharedPreferences.getBoolean(PKEY_NOTIFICATIONS_RISK_ENABLED, true) + + fun isNotificationsTestEnabled(): Boolean = sharedPreferences.getBoolean(PKEY_NOTIFICATIONS_TEST_ENABLED, true) + + fun numberOfRemainingSharePositiveTestResultReminders(): Int = + sharedPreferences.getInt(PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT, Int.MIN_VALUE) + + companion object { + private const val PREFERENCE_INTEROPERABILITY_WAS_USED = "preference_interoperability_is_used_at_least_once" + private const val PKEY_NOTIFICATIONS_RISK_ENABLED = "preference_notifications_risk_enabled" + private const val PKEY_NOTIFICATIONS_TEST_ENABLED = "preference_notifications_test_enabled" + private const val PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT = + "preference_positive_test_result_reminder_count" + } + } + + private class OnboardingLocalData(private val sharedPreferences: SharedPreferences) { + fun onboardingCompletedTimestamp(): Long? { + val timestamp = sharedPreferences.getLong(PKEY_ONBOARDING_COMPLETED_TIMESTAMP, 0L) + + if (timestamp == 0L) return null + return timestamp + } + + fun isBackgroundCheckDone(): Boolean = sharedPreferences.getBoolean(PKEY_BACKGROUND_CHECK_DONE, false) + + companion object { + private const val PKEY_ONBOARDING_COMPLETED_TIMESTAMP = "preference_onboarding_completed_timestamp" + private const val PKEY_BACKGROUND_CHECK_DONE = "preference_background_check_done" + } + } + + private class TracingLocalData(private val sharedPreferences: SharedPreferences) { + + fun initialPollingForTestResultTimeStamp() = sharedPreferences.getLong(PKEY_POOLING_TEST_RESULT_STARTED, 0L) + + fun isTestResultAvailableNotificationSent() = sharedPreferences.getBoolean(PKEY_TEST_RESULT_NOTIFICATION, false) + + fun isUserToBeNotifiedOfLoweredRiskLevel() = sharedPreferences.getBoolean(PKEY_HAS_RISK_STATUS_LOWERED, false) + + fun initialTracingActivationTimestamp(): Long = sharedPreferences.getLong(PKEY_TRACING_ACTIVATION_TIME, 0L) + + companion object { + private const val PKEY_POOLING_TEST_RESULT_STARTED = "preference_polling_test_result_started" + private const val PKEY_TEST_RESULT_NOTIFICATION = "preference_test_result_notification" + private const val PKEY_HAS_RISK_STATUS_LOWERED = "preference_has_risk_status_lowered" + private const val PKEY_TRACING_ACTIVATION_TIME = "preference_initial_tracing_activation_time" + } + } + + private class SubmissionLocalData(private val sharedPreferences: SharedPreferences) { + fun registrationToken(): String? = sharedPreferences.getString(PKEY_REGISTRATION_TOKEN, null) + + fun initialTestResultReceivedTimestamp(): Long? { + val timestamp = sharedPreferences.getLong(PKEY_INITIAL_RESULT_RECEIVED_TIME, 0L) + + if (timestamp == 0L) return null + return timestamp + } + + fun devicePairingSuccessfulTimestamp(): Long = sharedPreferences.getLong(PKEY_DEVICE_PARING_SUCCESSFUL_TIME, 0L) + + fun numberOfSuccessfulSubmissions(): Int = sharedPreferences.getInt(PKEY_NUMBER_SUCCESSFUL_SUBMISSIONS, 0) + + fun isAllowedToSubmitDiagnosisKeys(): Boolean = sharedPreferences.getBoolean(PKEY_IS_ALLOWED_TO_SUBMIT, false) + + companion object { + private const val PKEY_REGISTRATION_TOKEN = "preference_registration_token" + private const val PKEY_INITIAL_RESULT_RECEIVED_TIME = "preference_initial_result_received_time" + private const val PKEY_DEVICE_PARING_SUCCESSFUL_TIME = "preference_device_pairing_successful_time" + private const val PKEY_NUMBER_SUCCESSFUL_SUBMISSIONS = "preference_number_successful_submissions" + private const val PKEY_IS_ALLOWED_TO_SUBMIT = "preference_is_allowed_to_submit_diagnosis_keys" + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt index 8f496e37159faa663aadcb93e23adf3709b988b2..392fcd548e782a4f8cbd0eef0e9ee28c12e931fb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.service.submission.SubmissionService -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper @@ -35,10 +35,12 @@ class SubmissionRepository @Inject constructor( private val timeStamper: TimeStamper, private val tekHistoryStorage: TEKHistoryStorage, private val deadmanNotificationScheduler: DeadmanNotificationScheduler, - private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector + private val backgroundNoise: BackgroundNoise, + private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, + private val tracingSettings: TracingSettings ) { private val testResultReceivedDateFlowInternal = - MutableStateFlow(Date(LocalData.initialTestResultReceivedTimestamp() ?: System.currentTimeMillis())) + MutableStateFlow((submissionSettings.initialTestResultReceivedAt ?: timeStamper.nowUTC).toDate()) val testResultReceivedDateFlow: Flow<Date> = testResultReceivedDateFlowInternal private val deviceUIStateFlowInternal = @@ -77,18 +79,18 @@ class SubmissionRepository @Inject constructor( } fun refreshDeviceUIState(refreshTestResult: Boolean = true) { - if (LocalData.submissionWasSuccessful()) { + if (submissionSettings.isSubmissionSuccessful) { deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL) return } - val registrationToken = LocalData.registrationToken() + val registrationToken = submissionSettings.registrationToken.value if (registrationToken == null) { deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED) return } - if (LocalData.isAllowedToSubmitDiagnosisKeys()) { + if (submissionSettings.isAllowedToSubmitKeys) { deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE) return } @@ -123,20 +125,24 @@ class SubmissionRepository @Inject constructor( suspend fun asyncRegisterDeviceViaTAN(tan: String) { val registrationData = submissionService.asyncRegisterDeviceViaTAN(tan) - LocalData.registrationToken(registrationData.registrationToken) + submissionSettings.registrationToken.update { + registrationData.registrationToken + } updateTestResult(registrationData.testResult) - LocalData.devicePairingSuccessfulTimestamp(timeStamper.nowUTC.millis) - BackgroundNoise.getInstance().scheduleDummyPattern() + submissionSettings.devicePairingSuccessfulAt = timeStamper.nowUTC + backgroundNoise.scheduleDummyPattern() analyticsKeySubmissionCollector.reportTestRegistered() analyticsKeySubmissionCollector.reportRegisteredWithTeleTAN() } suspend fun asyncRegisterDeviceViaGUID(guid: String): TestResult { val registrationData = submissionService.asyncRegisterDeviceViaGUID(guid) - LocalData.registrationToken(registrationData.registrationToken) + submissionSettings.registrationToken.update { + registrationData.registrationToken + } updateTestResult(registrationData.testResult) - LocalData.devicePairingSuccessfulTimestamp(timeStamper.nowUTC.millis) - BackgroundNoise.getInstance().scheduleDummyPattern() + submissionSettings.devicePairingSuccessfulAt = timeStamper.nowUTC + backgroundNoise.scheduleDummyPattern() analyticsKeySubmissionCollector.reportTestRegistered() return registrationData.testResult } @@ -152,22 +158,22 @@ class SubmissionRepository @Inject constructor( testResultFlow.value = testResult if (testResult == TestResult.POSITIVE) { - LocalData.isAllowedToSubmitDiagnosisKeys(true) + submissionSettings.isAllowedToSubmitKeys = true analyticsKeySubmissionCollector.reportPositiveTestResultReceived() deadmanNotificationScheduler.cancelScheduledWork() } - val initialTestResultReceivedTimestamp = LocalData.initialTestResultReceivedTimestamp() + val initialTestResultReceivedTimestamp = submissionSettings.initialTestResultReceivedAt if (initialTestResultReceivedTimestamp == null) { - val currentTime = System.currentTimeMillis() - LocalData.initialTestResultReceivedTimestamp(currentTime) - testResultReceivedDateFlowInternal.value = Date(currentTime) + val currentTime = timeStamper.nowUTC + submissionSettings.initialTestResultReceivedAt = currentTime + testResultReceivedDateFlowInternal.value = currentTime.toDate() if (testResult == TestResult.PENDING) { BackgroundWorkScheduler.startWorkScheduler() } } else { - testResultReceivedDateFlowInternal.value = Date(initialTestResultReceivedTimestamp) + testResultReceivedDateFlowInternal.value = initialTestResultReceivedTimestamp.toDate() } } @@ -184,13 +190,13 @@ class SubmissionRepository @Inject constructor( submissionSettings.hasGivenConsent.update { false } analyticsKeySubmissionCollector.reset() revokeConsentToSubmission() - LocalData.registrationToken(null) - LocalData.devicePairingSuccessfulTimestamp(0L) - LocalData.initialPollingForTestResultTimeStamp(0L) - LocalData.initialTestResultReceivedTimestamp(0L) - LocalData.isAllowedToSubmitDiagnosisKeys(false) - LocalData.isTestResultAvailableNotificationSent(false) - LocalData.numberOfSuccessfulSubmissions(0) + submissionSettings.registrationToken.update { null } + submissionSettings.devicePairingSuccessfulAt = null + tracingSettings.initialPollingForTestResultTimeStamp = 0L + submissionSettings.initialTestResultReceivedAt = null + submissionSettings.isAllowedToSubmitKeys = false + tracingSettings.isTestResultAvailableNotificationSent = false + submissionSettings.isSubmissionSuccessful = false } private fun deriveUiState(testResult: TestResult?): DeviceUIState = when (testResult) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt index 486111f0e4c7cc6dc21e1cd88726451e93839cdd..1b362ef1a3eb843e762cba992a45ea33a5557c7f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt @@ -1,7 +1,9 @@ package de.rki.coronawarnapp.submission import android.content.Context +import androidx.core.content.edit import com.google.gson.Gson +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toInstantOrNull import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.preferences.FlowPreference import de.rki.coronawarnapp.util.preferences.clearAndNotify @@ -35,25 +37,46 @@ class SubmissionSettings @Inject constructor( context.getSharedPreferences("submission_localdata", Context.MODE_PRIVATE) } + val registrationToken = prefs.createFlowPreference<String?>( + key = TEST_REGISTRATION_TOKEN, + defaultValue = null + ) + + var initialTestResultReceivedAt: Instant? + get() = prefs.getLong(TEST_RESULT_RECEIVED_AT, 0L).toInstantOrNull() + set(value) = prefs.edit { putLong(TEST_RESULT_RECEIVED_AT, value?.millis ?: 0L) } + + var devicePairingSuccessfulAt: Instant? + get() = prefs.getLong(TEST_PARING_SUCCESSFUL_AT, 0L).toInstantOrNull() + set(value) = prefs.edit { putLong(TEST_PARING_SUCCESSFUL_AT, value?.millis ?: 0L) } + + var isSubmissionSuccessful: Boolean + get() = prefs.getBoolean(IS_KEY_SUBMISSION_SUCCESSFUL, false) + set(value) = prefs.edit { putBoolean(IS_KEY_SUBMISSION_SUCCESSFUL, value) } + + var isAllowedToSubmitKeys: Boolean + get() = prefs.getBoolean(IS_KEY_SUBMISSION_ALLOWED, false) + set(value) = prefs.edit { putBoolean(IS_KEY_SUBMISSION_ALLOWED, value) } + val hasGivenConsent = prefs.createFlowPreference( - key = "key_submission_consent", + key = SUBMISSION_CONSENT_GIVEN, defaultValue = false ) val hasViewedTestResult = prefs.createFlowPreference( - key = "key_submission_result_viewed", + key = SUBMISSION_RESULT_VIEWED, defaultValue = false ) val symptoms: FlowPreference<Symptoms?> = FlowPreference( prefs, - key = "submission.symptoms.latest", + key = SUBMISSION_SYMPTOMS_LATEST, reader = FlowPreference.gsonReader<Symptoms?>(gson, null), writer = FlowPreference.gsonWriter(gson) ) val lastSubmissionUserActivityUTC = prefs.createFlowPreference( - key = "submission.user.activity.last", + key = AUTO_SUBMISSION_LAST_USER_ACTIVITY, reader = { key -> Instant.ofEpochMilli(getLong(key, 0L)) }, @@ -63,17 +86,17 @@ class SubmissionSettings @Inject constructor( ) val autoSubmissionEnabled = prefs.createFlowPreference( - key = "submission.auto.enabled", + key = AUTO_SUBMISSION_ENABLED, defaultValue = false ) val autoSubmissionAttemptsCount = prefs.createFlowPreference( - key = "submission.auto.attempts.count", + key = AUTO_SUBMISSION_ATTEMPT_COUNT, defaultValue = 0 ) val autoSubmissionAttemptsLast = prefs.createFlowPreference( - key = "submission.auto.attempts.last", + key = AUTO_SUBMISSION_LAST_ATTEMPT, reader = { key -> Instant.ofEpochMilli(getLong(key, 0L)) }, @@ -82,7 +105,20 @@ class SubmissionSettings @Inject constructor( } ) - fun clear() { - prefs.clearAndNotify() + fun clear() = prefs.clearAndNotify() + + companion object { + private const val TEST_REGISTRATION_TOKEN = "submission.test.token" + private const val TEST_RESULT_RECEIVED_AT = "submission.test.result.receivedAt" + private const val TEST_PARING_SUCCESSFUL_AT = "submission.test.pairedAt" + private const val IS_KEY_SUBMISSION_ALLOWED = "submission.allowed" + private const val IS_KEY_SUBMISSION_SUCCESSFUL = "submission.successful" + private const val SUBMISSION_CONSENT_GIVEN = "key_submission_consent" + private const val SUBMISSION_RESULT_VIEWED = "key_submission_result_viewed" + private const val SUBMISSION_SYMPTOMS_LATEST = "submission.symptoms.latest" + private const val AUTO_SUBMISSION_LAST_USER_ACTIVITY = "submission.user.activity.last" + private const val AUTO_SUBMISSION_ENABLED = "submission.auto.enabled" + private const val AUTO_SUBMISSION_ATTEMPT_COUNT = "submission.auto.attempts.count" + private const val AUTO_SUBMISSION_LAST_ATTEMPT = "submission.auto.attempts.last" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt index 2bd1faa295558ef435ff124e78fc28ac8ea8c828..8232893d4888ca535c04abee92c7f978bfeb015c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt @@ -7,7 +7,6 @@ import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService import de.rki.coronawarnapp.playbook.Playbook -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.submission.auto.AutoSubmission @@ -119,7 +118,7 @@ class SubmissionTask @Inject constructor( } private suspend fun performSubmission(): Result { - val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() + val registrationToken = submissionSettings.registrationToken.value ?: throw NoRegistrationTokenSetException() Timber.tag(TAG).d("Using registrationToken=$registrationToken") val keys: List<TemporaryExposureKey> = try { @@ -167,7 +166,7 @@ class SubmissionTask @Inject constructor( private fun setSubmissionFinished() { Timber.tag(TAG).d("setSubmissionFinished()") BackgroundWorkScheduler.stopWorkScheduler() - LocalData.numberOfSuccessfulSubmissions(1) + submissionSettings.isSubmissionSuccessful = true BackgroundWorkScheduler.startWorkScheduler() shareTestResultNotificationService.cancelSharePositiveTestResultNotification() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt index add526c6fc104d3731060df0bcd13176a2222b29..d94e448b7c7e0789219c48b7f1344edf27a82801 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.submission.ui.homecards import dagger.Reusable import de.rki.coronawarnapp.exception.http.CwaServerError -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper @@ -18,18 +18,20 @@ import javax.inject.Inject @Reusable class SubmissionStateProvider @Inject constructor( - submissionRepository: SubmissionRepository + submissionRepository: SubmissionRepository, + submissionSettings: SubmissionSettings ) { val state: Flow<SubmissionState> = combine( submissionRepository.deviceUIStateFlow, submissionRepository.hasViewedTestResult, - submissionRepository.testResultReceivedDateFlow - ) { uiState, hasTestBeenSeen, testRegistrationDate -> + submissionRepository.testResultReceivedDateFlow, + submissionSettings.registrationToken.flow + ) { uiState, hasTestBeenSeen, testRegistrationDate, registrationToken -> val eval = Evaluation( deviceUiState = uiState, - isDeviceRegistered = LocalData.registrationToken() != null, + isDeviceRegistered = registrationToken != null, hasTestResultBeenSeen = hasTestBeenSeen ) Timber.d("eval: %s", eval) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt index 35b60f17d6cea8f1d3ae8fe623567a64b07d07ad..0c5b1284311d1ef7bdbac0ffcc245e8bbed24cb9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt @@ -84,8 +84,6 @@ data class IncreasedRisk( lastEncounterAt.toLocalDate().toString(DateTimeFormat.mediumDate()) ) } - - fun getProgressColorHighRisk(context: Context) = context.getColorCompat(R.color.colorStableLight) } // tracing_content_low_view @@ -95,7 +93,8 @@ data class LowRisk( val lastExposureDetectionTime: Instant?, val lastEncounterAt: Instant?, val allowManualUpdate: Boolean, - val daysWithEncounters: Int + val daysWithEncounters: Int, + val daysSinceInstallation: Long ) : TracingState() { val showUpdateButton: Boolean = allowManualUpdate && !isInDetailsMode @@ -131,6 +130,12 @@ data class LowRisk( ) } + fun getDaysSinceInstall(context: Context): String = + context.getString(R.string.risk_card_body_days_since_installation) + .format(daysSinceInstallation) + + fun appInstalledForOverTwoWeeks(): Boolean = daysSinceInstallation < 14 && lastEncounterAt == null + fun getRiskContactLast(context: Context): String? { if (lastEncounterAt == null) return null // caution! lastEncounterAt is null after migration from 1.7.x -> 1.8.x diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt index 2e70177578a512828ca632c58e65ba75df522ee7..c5168a647055003923754ed9b61cc2bf9c7ed3b5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.tracing.states import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.latestSubmission import de.rki.coronawarnapp.risk.RiskState @@ -25,9 +26,9 @@ class TracingStateProvider @AssistedInject constructor( backgroundModeStatus: BackgroundModeStatus, tracingRepository: TracingRepository, riskLevelStorage: RiskLevelStorage, - exposureDetectionTracker: ExposureDetectionTracker + exposureDetectionTracker: ExposureDetectionTracker, + installTimeProvider: InstallTimeProvider ) { - val state: Flow<TracingState> = combine( tracingStatus.generalStatus.onEach { Timber.v("tracingStatus: $it") @@ -72,7 +73,8 @@ class TracingStateProvider @AssistedInject constructor( lastExposureDetectionTime = latestSubmission?.startedAt, lastEncounterAt = latestCalc.lastRiskEncounterAt, daysWithEncounters = latestCalc.daysWithEncounters, - allowManualUpdate = !isBackgroundJobEnabled + allowManualUpdate = !isBackgroundJobEnabled, + daysSinceInstallation = installTimeProvider.daysSinceInstallation ) latestCalc.riskState == RiskState.INCREASED_RISK -> IncreasedRisk( isInDetailsMode = isDetailsMode, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt index 68ffdbac536510e10f6690795dc36132fbc55e1d..41de9b9285a31174afd8b13561af92100c8f02f7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.tracing.ui.details import dagger.Reusable import de.rki.coronawarnapp.datadonation.survey.Surveys +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults @@ -29,6 +30,7 @@ import javax.inject.Inject class TracingDetailsItemProvider @Inject constructor( tracingStatus: GeneralTracingStatus, riskLevelStorage: RiskLevelStorage, + installTimeProvider: InstallTimeProvider, surveys: Surveys ) { @@ -68,6 +70,7 @@ class TracingDetailsItemProvider @Inject constructor( if (latestCalc.riskState != RiskState.CALCULATION_FAILED && status != Status.TRACING_INACTIVE) { PeriodLoggedBox.Item( + daysSinceInstallation = installTimeProvider.daysSinceInstallation, tracingStatus = status ).also { add(it) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt index 985a26953ee6455c5b6ce8af5478ab576b82f26f..beb7540018c54a015621db14a52892c100b5c65c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt @@ -8,7 +8,6 @@ import de.rki.coronawarnapp.databinding.TracingDetailsItemPeriodloggedViewBindin import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.tracing.ui.details.TracingDetailsAdapter import de.rki.coronawarnapp.tracing.ui.details.items.DetailsItem -import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat class PeriodLoggedBox( parent: ViewGroup, @@ -34,15 +33,18 @@ class PeriodLoggedBox( } data class Item( + val daysSinceInstallation: Long, val tracingStatus: GeneralTracingStatus.Status ) : DetailsItem { - fun getProgressColor(context: Context) = when (tracingStatus) { - GeneralTracingStatus.Status.TRACING_INACTIVE, - GeneralTracingStatus.Status.BLUETOOTH_DISABLED, - GeneralTracingStatus.Status.LOCATION_DISABLED -> R.color.colorTextPrimary2 - GeneralTracingStatus.Status.TRACING_ACTIVE -> R.color.colorAccentTintIcon - }.let { context.getColorCompat(it) } + fun getInstallTimePeriodLogged(context: Context): String = + if (daysSinceInstallation < 14L) { + context.getString( + R.string.risk_details_information_body_period_logged_assessment_under_14_days + ).format(daysSinceInstallation) + } else context.getString( + R.string.risk_details_information_body_period_logged_assessment_over_14_days + ) override val stableId: Long get() = Item::class.java.name.hashCode().toLong() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt index 37a9cdd1e4ae11d24b963127f677c779c8fb377e..76af2958e4c24c74ad19e65dc40976470bb64161 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt @@ -10,6 +10,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.TracingPermissionHelper import de.rki.coronawarnapp.tracing.GeneralTracingStatus @@ -31,12 +32,18 @@ import timber.log.Timber class SettingsTracingFragmentViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, tracingStatus: GeneralTracingStatus, + installTimeProvider: InstallTimeProvider, private val backgroundStatus: BackgroundModeStatus, tracingPermissionHelperFactory: TracingPermissionHelper.Factory ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val loggingPeriod: LiveData<PeriodLoggedBox.Item> = - tracingStatus.generalStatus.map { PeriodLoggedBox.Item(it) } + tracingStatus.generalStatus.map { + PeriodLoggedBox.Item( + daysSinceInstallation = installTimeProvider.daysSinceInstallation, + tracingStatus = it + ) + } .onEach { Timber.v("logginPeriod onEach") } .asLiveData(dispatcherProvider.Main) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt index b3540383e0d0dce5408ef69d4ca2f5cfe591398e..3e891ee68f66768355aad77d441fa5914cc4f92c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt @@ -57,7 +57,7 @@ class InformationFragment : Fragment(R.layout.fragment_information), AutoInject setButtonOnClickListener() setAccessibilityDelegate() - // TODO Hidden until further clarification regarding release schedule is available + // TODO ¯\_(ツ)_/¯ binding.informationDebuglog.rootLayout.isGone = !CWADebug.isDeviceForTestersBuild } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt index acd7d63e6dc7763ab70d3da95733c8f1e61afded..5f83d8ea49767018da6f7c7a84879ec3296a7dde 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt @@ -4,7 +4,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.update.UpdateChecker import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -15,6 +15,7 @@ class LauncherActivityViewModel @AssistedInject constructor( private val updateChecker: UpdateChecker, dispatcherProvider: DispatcherProvider, private val cwaSettings: CWASettings, + private val onboardingSettings: OnboardingSettings ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val events = SingleLiveEvent<LauncherEvent>() @@ -31,7 +32,7 @@ class LauncherActivityViewModel @AssistedInject constructor( } private fun isJustInstalledOrUpdated() = - !LocalData.isOnboarded() || !LocalData.isInteroperabilityShownAtLeastOnce || + !onboardingSettings.isOnboarded || !cwaSettings.wasInteroperabilityShownAtLeastOnce || cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE @AssistedFactory diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt index f6984c21e47c7cae1c08ef02749d3d15df21926b..545557ddd88760054a96560d618d8db498bc01f7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt @@ -22,7 +22,7 @@ import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragmen import de.rki.coronawarnapp.databinding.ActivityMainBinding import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.ui.base.startActivitySafely import de.rki.coronawarnapp.ui.setupWithNavController2 import de.rki.coronawarnapp.util.AppShortcuts @@ -84,6 +84,7 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var deadmanScheduler: DeadmanNotificationScheduler @Inject lateinit var contactDiaryWorkScheduler: ContactDiaryWorkScheduler @Inject lateinit var dataDonationAnalyticsScheduler: DataDonationAnalyticsScheduler + @Inject lateinit var submissionSettings: SubmissionSettings override fun onCreate(savedInstanceState: Bundle?) { AppInjector.setup(this) @@ -163,7 +164,7 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { vm.doBackgroundNoiseCheck() contactDiaryWorkScheduler.schedulePeriodic() dataDonationAnalyticsScheduler.schedulePeriodic() - if (!LocalData.isAllowedToSubmitDiagnosisKeys()) { + if (!submissionSettings.isAllowedToSubmitKeys) { deadmanScheduler.schedulePeriodic() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt index 7db7bcbe8087531746be32bc27e7c5473de74daa..028197ca86763fa9b9bcfe559620172fc7f959e6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt @@ -7,7 +7,7 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.playbook.BackgroundNoise -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.device.BackgroundModeStatus @@ -20,7 +20,9 @@ class MainActivityViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val environmentSetup: EnvironmentSetup, private val backgroundModeStatus: BackgroundModeStatus, - private val contactDiarySettings: ContactDiarySettings + private val contactDiarySettings: ContactDiarySettings, + private val backgroundNoise: BackgroundNoise, + private val onboardingSettings: OnboardingSettings ) : CWAViewModel( dispatcherProvider = dispatcherProvider ) { @@ -43,8 +45,8 @@ class MainActivityViewModel @AssistedInject constructor( } launch { - if (!LocalData.isBackgroundCheckDone()) { - LocalData.isBackgroundCheckDone(true) + if (!onboardingSettings.isBackgroundCheckDone) { + onboardingSettings.isBackgroundCheckDone = true if (backgroundModeStatus.isBackgroundRestricted.first()) { showBackgroundJobDisabledNotification.postValue(Unit) } else { @@ -56,7 +58,7 @@ class MainActivityViewModel @AssistedInject constructor( fun doBackgroundNoiseCheck() { launch { - BackgroundNoise.getInstance().foregroundScheduleCheck() + backgroundNoise.foregroundScheduleCheck() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index f10170180f96824889fb921897e7607b463acf56..0c72da282baddaed96138f6756ab8620763cc3b6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -11,8 +11,8 @@ import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.TracingRepository +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.FetchingResult import de.rki.coronawarnapp.submission.ui.homecards.NoTest @@ -79,7 +79,8 @@ class HomeFragmentViewModel @AssistedInject constructor( appConfigProvider: AppConfigProvider, statisticsProvider: StatisticsProvider, private val deadmanNotificationScheduler: DeadmanNotificationScheduler, - private val appShortcutsHelper: AppShortcutsHelper + private val appShortcutsHelper: AppShortcutsHelper, + private val tracingSettings: TracingSettings, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { private val tracingStateProvider by lazy { tracingStateProviderFactory.create(isDetailsMode = false) } @@ -262,8 +263,9 @@ class HomeFragmentViewModel @AssistedInject constructor( // TODO only lazy to keep tests going which would break because of LocalData access val showLoweredRiskLevelDialog: LiveData<Boolean> by lazy { - LocalData - .isUserToBeNotifiedOfLoweredRiskLevelFlow + tracingSettings + .isUserToBeNotifiedOfLoweredRiskLevel + .flow .map { shouldBeNotified -> val shouldBeShown = shouldBeNotified && !isLoweredRiskLevelDialogBeingShown if (shouldBeShown) { @@ -302,7 +304,7 @@ class HomeFragmentViewModel @AssistedInject constructor( fun userHasAcknowledgedTheLoweredRiskLevel() { isLoweredRiskLevelDialogBeingShown = false - LocalData.isUserToBeNotifiedOfLoweredRiskLevel = false + tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel.update { false } } fun userHasAcknowledgedIncorrectDeviceTime() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt index fe8a9252ff56112046101f42edf4e0a5c6ff8861..3888efc6398c11c741d3fc29bc6d796e1c36254e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt @@ -13,16 +13,16 @@ import dagger.android.HasAndroidInjector import de.rki.coronawarnapp.R import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.util.AppShortcuts +import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppInjector import javax.inject.Inject /** * This activity holds all the onboarding fragments and isn't used after a successful onboarding flow. * - * @see LocalData */ class OnboardingActivity : AppCompatActivity(), LifecycleObserver, HasAndroidInjector { companion object { @@ -48,6 +48,8 @@ class OnboardingActivity : AppCompatActivity(), LifecycleObserver, HasAndroidInj override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector @Inject lateinit var settings: CWASettings + @Inject lateinit var onboardingSettings: OnboardingSettings + @Inject lateinit var timeStamper: TimeStamper private val FragmentManager.currentNavigationFragment: Fragment? get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first() @@ -71,9 +73,12 @@ class OnboardingActivity : AppCompatActivity(), LifecycleObserver, HasAndroidInj } fun completeOnboarding() { - LocalData.isOnboarded(true) - LocalData.onboardingCompletedTimestamp(System.currentTimeMillis()) - settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE } + onboardingSettings.onboardingCompletedTimestamp = timeStamper.nowUTC + + settings.lastChangelogVersion.update { + BuildConfigWrap.VERSION_CODE + } + MainActivity.start(this) finish() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt index 57dbcfc194b293b008651726c8c4dc7124f45fc5..487f4be42bc2c8068136fcb0b79a05f51b03586b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt @@ -4,21 +4,24 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory -class OnboardingLoadingViewModel @AssistedInject constructor(private val cwaSettings: CWASettings) : CWAViewModel() { +class OnboardingLoadingViewModel @AssistedInject constructor( + private val cwaSettings: CWASettings, + private val onboardingSettings: OnboardingSettings +) : CWAViewModel() { val navigationEvents = SingleLiveEvent<OnboardingFragmentEvents>() fun navigate() { when { - !LocalData.isOnboarded() -> { + !onboardingSettings.isOnboarded -> { navigationEvents.postValue(OnboardingFragmentEvents.ShowOnboarding) } - !LocalData.isInteroperabilityShownAtLeastOnce -> { + !cwaSettings.wasInteroperabilityShownAtLeastOnce -> { navigationEvents.postValue(OnboardingFragmentEvents.ShowInteropDeltaOnboarding) } cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE -> { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt index 844e900278f2dd137c08cddb441dd38f1f96ff03..dca5b110f9d065b6af5a29728cbeb3ae83dfa58b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.TracingPermissionHelper -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -20,7 +20,8 @@ import timber.log.Timber class OnboardingTracingFragmentViewModel @AssistedInject constructor( private val interoperabilityRepository: InteroperabilityRepository, tracingPermissionHelperFactory: TracingPermissionHelper.Factory, - dispatcherProvider: DispatcherProvider + dispatcherProvider: DispatcherProvider, + private val tracingSettings: TracingSettings, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val countryList = interoperabilityRepository.countryList @@ -62,8 +63,7 @@ class OnboardingTracingFragmentViewModel @AssistedInject constructor( try { if (InternalExposureNotificationClient.asyncIsEnabled()) { InternalExposureNotificationClient.asyncStop() - // Reset initial activation timestamp - LocalData.initialTracingActivationTimestamp(0L) + tracingSettings.isConsentGiven = false } } catch (exception: Exception) { exception.report( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/notifications/NotificationSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/notifications/NotificationSettings.kt index 3388bef869673426fcd8a42689d568f25fbfad09..6ecc209f9e0f46a21ee7645c350f5d6bef2248d3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/notifications/NotificationSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/notifications/NotificationSettings.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.ui.settings.notifications import androidx.core.app.NotificationManagerCompat -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.util.device.ForegroundState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -11,6 +11,7 @@ import javax.inject.Singleton @Singleton class NotificationSettings @Inject constructor( foregroundState: ForegroundState, + private val cwaSettings: CWASettings, private val notificationManagerCompat: NotificationManagerCompat ) { @@ -20,25 +21,23 @@ class NotificationSettings @Inject constructor( notificationManagerCompat.areNotificationsEnabled() } - val isNotificationsRiskEnabled: Flow<Boolean> = LocalData.isNotificationsRiskEnabledFlow + val isNotificationsRiskEnabled: Flow<Boolean> = cwaSettings.isNotificationsRiskEnabled.flow /** * Toggle notifications risk updates. * - * @see LocalData */ fun toggleNotificationsRiskEnabled() { - LocalData.isNotificationsRiskEnabled = !LocalData.isNotificationsRiskEnabled + cwaSettings.isNotificationsRiskEnabled.update { !it } } - val isNotificationsTestEnabled: Flow<Boolean> = LocalData.isNotificationsTestEnabledFlow + val isNotificationsTestEnabled: Flow<Boolean> = cwaSettings.isNotificationsTestEnabled.flow /** * Toggle notifications for test updates in shared preferences and refresh it afterwards. * - * @see LocalData */ fun toggleNotificationsTestEnabled() { - LocalData.isNotificationsTestEnabled = !LocalData.isNotificationsTestEnabled + cwaSettings.isNotificationsTestEnabled.update { !it } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt deleted file mode 100644 index 455b50499dbdf7d59466d2986b0b271056ca4dc7..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt +++ /dev/null @@ -1,168 +0,0 @@ -package de.rki.coronawarnapp.ui.view - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.RectF -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.FrameLayout -import androidx.core.content.ContextCompat -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.databinding.ViewCircleProgressBinding -import de.rki.coronawarnapp.risk.TimeVariables - -/** - * Used on the tracing details fragment without text and also on the risk card with the progress - * number in the circle. - * - * @param context - * @param attrs - * @param defStyleAttr - */ -class CircleProgress @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - companion object { - private const val START_ANGLE = 270f - private const val FULL_CIRCLE = 360f - private const val DEFAULT_WIDTH = 10f - private val DEFAULT_MAX_PROGRESS = TimeVariables.getDefaultRetentionPeriodInDays().toFloat() - } - - private val circlePaint: Paint - private var progressPaint: Paint - private val rect = RectF() - private var binding: ViewCircleProgressBinding - private var centerX: Float = 0f - private var centerY: Float = 0f - private var radius: Float = 0f - private var progressWidth: Float = 0f - private var disableText: Boolean = false - - /** - * Setter for progress. Text and icon depend on the progress value. - * The visibility is also influenced by the disableText attribute. - */ - var progress: Float = 0F - set(value) { - field = value - val body = binding.circleProgressBody - val icon = binding.circleProgressIcon - // text visibility - if (disableText || value == DEFAULT_MAX_PROGRESS) { - body.visibility = View.GONE - } else { - body.visibility = View.VISIBLE - body.text = context.getString(R.string.risk_details_information_active_tracing_days_circle_progress) - .format(value.toInt()) - } - // icon visibility - if (value == DEFAULT_MAX_PROGRESS) { - icon.visibility = View.VISIBLE - } else { - icon.visibility = View.GONE - } - invalidate() - } - - /** - * Setter for the progress circle color. - * The progress bar also needs to be repainted when the - * color changes (ex: when the risk calculation gets turned on/off) - */ - var progressColor: Int = Color.TRANSPARENT - set(value) { - field = value - binding.circleProgressIcon.setColorFilter(value, android.graphics.PorterDuff.Mode.SRC_IN) - progressPaint = paintProgressCircle() - invalidate() - } - - /** - * Initialise the view with the following attributes or some default values: - * - circleColor - * - textColor - * - disableText - * - progressWidth - */ - init { - setWillNotDraw(false) - binding = ViewCircleProgressBinding.inflate(LayoutInflater.from(context), this) - val styleAttrs = context.obtainStyledAttributes(attrs, R.styleable.CircleProgress) - val circleColor = styleAttrs.getColor( - R.styleable.CircleProgress_circleColor, - ContextCompat.getColor(context, R.color.colorSurface2) - ) - // attribute progressColor; default = colorAccentTintIcon - val progressColor = styleAttrs.getColor( - R.styleable.CircleProgress_progressColor, - ContextCompat.getColor(context, R.color.colorAccentTintIcon) - ) - // attribute textColor; default = colorTextPrimary2 - val textColor = styleAttrs.getColor( - R.styleable.CircleProgress_textColor, - ContextCompat.getColor(context, R.color.colorTextPrimary2) - ) - // attribute disableText; default = true - disableText = styleAttrs.getBoolean(R.styleable.CircleProgress_disableText, false) - // attribute progressWidth; default = DEFAULT_WIDTH - progressWidth = styleAttrs.getFloat(R.styleable.CircleProgress_circleWidth, DEFAULT_WIDTH) - // attribute progress; default = 0 - progress = styleAttrs.getFloat(R.styleable.CircleProgress_progress, 0F) - // set textColor - val body = binding.circleProgressBody - body.setTextColor(textColor) - // set icon color - val icon = binding.circleProgressIcon - icon.setColorFilter(progressColor, android.graphics.PorterDuff.Mode.SRC_IN) - // circlePaint based on the attributes and default value - circlePaint = Paint().apply { - color = circleColor - style = Paint.Style.STROKE - strokeWidth = progressWidth - isAntiAlias = true - } - // progressPaint based on the attributes and default value - progressPaint = paintProgressCircle() - styleAttrs.recycle() - } - - private fun paintProgressCircle() = - Paint().apply { - color = progressColor - style = Paint.Style.STROKE - strokeWidth = progressWidth - isAntiAlias = true - strokeCap = Paint.Cap.ROUND - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - centerX = w.toFloat().div(2) - centerY = h.toFloat().div(2) - radius = w.toFloat().div(2).minus(progressWidth) - rect.set( - centerX.minus(radius), - centerY.minus(radius), - centerX.plus(radius), - centerY.plus(radius) - ) - super.onSizeChanged(w, h, oldw, oldh) - } - - override fun onDraw(canvas: Canvas?) { - super.onDraw(canvas) - canvas?.drawCircle(centerX, centerY, radius, circlePaint) - canvas?.drawArc( - rect, - START_ANGLE, - FULL_CIRCLE.times(progress).div(DEFAULT_MAX_PROGRESS), - false, - progressPaint - ) - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt index d08cc5a6c3dd54ec1e878886eeb09813c8d87455..9cfc42f4eb2fac80e98b96c934975aeb17e90f36 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt @@ -4,14 +4,17 @@ import android.annotation.SuppressLint import android.app.Application import android.os.Build import androidx.annotation.VisibleForTesting -import de.rki.coronawarnapp.BuildConfig import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger +import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.util.debug.UncaughtExceptionLogger import de.rki.coronawarnapp.util.di.ApplicationComponent import timber.log.Timber object CWADebug { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + var debugLoggerFactory: (Application) -> DebugLogger = { DebugLogger(context = it) } + @SuppressLint("StaticFieldLeak") lateinit var debugLogger: DebugLogger @@ -24,15 +27,19 @@ object CWADebug { setupExceptionHandler() - debugLogger = DebugLogger(context = application).also { - it.init() + debugLogger = debugLoggerFactory(application).also { + // TODO Disabled until all parties are satisfied, search for ¯\_(ツ)_/¯ + if (isDeviceForTestersBuild) it.init() } logDeviceInfos() } fun initAfterInjection(component: ApplicationComponent) { - debugLogger.setInjectionIsReady(component) + // TODO ¯\_(ツ)_/¯ + if (isDeviceForTestersBuild) { + debugLogger.setInjectionIsReady(component) + } } val isLogging: Boolean @@ -42,12 +49,13 @@ object CWADebug { } val isDebugBuildOrMode: Boolean - get() = BuildConfig.DEBUG || buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS + get() = BuildConfigWrap.DEBUG || buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS val buildFlavor: BuildFlavor - get() = BuildFlavor.values().single { it.rawValue == BuildConfig.FLAVOR } + get() = BuildFlavor.values().single { it.rawValue == BuildConfigWrap.FLAVOR } - val isDeviceForTestersBuild: Boolean = buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS + val isDeviceForTestersBuild: Boolean + get() = buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS enum class BuildFlavor(val rawValue: String) { DEVICE("device"), @@ -64,8 +72,8 @@ object CWADebug { } fun logDeviceInfos() { - Timber.i("CWA version: %s (%s)", BuildConfig.VERSION_CODE, BuildConfig.GIT_COMMIT_SHORT_HASH) - Timber.i("CWA flavor: %s (%s)", BuildConfig.FLAVOR, BuildConfig.BUILD_TYPE) + Timber.i("CWA version: %s (%s)", BuildConfigWrap.VERSION_CODE, BuildConfigWrap.GIT_COMMIT_SHORT_HASH) + Timber.i("CWA flavor: %s (%s)", BuildConfigWrap.FLAVOR, BuildConfigWrap.BUILD_TYPE) Timber.i("Build.FINGERPRINT: %s", Build.FINGERPRINT) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt index d78661d2b7a2a4ac2d2152c02ade522a4c19440c..08eec626f20d6005b8aec183c6fa79f9a2fc0a7c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt @@ -15,10 +15,11 @@ import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.statistics.source.StatisticsProvider -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.di.AppContext -import de.rki.coronawarnapp.util.security.SecurityHelper import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import timber.log.Timber @@ -45,7 +46,10 @@ class DataReset @Inject constructor( private val surveySettings: SurveySettings, private val analyticsSettings: AnalyticsSettings, private val analytics: Analytics, - private val bugReportingSettings: BugReportingSettings + private val bugReportingSettings: BugReportingSettings, + private val tracingSettings: TracingSettings, + private val onboardingSettings: OnboardingSettings, + private val submissionSettings: SubmissionSettings ) { private val mutex = Mutex() @@ -57,11 +61,6 @@ class DataReset @Inject constructor( @SuppressLint("ApplySharedPref") // We need a commit here to ensure consistency suspend fun clearAllLocalData() = mutex.withLock { Timber.w("CWA LOCAL DATA DELETION INITIATED.") - // Because LocalData does not behave like a normal shared preference - LocalData.clear() - // Shared Preferences Reset - SecurityHelper.resetSharedPrefs() - // Triggers deletion of all analytics contributed data analytics.setAnalyticsEnabled(false) @@ -76,6 +75,9 @@ class DataReset @Inject constructor( cwaSettings.clear() surveySettings.clear() analyticsSettings.clear() + tracingSettings.clear() + onboardingSettings.clear() + submissionSettings.clear() // Clear contact diary database contactDiaryRepository.clear() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/RetryMechanism.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/RetryMechanism.kt index ba2a372b51a99e49b2fdd04881f0d47d2b99f2c6..474bd237cdda0f3d3ae0a2ff9c284c6e3ef04780 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/RetryMechanism.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/RetryMechanism.kt @@ -40,7 +40,7 @@ object RetryMechanism { } } - private const val DEFAULT_TOTAL_MAX_RETRY = 15 * 1000L // 15 seconds total delay + private const val DEFAULT_TOTAL_MAX_RETRY = 7 * 1000L // 7 seconds total delay private const val DEFAULT_MAX_DELAY = 3 * 1000L // 3 seconds max between retries private const val DEFAULT_MIN_DELAY = 25L // Almost immediate retry private const val DEFAULT_RETRY_MULTIPLIER = 1.5 diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt index 0a8833e9d86b937a49eab52c690c9e6a276cad6a..cfb9c9fca4ede8f16ae9bd4bf76ac2bfffde1c9c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt @@ -55,6 +55,14 @@ object TimeAndDateExtensions { } } + /** + * Converts a Long to Instant or null if the long is 0 or null + */ + fun Long?.toInstantOrNull(): Instant? = + if (this != null && this != 0L) { + Instant.ofEpochMilli(this) + } else null + /** * Converts milliseconds to human readable format hh:mm:ss * diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt index 3365de8462647f20759cd2856b946090ccac19e2..6ead4ba58ab9d791885e0f583891dcad02328f09 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt @@ -6,7 +6,7 @@ import android.os.PowerManager import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.common.DefaultTaskRequest import de.rki.coronawarnapp.task.submitBlocking @@ -27,7 +27,8 @@ class WatchdogService @Inject constructor( @AppContext private val context: Context, private val taskController: TaskController, private val backgroundModeStatus: BackgroundModeStatus, - @ProcessLifecycle private val processLifecycleOwner: LifecycleOwner + @ProcessLifecycle private val processLifecycleOwner: LifecycleOwner, + private val onboardingSettings: OnboardingSettings ) { private val powerManager by lazy { @@ -73,7 +74,9 @@ class WatchdogService @Inject constructor( // if the user is onboarded we will schedule period background jobs // in case the app was force stopped and woken up again by the Google WakeUpService - if (LocalData.onboardingCompletedTimestamp() != null) BackgroundWorkScheduler.startWorkScheduler() + if (onboardingSettings.isOnboarded) { + BackgroundWorkScheduler.startWorkScheduler() + } } private fun createWakeLock(): PowerManager.WakeLock = powerManager diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt index 9211c208ac46fab9ed713599db11e38789e7213c..f7557924b1dada96fa221eb94ec53a28a6396af8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt @@ -13,9 +13,13 @@ class UncaughtExceptionLogger( override fun uncaughtException(thread: Thread, error: Throwable) { Timber.tag(thread.name).e(error, "Uncaught exception!") - if (CWADebug.isLogging) { - // Make sure this crash is written before killing the app. - Thread.sleep(1500) + try { + if (CWADebug.isLogging) { + // Make sure this crash is written before killing the app. + Thread.sleep(1500) + } + } catch (e: Exception) { + Timber.w("Couldn't delay exception for debug logger.") } wrappedHandler?.uncaughtException(thread, error) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt index d548e33cdd1043f678b41558e4d97da5c38a2a43..813ad1b51f51fff8377f658371239fc01d278f6f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt @@ -6,7 +6,7 @@ import android.app.NotificationManager import android.bluetooth.BluetoothAdapter import android.content.ContentResolver import android.content.Context -import android.content.SharedPreferences +import android.content.pm.ApplicationInfo import androidx.core.app.NotificationManagerCompat import androidx.core.content.getSystemService import androidx.lifecycle.LifecycleOwner @@ -18,8 +18,6 @@ import com.google.android.gms.safetynet.SafetyNetClient import dagger.Module import dagger.Provides import de.rki.coronawarnapp.CoronaWarnApplication -import de.rki.coronawarnapp.storage.EncryptedPreferences -import de.rki.coronawarnapp.util.security.SecurityHelper import de.rki.coronawarnapp.util.worker.WorkManagerProvider import javax.inject.Singleton @@ -57,11 +55,6 @@ class AndroidModule { workManagerProvider: WorkManagerProvider ): WorkManager = workManagerProvider.workManager - @EncryptedPreferences - @Provides - @Singleton - fun encryptedPreferences(): SharedPreferences = SecurityHelper.globalEncryptedSharedPreferencesInstance - @Provides fun navDeepLinkBuilder(@AppContext context: Context): NavDeepLinkBuilder = NavDeepLinkBuilder(context) @@ -80,4 +73,7 @@ class AndroidModule { @Provides fun contentResolver(@AppContext context: Context): ContentResolver = context.contentResolver + + @Provides + fun applicationInfo(@AppContext context: Context): ApplicationInfo = context.applicationInfo } 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 da4d4e081a5b64cb8664754d5b950c536c7513f6..35ee578c350d1feeaaaf43f1a4a3e9852c896171 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 @@ -41,6 +41,7 @@ import de.rki.coronawarnapp.util.security.SecurityModule import de.rki.coronawarnapp.util.serialization.SerializationModule import de.rki.coronawarnapp.util.worker.WorkerBinder import de.rki.coronawarnapp.verification.VerificationModule +import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import javax.inject.Singleton @Singleton @@ -84,6 +85,7 @@ interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { val enfClient: ENFClient val encryptedPreferencesFactory: EncryptedPreferencesFactory + val errorResetTool: EncryptionErrorResetTool val playbook: Playbook @@ -96,6 +98,8 @@ interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { fun inject(logger: DebugLogger) + fun inject(backgroundWorkScheduler: BackgroundWorkScheduler) + @Component.Factory interface Factory { fun create(@BindsInstance app: CoronaWarnApplication): ApplicationComponent diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt index ae4f6a47b04c194e57d4a36a28ed30a3995579e5..fd87b8a0ab525bacf7f0e9640088e7e87aedc36d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt @@ -3,127 +3,25 @@ package de.rki.coronawarnapp.util.security import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit -import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppContext -import de.rki.coronawarnapp.util.errors.causes -import org.joda.time.Instant -import timber.log.Timber -import java.io.File -import java.security.GeneralSecurityException import javax.inject.Inject import javax.inject.Singleton -/** - * This tool determines the narrow scope for which we will recovery from an encryption error - * by resetting our encrypted data. - * This will allow users currently affected by it, that update the app, to keep using it without - * requiring any manual actions from their side. - * - * https://github.com/corona-warn-app/cwa-app-android/issues/642 - */ @Singleton class EncryptionErrorResetTool @Inject constructor( - @AppContext private val context: Context, - private val timeStamper: TimeStamper + @AppContext private val context: Context ) { - // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ContextImpl.java;drc=3b8e8d76315f6718a982d5e6a019b3aa4f634bcd;l=626 - private val encryptedPreferencesFile by lazy { - val appbaseDir = context.filesDir.parentFile!! - val sharedPrefsDir = File(appbaseDir, "shared_prefs") - File(sharedPrefsDir, "${SecurityConstants.ENCRYPTED_SHARED_PREFERENCES_FILE}.xml") - } private val prefs: SharedPreferences by lazy { context.getSharedPreferences("encryption_error_reset_tool", Context.MODE_PRIVATE) } - private var isResetWindowConsumed: Boolean - get() = prefs.getBoolean(PKEY_EA1851_WAS_WINDOW_CONSUMED, false) - set(value) = prefs.edit { - putBoolean(PKEY_EA1851_WAS_WINDOW_CONSUMED, value) - } - - private var resetPerformedAt: Instant? - get() = prefs.getLong(PKEY_EA1851_RESET_PERFORMED_AT, -1).let { - if (it == -1L) null else Instant.ofEpochMilli(it) - } - set(value) = prefs.edit { - if (value != null) { - putLong(PKEY_EA1851_RESET_PERFORMED_AT, value.millis) - } else { - remove(PKEY_EA1851_RESET_PERFORMED_AT) - } - } - var isResetNoticeToBeShown: Boolean get() = prefs.getBoolean(PKEY_EA1851_SHOW_RESET_NOTICE, false) set(value) = prefs.edit { putBoolean(PKEY_EA1851_SHOW_RESET_NOTICE, value) } - fun tryResetIfNecessary(error: Throwable): Boolean { - Timber.w(error, "tryResetIfNecessary()") - - // We only try to reset once, if the error reoccurs, on-going resets is not the solution. - if (isResetWindowConsumed) { - Timber.v("Reset window has been consumed -> no reset.") - return false - } - isResetWindowConsumed = true - - val keyException = error.causes().lastOrNull() - if (keyException == null || keyException !is GeneralSecurityException) { - Timber.v("Error has no GeneralSecurityException as cause -> no reset.") - return false - } - - // https://github.com/google/tink/blob/a8ec74d083068cd5e1ebed86fd8254630617b592/java_src/src/main/java/com/google/crypto/tink/aead/AeadWrapper.java#L83 - if (keyException.message?.trim()?.equals("decryption failed") != true) { - Timber.v("Not the GeneralSecurityException we are looking for -> no reset.") - return false - } - - if (!encryptedPreferencesFile.exists()) { - // The error we are looking for can only happen if there already is an encrypted preferences file - Timber.w( - "Error fits, but where is the existing preference file (%s)? -> no reset.", - encryptedPreferencesFile - ) - return false - } - - // So we have a GeneralSecurityException("decryption failed") and existing preferences - // And this is the first error we encountered after upgrading to 1.4.0, let's do this! - - return try { - performReset() - } catch (e: Exception) { - // If anything goes wrong, we return false, so that our caller can rethrow the original error. - Timber.e(e, "Error while performing the reset.") - false - } - } - - private fun performReset(): Boolean { - // Delete encrypted shared preferences file - if (!encryptedPreferencesFile.delete()) { - Timber.w("Couldn't delete %s", encryptedPreferencesFile) - // The encrypted preferences are a prerequisite for this error case - // If we can't delete that, we have to assume our reset approach failed. - return false - } - - resetPerformedAt = timeStamper.nowUTC - isResetNoticeToBeShown = true - - return true - } - companion object { - private const val PKEY_EA1851_RESET_PERFORMED_AT = "ea1851.reset.performedAt" - - @Suppress("unused") - private const val PKEY_EA1851_WAS_WINDOW_CONSUMED_OLD = "ea1851.reset.windowconsumed" - private const val PKEY_EA1851_WAS_WINDOW_CONSUMED = "ea1851.reset.windowconsumed.160" private const val PKEY_EA1851_SHOW_RESET_NOTICE = "ea1851.reset.shownotice" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt deleted file mode 100644 index cad222cb23d445517cd1760ef0762b155740f74a..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt +++ /dev/null @@ -1,57 +0,0 @@ -package de.rki.coronawarnapp.util.security - -import android.annotation.SuppressLint -import android.content.SharedPreferences -import android.util.Base64 -import androidx.annotation.VisibleForTesting -import de.rki.coronawarnapp.exception.CwaSecurityException -import de.rki.coronawarnapp.util.di.AppInjector -import de.rki.coronawarnapp.util.di.ApplicationComponent -import de.rki.coronawarnapp.util.preferences.clearAndNotify -import de.rki.coronawarnapp.util.security.SecurityConstants.ENCRYPTED_SHARED_PREFERENCES_FILE -import timber.log.Timber - -/** - * Key Store and Password Access - */ -object SecurityHelper { - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal val encryptedPreferencesProvider: (ApplicationComponent) -> SharedPreferences = { - val factory = it.encryptedPreferencesFactory - val encryptionErrorResetTool = it.errorResetTool - withSecurityCatch { - try { - factory.create(ENCRYPTED_SHARED_PREFERENCES_FILE) - } catch (e: Exception) { - if (encryptionErrorResetTool.tryResetIfNecessary(e)) { - Timber.w("We could recovery from this error via reset. Now retrying.") - factory.create(ENCRYPTED_SHARED_PREFERENCES_FILE) - } else { - throw e - } - } - } - } - - val globalEncryptedSharedPreferencesInstance: SharedPreferences by lazy { - encryptedPreferencesProvider(AppInjector.component) - } - - private val String.toPreservedByteArray: ByteArray - get() = Base64.decode(this, Base64.NO_WRAP) - - private val ByteArray.toPreservedString: String - get() = Base64.encodeToString(this, Base64.NO_WRAP) - - @SuppressLint("ApplySharedPref") - fun resetSharedPrefs() { - globalEncryptedSharedPreferencesInstance.clearAndNotify() - } - - fun <T> withSecurityCatch(doInCatch: () -> T) = try { - doInCatch.invoke() - } catch (e: Exception) { - throw CwaSecurityException(e) - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/views/MoreInformationView.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/views/MoreInformationView.kt new file mode 100644 index 0000000000000000000000000000000000000000..a36e2c0ba31cd4bc69328077c0aac71246b82ecf --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/views/MoreInformationView.kt @@ -0,0 +1,49 @@ +package de.rki.coronawarnapp.util.ui.views + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.withStyledAttributes +import androidx.core.view.isVisible +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.ViewMoreInformationBinding + +class MoreInformationView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private val binding: ViewMoreInformationBinding + + init { + LayoutInflater.from(context).inflate(R.layout.view_more_information, this, true) + + val outValue = TypedValue() + getContext().theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true) + setBackgroundResource(outValue.resourceId) + binding = ViewMoreInformationBinding.bind(this) + + context.withStyledAttributes(attrs, R.styleable.MoreInformationView) { + + val titleText = getText(R.styleable.MoreInformationView_titleText) ?: "" + val subtitleText = getText(R.styleable.MoreInformationView_subtitleText) ?: "" + val isTopDividerVisible = getBoolean(R.styleable.MoreInformationView_isTopDividerVisible, true) + val isBottomDividerVisible = getBoolean(R.styleable.MoreInformationView_isBottomDividerVisible, true) + + binding.apply { + + topDivider.isVisible = isTopDividerVisible + bottomDivider.isVisible = isBottomDividerVisible + + moreInformationTitle.text = titleText + moreInformationTitle.isVisible = titleText.isNotEmpty() + + moreInformationSubtitle.text = subtitleText + moreInformationSubtitle.isVisible = subtitleText.isNotEmpty() + } + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt index f66cda4621d977c92e19d16da1c1fe0b5c8cf9cd..31922abf4120e6341508e056d82b84386aec82ab 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt @@ -6,11 +6,12 @@ import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import de.rki.coronawarnapp.worker.BackgroundWorkScheduler.stop -import org.joda.time.DateTime -import org.joda.time.DateTimeZone +import org.joda.time.Duration +import org.joda.time.Instant import timber.log.Timber /** @@ -20,7 +21,9 @@ import timber.log.Timber */ class BackgroundNoisePeriodicWorker @AssistedInject constructor( @Assisted val context: Context, - @Assisted workerParams: WorkerParameters + @Assisted workerParams: WorkerParameters, + private val submissionSettings: SubmissionSettings, + private val timeStamper: TimeStamper ) : CoroutineWorker(context, workerParams) { /** @@ -31,13 +34,13 @@ class BackgroundNoisePeriodicWorker @AssistedInject constructor( var result = Result.success() try { - val initialPairingDate = DateTime( - LocalData.devicePairingSuccessfulTimestamp(), - DateTimeZone.UTC - ) + val initialPairingDate = submissionSettings.devicePairingSuccessfulAt ?: Instant.ofEpochMilli(0) // Check if the numberOfDaysToRunPlaybook are over - if (initialPairingDate.plusDays(BackgroundConstants.NUMBER_OF_DAYS_TO_RUN_PLAYBOOK).isBeforeNow) { + if (initialPairingDate + .plus(Duration.standardDays(NUMBER_OF_DAYS_TO_RUN_PLAYBOOK)) + .isBefore(timeStamper.nowUTC) + ) { stopWorker() return result } @@ -64,5 +67,6 @@ class BackgroundNoisePeriodicWorker @AssistedInject constructor( companion object { private val TAG = BackgroundNoisePeriodicWorker::class.java.simpleName + private const val NUMBER_OF_DAYS_TO_RUN_PLAYBOOK = BackgroundConstants.NUMBER_OF_DAYS_TO_RUN_PLAYBOOK.toLong() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt index 83804e32536b4875d0ae8917afbf951244699d5d..aaf6317d9be63bf12856a97c46cc9665512e65b7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt @@ -6,7 +6,7 @@ import androidx.work.Operation import androidx.work.WorkInfo import androidx.work.WorkManager import de.rki.coronawarnapp.CoronaWarnApplication -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.util.di.ApplicationComponent import timber.log.Timber import java.util.concurrent.ExecutionException @@ -17,7 +17,11 @@ import java.util.concurrent.ExecutionException * @see BackgroundConstants * @see BackgroundWorkHelper */ -object BackgroundWorkScheduler { +object BackgroundWorkScheduler : BackgroundWorkSchedulerBase() { + + fun init(component: ApplicationComponent) { + component.inject(this) + } /** * Enum class for work tags @@ -67,7 +71,7 @@ object BackgroundWorkScheduler { * Checks if periodic worker was already scheduled. If not - reschedule it again. * For [WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER] also checks if User is Registered * - * @see LocalData.registrationToken + * @see de.rki.coronawarnapp.submission.SubmissionSettings.registrationToken * @see isWorkActive */ fun startWorkScheduler() { @@ -82,12 +86,13 @@ object BackgroundWorkScheduler { WorkType.DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK.start() notificationBody.append("[DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK] ") } - if (!LocalData.submissionWasSuccessful()) { + if (!submissionSettings.isSubmissionSuccessful) { if (!isWorkActive(WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER.tag) && - LocalData.registrationToken() != null && !LocalData.isTestResultAvailableNotificationSent() + submissionSettings.registrationToken.value != null && + !tracingSettings.isTestResultAvailableNotificationSent ) { WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.start() - LocalData.initialPollingForTestResultTimeStamp(System.currentTimeMillis()) + tracingSettings.initialPollingForTestResultTimeStamp = System.currentTimeMillis() notificationBody.append("[DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER]") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkSchedulerBase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkSchedulerBase.kt new file mode 100644 index 0000000000000000000000000000000000000000..9085a1645570aa546c4f8294904096753b46b17c --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkSchedulerBase.kt @@ -0,0 +1,11 @@ +package de.rki.coronawarnapp.worker + +import de.rki.coronawarnapp.storage.TracingSettings +import de.rki.coronawarnapp.submission.SubmissionSettings +import javax.inject.Inject + +@Suppress("UnnecessaryAbstractClass") +abstract class BackgroundWorkSchedulerBase { + @Inject internal lateinit var submissionSettings: SubmissionSettings + @Inject internal lateinit var tracingSettings: TracingSettings +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt index 1540f8aa6716b3fe2f59563403557f5b3f6bc0ea..dc01b596996a6a4ba9f9e2da9e9ee1d4f944bcfc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt @@ -11,7 +11,7 @@ import de.rki.coronawarnapp.notification.NotificationConstants import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService import de.rki.coronawarnapp.service.submission.SubmissionService -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeAndDateExtensions import de.rki.coronawarnapp.util.TimeStamper @@ -32,7 +32,8 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( private val notificationHelper: NotificationHelper, private val submissionSettings: SubmissionSettings, private val submissionService: SubmissionService, - private val timeStamper: TimeStamper + private val timeStamper: TimeStamper, + private val tracingSettings: TracingSettings, ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { @@ -55,7 +56,8 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( } else { Timber.tag(TAG).d(" $id Running worker.") - val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() + val registrationToken = + submissionSettings.registrationToken.value ?: throw NoRegistrationTokenSetException() val testResult = submissionService.asyncRequestTestResult(registrationToken) Timber.tag(TAG).d("$id: Test Result retrieved is $testResult") @@ -80,7 +82,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( } private fun abortConditionsMet(currentMillis: Long): Boolean { - if (LocalData.isTestResultAvailableNotificationSent()) { + if (tracingSettings.isTestResultAvailableNotificationSent) { Timber.tag(TAG).d("$id: Notification already sent.") return true } @@ -90,7 +92,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( } val calculateDays = TimeAndDateExtensions.calculateDays( - LocalData.initialPollingForTestResultTimeStamp(), + tracingSettings.initialPollingForTestResultTimeStamp, currentMillis ) if (calculateDays >= BackgroundConstants.POLLING_VALIDITY_MAX_DAYS) { @@ -104,7 +106,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( private suspend fun sendTestResultAvailableNotification(testResult: TestResult) { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) - LocalData.isTestResultAvailableNotificationSent(true) + tracingSettings.isTestResultAvailableNotificationSent = true } private fun cancelRiskLevelScoreNotification() { @@ -114,7 +116,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( } private fun stopWorker() { - LocalData.initialPollingForTestResultTimeStamp(0L) + tracingSettings.initialPollingForTestResultTimeStamp = 0L BackgroundWorkScheduler.WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.stop() Timber.tag(TAG).d("$id: Background worker stopped") } diff --git a/Corona-Warn-App/src/main/res/drawable/ic_download.xml b/Corona-Warn-App/src/main/res/drawable/ic_download.xml new file mode 100644 index 0000000000000000000000000000000000000000..17573ee7cd83e65878fe579a058951a93e943c04 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_download.xml @@ -0,0 +1,13 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="40dp" + android:height="42dp" + android:viewportWidth="37" + android:viewportHeight="39"> + <group + android:translateX="8.5" + android:translateY="8.5"> + <path + android:fillColor="#ffffff" + android:pathData="M10,2.0132C14.41,2.0132 18,5.6032 18,10.0132C18,14.4232 14.41,18.0132 10,18.0132C5.59,18.0132 2,14.4232 2,10.0132C2,5.6032 5.59,2.0132 10,2.0132ZM10,0.0132C4.48,0.0132 0,4.4932 0,10.0132C0,15.5332 4.48,20.0132 10,20.0132C15.52,20.0132 20,15.5332 20,10.0132C20,4.4932 15.52,0.0132 10,0.0132ZM11,10.0132V6.0132H9V10.0132H6L10,14.0132L14,10.0132H11Z" /> + </group> +</vector> diff --git a/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml index dfc9429fea8f718c297e10d12afeae2dbed68eb9..404a352985dba21ef8178b33a0929670ba78edb6 100644 --- a/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_fragment.xml @@ -18,141 +18,83 @@ app:layout_constraintTop_toTopOf="parent" app:title="@string/debugging_debuglog_title" /> - <ScrollView + <androidx.core.widget.NestedScrollView android:id="@+id/scrollview" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="12dp" android:clipToPadding="false" - android:paddingBottom="32dp" + android:paddingBottom="8dp" app:layout_constraintBottom_toTopOf="@id/log_control_container" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/toolbar"> - <androidx.constraintlayout.widget.ConstraintLayout + <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp"> + android:layout_height="0dp" + android:orientation="vertical"> <TextView android:id="@+id/explanation_section_one" style="@style/body1" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="24dp" - android:padding="16dp" - android:text="@string/debugging_debuglog_intro_explanation_section_one" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + android:layout_marginHorizontal="24dp" + android:layout_marginTop="24dp" + android:text="@string/debugging_debuglog_intro_explanation_section_one" /> <TextView android:id="@+id/explanation_section_two" style="@style/body1" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="24dp" - android:text="@string/debugging_debuglog_intro_explanation_section_two" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/explanation_section_one" /> + android:layout_marginHorizontal="24dp" + android:layout_marginTop="16dp" + android:text="@string/debugging_debuglog_intro_explanation_section_two" /> <include android:id="@+id/debug_log_privacy_card" layout="@layout/include_debuglog_privacy_card" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/guideline_card" - android:layout_marginTop="16dp" - android:focusable="true" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/explanation_section_two" /> + android:layout_marginVertical="24dp" + android:focusable="true" /> - <androidx.constraintlayout.widget.ConstraintLayout + <de.rki.coronawarnapp.util.ui.views.MoreInformationView android:id="@+id/debug_log_history_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:visibility="gone" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/debug_log_privacy_card"> - - <TextView - android:id="@+id/log_history_title" - style="@style/body1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="24dp" - android:text="@string/debugging_debuglog_id_history_title" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <TextView - android:id="@+id/log_history_body" - style="@style/TextAppearance.AppCompat.Caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:layout_marginTop="4dp" - android:text="@string/debugging_debuglog_id_history_body" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/log_history_title" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - - <View - android:id="@+id/debug_log_first_divider" - android:layout_width="0dp" - android:layout_height="@dimen/card_divider" android:layout_marginHorizontal="24dp" - android:layout_marginTop="24dp" - android:background="?android:attr/listDivider" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/debug_log_history_container" /> + android:focusable="true" + android:visibility="gone" + app:isBottomDividerVisible="false" + app:subtitleText="@string/debugging_debuglog_id_history_body" + app:titleText="@string/debugging_debuglog_id_history_title" + tools:visibility="visible" /> - <TextView + <de.rki.coronawarnapp.util.ui.views.MoreInformationView android:id="@+id/debug_log_privacy_information" - style="@style/subtitle" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="24dp" - android:background="?selectableItemBackground" - android:clickable="true" android:focusable="true" - android:paddingVertical="8dp" - android:text="@string/contact_diary_onboarding_legal_information" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/debug_log_first_divider" - tools:text="@string/contact_diary_onboarding_legal_information" /> - - <View - android:id="@+id/debug_log_second_divider" - android:layout_width="0dp" - android:layout_height="@dimen/card_divider" - android:layout_marginHorizontal="24dp" - android:layout_marginBottom="24dp" - android:background="?android:attr/listDivider" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/debug_log_privacy_information" /> + app:titleText="@string/contact_diary_onboarding_legal_information" /> - </androidx.constraintlayout.widget.ConstraintLayout> + </LinearLayout> - </ScrollView> + </androidx.core.widget.NestedScrollView> <LinearLayout android:id="@+id/log_control_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorSurface1" + android:elevation="8dp" android:orientation="vertical" android:paddingStart="24dp" - android:paddingTop="16dp" + android:paddingTop="8dp" android:paddingEnd="24dp" android:paddingBottom="16dp" app:layout_constraintBottom_toBottomOf="parent" @@ -164,27 +106,31 @@ style="@style/headline5" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" + android:layout_marginBottom="8dp" android:accessibilityHeading="true" android:focusable="true" android:text="@string/debugging_debuglog_current_status_title" /> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/debug_log_current_status_card" - style="@style/cardTracing" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp"> + android:layout_marginBottom="16dp" + android:background="@drawable/card" + android:backgroundTint="@color/colorSurface2" + android:minHeight="64dp"> <ImageView android:id="@+id/debuglog_activity_indicator" android:layout_width="36dp" android:layout_height="36dp" + android:layout_marginVertical="8dp" + android:layout_marginStart="16dp" android:importantForAccessibility="no" android:src="@drawable/ic_debug_log_indicator_deactivated" - app:layout_constraintBottom_toBottomOf="@id/debuglog_status_secondary" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@id/debuglog_status_primary" + app:layout_constraintTop_toTopOf="parent" tools:src="@drawable/ic_debug_log_indicator_deactivated" /> <TextView @@ -193,9 +139,13 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + app:layout_constraintBottom_toTopOf="@id/debuglog_status_secondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/debuglog_activity_indicator" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" tools:text="@string/debugging_debuglog_status_not_recording" /> <TextView @@ -204,6 +154,9 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/debuglog_activity_indicator" app:layout_constraintTop_toBottomOf="@id/debuglog_status_primary" @@ -216,7 +169,7 @@ style="@style/buttonPrimary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" + android:layout_marginBottom="8dp" android:text="@string/debugging_debuglog_action_share_log" tools:text="@string/debugging_debuglog_action_share_log" /> @@ -225,7 +178,7 @@ style="@style/buttonPrimary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" + android:layout_marginBottom="8dp" android:text="@string/debugging_debuglog_action_local_log_store" tools:text="@string/debugging_debuglog_action_local_log_store" /> diff --git a/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_upload_fragment.xml b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_upload_fragment.xml index 236c03b46084c06bf16e5abe51d1809b155e74c8..6298fc9a4191e54736eba5c2e72d548ae515e4a5 100644 --- a/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_upload_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/bugreporting_debuglog_upload_fragment.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:bind="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/content_container" android:layout_width="match_parent" @@ -29,101 +28,53 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/toolbar"> - <androidx.constraintlayout.widget.ConstraintLayout + <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:orientation="vertical"> <TextView android:id="@+id/bugreporting_share_log_body_one" style="@style/subtitle" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="24dp" android:focusable="true" android:text="@string/debugging_debuglog_share_log_section_one" - app:layout_constraintEnd_toEndOf="@id/guideline_end" - app:layout_constraintStart_toStartOf="@id/guideline_start" - app:layout_constraintTop_toTopOf="parent" tools:text="@string/debugging_debuglog_share_log_section_one" /> <TextView android:id="@+id/bugreporting_share_log_body_two" style="@style/subtitle" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="16dp" android:focusable="true" android:text="@string/debugging_debuglog_share_log_section_two" - app:layout_constraintEnd_toEndOf="@id/guideline_end" - app:layout_constraintStart_toStartOf="@id/guideline_start" - app:layout_constraintTop_toBottomOf="@id/bugreporting_share_log_body_one" tools:text="@string/debugging_debuglog_share_log_section_two" /> <include android:id="@+id/bugreporting_share_log_privacy_card" layout="@layout/include_debugging_debuglog_share_privacy_card" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/guideline_card" - android:layout_marginTop="@dimen/spacing_normal" - android:focusable="true" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/bugreporting_share_log_body_two" /> - - <View - android:id="@+id/bugreporting_share_log_first_divider" - android:layout_width="0dp" - android:layout_height="@dimen/card_divider" - android:layout_marginTop="@dimen/spacing_small" - android:background="?android:attr/listDivider" - app:layout_constraintEnd_toStartOf="@id/guideline_end" - app:layout_constraintStart_toStartOf="@id/guideline_start" - app:layout_constraintTop_toBottomOf="@id/bugreporting_share_log_privacy_card" /> + android:layout_marginVertical="24dp" + android:focusable="true" /> - <TextView - android:id="@+id/debug_log_share_privacy_information" - style="@style/subtitle" - android:layout_width="0dp" + <de.rki.coronawarnapp.util.ui.views.MoreInformationView + android:id="@+id/debug_log_privacy_information" + android:layout_marginHorizontal="24dp" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?selectableItemBackground" - android:clickable="true" android:focusable="true" - android:paddingVertical="@dimen/spacing_tiny" - android:text="@string/debugging_debuglog_share_log_privacy_information" - app:layout_constraintEnd_toEndOf="@id/guideline_end" - app:layout_constraintStart_toStartOf="@id/guideline_start" - app:layout_constraintTop_toBottomOf="@id/bugreporting_share_log_first_divider" - tools:text="@string/debugging_debuglog_share_log_privacy_information" /> - - <View - android:id="@+id/bugreporting_share_log_second_divider" - android:layout_width="0dp" - android:layout_height="@dimen/card_divider" - android:background="?android:attr/listDivider" - app:layout_constraintBottom_toBottomOf="parent" - bind:layout_constraintEnd_toStartOf="@id/guideline_end" - bind:layout_constraintStart_toStartOf="@id/guideline_start" - bind:layout_constraintTop_toBottomOf="@id/debug_log_share_privacy_information" /> + app:titleText="@string/debugging_debuglog_share_log_privacy_information" /> - <androidx.constraintlayout.widget.Guideline - android:id="@+id/guideline_start" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintGuide_begin="@dimen/guideline_start" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/guideline_end" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintGuide_end="@dimen/guideline_end" /> - - </androidx.constraintlayout.widget.ConstraintLayout> + </LinearLayout> </ScrollView> - <android.widget.Button android:id="@+id/upload_action" style="@style/buttonPrimary" diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml index 3de93f29fcd5a4f7fecad7347cefc6ae7dbc4cbb..42477033021ca6ea808034d7e6b1cfc5436cf38f 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_item.xml @@ -27,9 +27,10 @@ <TextView android:id="@+id/duration_input" style="@style/bodyNeutral" - android:layout_width="70dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" + android:minWidth="70dp" android:paddingTop="11dp" android:paddingBottom="11dp" android:paddingLeft="13dp" diff --git a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml index 617cb80e27951675541028f754f587fb88c60ad1..de95af0364cb3e978990aa01ff4e975c10982a87 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml @@ -169,8 +169,8 @@ app:buttonText="@{@string/settings_tracing_status_location_button}" app:headline="@{@string/settings_tracing_status_location_headline}" app:icon="@{@drawable/ic_location}" - app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" - app:layout_constraintStart_toStartOf="@+id/guideline_card_start" + app:layout_constraintEnd_toStartOf="@id/guideline_card_end" + app:layout_constraintStart_toStartOf="@id/guideline_card_start" app:layout_constraintTop_toTopOf="parent" /> <include @@ -183,22 +183,58 @@ app:buttonText="@{@string/settings_tracing_status_bluetooth_button}" app:headline="@{@string/settings_tracing_status_bluetooth_headline}" app:icon="@{@drawable/ic_settings_tracing_bluetooth}" - app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" - app:layout_constraintStart_toStartOf="@+id/guideline_card_start" + app:layout_constraintEnd_toStartOf="@id/guideline_card_end" + app:layout_constraintStart_toStartOf="@id/guideline_card_start" app:layout_constraintTop_toTopOf="parent" /> <TextView - android:id="@+id/risk_details_period_logged_body_notice" - style="@style/subtitleMedium" - gone="@{!settingsTracingState.isTracingStatusTextVisible()}" + android:id="@+id/risk_details_period_logged_headline" + style="@style/headline5" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:focusable="false" + android:text="@string/risk_details_headline_period_logged" + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/settings_tracing_status_bluetooth" /> + + <TextView + android:id="@+id/risk_details_period_logged_subtitle" + style="@style/subtitle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_tiny" + android:focusable="false" + android:text="@string/risk_details_subtitle_period_logged" + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_headline" /> + + <TextView + android:id="@id/risk_details_period_logged_body_notice" + style="@style/subtitle" + gone="@{!settingsTracingState.isTracingStatusTextVisible()}" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" android:focusable="true" android:text="@string/risk_details_information_body_period_logged" - app:layout_constraintEnd_toStartOf="@+id/guideline_end" - app:layout_constraintStart_toStartOf="@+id/guideline_start" - app:layout_constraintTop_toBottomOf="@+id/settings_tracing_status_bluetooth" /> + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_subtitle" /> + + <TextView + android:id="@+id/risk_details_period_logged_days" + style="@style/subtitle" + gone="@{!settingsTracingState.isTracingStatusTextVisible()}" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:focusable="true" + android:text="@{loggedPeriod.getInstallTimePeriodLogged(context)}" + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_body_notice" /> <include layout="@layout/merge_guidelines_card" /> diff --git a/Corona-Warn-App/src/main/res/layout/submission_blocking_dialog_view.xml b/Corona-Warn-App/src/main/res/layout/submission_blocking_dialog_view.xml index 3ae19f36bc16dedab8b84a46e54bdb24e113bbe3..40cf09352152cdeeae077ace3141b38048ff4d62 100644 --- a/Corona-Warn-App/src/main/res/layout/submission_blocking_dialog_view.xml +++ b/Corona-Warn-App/src/main/res/layout/submission_blocking_dialog_view.xml @@ -23,7 +23,7 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" - android:text="@string/submission_test_result_pending_steps_waiting_body" + android:text="@string/submission_status_card_title_fetching" app:layout_constraintBottom_toBottomOf="@+id/progress_indicator" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml index 2d2c4b51f3e265c53351332975a12e8d3c135d58..12af62996f2fc7360995f144a1f974461e5fe820 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml @@ -24,7 +24,7 @@ android:accessibilityHeading="true" android:text="@string/risk_card_increased_risk_headline" android:textColor="@color/colorTextPrimary1InvertedStable" - app:layout_constraintEnd_toStartOf="@+id/details_icon" + app:layout_constraintEnd_toStartOf="@id/details_icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_goneMarginEnd="0dp" /> @@ -52,7 +52,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/headline" + app:layout_constraintTop_toBottomOf="@id/headline" tools:text="@plurals/risk_card_high_risk_encounter_days_body" /> <de.rki.coronawarnapp.ui.view.TracingCardInfoRow @@ -66,7 +66,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_contact" + app:layout_constraintTop_toBottomOf="@id/row_contact" tools:text="@string/risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day" /> <androidx.constraintlayout.widget.ConstraintLayout @@ -90,7 +90,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_tracing_days" + app:layout_constraintTop_toBottomOf="@id/row_contact_last" tools:text="@string/risk_card_body_not_yet_fetched" /> <Button @@ -103,7 +103,7 @@ android:text="@string/risk_card_button_update" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_time_fetched" /> + app:layout_constraintTop_toBottomOf="@id/row_time_fetched" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml index 048ee99fe023516b336372ecc940838b8998a936..1fbb229efc0de19a2b00f8c9ebd378da8cffd902 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml @@ -24,7 +24,7 @@ android:accessibilityHeading="true" android:text="@string/risk_card_low_risk_headline" android:textColor="@color/colorTextPrimary1InvertedStable" - app:layout_constraintEnd_toStartOf="@+id/details_icon" + app:layout_constraintEnd_toStartOf="@id/details_icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_goneMarginEnd="0dp" @@ -55,7 +55,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/headline" + app:layout_constraintTop_toBottomOf="@id/headline" tools:text="@plurals/risk_card_low_risk_encounter_days_body" /> <de.rki.coronawarnapp.ui.view.TracingCardInfoRow @@ -69,9 +69,23 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_contact" + app:layout_constraintTop_toBottomOf="@id/row_contact" tools:text="@string/risk_card_low_risk_most_recent_body_encounters_on_more_than_one_day" /> + <de.rki.coronawarnapp.ui.view.TracingCardInfoRow + android:id="@+id/row_days_since_installation" + gone="@{!state.appInstalledForOverTwoWeeks()}" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:icon="@drawable/ic_download" + android:text="@{state.getDaysSinceInstall(context)}" + android:textColor="@color/colorTextPrimary1InvertedStable" + app:compatIconTint="@color/colorStableLight" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/row_contact_last" + tools:text="@string/risk_card_body_days_since_installation" /> + <de.rki.coronawarnapp.ui.view.TracingCardInfoRow android:id="@+id/row_time_fetched" android:layout_width="0dp" @@ -82,7 +96,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_contact_last" + app:layout_constraintTop_toBottomOf="@id/row_days_since_installation" tools:text="@string/risk_card_body_not_yet_fetched" /> <Button @@ -95,7 +109,7 @@ android:text="@string/risk_card_button_update" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_time_fetched" /> + app:layout_constraintTop_toBottomOf="@id/row_time_fetched" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml index d89197811ff1f9d01ad25788caa8e5c266897c2d..b6a1b70d3cd7efbf602700bd7ed9ea99a89bc260 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml @@ -15,50 +15,51 @@ android:focusable="true" android:padding="@dimen/card_padding"> - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/risk_details_period_logged_layout" - android:layout_width="match_parent" + <TextView + android:id="@+id/risk_details_period_logged_headline" + style="@style/headline5" + android:layout_width="0dp" android:layout_height="wrap_content" - android:focusable="true" + android:focusable="false" + android:text="@string/risk_details_headline_period_logged" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintTop_toTopOf="parent" /> - <TextView - android:id="@+id/risk_details_period_logged_headline" - style="@style/headline5" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:focusable="false" - android:text="@string/risk_details_headline_period_logged" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + <TextView + android:id="@+id/risk_details_period_logged_subtitle" + style="@style/subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:focusable="false" + android:text="@string/risk_details_subtitle_period_logged" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_headline" /> - <TextView - android:id="@+id/risk_details_period_logged_subtitle" - style="@style/subtitle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:focusable="false" - android:text="@string/risk_details_subtitle_period_logged" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_headline" /> + <TextView + android:id="@+id/risk_details_period_logged_body_notice" + style="@style/subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:focusable="true" + android:text="@string/risk_details_information_body_period_logged" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_subtitle" /> - <TextView - android:id="@+id/risk_details_period_logged_body_notice" - style="@style/body1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_normal" - android:focusable="true" - android:text="@string/risk_details_information_body_period_logged" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_subtitle" /> - </androidx.constraintlayout.widget.ConstraintLayout> + <TextView + style="@style/subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:text="@{loggedPeriod.getInstallTimePeriodLogged(context)}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_body_notice" + tools:text="@string/risk_details_information_body_period_logged_assessment_under_14_days" /> </androidx.constraintlayout.widget.ConstraintLayout> -</layout> \ No newline at end of file +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/view_more_information.xml b/Corona-Warn-App/src/main/res/layout/view_more_information.xml new file mode 100644 index 0000000000000000000000000000000000000000..f5248050c17666091569256a668cbd4c53f03eea --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/view_more_information.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge 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" + tools:layout_width="match_parent" + tools:layout_height="wrap_content" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> + + <View + android:id="@+id/top_divider" + android:layout_width="match_parent" + android:layout_height="@dimen/card_divider" + android:background="@color/colorHairline" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/more_information_title" + style="@style/subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + app:layout_constraintBottom_toTopOf="@id/more_information_subtitle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginBottom="16dp" + app:layout_constraintVertical_chainStyle="packed" + tools:text="Custom Title (is mandatory)" /> + + <TextView + android:id="@+id/more_information_subtitle" + style="@style/TextAppearance.AppCompat.Caption" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/more_information_title" + tools:text="Custom Subtitle (is optional)" /> + + <View + android:id="@+id/bottom_divider" + android:layout_width="match_parent" + android:layout_height="@dimen/card_divider" + android:layout_marginTop="12dp" + android:background="@color/colorHairline" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</merge> diff --git a/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml index 07fcab5029d5c3969a433ff62e10985f97908bea..f5bac17c00372220f5e49c9b49af495e3c1aa3a9 100644 --- a/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml @@ -103,9 +103,9 @@ <!-- XHED: Title for the contact diary dialog to delete all persons --> <string name="contact_diary_delete_persons_title">"ÐаиÑтина ли иÑкате да изтриете вÑички лица?"</string> <!-- XTXT: Message for the contact diary dialog to delete all locations --> - <string name="contact_diary_delete_locations_message">"Ðко изтриете дадено мÑÑто, вÑички запиÑи за него ще бъдат премахнати от Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº."</string> + <string name="contact_diary_delete_locations_message">"Ðко премахнете вÑички меÑта, вÑички запиÑи за Ñ‚ÑÑ… ще бъдат премахнати от Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº."</string> <!-- XTXT: Message for the contact diary dialog to delete all persons --> - <string name="contact_diary_delete_persons_message">"Ðко изтриете дадено лице, вÑички запиÑи за него ще бъдат премахнати от Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº."</string> + <string name="contact_diary_delete_persons_message">"Ðко премахнете вÑички лица, вÑички запиÑи за Ñ‚ÑÑ… ще бъдат премахнати от дневника Ви."</string> <!-- XACT: edit icon description for screen readers --> <string name="contact_diary_edit_icon_content_description">"Редактиране на запиÑ"</string> <!-- XACT: edit icon description for screen readers --> @@ -114,13 +114,17 @@ <string name="contact_diary_delete_location_title">"ÐаиÑтина ли иÑкате да изтриете това мÑÑто?"</string> <!-- XHED: Title for the contact diary dialog to delete delete a single person --> <string name="contact_diary_delete_person_title">"ÐаиÑтина ли иÑкате да изтриете това лице?"</string> + <!-- XTXT: Message for the contact diary dialog to delete a single location --> + <string name="contact_diary_delete_location_message">"Ðко изтриете дадено мÑÑто, вÑички запиÑи за него ще бъдат премахнати от Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº."</string> + <!-- XTXT: Message for the contact diary dialog to delete a single person --> + <string name="contact_diary_delete_person_message">"Ðко изтриете дадено лице, вÑички запиÑи за него ще бъдат премахнати от Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº."</string> <!-- XHED: Title for the contact diary comment info screen --> <string name="contact_diary_comment_info_screen_title">"Бележка"</string> <!-- XTXT: Description for contact diary comment info screen --> - <string name="contact_diary_comment_info_screen_description">"Тук можете да въведете бележки, които ще ви помогнат да оцените по-добре ÑÐ²Ð¾Ñ Ñ€Ð¸Ñк от заразÑване."</string> + <string name="contact_diary_comment_info_screen_description">"Тук можете да въведете бележки, които ще Ви помогнат да оцените по-добре ÑÐ²Ð¾Ñ Ñ€Ð¸Ñк от заразÑване."</string> <!-- XTXT: Body for contact diary comment info screen --> - <string name="contact_diary_comment_info_screen_body">"Опишете обÑтоÑтелÑтвата, които биха могли да доведат до повишаване на вашето ниво на риÑк, например в каква близоÑÑ‚ Ñте били до други хора и каква дейноÑÑ‚ Ñ Ð¿Ð¾Ñ‚ÐµÐ½Ñ†Ð¸Ð°Ð»Ð½Ð¾ влиÑние върху качеÑтвото на въздуха Ñте извършвали (напр. „ÑедÑхме близо един до друг“, „Ñпортувахме“, „проÑтранÑтвото беше малко“ или „пÑхме“).\n\nÐко Ñе заразите, тази Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да помогне на Ñлужбата за общеÑтвено здравеопазване да проÑледи потенциалните вериги на заразÑване."</string> + <string name="contact_diary_comment_info_screen_body">"Опишете обÑтоÑтелÑтвата, които биха могли да доведат до повишаване на Вашето ниво на риÑк, например в каква близоÑÑ‚ Ñте били до други хора и каква дейноÑÑ‚ Ñ Ð¿Ð¾Ñ‚ÐµÐ½Ñ†Ð¸Ð°Ð»Ð½Ð¾ влиÑние върху качеÑтвото на въздуха Ñте извършвали (напр. „ÑедÑхме близо един до друг“, „Ñпортувахме“, „проÑтранÑтвото беше малко“ или „пÑхме“).\n\nÐко Ñе заразите, тази Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да помогне на Ñлужбата за общеÑтвено здравеопазване да проÑледи потенциалните вериги на заразÑване."</string> <!-- XTXT: location (description for screen readers) --> <string name="accessibility_location">"ÐœÑÑто %s"</string> diff --git a/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml index ad038d586b9f7aea1e7a6d21cf2406c00009ffb2..2b1c8215a61f296bdabe9ccd3f4b739573b5737d 100644 --- a/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml @@ -12,38 +12,30 @@ <!-- XBUT: Continue button for the release info screen --> <string name="release_info_continue_button">"Ðапред"</string> <!-- XTXT: New release info footer --> - <string name="new_release_bottom">"Може да видите промените в тази верÑиÑ, като отворите наÑтройките на приложението и изберете “Ðови функцииâ€."</string> + <string name="new_release_bottom">"Може да видите промените в тази верÑÐ¸Ñ Ð² подраздела „Ðови функции“ на информациÑта за приложението."</string> <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> - <item>"Допълнителни функции в дневника на контактите"</item> - <item>"Директен доÑтъп до дневника на контактите"</item> - <item>"Повече подробноÑти отноÑно вашето ниво на риÑк"</item> - <item>"Екранни Ñнимки на приложението Corona-Warn-App"</item> + <item>"Ð¨Ð²ÐµÐ¹Ñ†Ð°Ñ€Ð¸Ñ Ðµ добавена към международното региÑтриране на излаганиÑта на риÑк"</item> + <item>"Генериране на отчети за грешки"</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"Вече можете да въвеждате Ñпецифични обÑтоÑтелÑтва за вÑеки контакт. Можете да избирате от предварително зададени опции (напр. продължителноÑÑ‚ на контакта, използвана ли е маÑка, или не), както и да въвеждате кратка бележка, в коÑто да отбележите други обÑтоÑтелÑтва, които биха могли да доведат да повишен риÑк. Ðко Ñе заразите, тази Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да помогне на Ñлужбата за общеÑтвено здравеопазване да проÑледи потенциалните вериги на заразÑване."</item> - <item>"Вече можете да добавите Ð·Ð°Ð¿Ð¸Ñ Ð² дневника на контактите Ñи, без да е необходимо първо да отварÑте приложението. За да направите това, докоÑнете и задръжте иконата на приложението Corona-Warn-App в продължение на 2 Ñекунди, докато Ñе поÑви меню, и Ñлед това изберете „ДобавÑне на Ð·Ð°Ð¿Ð¸Ñ Ñ Ð´Ð½ÐµÑˆÐ½Ð° дата в дневника“."</item> - <item>"Ðко приложението указва, че вашиÑÑ‚ риÑк от заразÑване е повишен, вече можете да видите дали това Ñе дължи на едно или повече Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ñ Ð¿Ð¾Ð²Ð¸ÑˆÐµÐ½ риÑк, или на такива Ñ Ð½Ð¸Ñък риÑк."</item> - <item>"Ð’Ñички екранни Ñнимки на приложението вече Ñа доÑтъпни в уебÑайта https://www.coronawarn.app. Така ще можете да научавате например какви нови функции Ñе планират."</item> + <item>"Потребителите на Corona-Warn-App вече могат да разменÑÑ‚ криптирани Ñлучайни идентификатори Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ð¸Ñ‚Ðµ на официалното приложение за борба Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑа в ШвейцариÑ. Това означава, че потребителите там Ñъщо могат да изпращат и получават предупреждениÑ."</item> + <item>"Вече можете да генерирате отчети за грешките при поиÑкване от техничеÑката поддръжка и да запиÑвате Ñтъпките, които изпълнÑвате в приложението. Това ще улеÑни анализа на техничеÑките грешки и ще уÑкори коригирането им."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> <string-array name="new_release_linkified_labels"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app"</item> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app/en/screenshots"</item> </string-array> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-bg/strings.xml b/Corona-Warn-App/src/main/res/values-bg/strings.xml index e8f448daba8728232e67c4ff5388eb4b27eabc2e..498b4a70adc6927a2bfcbac7324d23829ba114a6 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -1,47 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?><resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> - - <!-- #################################### - Preference Keys - ###################################### - TODO: Check what is needed --> - - <!-- NOTR --> - <string name="preference_name"><xliff:g id="preference">"shared_preferences_cwa"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed"><xliff:g id="preference">"preference_onboarding_completed"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed_timestamp"><xliff:g id="preference">"preference_onboarding_completed_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_check_done"><xliff:g id="preference">"preference_background_check_done"</xliff:g></string> - <!-- NOTR --> - <string name="preference_reset_app"><xliff:g id="preference">"preference_reset_app"</xliff:g></string> - <!-- NOTR --> - <string name="preference_only_wifi"><xliff:g id="preference">"preference_only_wifi"</xliff:g></string> - <!-- NOTR --> - <string name="preference_tracing"><xliff:g id="preference">"preference_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> - <!-- NOTR --> - <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_result_received_time"><xliff:g id="preference">"preference_initial_result_received_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_is_allowed_to_submit_diagnosis_keys"><xliff:g id="preference">"preference_is_allowed_to_submit_diagnosis_keys"</xliff:g></string> - <!-- NOTR --> - <string name="preference_database_password"><xliff:g id="preference">"preference_database_password"</xliff:g></string> - <!-- NOTR --> - <string name="preference_total_non_active_tracing"><xliff:g id="preference">"preference_total_non_active_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_last_non_active_tracing_timestamp"><xliff:g id="preference">"preference_last_non_active_tracing_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_number_successful_submissions"><xliff:g id="preference">"preference_number_successful_submissions"</xliff:g></string> - <!-- NOTR --> - <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string> - <!-- NOTR --> - <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string> - <!-- #################################### Generics ###################################### --> @@ -105,6 +62,12 @@ Risk Card ###################################### --> + <!-- XTXT: risk card - Days since installation if < 14 days --> + <string name="risk_card_body_days_since_installation">"ИнÑталирано преди %s дни"</string> + <!-- XTXT: risk card - tracing active for x out of 14 days --> + <string name="risk_card_body_saved_days">"РегиÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк е било активно през %1$s от изминалите 14 дни"</string> + <!-- XTXT: risk card- tracing active for 14 out of 14 days --> + <string name="risk_card_body_saved_days_full">"ÐÑма прекъÑване на региÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк"</string> <!-- XTXT; risk card - no update done yet --> <string name="risk_card_body_not_yet_fetched">"Ð’Ñе още не е извършвана проверка на контактите."</string> <!-- XTXT: risk card - last successful update --> @@ -307,7 +270,7 @@ <!-- XMSG: risk details - go/stay home, something like a bullet point --> <string name="risk_details_behavior_body_stay_home">"По възможноÑÑ‚ Ñе приберете и Ñи оÑтанете вкъщи."</string> <!-- XMSG: risk details - get in touch with the corresponding people, something like a bullet point --> - <string name="risk_details_behavior_body_contact_doctor">"Ðко имате въпроÑи отноÑно Ñимптомите, възможноÑтите за теÑтване или ÑамоизолациÑта, Ñе обърнете към:"</string> + <string name="risk_details_behavior_body_contact_doctor">"Ðко имате въпроÑи отноÑно Ñимптомите, възможноÑтите за теÑтване или карантинните мерки, обърнете Ñе към:"</string> <!-- XMSG: risk details - wash your hands, something like a bullet point --> <string name="risk_details_behavior_body_wash_hands">"Мийте редовно ръцете Ñи ÑÑŠÑ Ñапун в продължение на 20 Ñек."</string> <!-- XMSG: risk details - wear a face mask, something like a bullet point --> @@ -322,6 +285,8 @@ <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> + <!-- XMSG: risk details - ventilation bullet point --> + <string name="risk_details_behavior_body_ventilation">"ПроветрÑвайте затворените Ð¿Ð¾Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ нÑколко пъти на ден. За целта отворете прозорците възможно най-широко за нÑколко минути (Ñ‚.нар. ударно проветрÑване)."</string> <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"Кихайте и кашлÑйте в кърпичка или в Ñгъвката на Ð»Ð°ÐºÑŠÑ‚Ñ Ñи."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -337,7 +302,13 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Този период Ñе включва в изчиÑлението."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> - <string name="risk_details_information_body_period_logged">"ВашиÑÑ‚ риÑк от заразÑване може да Ñе изчиÑли Ñамо за периодите, в които региÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк е било активно. Затова тази Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ñ‚Ñ€Ñбва да бъде активирана поÑтоÑнно."</string> + <string name="risk_details_information_body_period_logged">"ВашиÑÑ‚ риÑк от заразÑване може да Ñе изчиÑли Ñамо за периодите, в които региÑтрирането на излаганиÑта на риÑк е било активно. Ето защо тази Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ñ‚Ñ€Ñбва да бъде активирана поÑтоÑнно. РегиÑтрирането на контактите Ви покрива поÑледните 14 дни."</string> + <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Приложението Corona-Warn-App е инÑталирано преди %s дни. РиÑкът да Ñте Ñе заразили Ñе изчиÑлÑва за периодите, през които е било активно региÑтрирането на контактите Ви. Ðко Ñте били в близоÑÑ‚ до други хора и региÑтрирането на излаганиÑта е било активно, риÑкът да Ñте Ñе заразили ще бъде изчиÑлен."</string> + <!-- XTXT: risk details - infection period logged information body, over 14 days --> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"Ðко региÑтрирането на контактите Ви е било активно, когато Ñте Ñе Ñрещали Ñ Ð´Ñ€ÑƒÐ³Ð¸ хора, риÑкът да Ñте Ñе заразили през този период ще бъде изчиÑлен."</string> + <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment">"РегиÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк покрива поÑледните 14 дни. За този период от време функциÑта е била активна на Ð’Ð°ÑˆÐ¸Ñ Ñмартфон в продължение на %1$s дни. Приложението изтрива автоматично по-Ñтарите региÑтри, тъй като те вече не могат да Ñлужат за предотвратÑване на заразÑването."</string> <!-- XTXT: risk details - infection period days logged/14 --> <string name="risk_details_information_active_tracing_days_circle_progress">"%s/14"</string> <!-- XHED: risk details - how your risk level was calculated, below behaviors --> @@ -350,8 +321,12 @@ <string name="risk_details_information_body_low_risk">"Вашето ниво на риÑк от заразÑване е ниÑко, защото нÑмате региÑтрирани контакти Ñ Ð»Ð¸Ñ†Ð°, които впоÑледÑтвие Ñа били диагноÑтицирани Ñ COVID-19, или ако Ñте имали такива, те Ñа били краткотрайни и от по-голÑмо разÑтоÑние."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"РиÑкът от заразÑване Ñе изчиÑлÑва локално на Ñмартфона Ви въз оÑнова на региÑтрираните данни за излагане. ИзчиÑлението включва Ñъщо така разÑтоÑнието от и продължителноÑтта на вÑички контакти Ñ Ð»Ð¸Ñ†Ð°, диагноÑтицирани Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ, както и възможноÑтта им да заразÑÑ‚ околните. Ðикой оÑвен Ð’Ð°Ñ Ð½Ðµ може да види или да получи данни за Вашето ниво на риÑк."</string> + <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> + <string name="risk_details_explanation_dialog_faq_body">"За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð²Ð¸Ð¶Ñ‚Ðµ Ñтраницата „ЧЗВ“."</string> + <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> + <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> - <string name="risk_details_information_body_increased_risk_date">"Ðивото на риÑка Ви от заразÑване е повишено, защото на %1$s Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ."</string> + <string name="risk_details_information_body_increased_risk_date">"Ðивото на риÑка Ви от заразÑване е повишено, защото Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ COVID-19."</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">"Ðивото на риÑка Ви от заразÑване е повишено, защото преди %1$s дни Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ."</item> @@ -362,9 +337,9 @@ <item quantity="many">"Ðивото на риÑка Ви от заразÑване е повишено, защото преди %1$s дни Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ."</item> </plurals> <!-- YTXT: risk details - risk calculation explanation --> - <string name="risk_details_information_body_notice">"РиÑкът от заразÑване Ñе изчиÑлÑва въз оÑнова на данните за излагане (продължителноÑÑ‚ и близоÑÑ‚ на контакта), региÑтрирани във Ð²Ð°ÑˆÐ¸Ñ Ñмартфон. Ðикой оÑвен Ð’Ð°Ñ Ð½Ðµ може да види или да получи данни за Вашето ниво на риÑк."</string> + <string name="risk_details_information_body_notice">"РиÑкът от заразÑване Ñе изчиÑлÑва въз оÑнова на данните за излагане (продължителноÑÑ‚ и близоÑÑ‚ на контакта), региÑтрирани във Ð’Ð°ÑˆÐ¸Ñ Ñмартфон. Ðикой оÑвен Ð’Ð°Ñ Ð½Ðµ може да види или да получи данни за Вашето ниво на риÑк."</string> <!-- YTXT: risk details - risk calculation explanation for increased risk --> - <string name="risk_details_information_body_notice_increased">"Това е причината да определим Ð’Ð°ÑˆÐ¸Ñ Ñ€Ð¸Ñк от заразÑване като повишен. РиÑкът от заразÑване Ñе изчиÑлÑва въз оÑнова на данните за излагане (продължителноÑÑ‚ и близоÑÑ‚ на контакта), региÑтрирани на вашето локално уÑтройÑтво. Ðикой оÑвен Ð’Ð°Ñ Ð½Ðµ може да види или да получи данни за Вашето ниво на риÑк. Когато Ñе приберете у дома, избÑгвайте близките контакти Ñ Ñ‡Ð»ÐµÐ½Ð¾Ð²ÐµÑ‚Ðµ на домакинÑтвото Ñи."</string> + <string name="risk_details_information_body_notice_increased">"Това е причината да определим Ð’Ð°ÑˆÐ¸Ñ Ñ€Ð¸Ñк от заразÑване като повишен. РиÑкът от заразÑване Ñе изчиÑлÑва въз оÑнова на данните за излагане (продължителноÑÑ‚ и близоÑÑ‚ на контакта), региÑтрирани на Вашето локално уÑтройÑтво. Ðикой оÑвен Ð’Ð°Ñ Ð½Ðµ може да види или да получи данни за Вашето ниво на риÑк. Когато Ñе приберете у дома, избÑгвайте близките контакти Ñ Ñ‡Ð»ÐµÐ½Ð¾Ð²ÐµÑ‚Ðµ на домакинÑтвото Ñи."</string> <!-- NOTR --> <string name="risk_details_button_update">@string/risk_card_button_update</string> <!-- NOTR --> @@ -470,7 +445,7 @@ <!-- XACT: onboarding(tracing) - dialog about energy optimized header text --> <string name="onboarding_energy_optimized_dialog_headline">"Разрешаване на приоритетната работа във фонов режим"</string> <!-- YMSI: onboarding(tracing) - dialog about energy optimized --> - <string name="onboarding_energy_optimized_dialog_body">"Ðктивирайте приоритетната работа във фонов режим, за да позволите на приложението да Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ Ñ€Ð¸Ñковете, на които Ñте изложени, по вÑÑко време като работи на заден план (препоръчително). Това ще изключи оптимизациÑта на потребление на Ð±Ð°Ñ‚ÐµÑ€Ð¸Ñ Ñамо за приложението Corona-Warn-App. Ðе очакваме това да доведе до значително по-бързо изтощаване на батериÑта на Ð²Ð°ÑˆÐ¸Ñ Ñмартфон.\n\nÐко не разрешите тази наÑтройка, препоръчваме да отварÑте приложението ръчно поне веднъж на вÑеки 24 чаÑа."</string> + <string name="onboarding_energy_optimized_dialog_body">"Ðктивирайте приоритетната работа във фонов режим, за да позволите на приложението да Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ Ñ€Ð¸Ñковете, на които Ñте изложени, по вÑÑко време като работи на заден план (препоръчително). Това ще изключи оптимизациÑта на потребление на Ð±Ð°Ñ‚ÐµÑ€Ð¸Ñ Ñамо за приложението Corona-Warn-App. Ðе очакваме това да доведе до значително по-бързо изтощаване на батериÑта на Ð’Ð°ÑˆÐ¸Ñ Ñмартфон.\n\nÐко не разрешите тази наÑтройка, препоръчваме да отварÑте приложението ръчно поне веднъж на вÑеки 24 чаÑа."</string> <!-- XBUT: onboarding(tracing) - dialog about energy optimized, open device settings --> <string name="onboarding_energy_optimized_dialog_button_positive">"Разрешавам"</string> <!-- XBUT: onboarding(tracing) - dialog about energy optimized, continue in app --> @@ -486,11 +461,11 @@ <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> <string name="onboarding_tracing_location_headline">"Ðктивиране на наÑтройките за меÑтоположение"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Приложението не може да определи къде Ñе намирате, но нÑкои верÑии на Android изиÑкват наÑтройката за меÑтоположение да бъде активирана, за да използват режима на Bluetooth Ñ Ð½Ð¸Ñка конÑÑƒÐ¼Ð°Ñ†Ð¸Ñ Ð½Ð° енергиÑ."</string> + <string name="onboarding_tracing_location_body">"Приложението не може да определи къде Ñе намирате, но Android 10 и по-Ñтарите от него верÑии изиÑкват наÑтройката за меÑтоположение да бъде активна, за да могат да използват режима на Bluetooth Ñ Ð½Ð¸Ñка конÑÑƒÐ¼Ð°Ñ†Ð¸Ñ Ð½Ð° енергиÑ."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Към наÑтройките на уÑтройÑтвото"</string> <!-- XACT: Onboarding (test) page title --> - <string name="onboarding_test_accessibility_title">"Въведение – Ñтраница 5 от 6: Ðко имате поÑтавена диагноза \"коронавируÑ\""</string> + <string name="onboarding_test_accessibility_title">"Въведение – Ñтраница 5 от 6: Ðко имате поÑтавена диагноза „коронавируÑ“"</string> <!-- XHED: onboarding(test) - about positive tests --> <string name="onboarding_test_headline">"Ðко имате поÑтавена диагноза „коронавируÑ“"</string> <!-- XHED: onboarding(test) - two/three line headline under an illustration --> @@ -599,9 +574,6 @@ <string name="onboarding_ppa_more_info_much_privacy_body">"Тази Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ðµ може да Ñе използва за уÑтановÑване на ÑамоличноÑтта Ви – Вашата анонимноÑÑ‚ ще бъде запазена.\nДанните ще бъдат анализирани за ÑтатиÑтичеÑки цели и нÑма да Ñе използват за Ñъздаване на профил."</string> - - - <!-- #################################### Onboarding sixteen include ###################################### --> @@ -656,7 +628,7 @@ <!--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 Ñ Ð½Ð¸Ñък разход на ÐµÐ½ÐµÑ€Ð³Ð¸Ñ Ð¸Ð·Ð¸Ñква активирани уÑлуги за ÑподелÑне на меÑтоположението, за да може да изчиÑли физичеÑкото разÑтоÑние, но в момента нÑма доÑтъп до Вашето меÑтоположение. Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите на Ñтраницата “ЧеÑто задавани въпроÑиâ€."</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 --> @@ -851,26 +823,76 @@ <!-- XACT: describes illustration --> <string name="information_legal_illustration_description">"Ръка държи Ñмартфон, на чийто екран Ñе вижда голÑмо количеÑтво текÑÑ‚, а до Ð½ÐµÑ Ñе вижда знакът за раздел, Ñимволизиращ правната информациÑ."</string> + <!-- #################################### + App Information - Bug Reporting + ###################################### --> + <!-- XHED: Headline for debug log screen --> <string name="debugging_debuglog_title">"Отчети за грешки"</string> - <!-- YTXT: Description for the debug option to record log files --> - <string name="debugging_debuglog_intro_explanation_section_one">"Тази Ð¾Ð¿Ñ†Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñва дейÑтвиÑта на приложението в журнал. Ðко е активна и възникне грешка, ще можете да Ñподелите отчета Ñ Ñ€Ð°Ð·Ñ€Ð°Ð±Ð¾Ñ‚Ñ‡Ð¸Ñ†Ð¸Ñ‚Ðµ, за да им помогнете да решат проблема.\nÐко оÑтавите опциÑта активна, това може да доведе до запълване на проÑтранÑтвото за Ñъхранение. Ðко Ñ Ð´ÐµÐ·Ð°ÐºÑ‚Ð¸Ð²Ð¸Ñ€Ð°Ñ‚Ðµ, отчетите за грешки Ñе изтриват."</string> + <!-- XHED: Headline for current status of debug log screen --> + <string name="debugging_debuglog_current_status_title">"Ðнализ на грешките"</string> + <!-- YTXT: Description one for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_one">"За да помогнете на екипа за техничеÑка поддръжка на приложението да анализира грешките, можете да запишете отчет за грешките в CWA. Когато го направите, отделните техничеÑки Ñтъпки и резултатите от процеÑите в приложението Ñе запиÑват. Можете да изпратите отчета за грешките на екипа за техничеÑка поддръжка, за да им помогнете да уÑтановÑÑ‚ и поправÑÑ‚ грешката."</string> + <!-- YTXT: Description two for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_two">"Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите в Ñтраницата ЧЗВ: ЧЗВ за отчетите за грешки"</string> + <!-- XTXT: Debug Log screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="debugging_debuglog_intro_explanation_section_two_link_label">"ЧЗВ за отчетите за грешки"</string> + <!-- XTXT: Explains user about about debug log: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> + <string name="debugging_debuglog_intro_explanation_section_two_faq_link">"https://www.coronawarn.app/en/faq/#error_log"</string> + <!-- YTXT: Title of ID History --> + <string name="debugging_debuglog_id_history_title">"Ð¥Ñ€Ð¾Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñ Ð½Ð° ИД"</string> + <!-- YTXT: Description of ID History --> + <string name="debugging_debuglog_id_history_body">"ИД на анализите на грешки, Ñподелени доÑега"</string> <!-- YTXT: Warning regarding downsides of recording a log file --> <string name="debugging_debuglog_intro_warning">"МолÑ, имайте предвид, че отчетите за грешки може да Ñъдържат поверителни данни (например резултати от теÑтове или запиÑи в дневника на контактите). Ето защо не бива да ÑподелÑте отчетите за грешки публично."</string> <!-- XBUT: Button text to start the log recording --> <string name="debugging_debuglog_action_start_recording">"Ðачало"</string> <!-- XBUT: Button text to stop the log recording --> - <string name="debugging_debuglog_action_stop_recording">"Изтриване"</string> + <string name="debugging_debuglog_action_stop_recording">"Спиране и изтриване"</string> <!-- XBUT: Button text to share the log recording --> - <string name="debugging_debuglog_action_share_log">"СподелÑне"</string> + <string name="debugging_debuglog_action_share_log">"Изпращане на отчет за грешки"</string> + <!-- XBUT: Button text to locally store the log recording --> + <string name="debugging_debuglog_action_local_log_store">"Локално запазване и продължаване"</string> <!-- YTXT: Status text if a debug log is being recorded --> <string name="debugging_debuglog_status_recording">"ЗапиÑването е активно"</string> + <!-- YTXT: Status text if a debug log is being recorded but there is not enough free storage space --> + <string name="debugging_debuglog_status_lowstorage">"ÐедоÑтатъчна памет"</string> <!-- YTXT: Status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_not_recording">"Ðеактивно"</string> <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_additional_infos">"Текущ размер: %1$s (без компреÑиÑ)"</string> - <!-- XHED: Title for native sharing dialog --> - <string name="debugging_debuglog_sharing_dialog_title">"Споделете отчет за грешка в CWA"</string> + <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_message">"Отчетът за грешките е изтрит. Вашето локално копие на отчета, ако Ñте запазили такова, не е изтрито."</string> + <!-- YTXT: Dialog message if there is not enough free storage to start a debug log --> + <string name="debugging_debuglog_start_low_storage_error">"Ðеобходими Ñа поне 200 MB памет, за да Ñе Ñтартира анализа на грешките. МолÑ, оÑвободете мÑÑто."</string> + <!-- XHED: Dialog title if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_title">"Запазено локално"</string> + <!-- YTXT: Dialog message if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_message">"Ðнализът на грешките е запазен локално."</string> + <!-- YTXT: Dialog message if local export has failed --> + <string name="debugging_debuglog_localexport_error_message">"Отчетът за грешките не беше запазен. МолÑ, проверете дали има доÑтатъчно Ñвободна памет."</string> + + <!-- XHED: Title for debug legal screen --> + <string name="debugging_debuglog_legal_dialog_title">"Подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно изпращането на отчети за грешки"</string> + <!-- YTXT: Section Title for debug legal screen --> + <string name="debugging_debuglog_legal_section_title">"Как инÑтитутът „Роберт Кох“ анализира отчета за грешките"</string> + <!-- YTXT: Section Body for debug legal screen --> + <string name="debugging_debuglog_legal_section_body">"След удоÑтоверÑването на автентичноÑтта на вашето приложение отчетът за грешките Ñе изпраща на инÑтитута „Роберт Кох“ поÑредÑтвом защитена връзка. Отчетите за грешки ще Ñе използват Ñамо за отÑтранÑване на неизправноÑти и корекции на грешки в рамките на бъдещи актуализации на приложението. Само Ñлужителите от техничеÑката поддръжка могат да оÑъщеÑтвÑват доÑтъп до Ñ‚ÑÑ…. Отчетите за грешки Ñъдържат различни ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð·Ð° ÑтатуÑи и инициирани в приложението ÑъбитиÑ, но не Ñъдържат информациÑ, коÑто би позволила на инÑтитута „Роберт Кох“ да Ви идентифицира. Само ако поÑочите ИД на отчета във връзка ÑÑŠÑ Ñледващи ÑъобщениÑ, може да бъде уÑтановена връзка между Ñъобщението (и Вашето име, поÑочено в него) и информациÑта, Ñъдържаща Ñе в отчета за грешките (например техничеÑки ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð·Ð° изчиÑление като чаÑÑ‚ от региÑтрирането на излаганиÑта и, ако е приложимо, извлечен резултат от теÑÑ‚ и Ñлучайни ИД, Ñподелени Ñ Ñ†ÐµÐ» уведомÑване на оÑтаналите потребители)."</string> + + <!-- XHED: Title for Bugreporting share log screen --> + <string name="debugging_debuglog_share_log_title">"Изпращане на отчет за грешки"</string> + <!-- YTXT: First body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_one">"Ðеобходимо е да дадете Ñвоето ÑъглаÑие, за да може да изпратите запиÑÐ°Ð½Ð¸Ñ Ð¾Ñ‚Ñ‡ÐµÑ‚ за грешките до отдела по техничеÑка поддръжка на инÑтитута „Роберт Кох“."</string> + <!-- YTXT: Second body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_two">"След изпращането ще получите ИД на отчета за грешките. Този ИД може да Ñе използва например за предоÑтавÑне на допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° отдела по поддръжка и за приÑъединÑването му към Ð’Ð°ÑˆÐ¸Ñ Ð¾Ñ‚Ñ‡ÐµÑ‚. Ðко не поÑочите ИД на отчета за грешките, техничеÑкиÑÑ‚ отдел на инÑтитута „Роберт Кох“ не може да аÑоциира отчета Ñ Ð²Ð°Ñ."</string> + <!-- YTXT: Privacy Information section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_privacy_information">"Подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно обработката на тези данни и риÑковете отноÑно защитата на данните в СÐЩ и други държави"</string> + <!-- XBUT: Button for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_button">"Приемане и изпращане"</string> + <!-- XHED: Title for log upload history --> + <string name="debugging_debuglog_uploadhistory_title">"Ð¥Ñ€Ð¾Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñ Ð½Ð° ИД"</string> + <!-- YTXT: Description for log upload history --> + <string name="debugging_debuglog_uploadhistory_description">"Тук ще намерите идентификаторите (ИД) на Вашите журнали за анализ на грешките."</string> <!-- #################################### Interoperability @@ -894,11 +916,11 @@ <!-- 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: qr --> - <string name="submission_error_dialog_web_test_paired_body">"QR кодът е невалиден или вече е региÑтриран на друг Ñмартфон. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза \"коронавируÑ\", Ñлужбата за общеÑтвено здравеопазване ще Ви уведоми за това."</string> + <string name="submission_error_dialog_web_test_paired_body">"QR кодът е невалиден или вече е региÑтриран на друг Ñмартфон. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза „коронавируÑ“, Ñлужбата за общеÑтвено здравеопазване ще Ви уведоми за това."</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> + <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> @@ -954,7 +976,7 @@ <!-- XHED: Dialog headline for invalid QR code --> <string name="submission_qr_code_scan_invalid_dialog_headline">"Ðевалиден QR код"</string> <!-- YTXT: Dialog Body text for invalid QR code --> - <string name="submission_qr_code_scan_invalid_dialog_body">"QR кодът е невалиден или вече е региÑтриран на друг Ñмартфон. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза \"коронавируÑ\", Ñлужбата за общеÑтвено здравеопазване ще бъде уведомена за това по уÑÑ‚Ð°Ð½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¾Ñ‚ правните норми канал и ще Ñе Ñвърже Ñ Ð’Ð°Ñ."</string> + <string name="submission_qr_code_scan_invalid_dialog_body">"QR кодът е невалиден или вече е региÑтриран на друг Ñмартфон. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза „коронавируÑ“, Ñлужбата за общеÑтвено здравеопазване ще бъде уведомена за това по уÑÑ‚Ð°Ð½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¾Ñ‚ правните норми канал и ще Ñе Ñвърже Ñ Ð’Ð°Ñ."</string> <!-- XBUT: Dialog(Invalid QR code) - positive button (right) --> <string name="submission_qr_code_scan_invalid_dialog_button_positive">"МолÑ, опитайте отново."</string> <!-- XBUT: Dialog(Invalid QR code) - negative button (left) --> @@ -981,7 +1003,7 @@ <!-- XHED: Page subheadline for consent help by warning others --> <string name="submission_consent_help_by_warning_others_headline">"МолÑ, помогнете на хората, Ñ ÐºÐ¾Ð¸Ñ‚Ð¾ Ñте имали контакт, като ги предупредите!"</string> <!-- YTXT: Body for consent help by warning others --> - <string name="submission_consent_help_by_warning_others_body">"Ðко Ви е поÑтавена диагноза \"коронавируÑ\", можете да предупредите оÑтаналите Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰Ñ‚Ð° на приложението. ФункциÑта за предупреждаване работи в нÑколко държави. Към момента това Ñа:"</string> + <string name="submission_consent_help_by_warning_others_body">"Ðко Ви е поÑтавена диагноза „коронавируÑ“, можете да предупредите оÑтаналите Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰Ñ‚Ð° на приложението. ФункциÑта за предупреждаване работи в нÑколко държави. Към момента това Ñа:"</string> <!-- YTXT: Page bottom text for consent screen --> <string name="submission_consent_main_bottom_body">"Подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° обработването на данни и Вашето ÑъглаÑие"</string> <!-- YTXT: Body for consent main section first point --> @@ -1007,7 +1029,7 @@ <!-- XHED: Page headline for pending test result next steps --> <string name="submission_test_result_pending_steps_waiting_heading">"Ð’Ñе още нÑма резултат от теÑта Ви."</string> <!-- YTXT: Body text for next steps section of waiting test result page --> - <string name="submission_test_result_pending_steps_waiting_body">"Резултатът от Ð’Ð°ÑˆÐ¸Ñ Ñ‚ÐµÑÑ‚ ще Ñе покаже в приложението веднага щом бъде готов.\n\nТой ще Ви бъде изпратен Ñъщо и извън приложението. Службата за общеÑтвено здравеопазване ще Ви уведоми, ако е положителен.\n\nÐко получите такова уведомление, Ð¼Ð¾Ð»Ñ Ð¸Ð·Ñ‚Ñ€Ð¸Ð¹Ñ‚Ðµ теÑта, който в момента е региÑтриран в приложението. Позвънете на номера, показан под “ЗаÑвка за ТÐРкодâ€, за да получите ТÐÐ. Използвайте кода, за да региÑтрирате ÑÐ²Ð¾Ñ Ñ€ÐµÐ·ÑƒÐ»Ñ‚Ð°Ñ‚ в приложението и да предупредите оÑтаналите потребители."</string> + <string name="submission_test_result_pending_steps_waiting_body">"Резултатът от Ð’Ð°ÑˆÐ¸Ñ Ñ‚ÐµÑÑ‚ ще Ñе покаже в приложението веднага щом бъде готов.\n\nТой ще Ви бъде изпратен Ñъщо и извън приложението. Службата за общеÑтвено здравеопазване ще Ви уведоми, ако е положителен.\n\nÐко получите такова уведомление, Ð¼Ð¾Ð»Ñ Ð¸Ð·Ñ‚Ñ€Ð¸Ð¹Ñ‚Ðµ теÑта, който в момента е региÑтриран в приложението. Позвънете на номера, показан под „ЗаÑвка за ТÐРкод“, за да получите ТÐÐ. Използвайте кода, за да региÑтрирате ÑÐ²Ð¾Ñ Ñ€ÐµÐ·ÑƒÐ»Ñ‚Ð°Ñ‚ в приложението и да предупредите оÑтаналите потребители."</string> <!-- XBUT: test result pending : refresh button --> <string name="submission_test_result_pending_refresh_button">"Ðктуализиране"</string> <!-- XBUT: test result pending : remove the test button --> @@ -1043,7 +1065,7 @@ <!-- XHED: Dialog title for test removal --> <string name="submission_test_result_dialog_remove_test_title">"ТеÑÑ‚ÑŠÑ‚ може да Ñе Ñканира Ñамо веднъж."</string> <!-- YTXT: Dialog text for test removal --> - <string name="submission_test_result_dialog_remove_test_message">"Ðко изтриете теÑта, повече нÑма да можете да извлечете данните за резултата Ñи. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза \"коронавируÑ\", Ñлужбата за общеÑтвено здравеопазване ще бъде уведомена за това по уÑÑ‚Ð°Ð½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¾Ñ‚ правните норми канал и ще Ñе Ñвърже Ñ Ð’Ð°Ñ."</string> + <string name="submission_test_result_dialog_remove_test_message">"Ðко изтриете теÑта, повече нÑма да можете да извлечете данните за резултата Ñи. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза „коронавируÑ“, Ñлужбата за общеÑтвено здравеопазване ще бъде уведомена за това по уÑÑ‚Ð°Ð½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¾Ñ‚ правните норми канал и ще Ñе Ñвърже Ñ Ð’Ð°Ñ."</string> <!-- XBUT: Positive button for test removal --> <string name="submission_test_result_dialog_remove_test_button_positive">"Изтриване"</string> <!-- XBUT: Negative button for test removal --> @@ -1201,7 +1223,7 @@ <!-- XTXT: headline text for initial symptom screen --> <string name="submission_symptom_initial_headline">"Имали ли Ñте един или повече от Ñледните Ñимптоми през поÑледните нÑколко дни?"</string> <!-- YTXT: explanation text for initial symptom screen --> - <string name="submission_symptom_initial_explanation">"Може да поÑочите дали и кога Ñте проÑвили Ñимптоми на коронавируÑ, за да позволите на приложението да оцени по-точно риÑка от заразÑване за оÑтаналите потребители. Тази Ñтъпка не е задължителна. Ðко не желаете да предоÑтавите такава информациÑ, проÑто изберете \"Без отговор\"."</string> + <string name="submission_symptom_initial_explanation">"Може да поÑочите дали и кога Ñте проÑвили Ñимптоми на коронавируÑ, за да позволите на приложението да оцени по-точно риÑка от заразÑване за оÑтаналите потребители. Тази Ñтъпка не е задължителна. Ðко не желаете да предоÑтавите такава информациÑ, проÑто изберете „Без отговор“."</string> <!-- YTXT: Bullet points for symptoms --> <string-array name="submission_symptom_symptom_bullets"> <item>"Повишена температура или треÑка"</item> @@ -1330,7 +1352,7 @@ <!-- YTXT: Body text for test registration date --> <string name="reenable_risk_card_test_registration_string">"Дата на региÑтриране на теÑта: %s"</string> <!-- YTXT: Description text for re-enable risk card --> - <string name="reenable_risk_card_description_text">"Ðвтоматичната проверка за излагане на риÑк е ÑпрÑна, защото Ви е била поÑтавена диагноза “коронавируци понаÑтоÑщем Ñте в изолациÑ. Можете да активирате проверката отново по вÑÑко време, като в този Ñлучай региÑтрираниÑÑ‚ от Ð’Ð°Ñ Ñ‚ÐµÑÑ‚ ще бъде изтрит автоматично."</string> + <string name="reenable_risk_card_description_text">"Ðвтоматичната проверка за излагане на риÑк е ÑпрÑна, защото Ви е била поÑтавена диагноза „коронавируÑ“ и понаÑтоÑщем Ñте в изолациÑ. Можете да активирате проверката отново по вÑÑко време, като в този Ñлучай региÑтрираниÑÑ‚ от Ð’Ð°Ñ Ñ‚ÐµÑÑ‚ ще бъде изтрит автоматично."</string> <!-- XBUT: Button for re-enabling risk calculation --> <string name="reenable_risk_card_button_text">"Включване на проверката за излагане на риÑк "</string> <!-- XHED: Dialog title for reactivate risk calculation --> @@ -1371,9 +1393,9 @@ <!-- Submission consent custom view --> <!-- XHED: Title for consent given --> - <string name="submission_consent_view_consent_given">"СъглаÑие\n“Предупредете другите†е дадено"</string> + <string name="submission_consent_view_consent_given">"СъглаÑие\n„Предупредете другите“ е дадено"</string> <!-- XHED: Title for consent NOT given --> - <string name="submission_consent_view_consent_not_given">"СъглаÑие\n“Предупредете другите†не е дадено"</string> + <string name="submission_consent_view_consent_not_given">"СъглаÑие\n„Предупредете другите“ не е дадено"</string> <!-- Submission test result available --> <!-- XHED: Page title for test results available step --> @@ -1381,11 +1403,11 @@ <!-- XACT: Test result available illustration description --> <string name="submission_test_result_available_illustration_description">"Жена държи Ñмартфон в ръката Ñи. Той изпраща Ñигнал към друг Ñмартфон."</string> <!-- YTXT: Text for consent given --> - <string name="submission_test_result_available_text_consent_given">"Благодарим Ви, че Ñе ÑъглаÑихте да Ñподелите резултата от Ð’Ð°ÑˆÐ¸Ñ Ñ‚ÐµÑÑ‚ и така да помогнете, като предупредите оÑтаналите. \n\n"<b>"Ð’ Ñледващата Ñтъпка, молÑ, Ñподелете резултата от теÑта Ñи, като натиÑнете \"СподелÑнеâ€."</b></string> + <string name="submission_test_result_available_text_consent_given">"Благодарим Ви, че Ñе ÑъглаÑихте да Ñподелите резултата от Ð’Ð°ÑˆÐ¸Ñ Ñ‚ÐµÑÑ‚ и така да помогнете, като предупредите оÑтаналите. \n\n"<b>"Ð’ Ñледващата Ñтъпка, молÑ, Ñподелете резултата от теÑта Ñи, като натиÑнете „СподелÑне“."</b></string> <!-- XHED: Close screen popup title for consent given --> - <string name="submission_test_result_available_close_dialog_title_consent_given">"Потвърждавате ли, че желаете да отмените процеÑа?"</string> + <string name="submission_test_result_available_close_dialog_title_consent_given">"Показване на резултат от теÑÑ‚"</string> <!-- XTXT: Close screen popup text for consent given --> - <string name="submission_test_result_available_close_dialog_body_consent_given">"Важно:\nÐа път Ñте да отмените този процеÑ.\nÐ’Ñе още не Ñте предупредили оÑтаналите. МолÑ, завършете процеÑа, за да помогнете на оÑтаналите да Ñе предпазÑÑ‚."</string> + <string name="submission_test_result_available_close_dialog_body_consent_given">"Ðко е необходимо, Ñлед като видите резултата от теÑта Ñи, можете да уведомите оÑтаналите потребители, за да прекъÑнете веригата на заразÑване."</string> <!-- YTXT: Text for consent NOT given --> <string name="submission_test_result_available_text_consent_not_given">"Избрали Ñте да не ÑподелÑте резултата от теÑта Ñи. ОÑтаналите нÑма да бъдат предупредени. \n\nÐ’ Ñледващата Ñтъпка можете да промените решението Ñи и да Ñподелите резултата от теÑта Ñи, за да помогнете да Ñе Ñпре разпроÑтранението на коронавируÑа и да предпазите оÑтаналите."</string> <!-- XHED: Close screen popup title for consent NOT given --> @@ -1393,9 +1415,9 @@ <!-- XTXT: Close screen popup text for consent NOT given --> <string name="submission_test_result_available_close_dialog_body_consent_not_given">"Въведените от Ð’Ð°Ñ Ð´Ð°Ð½Ð½Ð¸ нÑма да бъдат запазени."</string> <!-- XBUT: Close screen popup cancel button --> - <string name="submission_test_result_available_close_dialog_cancel_button">"Отказ"</string> + <string name="submission_test_result_available_close_dialog_cancel_button">"Ðе показвай"</string> <!-- XBUT: Close screen popup continue button --> - <string name="submission_test_result_available_close_dialog_continue_button">"Ðапред"</string> + <string name="submission_test_result_available_close_dialog_continue_button">"Покажи"</string> <!-- Submission your consent screen --> <!-- XHED: Your consent screen title --> @@ -1461,7 +1483,7 @@ <!-- XHED: Explanation screen confirmed new infections title --> <string name="statistics_explanation_confirmed_new_infection_title">"Потвърдени нови Ñлучаи на заразÑване"</string> <!-- XTXT: Explanation screen confirmed new infections text --> - <string name="statistics_explanation_confirmed_new_infection_text">"Брой диагноÑтицирани Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ Ð»Ð¸Ñ†Ð°, региÑтрирани в инÑтитута “Роберт Кохâ€"</string> + <string name="statistics_explanation_confirmed_new_infection_text">"Брой диагноÑтицирани Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ Ð»Ð¸Ñ†Ð°, региÑтрирани в инÑтитута „Роберт Кох“"</string> <!-- XHED: Explanation screen warned persons title --> <string name="statistics_explanation_warned_persons_title">"Лица, изпратили предупреждениÑ"</string> <!-- XTXT: Explanation screen warned persons text --> @@ -1512,7 +1534,7 @@ <string name="statistics_explanation_illustration_description">"ÐбÑтрактно изображение на Ñмартфон Ñ 4 контейнера за данни"</string> <!-- XTXT: Statistics Card Announcement --> - <string name="accessibility_statistics_card_announcement">"Плочка “СтатиÑтикаâ€"</string> + <string name="accessibility_statistics_card_announcement">"Плочка „СтатиÑтика“"</string> <!-- XTXT: Statistics Card Navigation Announcement --> <string name="accessibility_statistics_card_navigation_information">"Плъзнете хоризонтално между плочките, за да покажете още ÑтатиÑтичеÑки данни"</string> @@ -1544,20 +1566,22 @@ <!-- 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> <!-- XTXT: error dialog - Error title when the provideDiagnosisKeys quota limit was reached. --> <string name="errors_risk_detection_limit_reached_title">"Лимитът вече е доÑтигнат"</string> <!-- XTXT: error dialog - Error description when the provideDiagnosisKeys quota limit was reached. --> - <string name="errors_risk_detection_limit_reached_description">"Ðе може да правите повече проверки за излагане на риÑк до ÐºÑ€Ð°Ñ Ð½Ð° денÑ, тъй като Ñте доÑтигнали макÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ð±Ñ€Ð¾Ð¹ проверки, определен от вашата операционна ÑиÑтема. МолÑ, проверете ÑтатуÑа Ñи на риÑк утре."</string> + <string name="errors_risk_detection_limit_reached_description">"Ðе може да правите повече проверки за излагане на риÑк до ÐºÑ€Ð°Ñ Ð½Ð° денÑ, тъй като Ñте доÑтигнали макÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ð±Ñ€Ð¾Ð¹ проверки, определен от Вашата операционна ÑиÑтема. МолÑ, проверете ÑтатуÑа Ñи на риÑк утре."</string> <!-- XTXT: error dialog - Error description when the ssl certificate is deactivated on the device. --> - <string name="errors_ssl_certificate_deactivated">"МолÑ, активирайте на вашето уÑтройÑтво Ñертификата за ÑигурноÑÑ‚ T-TeleSec GlobalRoot Class 2, издаден от T-Systems Enterprise Services GmbH. Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите в ЧЗВ на Ð°Ð´Ñ€ÐµÑ https://coronawarn.app/en/ under “CAUSE: 2001â€."</string> + <string name="errors_ssl_certificate_deactivated">"МолÑ, активирайте на Вашето уÑтройÑтво Ñертификата за ÑигурноÑÑ‚ T-TeleSec GlobalRoot Class 2, издаден от T-Systems Enterprise Services GmbH. Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите в ЧЗВ на Ð°Ð´Ñ€ÐµÑ https://coronawarn.app/en/ в раздел „CAUSE: 2001“."</string> <!-- #################################### Generic Error Messages ###################################### --> <!-- XHED: error dialog - headline --> + <string name="errors_generic_headline_short">"Грешка"</string> + <!-- XHED: error dialog - headline --> <string name="errors_generic_headline">"Ðещо Ñе обърка."</string> <!-- XTXT: error dialog - short text for error reason --> <string name="errors_generic_details_headline">"Причина"</string> @@ -1688,12 +1712,12 @@ <!-- XHED: Header right above the country list in the interoperability information/configuration view --> <string name="interoperability_configuration_list_title">"Ð’ момента в международното региÑтриране на излаганиÑта на риÑк от заразÑване учаÑтват Ñледните държави:"</string> <!-- XTXT: Text right under the country list in the interoperability information/configuration view --> - <string name="interoperability_configuration_information">"ДекларациÑта за поверителноÑÑ‚ на приложението (коÑто включва Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° обработката на данни от функциÑта за международно региÑтриране на излаганиÑта) ще откриете в менюто в раздел “За приложението†> “ПоверителноÑÑ‚â€."</string> + <string name="interoperability_configuration_information">"ДекларациÑта за поверителноÑÑ‚ на приложението (коÑто включва Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° обработката на данни от функциÑта за международно региÑтриране на излаганиÑта) ще откриете в менюто в раздел „За приложението“ > „ПоверителноÑт“."</string> <!-- XHED: Sub header introducing interoperability in the tracing step of onboarding --> <string name="interoperability_onboarding_title">"Международно\nрегиÑтриране на излаганиÑта"</string> <!-- YMSG: Onboarding tracing step first section in interoperability after the title --> - <string name="interoperability_onboarding_first_section">"ÐÑколко държави обединиха уÑилиÑ, за да направÑÑ‚ възможни международните предупреждениÑ. С други думи, вече могат да Ñе вземат под внимание потенциалните ви контакти Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ð¸ на официалните Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð° проÑледÑване на разпроÑтранението на коронавируÑа във вÑички учаÑтващи държави."</string> + <string name="interoperability_onboarding_first_section">"ÐÑколко държави обединиха уÑилиÑ, за да направÑÑ‚ възможни международните предупреждениÑ. С други думи, вече могат да Ñе вземат под внимание потенциалните Ви контакти Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ð¸ на официалните Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð° проÑледÑване на разпроÑтранението на коронавируÑа във вÑички учаÑтващи държави."</string> <!-- YMSG: Onboarding tracing step second section in interoperability after the title --> <string name="interoperability_onboarding_second_section">"Когато нÑкой потребител изпрати ÑÐ²Ð¾Ñ Ð¿Ð¾Ð»Ð¾Ð¶Ð¸Ñ‚ÐµÐ»ÐµÐ½ резултат от теÑта (по-конкретно: Ñвоите Ñлучайни идентификатори), за да предупреди оÑтаналите, към Ñървъра за обмен на данни, ÑъвмеÑтно поддържан от учаÑтващите държави, вÑички потребители на официалните Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð° борба Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑа във вÑÑка една от Ñ‚ÑÑ… получават необходимите предупреждениÑ."</string> <!-- YMSG: Onboarding tracing step third section in interoperability after the title. --> @@ -1706,7 +1730,7 @@ <!-- XLNK: Terms of use link inside delta interoperability screen--> <string name="interoperability_onboarding_delta_terms_link">"Покажи уÑловиÑта за ползване"</string> <!-- XTXT: Description of the expanded terms in delta interopoerability screen part 2 --> - <string name="interoperability_onboarding_delta_expanded_terms_text_part_2">"УÑловиÑта за ползване и декларациÑта за поверителноÑÑ‚ могат да бъдат прегледани от опциÑта \"За приложението\" в менюто и от опиÑанието в онлайн магазина. Промените нÑма да Ñе отразÑÑ‚ на работата на приложението. Ðко продължите да го ползвате или го отворите повторно, ще приемем, че Ñте Ñе ÑъглаÑили Ñ Ð¾Ð±Ð½Ð¾Ð²ÐµÐ½Ð¸Ñ‚Ðµ уÑÐ»Ð¾Ð²Ð¸Ñ Ð·Ð° ползване."</string> + <string name="interoperability_onboarding_delta_expanded_terms_text_part_2">"УÑловиÑта за ползване и декларациÑта за поверителноÑÑ‚ могат да бъдат прегледани от опциÑта „За приложението“ в менюто и от опиÑанието в онлайн магазина. Промените нÑма да Ñе отразÑÑ‚ на работата на приложението. Ðко продължите да го ползвате или го отворите повторно, ще приемем, че Ñте Ñе ÑъглаÑили Ñ Ð¾Ð±Ð½Ð¾Ð²ÐµÐ½Ð¸Ñ‚Ðµ уÑÐ»Ð¾Ð²Ð¸Ñ Ð·Ð° ползване."</string> <!-- XHED: Header of the delta onboarding screen for interoperability. If the user opens the app for the first time after the interoperability update --> <string name="interoperability_onboarding_delta_title">"Международно\nрегиÑтриране на излаганиÑта"</string> <!-- XTXT: Description of the interoperability extension of the app. Below interoperability_onboarding_delta_title --> @@ -1781,7 +1805,7 @@ <!-- XHED: Text for the access survey title displayed at the top of survey consent screen --> <string name="datadonation_details_survey_consent_top_title">"Ðнкета за оценÑване и подобрÑване на приложениет Corona-Warn-App."</string> <!-- XHED: Text for the access survey body displayed under the title of survey consent screen --> - <string name="datadonation_details_survey_consent_top_body"><b>"Помогнете ни да подобрим Corona-Warn-App, като попълните анкетата отноÑно работата Ви Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÑ‚Ð¾. По този начин екипът от инÑтитута “Роберт Кох†ще може да оцени ефективноÑтта на приложението, да го подобри, както и да разбере какво влиÑние оказват предупреждениÑта от приложението върху поведението на хората Ñ Ð¿Ð¾Ð²Ð¸ÑˆÐµÐ½Ð¾ ниво на риÑк от заразÑване."</b>"\n\nÐнкетата е предназначена единÑтвено за тези, чието приложение е отчело излагане Ñ Ð¿Ð¾Ð²Ð¸ÑˆÐµÐ½ риÑк. Можете да Ñ Ð¿Ð¾Ð¿ÑŠÐ»Ð½Ð¸Ñ‚Ðµ на уебÑайта на инÑтитута “Роберт Кох†– в Ñледващата Ñтъпка ще видите връзка към него. Преди това обаче Ñ‚Ñ€Ñбва да потвърдим автентичноÑтта на приложението Ви, за което ни е необходимо Вашето ÑъглаÑие.\n\nКогато опитате да отворите връзката, ще получите допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° анкетата. ОÑвен това отново ще поиÑкаме Вашето изрично ÑъглаÑие, преди да започнете да Ñ Ð¿Ð¾Ð¿ÑŠÐ»Ð²Ð°Ñ‚Ðµ."</string> + <string name="datadonation_details_survey_consent_top_body"><b>"Помогнете ни да подобрим Corona-Warn-App, като попълните анкетата отноÑно работата Ви Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÑ‚Ð¾. По този начин екипът от инÑтитута „Роберт Кох“ ще може да оцени ефективноÑтта на приложението, да го подобри, както и да разбере какво влиÑние оказват предупреждениÑта от приложението върху поведението на хората Ñ Ð¿Ð¾Ð²Ð¸ÑˆÐµÐ½Ð¾ ниво на риÑк от заразÑване."</b>"\n\nÐнкетата е предназначена единÑтвено за тези, чието приложение е отчело излагане Ñ Ð¿Ð¾Ð²Ð¸ÑˆÐµÐ½ риÑк. Можете да Ñ Ð¿Ð¾Ð¿ÑŠÐ»Ð½Ð¸Ñ‚Ðµ на уебÑайта на инÑтитута „Роберт Кох“ – в Ñледващата Ñтъпка ще видите връзка към него. Преди това обаче Ñ‚Ñ€Ñбва да потвърдим автентичноÑтта на приложението Ви, за което ни е необходимо Вашето ÑъглаÑие.\n\nКогато опитате да отворите връзката, ще получите допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° анкетата. ОÑвен това отново ще поиÑкаме Вашето изрично ÑъглаÑие, преди да започнете да Ñ Ð¿Ð¾Ð¿ÑŠÐ»Ð²Ð°Ñ‚Ðµ."</string> <!-- XHED: Title of the survey consent detail screen --> <string name="datadonation_details_survey_consent_details_title">"Подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно обработката на данни при учаÑтие в анкетата"</string> @@ -1812,9 +1836,9 @@ <string name="datadonation_details_survey_consent_progress_dialog_msg">"Зареждане на анкетата..."</string> <!-- XTXT: Title for the information below the box in the survey consent detail screen --> - <string name="datadonation_survey_consent_details_title_below"><b>"ОтноÑно анкетата на инÑтитута “Роберт Кохâ€"</b></string> + <string name="datadonation_survey_consent_details_title_below"><b>"ОтноÑно анкетата на инÑтитута „Роберт Кох“"</b></string> <!-- XHED: Text for the information below the box in the survey consent detail screen --> - <string name="datadonation_survey_consent_details_text_below">"Когато автентичноÑтта на Вашето приложение бъде потвърдена, то ще Ви препрати към уебÑайта на инÑтитута “Роберт Кох†чрез генерирана за Ð’Ð°Ñ Ð²Ñ€ÑŠÐ·ÐºÐ° към анкетата. Връзката Ñъдържа еднократна парола, коÑто Ñе генерира от Вашето приложение Corona-Warn-App. Когато докоÑнете връзката и отворите уебÑайта Ñ Ð°Ð½ÐºÐµÑ‚Ð°Ñ‚Ð°, еднократната парола ще бъде запазена временно в браузъра Ви. Този екран Ñъдържа допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно анкетата и бележки Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ â€“ например за това как да Ñтартирате анкетата. При Ñтартирането на анкетата еднократната парола ще бъде изпратена към Ñървърите на Corona-Warn-App, където ще бъде обозначена като “използванаâ€. След това Ñървърът проверÑва дали може да попълните анкетата. По този начин Ñе гарантира, че вÑеки отделен потребител ще попълни анкетата Ñамо веднъж."</string> + <string name="datadonation_survey_consent_details_text_below">"Когато автентичноÑтта на Вашето приложение бъде потвърдена, то ще Ви препрати към уебÑайта на инÑтитута „Роберт Кох“ чрез генерирана за Ð’Ð°Ñ Ð²Ñ€ÑŠÐ·ÐºÐ° към анкетата. Връзката Ñъдържа еднократна парола, коÑто Ñе генерира от Вашето приложение Corona-Warn-App. Когато докоÑнете връзката и отворите уебÑайта Ñ Ð°Ð½ÐºÐµÑ‚Ð°Ñ‚Ð°, еднократната парола ще бъде запазена временно в браузъра Ви. Този екран Ñъдържа допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно анкетата и бележки Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ â€“ например за това как да Ñтартирате анкетата. При Ñтартирането на анкетата еднократната парола ще бъде изпратена към Ñървърите на Corona-Warn-App, където ще бъде обозначена като „използвана“. След това Ñървърът проверÑва дали може да попълните анкетата. По този начин Ñе гарантира, че вÑеки отделен потребител ще попълни анкетата Ñамо веднъж."</string> <!-- XHED: Analytics voluntary user input, age group toolbar title --> <string name="analytics_userinput_agegroup_title">"Вашата възраÑтова група"</string> diff --git a/Corona-Warn-App/src/main/res/values-de/legal_strings.xml b/Corona-Warn-App/src/main/res/values-de/legal_strings.xml index 506e8246cbd4ad11a37e2f42c35a1d6e2c77a493..4bce7d55bc5cd4c850a6e0669f6d0f1440690cf1 100644 --- a/Corona-Warn-App/src/main/res/values-de/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/legal_strings.xml @@ -65,7 +65,7 @@ <!-- XHED: Title for the information box in the survey consent detail screen --> <string name="datadonation_survey_consent_details_title">"Prüfung der Echtheit und Drittlandsübermittlung"</string> <!-- XTXT: Text for the information box in the survey consent detail screen --> - <string name="datadonation_survey_consent_details_text">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Nutzer mehrfach der Befragung teilnehmen und so die Ergebnisse der Befragung verfälschen. Die Kennung wird hier einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Teilnahme an der Befragung erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\n\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Teilnahme an dieser Befragung ist dann jedoch nicht möglich."</string> + <string name="datadonation_survey_consent_details_text">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Nutzer mehrfach an der Befragung teilnehmen und so die Ergebnisse der Befragung verfälschen. Die Kennung wird hier einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Teilnahme an der Befragung erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\n\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Teilnahme an dieser Befragung ist dann jedoch nicht möglich."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> <string name="ppa_onboarding_consent_title" translatable="false">"Ihr Einverständnis"</string> @@ -80,19 +80,12 @@ <!-- XHED: Title for Privacy-preserving Analytics additional info --> <string name="ppa_onboarding_more_info_title" translatable="false">"Prüfung der Echtheit und Drittlandsübermittlung"</string> <!-- XTXT: Body for Privacy-preserving Analytics additional info --> - <string name="ppa_onboarding_more_info_body" translatable="false">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Nutzer mehrfach der Befragung teilnehmen und so die Ergebnisse der Befragung verfälschen. Die Kennung wird hier einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Teilnahme an der Befragung erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\n\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Teilnahme an dieser Befragung ist dann jedoch nicht möglich."</string> + <string name="ppa_onboarding_more_info_body" translatable="false">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Daten mehrfach oder missbräuchlich an das RKI übermittelt werden und so die Ergebnisse der Analyse verfälschen. Die Kennung wird an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht angemessenes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Nutzung der Corona-Warn-App erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, die Datenspende ist dann jedoch nicht möglich."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> <string name="ppa_onboarding_privacy_information_title" translatable="false">"Ihr Einverständnis"</string> <!-- XTXT: Body for Privacy-preserving Analytics settings --> <string name="ppa_settings_privacy_information_body" translatable="false">"Indem Sie oben „Datenspende“ aktivieren, willigen Sie ein:\n\nDie App übermittelt täglich von ihr erfasste Angaben an das RKI. Die Daten betreffen angezeigte Risiko-Begegnungen und Warnungen, durch Sie abgerufene Testergebnisse, ob Sie andere Nutzer gewarnt haben sowie Angaben über das Betriebssystem Ihres Smartphones. Wenn Sie oben weitere Angaben gemacht haben (Region, Altersgruppe), werden auch diese an das RKI übermittelt.\n\nDas RKI wird diese Daten zu Statistiken zusammenfassen und auswerten, um die Wirksamkeit und Funktionsweise der App zu bewerten und Rückschlüsse auf das Pandemiegeschehen zu ziehen. Die dabei gefundenen Erkenntnisse helfen bei der Verbesserung der Funktionen und Nutzerfreundlichkeit der App sowie bei der Steuerung anderer Maßnahmen der Pandemiebekämpfung.\n\nBevor Ihre Daten ausgewertet werden, muss sichergestellt sein, dass jede an der Datenspende teilnehmende App nur einmal gezählt wird und die Statistiken nicht verfälscht werden. Hierfür muss die Echtheit Ihrer App geprüft werden. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Google in die USA oder andere Drittländer übermittelt, damit Google die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Google kann damit möglicherweise auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\nWeitere Angaben aus der App erhält Google hierbei nicht.\n\nSie können Ihr Einverständnis jederzeit zurücknehmen, indem Sie oben „Datenspende“ deaktivieren."</string> - <!-- XHED: Title for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_title" translatable="false">"Prüfung der Echtheit und Drittlandsübermittlung"</string> - <!-- YTXT: First section for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_first_section" translatable="false">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um sicherzustellen, dass nur Nutzer Daten auf diesem Weg an den technischen Support übersenden, die tatsächlich die Corona-Warn-App nutzen und nicht manipulierte Fehlerberichte bereitstellen. Die Kennung wird dafür einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die Angaben aus Ihrem Fehlerbericht erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat."</string> - <!-- YTXT: Second section for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_second_section" translatable="false">"Wenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden und Fehlerbericht senden“ an. Sie können die App weiterhin nutzen, eine Ãœbersendung des Fehlerberichtes über die App ist dann jedoch nicht möglich."</string> - <!-- XHED: Title for debug upload screen privacy card --> <string name="debugging_debuglog_privacy_card_title" translatable="false">"Datenschutz und Datensicherheit"</string> <!-- YTXT: First bullet point for debug upload screen privacy card --> 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 0055ef71a88b3db5ed9a851619a8fd2b25694e31..6e6d5106ce8aac361f155b78f0b1aa73f19296d9 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -1,48 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> - - <!-- #################################### - Preference Keys - ###################################### - TODO: Check what is needed --> - - <!-- NOTR --> - <string name="preference_name"><xliff:g id="preference">"shared_preferences_cwa"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed"><xliff:g id="preference">"preference_onboarding_completed"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed_timestamp"><xliff:g id="preference">"preference_onboarding_completed_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_check_done"><xliff:g id="preference">"preference_background_check_done"</xliff:g></string> - <!-- NOTR --> - <string name="preference_reset_app"><xliff:g id="preference">"preference_reset_app"</xliff:g></string> - <!-- NOTR --> - <string name="preference_only_wifi"><xliff:g id="preference">"preference_only_wifi"</xliff:g></string> - <!-- NOTR --> - <string name="preference_tracing"><xliff:g id="preference">"preference_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> - <!-- NOTR --> - <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_result_received_time"><xliff:g id="preference">"preference_initial_result_received_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_is_allowed_to_submit_diagnosis_keys"><xliff:g id="preference">"preference_is_allowed_to_submit_diagnosis_keys"</xliff:g></string> - <!-- NOTR --> - <string name="preference_database_password"><xliff:g id="preference">"preference_database_password"</xliff:g></string> - <!-- NOTR --> - <string name="preference_total_non_active_tracing"><xliff:g id="preference">"preference_total_non_active_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_last_non_active_tracing_timestamp"><xliff:g id="preference">"preference_last_non_active_tracing_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_number_successful_submissions"><xliff:g id="preference">"preference_number_successful_submissions"</xliff:g></string> - <!-- NOTR --> - <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string> - <!-- NOTR --> - <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string> - <!-- #################################### Generics ###################################### --> @@ -905,8 +862,22 @@ <string name="debugging_debuglog_status_not_recording">"Inaktiv"</string> <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_additional_infos">"Derzeitige Größe: %1$s (unkomprimiert)"</string> + + <!-- XHED: Title for debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_title">"Prüfung der Echtheit und Drittlandsübermittlung"</string> + <!-- YTXT: First section for debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_first_section">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um sicherzustellen, dass nur Nutzer Daten auf diesem Weg an den technischen Support übersenden, die tatsächlich die Corona-Warn-App nutzen und nicht manipulierte Fehlerberichte bereitstellen. Die Kennung wird dafür einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die Angaben aus Ihrem Fehlerbericht erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat."</string> + <!-- YTXT: Second section for debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_second_section">"Wenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden und senden“ an. Sie können die App weiterhin nutzen, eine Ãœbersendung des Fehlerberichtes über die App ist dann jedoch nicht möglich."</string> + + <!-- YTXT: Dialog title if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_title">"Wollen Sie die Fehleranalyse wirklich stoppen?"</string> <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> - <string name="debugging_debuglog_stop_confirmation_message">"Der Fehlerbericht wurde gelöscht. Wenn Sie eine lokale Kopie des Fehlerberichts gespeichert haben, wurde diese nicht gelöscht."</string> + <string name="debugging_debuglog_stop_confirmation_message">"Hierbei werden alle bereits aufgezeichneten Daten gelöscht. Wenn Sie eine lokale Kopie des Fehlerberichts gespeichert haben, wird diese nicht gelöscht."</string> + <!-- YTXT: Dialog confirmation button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Analyse stoppen"</string> + <!-- YTXT: Dialog discard button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Analyse fortführen"</string> <!-- YTXT: Dialog message if there is not enough free storage to start a debug log --> <string name="debugging_debuglog_start_low_storage_error">"Sie brauchen mindestens 200 MB Speicherplatz, um die Fehleranalyse zu starten. Bitte geben Sie Speicherplatz frei."</string> <!-- XHED: Dialog title if a user has stored a debug log locally --> @@ -915,7 +886,6 @@ <string name="debugging_debuglog_localexport_message">"Die Fehleranalyse wurde lokal gespeichert."</string> <!-- YTXT: Dialog message if local export has failed --> <string name="debugging_debuglog_localexport_error_message">"Das Speichern des Fehlerberichts ist fehlgeschlagen. Bitte überprüfen Sie, ob genügend Speicherplatz zur Verfügung steht."</string> - <!-- XHED: Title for debug legal screen --> <string name="debugging_debuglog_legal_dialog_title">"Ausführliche Informationen zur Ãœbersendung der Fehlerberichte"</string> <!-- YTXT: Section Title for debug legal screen --> @@ -930,7 +900,7 @@ <!-- YTXT: Second body section for bugreporting share log screen --> <string name="debugging_debuglog_share_log_section_two">"Nach der Ãœbersendung erhalten Sie eine Fehlerbericht-ID. Diese können Sie angeben, um dem technischen Support z.B. weitere Informationen zukommen zu lassen und dabei eine Zuordnung zu Ihrem Fehlerbericht zu ermöglichen. Wenn Sie die Fehlerbericht-ID nicht mitteilen, ist dem RKI eine Zuordnung zu Ihrer Person nicht möglich."</string> <!-- YTXT: Privacy Information section for bugreporting share log screen --> - <string name="debugging_debuglog_share_log_privacy_information">"Ausführliche Informationen zu dieser Datenverarbeitung und den Datenschutzrisiken in den USA und anderen Drittländern."</string> + <string name="debugging_debuglog_share_log_privacy_information">"Ausführliche Informationen zu dieser Datenverarbeitung und den Datenschutzrisiken in den USA und anderen Drittländern"</string> <!-- XBUT: Button for bugreporting share log screen --> <string name="debugging_debuglog_share_log_button">"Einverstanden und senden"</string> <!-- XHED: Title for log upload history --> diff --git a/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml index 67d81f0efc1f1469fdb4bfba79a26f3cd3a55d34..a22bfd48628ca00e49482eb0232fe4069fe7cac8 100644 --- a/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml @@ -103,9 +103,9 @@ <!-- XHED: Title for the contact diary dialog to delete all persons --> <string name="contact_diary_delete_persons_title">"Do you really want to remove all people?"</string> <!-- XTXT: Message for the contact diary dialog to delete all locations --> - <string name="contact_diary_delete_locations_message">"If you remove a place, all the entries for that place will be removed from your journal."</string> + <string name="contact_diary_delete_locations_message">"If you remove all places, all the entries for all places will be removed from your journal."</string> <!-- XTXT: Message for the contact diary dialog to delete all persons --> - <string name="contact_diary_delete_persons_message">"If you remove a person, all the entries for that person will be removed from your journal."</string> + <string name="contact_diary_delete_persons_message">"If you remove all people, all the entries for all people will be removed from your journal."</string> <!-- XACT: edit icon description for screen readers --> <string name="contact_diary_edit_icon_content_description">"Edit Entry"</string> <!-- XACT: edit icon description for screen readers --> @@ -114,6 +114,10 @@ <string name="contact_diary_delete_location_title">"Do you really want to remove this place?"</string> <!-- XHED: Title for the contact diary dialog to delete delete a single person --> <string name="contact_diary_delete_person_title">"Do you really want to remove this person?"</string> + <!-- XTXT: Message for the contact diary dialog to delete a single location --> + <string name="contact_diary_delete_location_message">"If you remove a place, all the entries for that place will be removed from your journal."</string> + <!-- XTXT: Message for the contact diary dialog to delete a single person --> + <string name="contact_diary_delete_person_message">"If you remove a person, all the entries for that person will be removed from your journal."</string> <!-- XHED: Title for the contact diary comment info screen --> <string name="contact_diary_comment_info_screen_title">"Note"</string> diff --git a/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml index 97db0a2ff3c673927a356ef88cb27e6801fab66b..a38294582a252128ce8c879e7f0876c1d713e2b3 100644 --- a/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml @@ -12,38 +12,30 @@ <!-- XBUT: Continue button for the release info screen --> <string name="release_info_continue_button">"Next"</string> <!-- XTXT: New release info footer --> - <string name="new_release_bottom">"Changes in this release can be found in the app settings, under “New Featuresâ€."</string> + <string name="new_release_bottom">"Changes in this release can be found in the app information, under “New Featuresâ€."</string> <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> - <item>"Additional Features in the Contact Journal"</item> - <item>"Direct Access to Contact Journal"</item> - <item>"More Details about Your Risk Status"</item> - <item>"Screenshots for the Corona-Warn-App"</item> + <item>"Switzerland Added to Transnational Exposure Logging"</item> + <item>"Generation of Error Reports"</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"You can now enter the specific circumstances of each encounter. You can select from the predefined options (such as duration of the encounter, with or without mask) and also enter a brief note to record other circumstances that might result in an increased risk. If you become infected, this information can help the public health authority trace potential chains of infection."</item> - <item>"You can now add an entry to your contact journal directly, without having to open the app first. To do so, tap and hold the icon for the Corona-Warn-App for approximately 2 seconds, until a menu appears, and then tap “Add Journal Entry for Todayâ€."</item> - <item>"If the app indicates that you have an increased risk, you can now see whether the risk status is displayed based on one or more exposures with an increased risk or one or more exposures with a low risk."</item> - <item>"The website https://www.coronawarn.app now contains all the screenshots for the app, so you can find out about planned features, for example."</item> + <item>"Corona-Warn-App users can now exchange encrypted random IDs with users of the official warning app in Switzerland. This means warnings can now be sent to and received from app users in Switzerland."</item> + <item>"You can now generate an error report, upon request by technical support, and record the steps that you perform in the app. This will make it easier to analyze technical errors and correct them more quickly."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> <string-array name="new_release_linkified_labels"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app"</item> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app/en/screenshots"</item> </string-array> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-en/strings.xml b/Corona-Warn-App/src/main/res/values-en/strings.xml index aba2825d1892a209d67585843c711f1da7f1535c..d21b9aeb089913ffc7d1e701b4293a6ab9c9c404 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -1,47 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?><resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> - - <!-- #################################### - Preference Keys - ###################################### - TODO: Check what is needed --> - - <!-- NOTR --> - <string name="preference_name"><xliff:g id="preference">"shared_preferences_cwa"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed"><xliff:g id="preference">"preference_onboarding_completed"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed_timestamp"><xliff:g id="preference">"preference_onboarding_completed_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_check_done"><xliff:g id="preference">"preference_background_check_done"</xliff:g></string> - <!-- NOTR --> - <string name="preference_reset_app"><xliff:g id="preference">"preference_reset_app"</xliff:g></string> - <!-- NOTR --> - <string name="preference_only_wifi"><xliff:g id="preference">"preference_only_wifi"</xliff:g></string> - <!-- NOTR --> - <string name="preference_tracing"><xliff:g id="preference">"preference_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> - <!-- NOTR --> - <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_result_received_time"><xliff:g id="preference">"preference_initial_result_received_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_is_allowed_to_submit_diagnosis_keys"><xliff:g id="preference">"preference_is_allowed_to_submit_diagnosis_keys"</xliff:g></string> - <!-- NOTR --> - <string name="preference_database_password"><xliff:g id="preference">"preference_database_password"</xliff:g></string> - <!-- NOTR --> - <string name="preference_total_non_active_tracing"><xliff:g id="preference">"preference_total_non_active_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_last_non_active_tracing_timestamp"><xliff:g id="preference">"preference_last_non_active_tracing_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_number_successful_submissions"><xliff:g id="preference">"preference_number_successful_submissions"</xliff:g></string> - <!-- NOTR --> - <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string> - <!-- NOTR --> - <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string> - <!-- #################################### Generics ###################################### --> @@ -105,6 +62,12 @@ Risk Card ###################################### --> + <!-- XTXT: risk card - Days since installation if < 14 days --> + <string name="risk_card_body_days_since_installation">"Installed %s days ago"</string> + <!-- XTXT: risk card - tracing active for x out of 14 days --> + <string name="risk_card_body_saved_days">"Exposure logging was active for %1$s of the past 14 days"</string> + <!-- XTXT: risk card- tracing active for 14 out of 14 days --> + <string name="risk_card_body_saved_days_full">"Exposure logging permanently active"</string> <!-- XTXT; risk card - no update done yet --> <string name="risk_card_body_not_yet_fetched">"Encounters have not yet been checked."</string> <!-- XTXT: risk card - last successful update --> @@ -307,7 +270,7 @@ <!-- XMSG: risk details - go/stay home, something like a bullet point --> <string name="risk_details_behavior_body_stay_home">"If possible, please go home and stay at home."</string> <!-- XMSG: risk details - get in touch with the corresponding people, something like a bullet point --> - <string name="risk_details_behavior_body_contact_doctor">"If you have questions about symptoms, testing availability, or self-isolation, please contact one of the following:"</string> + <string name="risk_details_behavior_body_contact_doctor">"If you have questions about symptoms, testing availability, or quarantine measures, please contact one of the following:"</string> <!-- XMSG: risk details - wash your hands, something like a bullet point --> <string name="risk_details_behavior_body_wash_hands">"Wash your hands regularly, with soap, for 20 seconds."</string> <!-- XMSG: risk details - wear a face mask, something like a bullet point --> @@ -322,6 +285,8 @@ <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> + <!-- XMSG: risk details - ventilation bullet point --> + <string name="risk_details_behavior_body_ventilation">"Air out indoor spaces several times per day. To do so, open the windows as far as possible for several minutes (“shock ventilationâ€)."</string> <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"Sneeze or cough into your elbow or a tissue."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -337,7 +302,13 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"This period is included in the calculation."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> - <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently."</string> + <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently. Exposure logging covers the last 14 days."</string> + <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> + <!-- XTXT: risk details - infection period logged information body, over 14 days --> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"If exposure logging was active in times during which you encountered other people, your risk of infection can be calculated for this period."</string> + <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment">"Exposure logging covers the past 14 days. During this time, the logging feature on your smartphone was active for %1$s days. The app automatically deletes older logs, as these are no longer relevant for infection prevention."</string> <!-- XTXT: risk details - infection period days logged/14 --> <string name="risk_details_information_active_tracing_days_circle_progress">"%s/14"</string> <!-- XHED: risk details - how your risk level was calculated, below behaviors --> @@ -350,8 +321,12 @@ <string name="risk_details_information_body_low_risk">"You have a low risk of infection because no exposure to people later diagnosed with COVID-19 was logged, or because your encounters were only for a short time and at a greater distance."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"The risk of infection is calculated locally on your smartphone, using exposure logging data. The calculation also takes into account distance and duration of any exposure to persons diagnosed with coronavirus, as well as their potential infectiousness. Your risk of infection cannot be seen by or passed on to anyone else."</string> + <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> + <string name="risk_details_explanation_dialog_faq_body">"For further information, please see our FAQ page."</string> + <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> + <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> - <string name="risk_details_information_body_increased_risk_date">"You have an increased risk of infection because you were last exposed on %1$s over a longer period of time and at close proximity to at least one person diagnosed with coronavirus."</string> + <string name="risk_details_information_body_increased_risk_date">"You have an increased risk of infection because you were exposed over a longer period of time and at close proximity to at least one person diagnosed with COVID-19."</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">"You have an increased risk of infection because you were last exposed %1$s days ago over a longer period of time and at close proximity to at least one person diagnosed with coronavirus."</item> @@ -486,7 +461,7 @@ <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> <string name="onboarding_tracing_location_headline">"Activate Location Setting"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Your device location cannot be determined by the app, but the device location setting must be activated to use Bluetooth Low Energy in some Android versions."</string> + <string name="onboarding_tracing_location_body">"The app cannot determine your location. However, the device location setting must be activated to use Bluetooth Low Energy in Android 10 and earlier versions."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Open Device Settings"</string> <!-- XACT: Onboarding (test) page title --> @@ -599,9 +574,6 @@ <string name="onboarding_ppa_more_info_much_privacy_body">"This information cannot be linked with you personally. Your identity will remain confidential.\nThe information will be analyzed for statistical purposes. It will not be saved for a profile."</string> - - - <!-- #################################### Onboarding sixteen include ###################################### --> @@ -851,26 +823,76 @@ <!-- XACT: describes illustration --> <string name="information_legal_illustration_description">"A hand holds a smartphone displaying a large body of text on the screen. Next to the text is a section symbol representing the imprint."</string> + <!-- #################################### + App Information - Bug Reporting + ###################################### --> + <!-- XHED: Headline for debug log screen --> - <string name="debugging_debuglog_title">"Error Report"</string> - <!-- YTXT: Description for the debug option to record log files --> - <string name="debugging_debuglog_intro_explanation_section_one">"This option logs the app’s behavior in a text file. If this option is activated before an error occurs, you can make this report available to help the developers solve the problem.\nIf you leave this option activated, it will result in high storage requirements. If you deactivate the option, the error report is deleted."</string> + <string name="debugging_debuglog_title">"Error Reports"</string> + <!-- XHED: Headline for current status of debug log screen --> + <string name="debugging_debuglog_current_status_title">"Error Analysis"</string> + <!-- YTXT: Description one for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_one">"To help the app technical support team with error analysis, you can record an error report from the CWA. When you do so, the individual technical steps and results of app processes are recorded. You can then send the error report to technical support and help to identify and correct the error."</string> + <!-- YTXT: Description two for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_two">"For further information, please see our FAQ page:\nFAQ for error reports"</string> + <!-- XTXT: Debug Log screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="debugging_debuglog_intro_explanation_section_two_link_label">"FAQ for error reports"</string> + <!-- XTXT: Explains user about about debug log: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> + <string name="debugging_debuglog_intro_explanation_section_two_faq_link">"https://www.coronawarn.app/en/faq/#error_log"</string> + <!-- YTXT: Title of ID History --> + <string name="debugging_debuglog_id_history_title">"ID History"</string> + <!-- YTXT: Description of ID History --> + <string name="debugging_debuglog_id_history_body">"IDs of the error analyses shared so far"</string> <!-- YTXT: Warning regarding downsides of recording a log file --> <string name="debugging_debuglog_intro_warning">"Please note that error reports may contain sensitive data (such as a test result or contact journal entries). Therefore, you should not share these error reports publicly."</string> <!-- XBUT: Button text to start the log recording --> <string name="debugging_debuglog_action_start_recording">"Start"</string> <!-- XBUT: Button text to stop the log recording --> - <string name="debugging_debuglog_action_stop_recording">"Delete"</string> + <string name="debugging_debuglog_action_stop_recording">"Stop and Delete"</string> <!-- XBUT: Button text to share the log recording --> - <string name="debugging_debuglog_action_share_log">"Share"</string> + <string name="debugging_debuglog_action_share_log">"Send Error Report"</string> + <!-- XBUT: Button text to locally store the log recording --> + <string name="debugging_debuglog_action_local_log_store">"Save Locally and Continue"</string> <!-- YTXT: Status text if a debug log is being recorded --> <string name="debugging_debuglog_status_recording">"Recording Active"</string> + <!-- YTXT: Status text if a debug log is being recorded but there is not enough free storage space --> + <string name="debugging_debuglog_status_lowstorage">"Not enough memory"</string> <!-- YTXT: Status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_not_recording">"Inactive"</string> <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_additional_infos">"Current size: %1$s (uncompressed)"</string> - <!-- XHED: Title for native sharing dialog --> - <string name="debugging_debuglog_sharing_dialog_title">"Share CWA Error Report"</string> + <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_message">"The error report was deleted. If you have saved a local copy of the error report, it was not deleted."</string> + <!-- YTXT: Dialog message if there is not enough free storage to start a debug log --> + <string name="debugging_debuglog_start_low_storage_error">"You need at least 200 MB of memory to start the error analysis. Please free up memory."</string> + <!-- XHED: Dialog title if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_title">"Saved Locally"</string> + <!-- YTXT: Dialog message if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_message">"The error analysis was saved locally."</string> + <!-- YTXT: Dialog message if local export has failed --> + <string name="debugging_debuglog_localexport_error_message">"The attempt to save the error report failed. Please check whether enough memory is available."</string> + + <!-- XHED: Title for debug legal screen --> + <string name="debugging_debuglog_legal_dialog_title">"Detailed Information on Sending Error Reports"</string> + <!-- YTXT: Section Title for debug legal screen --> + <string name="debugging_debuglog_legal_section_title">"How the RKI analyzes the error reports"</string> + <!-- YTXT: Section Body for debug legal screen --> + <string name="debugging_debuglog_legal_section_body">"After the authenticity of your app has been verified, the error report is sent to the RKI through a secure connection. Error reports will only be used for troubleshooting and error correction within the framework of future app updates. Only technical support employees can access error reports. Error reports contain a variety of status messages and events that are triggered in the app, but do not contain any information that would enable the RKI to determine your identity. Only if you name the error report ID in connection with further messages can a connection be made between the message (and your name contained there, for example) and the information contained in the error report (such as technical messages for calculation as part of exposure logging, information displayed and steps performed in the app, and, where applicable, a retrieved test result and random IDs shared as part of notifying others)."</string> + + <!-- XHED: Title for Bugreporting share log screen --> + <string name="debugging_debuglog_share_log_title">"Send Error Report"</string> + <!-- YTXT: First body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_one">"Your consent is needed before you can send the recorded error report to RKI technical support."</string> + <!-- YTXT: Second body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_two">"After you send the report, you will receive an error report ID. You can specify this ID to provide technical support with further information, for example, and enable its assignment to your error report. If you do not provide the error report ID, the RKI cannot assign the report to you as an individual."</string> + <!-- YTXT: Privacy Information section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_privacy_information">"Detailed Information about This Data Processing and Data Protection Risks in the U.S. and Other Third Countries"</string> + <!-- XBUT: Button for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_button">"Agree and Send"</string> + <!-- XHED: Title for log upload history --> + <string name="debugging_debuglog_uploadhistory_title">"ID History"</string> + <!-- YTXT: Description for log upload history --> + <string name="debugging_debuglog_uploadhistory_description">"You see the IDs of your error analysis logs here."</string> <!-- #################################### Interoperability @@ -1383,9 +1405,9 @@ <!-- YTXT: Text for consent given --> <string name="submission_test_result_available_text_consent_given">"Thank you for agreeing to share your test result and helping to warn others as a result. \n\n"<b>"In the next step, please share your test result by tapping “Shareâ€."</b></string> <!-- XHED: Close screen popup title for consent given --> - <string name="submission_test_result_available_close_dialog_title_consent_given">"Do you really want to cancel the process?"</string> + <string name="submission_test_result_available_close_dialog_title_consent_given">"Display Test Result"</string> <!-- XTXT: Close screen popup text for consent given --> - <string name="submission_test_result_available_close_dialog_body_consent_given">"Important:\nYou are about to cancel this process.\nYou have not warned others yet. Please complete the process and help to protect others."</string> + <string name="submission_test_result_available_close_dialog_body_consent_given">"After you have read your test result, you can notify others if necessary and break the chain of infection."</string> <!-- YTXT: Text for consent NOT given --> <string name="submission_test_result_available_text_consent_not_given">"You have chosen not to share your test result. Others will not be warned. \n\nIn the next step, you have the opportunity to change your mind and share your test result after all, to help stop the spread of coronavirus and protect others."</string> <!-- XHED: Close screen popup title for consent NOT given --> @@ -1393,9 +1415,9 @@ <!-- XTXT: Close screen popup text for consent NOT given --> <string name="submission_test_result_available_close_dialog_body_consent_not_given">"Your entries will not be saved."</string> <!-- XBUT: Close screen popup cancel button --> - <string name="submission_test_result_available_close_dialog_cancel_button">"Cancel"</string> + <string name="submission_test_result_available_close_dialog_cancel_button">"Do Not Display"</string> <!-- XBUT: Close screen popup continue button --> - <string name="submission_test_result_available_close_dialog_continue_button">"Continue"</string> + <string name="submission_test_result_available_close_dialog_continue_button">"Display"</string> <!-- Submission your consent screen --> <!-- XHED: Your consent screen title --> @@ -1558,6 +1580,8 @@ Generic Error Messages ###################################### --> <!-- XHED: error dialog - headline --> + <string name="errors_generic_headline_short">"Error"</string> + <!-- XHED: error dialog - headline --> <string name="errors_generic_headline">"Something went wrong."</string> <!-- XTXT: error dialog - short text for error reason --> <string name="errors_generic_details_headline">"Cause"</string> diff --git a/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml index def8dbd37cbf58ecb39050b2a2c6ed1f712005d3..e5e2c254fd356e1a863dc83095273658a237032e 100644 --- a/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml @@ -103,9 +103,9 @@ <!-- XHED: Title for the contact diary dialog to delete all persons --> <string name="contact_diary_delete_persons_title">"Czy na pewno chcesz usunąć wszystkie osoby?"</string> <!-- XTXT: Message for the contact diary dialog to delete all locations --> - <string name="contact_diary_delete_locations_message">"JeÅ›li usuniesz miejsce, wszystkie wpisy dotyczÄ…ce tego miejsca zostanÄ… usuniÄ™te z Twojego dziennika."</string> + <string name="contact_diary_delete_locations_message">"JeÅ›li usuniesz wszystkie miejsca, wszystkie wpisy dotyczÄ…ce tych miejsc zostanÄ… usuniÄ™te z Twojego dziennika."</string> <!-- XTXT: Message for the contact diary dialog to delete all persons --> - <string name="contact_diary_delete_persons_message">"JeÅ›li usuniesz osobÄ™, wszystkie wpisy dotyczÄ…ce tej osoby zostanÄ… usuniÄ™te z Twojego dziennika."</string> + <string name="contact_diary_delete_persons_message">"JeÅ›li usuniesz wszystkie osoby, wszystkie wpisy dotyczÄ…ce tych osób zostanÄ… usuniÄ™te z Twojego dziennika."</string> <!-- XACT: edit icon description for screen readers --> <string name="contact_diary_edit_icon_content_description">"Edytuj wpis"</string> <!-- XACT: edit icon description for screen readers --> @@ -114,6 +114,10 @@ <string name="contact_diary_delete_location_title">"Czy na pewno chcesz usunąć to miejsce?"</string> <!-- XHED: Title for the contact diary dialog to delete delete a single person --> <string name="contact_diary_delete_person_title">"Czy na pewno chcesz usunąć tÄ™ osobÄ™?"</string> + <!-- XTXT: Message for the contact diary dialog to delete a single location --> + <string name="contact_diary_delete_location_message">"JeÅ›li usuniesz miejsce, wszystkie wpisy dotyczÄ…ce tego miejsca zostanÄ… usuniÄ™te z Twojego dziennika."</string> + <!-- XTXT: Message for the contact diary dialog to delete a single person --> + <string name="contact_diary_delete_person_message">"JeÅ›li usuniesz osobÄ™, wszystkie wpisy dotyczÄ…ce tej osoby zostanÄ… usuniÄ™te z Twojego dziennika."</string> <!-- XHED: Title for the contact diary comment info screen --> <string name="contact_diary_comment_info_screen_title">"Uwaga"</string> diff --git a/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml index f04a4e4705434e19155807dc01b3b52898a4fc11..5bc9b0023e7e19071f869c456e2e5f2844c2b34e 100644 --- a/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml @@ -12,38 +12,30 @@ <!-- XBUT: Continue button for the release info screen --> <string name="release_info_continue_button">"Dalej"</string> <!-- XTXT: New release info footer --> - <string name="new_release_bottom">"Zmiany w tej wersji można znaleźć w ustawieniach aplikacji w sekcji „Nowe funkcjeâ€."</string> + <string name="new_release_bottom">"Dane o zmianach w tej wersji można znaleźć w informacjach o aplikacji w sekcji „Nowe funkcjeâ€."</string> <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> - <item>"Dodatkowe funkcje w dzienniku kontaktów"</item> - <item>"BezpoÅ›redni dostÄ™p do dziennika kontaktów"</item> - <item>"WiÄ™cej szczegółów na temat Twojego statusu ryzyka"</item> - <item>"Zrzuty ekranu dla Corona-Warn-App"</item> + <item>"Do miÄ™dzynarodowego rejestrowania narażenia dodano SzwajcariÄ™."</item> + <item>"Generowanie raportów o bÅ‚Ä™dach"</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"Teraz możesz wprowadzić szczegółowe okolicznoÅ›ci każdego kontaktu. Możesz dokonać wyboru spoÅ›ród predefiniowanych opcji (takich jak czas trwania kontaktu, z maseczkÄ… lub bez), a także wprowadzić krótkÄ… notatkÄ™ w celu zapisania innych okolicznoÅ›ci, które mogÄ… zwiÄ™kszać ryzyko. W przypadku Twojego zakażenia informacje te bÄ™dÄ… mogÅ‚y zostać wykorzystane przez organy ds. zdrowia publicznego do przeÅ›ledzenia potencjalnych Å‚aÅ„cuchów zakażenia."</item> - <item>"Możesz teraz bezpoÅ›rednio dodać wpis do dziennika kontaktów bez koniecznoÅ›ci otwierania aplikacji. Aby to zrobić, dotknij i przytrzymaj ikonÄ™ aplikacji Corona-Warn-App przez okoÅ‚o 2 sekundy, aż pojawi siÄ™ menu, a nastÄ™pnie dotknij opcji „Dodaj wpis do dziennika na dziÅ›\"."</item> - <item>"JeÅ›li aplikacja wskaże podwyższone ryzyko, możesz sprawdzić, czy status ryzyka jest wyÅ›wietlany na podstawie jednego lub wiÄ™kszej liczby narażeÅ„ o podwyższonym ryzyku czy jednego lub wiÄ™kszej liczby o niskim ryzyku."</item> - <item>"Witryna https://www.coronawarn.app zawiera teraz wszystkie zrzuty ekranu aplikacji, dziÄ™ki czemu możesz uzyskać informacji na przykÅ‚ad o planowanych funkcjach."</item> + <item>"Użytkownicy aplikacji Corona-Warn-App mogÄ… teraz wymieniać zaszyfrowane losowe identyfikatory z użytkownikami oficjalnej aplikacji ostrzegawczej w Szwajcarii. Oznacza to, że ostrzeżenia mogÄ… być teraz wysyÅ‚ane do użytkowników aplikacji w Szwajcarii i od nich odbierane."</item> + <item>"Na proÅ›bÄ™ dziaÅ‚u wsparcia technicznego możesz teraz wygenerować raport o bÅ‚Ä™dach i zapisać czynnoÅ›ci wykonane w aplikacji. UÅ‚atwi to analizÄ™ bÅ‚Ä™dów technicznych i przyÅ›pieszy ich usuwanie."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> <string-array name="new_release_linkified_labels"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app"</item> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app/en/screenshots"</item> </string-array> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-pl/strings.xml b/Corona-Warn-App/src/main/res/values-pl/strings.xml index 8736f5ab18f43c9e62a50432db2f01e9abde6885..37ffe028420b7ee05fc4b06555231732833cf717 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -1,47 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?><resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> - - <!-- #################################### - Preference Keys - ###################################### - TODO: Check what is needed --> - - <!-- NOTR --> - <string name="preference_name"><xliff:g id="preference">"shared_preferences_cwa"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed"><xliff:g id="preference">"preference_onboarding_completed"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed_timestamp"><xliff:g id="preference">"preference_onboarding_completed_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_check_done"><xliff:g id="preference">"preference_background_check_done"</xliff:g></string> - <!-- NOTR --> - <string name="preference_reset_app"><xliff:g id="preference">"preference_reset_app"</xliff:g></string> - <!-- NOTR --> - <string name="preference_only_wifi"><xliff:g id="preference">"preference_only_wifi"</xliff:g></string> - <!-- NOTR --> - <string name="preference_tracing"><xliff:g id="preference">"preference_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> - <!-- NOTR --> - <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_result_received_time"><xliff:g id="preference">"preference_initial_result_received_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_is_allowed_to_submit_diagnosis_keys"><xliff:g id="preference">"preference_is_allowed_to_submit_diagnosis_keys"</xliff:g></string> - <!-- NOTR --> - <string name="preference_database_password"><xliff:g id="preference">"preference_database_password"</xliff:g></string> - <!-- NOTR --> - <string name="preference_total_non_active_tracing"><xliff:g id="preference">"preference_total_non_active_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_last_non_active_tracing_timestamp"><xliff:g id="preference">"preference_last_non_active_tracing_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_number_successful_submissions"><xliff:g id="preference">"preference_number_successful_submissions"</xliff:g></string> - <!-- NOTR --> - <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string> - <!-- NOTR --> - <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string> - <!-- #################################### Generics ###################################### --> @@ -105,6 +62,12 @@ Risk Card ###################################### --> + <!-- XTXT: risk card - Days since installation if < 14 days --> + <string name="risk_card_body_days_since_installation">"Zainstalowano %s dni temu"</string> + <!-- XTXT: risk card - tracing active for x out of 14 days --> + <string name="risk_card_body_saved_days">"Rejestrowanie narażenia byÅ‚o aktywne przez %1$s z ostatnich 14 dni"</string> + <!-- XTXT: risk card- tracing active for 14 out of 14 days --> + <string name="risk_card_body_saved_days_full">"Rejestrowanie narażenia stale aktywne"</string> <!-- XTXT; risk card - no update done yet --> <string name="risk_card_body_not_yet_fetched">"Kontakty nie zostaÅ‚y jeszcze sprawdzone."</string> <!-- XTXT: risk card - last successful update --> @@ -307,7 +270,7 @@ <!-- XMSG: risk details - go/stay home, something like a bullet point --> <string name="risk_details_behavior_body_stay_home">"JeÅ›li to możliwe, idź do domu i w nim pozostaÅ„."</string> <!-- XMSG: risk details - get in touch with the corresponding people, something like a bullet point --> - <string name="risk_details_behavior_body_contact_doctor">"W przypadku pytaÅ„ dotyczÄ…cych objawów, możliwoÅ›ci wykonania testu lub samoizolacji skontaktuj siÄ™ z jednym z poniższych miejsc:"</string> + <string name="risk_details_behavior_body_contact_doctor">"W przypadku pytaÅ„ dotyczÄ…cych objawów, możliwoÅ›ci wykonania testu lub kwarantanny skontaktuj siÄ™ z jednym z poniższych miejsc:"</string> <!-- XMSG: risk details - wash your hands, something like a bullet point --> <string name="risk_details_behavior_body_wash_hands">"Regularnie myj rÄ™ce mydÅ‚em przez 20 sekund."</string> <!-- XMSG: risk details - wear a face mask, something like a bullet point --> @@ -322,6 +285,8 @@ <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> + <!-- XMSG: risk details - ventilation bullet point --> + <string name="risk_details_behavior_body_ventilation">"Wietrz pomieszczenia kilka razy dziennie. W tym celu maksymalnie otwórz okna na kilka minut („wentylacja szokowaâ€)."</string> <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"Kichaj i kaszl w Å‚okieć lub chusteczkÄ™."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -337,7 +302,13 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Ten okres jest uwzglÄ™dniony w obliczeniu."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> - <string name="risk_details_information_body_period_logged">"Ryzyko zakażenia można obliczyć tylko dla okresów, w których rejestrowanie narażenia byÅ‚o aktywne. Dlatego też funkcja rejestrowania powinna być stale aktywna."</string> + <string name="risk_details_information_body_period_logged">"Ryzyko zakażenia można obliczyć tylko dla okresów, w których rejestrowanie narażenia byÅ‚o aktywne. Dlatego też funkcja rejestrowania powinna być stale aktywna. Rejestrowanie narażenia obejmuje 14 ostatnich dni."</string> + <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Aplikacja Corona-Warn-App zostaÅ‚a zainstalowana %s temu. Ryzyko zakażenia jest obliczane dla okresów, w których aktywne byÅ‚o rejestrowanie narażenia. Oblicza siÄ™ je w przypadku kontaktowania siÄ™ z innymi ludźmi przy aktywnej funkcji rejestrowania narażenia."</string> + <!-- XTXT: risk details - infection period logged information body, over 14 days --> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"JeÅ›li rejestrowanie narażenia byÅ‚o aktywne podczas kontaktowania siÄ™ z innymi ludźmi, można obliczyć ryzyko zakażenia dla tego okresu."</string> + <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment">"Rejestrowanie narażenia obejmuje ostatnie 14 dni. W tym czasie funkcja rejestrowania w Twoim smartfonie byÅ‚a aktywna przez %1$s dni. Aplikacja automatycznie usuwa starsze dzienniki, ponieważ nie sÄ… one już istotne dla zapobiegania zakażeniom."</string> <!-- XTXT: risk details - infection period days logged/14 --> <string name="risk_details_information_active_tracing_days_circle_progress">"%s/14"</string> <!-- XHED: risk details - how your risk level was calculated, below behaviors --> @@ -350,8 +321,12 @@ <string name="risk_details_information_body_low_risk">"Masz niskie ryzyko zakażenia, ponieważ nie zarejestrowano narażenia na kontakt z osobami, u których później zdiagnozowano COVID-19, lub ponieważ Twoje kontakty trwaÅ‚y krótko przy zachowaniu odpowiednio dużej odlegÅ‚oÅ›ci."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"Ryzyko zakażenia jest obliczane lokalnie na Twoim smartfonie na podstawie danych rejestrowania narażenia. Ta kalkulacja uwzglÄ™dnia również dystans i czas trwania narażenia na kontakt z osobami, u których zdiagnozowano koronawirusa, a także ich potencjalnÄ… zakaźność. Twoje ryzyko zakażenia nie jest widoczne dla nikogo ani nikomu przekazywane."</string> + <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> + <string name="risk_details_explanation_dialog_faq_body">"WiÄ™cej informacji znajduje siÄ™ na naszej stronie „CzÄ™sto zadawane pytaniaâ€."</string> + <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> + <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> - <string name="risk_details_information_body_increased_risk_date">"Masz podwyższone ryzyko zakażenia, ponieważ w dniu %1$s byÅ‚eÅ›(-aÅ›) narażony(-a) na dÅ‚uższy, bliski kontakt z co najmniej jednÄ… osobÄ…, u której zdiagnozowano koronawirusa."</string> + <string name="risk_details_information_body_increased_risk_date">"Masz podwyższone ryzyko zakażenia, ponieważ byÅ‚eÅ›(-aÅ›) narażony(-a) na dÅ‚uższy, bliski kontakt z co najmniej jednÄ… osobÄ…, u której zdiagnozowano COVID-19."</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">"Masz podwyższone ryzyko zakażenia, ponieważ %1$s dzieÅ„ temu byÅ‚eÅ›(-aÅ›) narażony(-a) na dÅ‚uższy, bliski kontakt z co najmniej jednÄ… osobÄ…, u której zdiagnozowano koronawirusa."</item> @@ -486,7 +461,7 @@ <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> <string name="onboarding_tracing_location_headline">"Aktywuj ustawienie lokalizacji"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Aplikacja nie może ustalić lokalizacji urzÄ…dzenia, której ustawienie musi być aktywowane w niektórych wersjach Androida, aby możliwe byÅ‚o korzystanie z technologii Bluetooth Low Energy."</string> + <string name="onboarding_tracing_location_body">"Aplikacja nie może ustalić lokalizacji urzÄ…dzenia, której ustawienie musi być aktywowane w wersji Android 10 i wczeÅ›niejszych, aby możliwe byÅ‚o korzystanie z technologii Bluetooth Low Energy."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Otwórz ustawienia urzÄ…dzenia"</string> <!-- XACT: Onboarding (test) page title --> @@ -599,9 +574,6 @@ <string name="onboarding_ppa_more_info_much_privacy_body">"Tych informacji nie można powiÄ…zać z TwojÄ… osobÄ…. Twoja tożsamość zostanie zachowana w poufnoÅ›ci.\nInformacje zostanÄ… przeanalizowane w celach statystycznych. Nie zostanÄ… zapisane w profilu."</string> - - - <!-- #################################### Onboarding sixteen include ###################################### --> @@ -851,26 +823,76 @@ <!-- XACT: describes illustration --> <string name="information_legal_illustration_description">"W rÄ™ce trzymany jest smartfon z dużą iloÅ›ciÄ… tekstu na ekranie. Obok tekstu znajduje siÄ™ symbol paragrafu reprezentujÄ…cy impressum."</string> + <!-- #################################### + App Information - Bug Reporting + ###################################### --> + <!-- XHED: Headline for debug log screen --> - <string name="debugging_debuglog_title">"Raport o bÅ‚Ä™dach"</string> - <!-- YTXT: Description for the debug option to record log files --> - <string name="debugging_debuglog_intro_explanation_section_one">"Ta opcja umożliwia rejestrowanie zachowania aplikacji w pliku tekstowym. JeÅ›li zostanie ona aktywowana przed wystÄ…pieniem bÅ‚Ä™du, bÄ™dziesz mieć możliwość udostÄ™pnienia tego raportu, aby pomóc programistom rozwiÄ…zać problem.\nPozostawienie tej opcji w stanie aktywnoÅ›ci wiąże siÄ™ z dużym zapotrzebowaniem na pamięć. JeÅ›li jÄ… wyÅ‚Ä…czysz, raport o bÅ‚Ä™dach zostanie usuniÄ™ty."</string> + <string name="debugging_debuglog_title">"Raporty o bÅ‚Ä™dach"</string> + <!-- XHED: Headline for current status of debug log screen --> + <string name="debugging_debuglog_current_status_title">"Analiza bÅ‚Ä™du"</string> + <!-- YTXT: Description one for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_one">"Aby pomóc zespoÅ‚owi pomocy technicznej aplikacji w analizie bÅ‚Ä™dów, możesz zarejestrować raport o bÅ‚Ä™dzie z CWA. Gdy to zrobisz, poszczególne kroki techniczne i wyniki procesów aplikacji zostanÄ… zarejestrowane. NastÄ™pnie możesz wysÅ‚ać raport o bÅ‚Ä™dzie do pomocy technicznej i pomóc w zidentyfikowaniu i usuniÄ™ciu bÅ‚Ä™du."</string> + <!-- YTXT: Description two for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_two">"WiÄ™cej informacji znajduje siÄ™ na naszej stronie „CzÄ™sto zadawane pytaniaâ€:\nCzÄ™sto zadawane pytania dotyczÄ…ce raportów o bÅ‚Ä™dach"</string> + <!-- XTXT: Debug Log screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="debugging_debuglog_intro_explanation_section_two_link_label">"CzÄ™sto zadawane pytania dotyczÄ…ce raportów o bÅ‚Ä™dach"</string> + <!-- XTXT: Explains user about about debug log: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> + <string name="debugging_debuglog_intro_explanation_section_two_faq_link">"https://www.coronawarn.app/en/faq/#error_log"</string> + <!-- YTXT: Title of ID History --> + <string name="debugging_debuglog_id_history_title">"Historia identyfikatorów"</string> + <!-- YTXT: Description of ID History --> + <string name="debugging_debuglog_id_history_body">"Identyfikatory dotychczas udostÄ™pnionych analiz bÅ‚Ä™dów"</string> <!-- YTXT: Warning regarding downsides of recording a log file --> <string name="debugging_debuglog_intro_warning">"Należy pamiÄ™tać, że raporty o bÅ‚Ä™dach mogÄ… zawierać dane wrażliwe (takie jak wynik testu lub wpisy dziennika kontaktów). Z tego wzglÄ™du takie raporty nie powinny być udostÄ™pniane publicznie."</string> <!-- XBUT: Button text to start the log recording --> <string name="debugging_debuglog_action_start_recording">"Uruchom"</string> <!-- XBUT: Button text to stop the log recording --> - <string name="debugging_debuglog_action_stop_recording">"UsuÅ„"</string> + <string name="debugging_debuglog_action_stop_recording">"Zatrzymaj i usuÅ„"</string> <!-- XBUT: Button text to share the log recording --> - <string name="debugging_debuglog_action_share_log">"UdostÄ™pnij"</string> + <string name="debugging_debuglog_action_share_log">"WyÅ›lij raport o bÅ‚Ä™dzie"</string> + <!-- XBUT: Button text to locally store the log recording --> + <string name="debugging_debuglog_action_local_log_store">"Zapisz lokalnie i kontynuuj"</string> <!-- YTXT: Status text if a debug log is being recorded --> <string name="debugging_debuglog_status_recording">"Nagrywanie aktywne"</string> + <!-- YTXT: Status text if a debug log is being recorded but there is not enough free storage space --> + <string name="debugging_debuglog_status_lowstorage">"Za maÅ‚o pamiÄ™ci"</string> <!-- YTXT: Status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_not_recording">"Nieaktywne"</string> <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_additional_infos">"Bieżący rozmiar: %1$s (nieskompresowane)"</string> - <!-- XHED: Title for native sharing dialog --> - <string name="debugging_debuglog_sharing_dialog_title">"UdostÄ™pnij raport o bÅ‚Ä™dach CWA"</string> + <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_message">"Raport o bÅ‚Ä™dzie zostaÅ‚ usuniÄ™ty. JeÅ›li zapisano lokalnÄ… kopiÄ™ raportu o bÅ‚Ä™dzie, nie zostaÅ‚a ona usuniÄ™ta."</string> + <!-- YTXT: Dialog message if there is not enough free storage to start a debug log --> + <string name="debugging_debuglog_start_low_storage_error">"Aby rozpocząć analizÄ™ bÅ‚Ä™du, potrzebujesz co najmniej 200 MB pamiÄ™ci. Zwolnij pamięć."</string> + <!-- XHED: Dialog title if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_title">"Zapisano lokalnie"</string> + <!-- YTXT: Dialog message if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_message">"Analiza bÅ‚Ä™du zostaÅ‚a zapisana lokalnie."</string> + <!-- YTXT: Dialog message if local export has failed --> + <string name="debugging_debuglog_localexport_error_message">"Próba zapisania raportu o bÅ‚Ä™dzie nie powiodÅ‚a siÄ™. Sprawdź, czy dostÄ™pna jest wystarczajÄ…ca ilość pamiÄ™ci."</string> + + <!-- XHED: Title for debug legal screen --> + <string name="debugging_debuglog_legal_dialog_title">"Szczegółowe informacje na temat wysyÅ‚ania raportów o bÅ‚Ä™dach"</string> + <!-- YTXT: Section Title for debug legal screen --> + <string name="debugging_debuglog_legal_section_title">"Jak RKI analizuje raporty o bÅ‚Ä™dach"</string> + <!-- YTXT: Section Body for debug legal screen --> + <string name="debugging_debuglog_legal_section_body">"Po zweryfikowaniu autentycznoÅ›ci aplikacji raport o bÅ‚Ä™dzie jest wysyÅ‚any do RKI za poÅ›rednictwem bezpiecznego poÅ‚Ä…czenia. Raporty o bÅ‚Ä™dach bÄ™dÄ… wykorzystywane wyÅ‚Ä…cznie do rozwiÄ…zywania problemów i korygowania bÅ‚Ä™dów w ramach przyszÅ‚ych aktualizacji aplikacji. DostÄ™p do raportów o bÅ‚Ä™dach ma tylko personel pomocy technicznej. Raporty o bÅ‚Ä™dach zawierajÄ… różne komunikaty o statusie i dane o zdarzeniach wyzwalanych w aplikacji, ale nie zawierajÄ… żadnych informacji, które umożliwiÅ‚yby RKI ustalenie Twojej tożsamoÅ›ci. Tylko jeÅ›li podasz identyfikator raportu o bÅ‚Ä™dzie w zwiÄ…zku z kolejnymi komunikatami, możliwe bÄ™dzie powiÄ…zanie komunikatu (i na przykÅ‚ad zawartego w nim Twojego nazwiska) z informacjami zawartymi w raporcie o bÅ‚Ä™dzie (takimi jak komunikaty techniczne dotyczÄ…ce obliczeÅ„ w ramach rejestrowania narażenia, wyÅ›wietlane informacje i czynnoÅ›ci wykonane w aplikacji oraz, w stosownych przypadkach, pobrany wynik testu i losowe identyfikatory udostÄ™pniane w ramach powiadamiania innych)."</string> + + <!-- XHED: Title for Bugreporting share log screen --> + <string name="debugging_debuglog_share_log_title">"WyÅ›lij raport o bÅ‚Ä™dzie"</string> + <!-- YTXT: First body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_one">"WysÅ‚anie zarejestrowanego raportu o bÅ‚Ä™dzie do pomocy technicznej RKI wymaga Twojej zgody."</string> + <!-- YTXT: Second body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_two">"Po wysÅ‚aniu raportu otrzymasz identyfikator raportu o bÅ‚Ä™dzie. Możesz podać ten identyfikator, aby na przykÅ‚ad zapewnić personelowi pomocy technicznej wiÄ™cej informacji i umożliwić przypisanie go do raportu o bÅ‚Ä™dzie. JeÅ›li nie podasz identyfikatora raportu o bÅ‚Ä™dzie, RKI nie bÄ™dzie mieć możliwoÅ›ci przypisania raportu do Ciebie."</string> + <!-- YTXT: Privacy Information section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_privacy_information">"Szczegółowe informacje o ryzyku zwiÄ…zanym z przetwarzaniem i ochronÄ… danych w USA i innych krajach trzecich"</string> + <!-- XBUT: Button for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_button">"Akceptuj i wyÅ›lij"</string> + <!-- XHED: Title for log upload history --> + <string name="debugging_debuglog_uploadhistory_title">"Historia identyfikatorów"</string> + <!-- YTXT: Description for log upload history --> + <string name="debugging_debuglog_uploadhistory_description">"W tym miejscu wyÅ›wietlane sÄ… identyfikatory dzienników analizy bÅ‚Ä™dów."</string> <!-- #################################### Interoperability @@ -1383,9 +1405,9 @@ <!-- YTXT: Text for consent given --> <string name="submission_test_result_available_text_consent_given">"DziÄ™kujemy za wyrażenie zgody na udostÄ™pnienie wyniku testu i pomoc w ostrzeganiu innych osób. \n\n"<b>"W nastÄ™pnym kroku udostÄ™pnij wynik testu, klikajÄ…c opcjÄ™ „UdostÄ™pnijâ€."</b></string> <!-- XHED: Close screen popup title for consent given --> - <string name="submission_test_result_available_close_dialog_title_consent_given">"Czy na pewno chcesz anulować ten proces?"</string> + <string name="submission_test_result_available_close_dialog_title_consent_given">"WyÅ›wietl wynik testu"</string> <!-- XTXT: Close screen popup text for consent given --> - <string name="submission_test_result_available_close_dialog_body_consent_given">"Ważne:\nTen proces zostanie anulowany.\nNie ostrzegÅ‚eÅ›(-aÅ›) jeszcze innych osób. UkoÅ„cz proces i pomóż chronić innych."</string> + <string name="submission_test_result_available_close_dialog_body_consent_given">"Po zapoznaniu siÄ™ z wynikiem testu możesz w razie potrzeby powiadomić innych i przerwać Å‚aÅ„cuch zakażeÅ„."</string> <!-- YTXT: Text for consent NOT given --> <string name="submission_test_result_available_text_consent_not_given">"ZdecydowaÅ‚eÅ›(-aÅ›) siÄ™ nie udostÄ™pniać wyniku testu. Inne osoby nie zostanÄ… ostrzeżone. \n\nW kolejnym kroku możesz zmienić zdanie i udostÄ™pnić wynik testu, aby powstrzymać rozprzestrzenianie siÄ™ koronawirusa i uchronić inne osoby przed zakażeniem."</string> <!-- XHED: Close screen popup title for consent NOT given --> @@ -1393,9 +1415,9 @@ <!-- XTXT: Close screen popup text for consent NOT given --> <string name="submission_test_result_available_close_dialog_body_consent_not_given">"Twoje wpisy nie zostanÄ… zapisane."</string> <!-- XBUT: Close screen popup cancel button --> - <string name="submission_test_result_available_close_dialog_cancel_button">"Anuluj"</string> + <string name="submission_test_result_available_close_dialog_cancel_button">"Nie wyÅ›wietlaj"</string> <!-- XBUT: Close screen popup continue button --> - <string name="submission_test_result_available_close_dialog_continue_button">"Kontynuuj"</string> + <string name="submission_test_result_available_close_dialog_continue_button">"WyÅ›wietl"</string> <!-- Submission your consent screen --> <!-- XHED: Your consent screen title --> @@ -1558,6 +1580,8 @@ Generic Error Messages ###################################### --> <!-- XHED: error dialog - headline --> + <string name="errors_generic_headline_short">"BÅ‚Ä…d"</string> + <!-- XHED: error dialog - headline --> <string name="errors_generic_headline">"CoÅ› poszÅ‚o nie tak."</string> <!-- XTXT: error dialog - short text for error reason --> <string name="errors_generic_details_headline">"Przyczyna"</string> diff --git a/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml index 322b6dc22bc51d3c16219331c408eb3c83820acd..c20ccc9263e7b6b1efc1b3f84ab5e9454564b60a 100644 --- a/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml @@ -103,9 +103,9 @@ <!-- XHED: Title for the contact diary dialog to delete all persons --> <string name="contact_diary_delete_persons_title">"Sigur doriÈ›i să eliminaÈ›i toate persoanele?"</string> <!-- XTXT: Message for the contact diary dialog to delete all locations --> - <string name="contact_diary_delete_locations_message">"Dacă eliminaÈ›i un loc, toate intrările pentru respectivul loc vor fi eliminate din jurnalul dvs."</string> + <string name="contact_diary_delete_locations_message">"Dacă eliminaÈ›i toate locurile, toate intrările pentru toate locurile vor fi eliminate din jurnalul dvs."</string> <!-- XTXT: Message for the contact diary dialog to delete all persons --> - <string name="contact_diary_delete_persons_message">"Dacă eliminaÈ›i o persoană, toate intrările pentru respectiva persoană vor fi eliminate din jurnalul dvs."</string> + <string name="contact_diary_delete_persons_message">"Dacă eliminaÈ›i toate persoanele, toate intrările pentru toate persoanele vor fi eliminate din jurnalul dvs."</string> <!-- XACT: edit icon description for screen readers --> <string name="contact_diary_edit_icon_content_description">"Editare intrare"</string> <!-- XACT: edit icon description for screen readers --> @@ -114,6 +114,10 @@ <string name="contact_diary_delete_location_title">"Sigur doriÈ›i să eliminaÈ›i acest loc?"</string> <!-- XHED: Title for the contact diary dialog to delete delete a single person --> <string name="contact_diary_delete_person_title">"Sigur doriÈ›i să eliminaÈ›i această persoană?"</string> + <!-- XTXT: Message for the contact diary dialog to delete a single location --> + <string name="contact_diary_delete_location_message">"Dacă eliminaÈ›i un loc, toate intrările pentru respectivul loc vor fi eliminate din jurnalul dvs."</string> + <!-- XTXT: Message for the contact diary dialog to delete a single person --> + <string name="contact_diary_delete_person_message">"Dacă eliminaÈ›i o persoană, toate intrările pentru respectiva persoană vor fi eliminate din jurnalul dvs."</string> <!-- XHED: Title for the contact diary comment info screen --> <string name="contact_diary_comment_info_screen_title">"Notă"</string> diff --git a/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml index 7015d94907b8d2201d60cd8c67aba2414efc3737..b94156bacd13e0489712e0d481d712c0f3b60e90 100644 --- a/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml @@ -12,38 +12,30 @@ <!-- XBUT: Continue button for the release info screen --> <string name="release_info_continue_button">"ÃŽnainte"</string> <!-- XTXT: New release info footer --> - <string name="new_release_bottom">"Modificările din această versiune pot fi găsite în setările aplicaÈ›iei, la „Caracteristici noiâ€."</string> + <string name="new_release_bottom">"Modificările din această versiune pot fi găsite în informaÈ›iile aplicaÈ›iei, la „Caracteristici noiâ€."</string> <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> - <item>"Caracteristici suplimentare în jurnalul de contacte"</item> - <item>"Acces direct la jurnalul de contacte"</item> - <item>"Mai multe detalii despre starea riscului dvs."</item> - <item>"Capturi de ecran ale aplicaÈ›iei Corona-Warn"</item> + <item>"A fost adăugată ElveÈ›ia la înregistrarea transnaÈ›ională a expunerilor în jurnal"</item> + <item>"Generarea rapoartelor de erori"</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"Acum puteÈ›i introduce circumstanÈ›ele specifice ale fiecărei întâlniri. PuteÈ›i selecta din opÈ›iunile predefinite (precum durata întâlnirii, cu mască sau fără mască) È™i puteÈ›i, de asemenea, introduce o notă scurtă pentru a înregistra alte circumstanÈ›e care pot duce la un risc crescut. Dacă vă infectaÈ›i, aceste informaÈ›ii pot ajuta autoritatea de sănătate publică să urmărească posibilele lanÈ›uri de infectare."</item> - <item>"Acum puteÈ›i adăuga o intrare direct la jurnalul dvs. de contacte, fără a mai fi nevoie să deschideÈ›i mai întâi aplicaÈ›ia. Pentru aceasta, atingeÈ›i È™i menÈ›ineÈ›i apăsată pictograma pentru aplicaÈ›ia Corona-Warn timp de aproximativ 2 secunde, până apare un meniu, apoi apăsaÈ›i pe „Adăugare intrare în jurnal pentru astăziâ€."</item> - <item>"Dacă aplicaÈ›ia indică un risc crescut pentru dvs., acum puteÈ›i vedea dacă starea riscului este afiÈ™ată pe baza uneia sau mai multor expuneri cu un risc crescut sau pe baza mai multor expuneri cu risc redus."</item> - <item>"Site-ul web https://www.coronawarn.app conÈ›ine acum toate capturile de ecran ale aplicaÈ›iei, pentru a găsi, de exemplu, caracteristicile planificate."</item> + <item>"Utilizatorii aplicaÈ›iei Corona-Warn pot schimba acum ID-uri aleatorii criptate cu utilizatorii aplicaÈ›iei oficiale de avertizare din ElveÈ›ia. Aceasta înseamnă că avertizările pot fi acum trimise È™i primite de utilizatorii aplicaÈ›iei din ElveÈ›ia."</item> + <item>"Acum puteÈ›i genera un raport de erori, la cerere de la suportul tehnic, È™i puteÈ›i înregistra etapele pe care le efectuaÈ›i în aplicaÈ›ie. Această acÈ›iune va facilita analizarea erorilor tehnice È™i corecÈ›ia mai rapidă a acestora."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> <string-array name="new_release_linkified_labels"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app"</item> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app/en/screenshots"</item> </string-array> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-ro/strings.xml b/Corona-Warn-App/src/main/res/values-ro/strings.xml index 8dbf8987e307944ae2a75a3c8f7fbcaeb173c4d9..ce6862eda5e9ecc090476dddc2648c28bec826a7 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -1,47 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?><resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> - - <!-- #################################### - Preference Keys - ###################################### - TODO: Check what is needed --> - - <!-- NOTR --> - <string name="preference_name"><xliff:g id="preference">"shared_preferences_cwa"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed"><xliff:g id="preference">"preference_onboarding_completed"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed_timestamp"><xliff:g id="preference">"preference_onboarding_completed_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_check_done"><xliff:g id="preference">"preference_background_check_done"</xliff:g></string> - <!-- NOTR --> - <string name="preference_reset_app"><xliff:g id="preference">"preference_reset_app"</xliff:g></string> - <!-- NOTR --> - <string name="preference_only_wifi"><xliff:g id="preference">"preference_only_wifi"</xliff:g></string> - <!-- NOTR --> - <string name="preference_tracing"><xliff:g id="preference">"preference_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> - <!-- NOTR --> - <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_result_received_time"><xliff:g id="preference">"preference_initial_result_received_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_is_allowed_to_submit_diagnosis_keys"><xliff:g id="preference">"preference_is_allowed_to_submit_diagnosis_keys"</xliff:g></string> - <!-- NOTR --> - <string name="preference_database_password"><xliff:g id="preference">"preference_database_password"</xliff:g></string> - <!-- NOTR --> - <string name="preference_total_non_active_tracing"><xliff:g id="preference">"preference_total_non_active_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_last_non_active_tracing_timestamp"><xliff:g id="preference">"preference_last_non_active_tracing_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_number_successful_submissions"><xliff:g id="preference">"preference_number_successful_submissions"</xliff:g></string> - <!-- NOTR --> - <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string> - <!-- NOTR --> - <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string> - <!-- #################################### Generics ###################################### --> @@ -105,6 +62,12 @@ Risk Card ###################################### --> + <!-- XTXT: risk card - Days since installation if < 14 days --> + <string name="risk_card_body_days_since_installation">"Instalat acum %s zile"</string> + <!-- XTXT: risk card - tracing active for x out of 14 days --> + <string name="risk_card_body_saved_days">"ÃŽn ultimele 14 zile, înregistrarea în jurnal a expunerilor a fost activă timp de %1$s zile"</string> + <!-- XTXT: risk card- tracing active for 14 out of 14 days --> + <string name="risk_card_body_saved_days_full">"ÃŽnregistrarea în jurnal a expunerilor este permanent activă"</string> <!-- XTXT; risk card - no update done yet --> <string name="risk_card_body_not_yet_fetched">"ÃŽntâlnirile nu au fost încă verificate."</string> <!-- XTXT: risk card - last successful update --> @@ -307,7 +270,7 @@ <!-- XMSG: risk details - go/stay home, something like a bullet point --> <string name="risk_details_behavior_body_stay_home">"Dacă este posibil, mergeÈ›i acasă È™i rămâneÈ›i în casă."</string> <!-- XMSG: risk details - get in touch with the corresponding people, something like a bullet point --> - <string name="risk_details_behavior_body_contact_doctor">"Dacă aveÈ›i întrebări despre simptome, disponibilitatea testării sau autoizolare, contactaÈ›i:"</string> + <string name="risk_details_behavior_body_contact_doctor">"Dacă aveÈ›i întrebări despre simptome, disponibilitatea testării sau măsurile de carantină, contactaÈ›i fie:"</string> <!-- XMSG: risk details - wash your hands, something like a bullet point --> <string name="risk_details_behavior_body_wash_hands">"SpălaÈ›i-vă frecvent pe mâini cu săpun timp de 20 de secunde."</string> <!-- XMSG: risk details - wear a face mask, something like a bullet point --> @@ -322,6 +285,8 @@ <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> + <!-- XMSG: risk details - ventilation bullet point --> + <string name="risk_details_behavior_body_ventilation">"AerisiÈ›i spaÈ›iile interioare de mai multe ori pe zi. Astfel, deschideÈ›i ferestrele cât mai larg posibil timp de câteva minute („ventilaÈ›ie È™ocâ€)."</string> <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"StrănutaÈ›i sau tuÈ™iÈ›i în pliul cotului sau într-un È™erveÈ›el."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -337,7 +302,13 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Această perioadă este inclusă în calcul."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> - <string name="risk_details_information_body_period_logged">"Riscul dvs. de infectare poate fi calculat doar pentru perioadele în care a fost activă înregistrarea în jurnal a expunerilor. Prin urmare, caracteristica de înregistrare în jurnal trebuie să rămână permanent activă."</string> + <string name="risk_details_information_body_period_logged">"Riscul dvs. de infectare poate fi calculat doar pentru perioadele în care a fost activă înregistrarea în jurnal a expunerilor. Prin urmare, caracteristica de înregistrare în jurnal trebuie să rămână permanent activă. ÃŽnregistrarea în jurnal a expunerilor acoperă ultimele 14 zile."</string> + <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"AplicaÈ›ia Corona-Warn a fost instalată acum %s. Riscul dvs. de infectare este calculat pentru perioadele în care înregistrarea în jurnal a expunerilor a fost activă. Dacă v-aÈ›i întâlnit cu alte persoane È™i înregistrarea în jurnal a expunerilor a fost activă, este calculat riscul dvs. de infectare."</string> + <!-- XTXT: risk details - infection period logged information body, over 14 days --> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"Dacă înregistrarea în jurnal a expunerilor a fost activă pe durata în care v-aÈ›i întâlnit cu alte persoane, riscul dvs. de infectare poate fi calculat pentru această perioadă."</string> + <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment">"ÃŽnregistrarea în jurnal a expunerilor acoperă ultimele 14 zile. ÃŽn această perioadă, caracteristica de înregistrare în jurnal de pe smartphone-ul dvs. a fost activă timp de %1$s zile. AplicaÈ›ia È™terge automat înregistrările mai vechi din jurnal, întrucât acestea nu mai sunt relevante pentru prevenirea infectării."</string> <!-- XTXT: risk details - infection period days logged/14 --> <string name="risk_details_information_active_tracing_days_circle_progress">"%s/14"</string> <!-- XHED: risk details - how your risk level was calculated, below behaviors --> @@ -350,8 +321,12 @@ <string name="risk_details_information_body_low_risk">"AveÈ›i un risc redus de infectare deoarece nu a fost înregistrată nicio expunere la persoane diagnosticate ulterior cu COVID-19 sau întâlnirile dvs. au fost limitate la o perioadă scurtă È™i la o distanță mai mare."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"Riscul de infectare este calculat local pe smartphone-ul dvs., utilizând datele de înregistrare în jurnal a expunerilor. Calculul poate È›ine cont È™i de distanÈ›a È™i durata expunerii la persoane diagnosticate cu coronavirus, precum È™i de potenÈ›iala contagiozitate a acestora. Riscul dvs. de infectare nu poate fi observat sau transmis mai departe niciunei alte persoane."</string> + <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> + <string name="risk_details_explanation_dialog_faq_body">"Pentru mai multe informaÈ›ii, consultaÈ›i pagina noastră de întrebări frecvente."</string> + <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> + <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> - <string name="risk_details_information_body_increased_risk_date">"AveÈ›i un risc crescut de infectare deoarece aÈ›i fost expus ultima dată pe %1$s pe o perioadă mai lungă de timp È™i în strânsă proximitate cu cel puÈ›in o persoană diagnosticată cu coronavirus."</string> + <string name="risk_details_information_body_increased_risk_date">"AveÈ›i un risc crescut de infectare deoarece aÈ›i fost expus ultima dată pe o perioadă mai lungă de timp È™i în strânsă proximitate cu cel puÈ›in o persoană diagnosticată cu COVID-19."</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">"AveÈ›i un risc crescut de infectare deoarece aÈ›i fost expus ultima dată acum %1$s zi pe o perioadă mai lungă de timp È™i în strânsă proximitate cu cel puÈ›in o persoană diagnosticată cu coronavirus."</item> @@ -486,7 +461,7 @@ <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> <string name="onboarding_tracing_location_headline">"ActivaÈ›i setarea locaÈ›iei"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"LocaÈ›ia dispozitivului dvs. nu poate fi determinată de aplicaÈ›ie, dar setarea locaÈ›iei dispozitivului trebuie să fie activată pentru a utiliza Bluetooth Low Energy pe unele versiuni Android."</string> + <string name="onboarding_tracing_location_body">"AplicaÈ›ia nu poate determina locaÈ›ia dvs. TotuÈ™i, setarea locaÈ›iei dispozitivului trebuie să fie activată pentru a utiliza Bluetooth Low Energy pe Android 10 È™i versiunile anterioare."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"DeschideÈ›i configurările dispozitivului"</string> <!-- XACT: Onboarding (test) page title --> @@ -599,9 +574,6 @@ <string name="onboarding_ppa_more_info_much_privacy_body">"Aceste informaÈ›ii nu pot fi legate de dvs. în mod personal. Identitatea dvs. va rămâne confidenÈ›ială.\nInformaÈ›iile vor fi analizate în scop statistic. Ele nu vor fi salvate pentru un profil."</string> - - - <!-- #################################### Onboarding sixteen include ###################################### --> @@ -851,26 +823,76 @@ <!-- XACT: describes illustration --> <string name="information_legal_illustration_description">"O mână È›ine un smartphone pe al cărui ecran este afiÈ™at un corp mare de text. Lângă text există un simbol de secÈ›iune, reprezentând informaÈ›iile de contact."</string> + <!-- #################################### + App Information - Bug Reporting + ###################################### --> + <!-- XHED: Headline for debug log screen --> - <string name="debugging_debuglog_title">"Raport de erori"</string> - <!-- YTXT: Description for the debug option to record log files --> - <string name="debugging_debuglog_intro_explanation_section_one">"Această opÈ›iune înregistrează comportamentul aplicaÈ›iei într-un fiÈ™ier text de jurnal. Dacă opÈ›iunea este activată înainte de apariÈ›ia unei erori, puteÈ›i face disponibil acest raport pentru a-i ajuta pe dezvoltatori să rezolve problema.\nDacă lăsaÈ›i activată această opÈ›iune, acest lucru va conduce la cerinÈ›e de stocare ridicate. Dacă dezactivaÈ›i opÈ›iunea, raportul de erori este È™ters."</string> + <string name="debugging_debuglog_title">"Rapoarte de erori"</string> + <!-- XHED: Headline for current status of debug log screen --> + <string name="debugging_debuglog_current_status_title">"Analiză erori"</string> + <!-- YTXT: Description one for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_one">"Pentru a ajuta echipa de suport tehnic a aplicaÈ›iei la analiza erorilor, puteÈ›i înregistra un raport de erori din aplicaÈ›ia Corona-Warn. Astfel, sunt înregistrate etapele tehnice individuale È™i rezultatele proceselor aplicaÈ›iei. PuteÈ›i trimite apoi raportul de erori la suportul tehnic È™i ajutaÈ›i la identificarea È™i corectarea erorilor."</string> + <!-- YTXT: Description two for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_two">"Pentru mai multe informaÈ›ii, consultaÈ›i pagina noastră de întrebări frecvente:\nÃŽntrebări frecvente despre rapoartele de erori"</string> + <!-- XTXT: Debug Log screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="debugging_debuglog_intro_explanation_section_two_link_label">"ÃŽntrebări frecvente despre rapoartele de erori"</string> + <!-- XTXT: Explains user about about debug log: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> + <string name="debugging_debuglog_intro_explanation_section_two_faq_link">"https://www.coronawarn.app/en/faq/#error_log"</string> + <!-- YTXT: Title of ID History --> + <string name="debugging_debuglog_id_history_title">"Istoric ID-uri"</string> + <!-- YTXT: Description of ID History --> + <string name="debugging_debuglog_id_history_body">"ID-urile analizelor de erori partajate până în prezent"</string> <!-- YTXT: Warning regarding downsides of recording a log file --> <string name="debugging_debuglog_intro_warning">"ReÈ›ineÈ›i că rapoartele de erori pot conÈ›ine date sensibile (precum un rezultat al testului sau intrări în jurnal ale contactelor). ÃŽn consecință, nu este recomandat să împărtășiÈ›i public aceste rapoarte de erori."</string> <!-- XBUT: Button text to start the log recording --> <string name="debugging_debuglog_action_start_recording">"Pornire"</string> <!-- XBUT: Button text to stop the log recording --> - <string name="debugging_debuglog_action_stop_recording">"Ștergere"</string> + <string name="debugging_debuglog_action_stop_recording">"Oprire È™i È™tergere"</string> <!-- XBUT: Button text to share the log recording --> - <string name="debugging_debuglog_action_share_log">"Partajare"</string> + <string name="debugging_debuglog_action_share_log">"Trimitere raport de erori"</string> + <!-- XBUT: Button text to locally store the log recording --> + <string name="debugging_debuglog_action_local_log_store">"Salvare locală È™i continuare"</string> <!-- YTXT: Status text if a debug log is being recorded --> <string name="debugging_debuglog_status_recording">"ÃŽnregistrare activă"</string> + <!-- YTXT: Status text if a debug log is being recorded but there is not enough free storage space --> + <string name="debugging_debuglog_status_lowstorage">"Memorie insuficientă"</string> <!-- YTXT: Status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_not_recording">"Inactivă"</string> <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_additional_infos">"Mărime curentă: %1$s (necomprimat)"</string> - <!-- XHED: Title for native sharing dialog --> - <string name="debugging_debuglog_sharing_dialog_title">"Partajare raport de erori CWA"</string> + <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_message">"Raportul de erori a fost È™ters. Dacă aÈ›i salvat o copie locală a raportului de erori, nu a fost È™tearsă."</string> + <!-- YTXT: Dialog message if there is not enough free storage to start a debug log --> + <string name="debugging_debuglog_start_low_storage_error">"AveÈ›i nevoie de cel puÈ›in 200 MB de memorie pentru a începe analiza erorilor. EliberaÈ›i memorie."</string> + <!-- XHED: Dialog title if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_title">"Salvat local"</string> + <!-- YTXT: Dialog message if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_message">"Analiza erorilor a fost salvată local."</string> + <!-- YTXT: Dialog message if local export has failed --> + <string name="debugging_debuglog_localexport_error_message">"ÃŽncercarea de a salva un raport de erori a eÈ™uat. VerificaÈ›i dacă este disponibilă suficientă memorie."</string> + + <!-- XHED: Title for debug legal screen --> + <string name="debugging_debuglog_legal_dialog_title">"InformaÈ›ii detaliate despre trimiterea rapoartelor de erori"</string> + <!-- YTXT: Section Title for debug legal screen --> + <string name="debugging_debuglog_legal_section_title">"Cum analizează RKI rapoartele de erori"</string> + <!-- YTXT: Section Body for debug legal screen --> + <string name="debugging_debuglog_legal_section_body">"După ce este verificată autenticitatea aplicaÈ›iei dvs., raportul de erori este trimis la RKI printr-o conexiune sigură. Rapoartele de erori vor fi utilizate doar pentru depanarea È™i corecÈ›ia erorilor în cadrul unor actualizări viitoare ale aplicaÈ›iei. Doar angajaÈ›ii responsabili cu suportul tehnic pot accesa rapoartele de erori. Rapoartele de erori conÈ›in o varietate de mesaje de stare È™i evenimente declanÈ™ate în aplicaÈ›ie, dar nu conÈ›in informaÈ›ii care i-ar permite institutului RKI să vă determine identitatea. Doar dacă dezvăluiÈ›i ID-ul raportului de erori în legătură cu mesaje viitoare se poate face o conexiune între mesaj (È™i, de exemplu, numele dvs. conÈ›inut în acesta) È™i informaÈ›iile conÈ›inute în raportul de erori (precum mesajele tehnice pentru calculul ca parte din înregistrarea în jurnal a expunerilor, informaÈ›iile afiÈ™ate È™i etapele efectuate în aplicaÈ›ie È™i, unde este cazul, un rezultat de test obÈ›inut È™i ID-urile aleatorii partajate ca parte din notificarea altor utilizatori)."</string> + + <!-- XHED: Title for Bugreporting share log screen --> + <string name="debugging_debuglog_share_log_title">"Trimitere raport de erori"</string> + <!-- YTXT: First body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_one">"Consimțământul dvs. este necesar înainte de a trimite raportul de erori înregistrate la suportul tehnic al RKI."</string> + <!-- YTXT: Second body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_two">"După ce trimiteÈ›i raportul, veÈ›i primi un ID pentru raportul de erori. PuteÈ›i specifica acest ID pentru a-i furniza suportului tehnic, de exemplu, informaÈ›ii suplimentare È™i permiteÈ›i astfel atribuirea acestora la raportul dvs. de erori. Dacă nu furnizaÈ›i ID-ul raportului de erori, RKI nu vă poate atribui raportul dvs. individual."</string> + <!-- YTXT: Privacy Information section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_privacy_information">"InformaÈ›ii detaliate despre prelucrarea acestor date È™i riscurile privind prelucrarea datelor din SUA È™i din alte țări terÈ›e"</string> + <!-- XBUT: Button for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_button">"Sunt de acord È™i trimitere"</string> + <!-- XHED: Title for log upload history --> + <string name="debugging_debuglog_uploadhistory_title">"Istoric ID-uri"</string> + <!-- YTXT: Description for log upload history --> + <string name="debugging_debuglog_uploadhistory_description">"Aici vedeÈ›i ID-urile jurnalelor de analiză a erorilor."</string> <!-- #################################### Interoperability @@ -1383,9 +1405,9 @@ <!-- YTXT: Text for consent given --> <string name="submission_test_result_available_text_consent_given">"Vă mulÈ›umim pentru că sunteÈ›i de acord să partajaÈ›i rezultatul testului dvs. È™i să ajutaÈ›i astfel la avertizarea celorlalÈ›i. \n\n"<b>"La pasul următor, partajaÈ›i rezultatul testului dvs. apăsând „Partajareâ€."</b></string> <!-- XHED: Close screen popup title for consent given --> - <string name="submission_test_result_available_close_dialog_title_consent_given">"Sigur doriÈ›i să anulaÈ›i acest proces?"</string> + <string name="submission_test_result_available_close_dialog_title_consent_given">"AfiÈ™are rezultat test"</string> <!-- XTXT: Close screen popup text for consent given --> - <string name="submission_test_result_available_close_dialog_body_consent_given">"Important:\nSunteÈ›i pe punctul de a anula acest proces.\nÃŽncă nu i-aÈ›i avertizat pe ceilalÈ›i. Vă rugăm să finalizaÈ›i procesul È™i să ajutaÈ›i la protejarea celorlalÈ›i."</string> + <string name="submission_test_result_available_close_dialog_body_consent_given">"După ce aÈ›i citit rezultatul testului dvs., îi puteÈ›i notifica pe ceilalÈ›i dacă este necesar È™i puteÈ›i întrerupe lanÈ›ul de infectare."</string> <!-- YTXT: Text for consent NOT given --> <string name="submission_test_result_available_text_consent_not_given">"AÈ›i ales să nu partajaÈ›i rezultatul testului dvs. CeilalÈ›i nu vor fi avertizaÈ›i. \n\nLa pasul următor, aveÈ›i ocazia să vă răzgândiÈ›i È™i să partajaÈ›i totuÈ™i rezultatul testului dvs., pentru a ajuta la oprirea răspândirii coronavirusului È™i a-i proteja pe ceilalÈ›i."</string> <!-- XHED: Close screen popup title for consent NOT given --> @@ -1393,9 +1415,9 @@ <!-- XTXT: Close screen popup text for consent NOT given --> <string name="submission_test_result_available_close_dialog_body_consent_not_given">"Intrările dvs. nu vor fi salvate."</string> <!-- XBUT: Close screen popup cancel button --> - <string name="submission_test_result_available_close_dialog_cancel_button">"Anulare"</string> + <string name="submission_test_result_available_close_dialog_cancel_button">"Fără afiÈ™are"</string> <!-- XBUT: Close screen popup continue button --> - <string name="submission_test_result_available_close_dialog_continue_button">"Continuare"</string> + <string name="submission_test_result_available_close_dialog_continue_button">"AfiÈ™are"</string> <!-- Submission your consent screen --> <!-- XHED: Your consent screen title --> @@ -1558,6 +1580,8 @@ Generic Error Messages ###################################### --> <!-- XHED: error dialog - headline --> + <string name="errors_generic_headline_short">"Eroare"</string> + <!-- XHED: error dialog - headline --> <string name="errors_generic_headline">"A apărut o problemă."</string> <!-- XTXT: error dialog - short text for error reason --> <string name="errors_generic_details_headline">"Cauză"</string> diff --git a/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml index eb4308ad5f45b4efc85cf53be6b9132b5bd60fb5..2f3e132f5c7cb581c4766568271ba070be369230 100644 --- a/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml @@ -103,9 +103,9 @@ <!-- XHED: Title for the contact diary dialog to delete all persons --> <string name="contact_diary_delete_persons_title">"Tüm kiÅŸileri gerçekten kaldırmak istiyor musunuz?"</string> <!-- XTXT: Message for the contact diary dialog to delete all locations --> - <string name="contact_diary_delete_locations_message">"Bir yeri kaldırdığınızda o yere iliÅŸkin tüm giriÅŸler güncenizden kaldırılacaktır."</string> + <string name="contact_diary_delete_locations_message">"Tüm yerleri kaldırdığınızda tüm yerlere iliÅŸkin tüm giriÅŸler güncenizden kaldırılacaktır."</string> <!-- XTXT: Message for the contact diary dialog to delete all persons --> - <string name="contact_diary_delete_persons_message">"Bir kiÅŸiyi kaldırdığınızda o kiÅŸiye iliÅŸkin tüm giriÅŸler güncenizden kaldırılacaktır."</string> + <string name="contact_diary_delete_persons_message">"Tüm kiÅŸileri kaldırdığınızda tüm kiÅŸilere iliÅŸkin tüm giriÅŸler güncenizden kaldırılacaktır."</string> <!-- XACT: edit icon description for screen readers --> <string name="contact_diary_edit_icon_content_description">"GiriÅŸi Düzenle"</string> <!-- XACT: edit icon description for screen readers --> @@ -114,6 +114,10 @@ <string name="contact_diary_delete_location_title">"Bu yeri gerçekten kaldırmak istiyor musunuz?"</string> <!-- XHED: Title for the contact diary dialog to delete delete a single person --> <string name="contact_diary_delete_person_title">"Bu kiÅŸiyi gerçekten kaldırmak istiyor musunuz?"</string> + <!-- XTXT: Message for the contact diary dialog to delete a single location --> + <string name="contact_diary_delete_location_message">"Bir yeri kaldırdığınızda o yere iliÅŸkin tüm giriÅŸler güncenizden kaldırılacaktır."</string> + <!-- XTXT: Message for the contact diary dialog to delete a single person --> + <string name="contact_diary_delete_person_message">"Bir kiÅŸiyi kaldırdığınızda o kiÅŸiye iliÅŸkin tüm giriÅŸler güncenizden kaldırılacaktır."</string> <!-- XHED: Title for the contact diary comment info screen --> <string name="contact_diary_comment_info_screen_title">"Not"</string> diff --git a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml index 3f4420cbf250bea5b664765c4cc2ffb0c0fdc373..c6d08991c39b71efc58f030b6dd3c96261632f60 100644 --- a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml @@ -86,13 +86,6 @@ <!-- XTXT: Body for Privacy-preserving Analytics settings --> <string name="ppa_settings_privacy_information_body" translatable="false">"Yukarıdaki “Veri bağışı" seçeneÄŸini etkinleÅŸtirerek, ÅŸunlara onay vermiÅŸ olursunuz:\n\nUygulama, topladığı bilgileri her gün RKI’ye aktarır. Bunlar, görüntülenen riskli karşılaÅŸmalar ve uyarılar, size gönderilen test sonuçları, diÄŸer kullanıcıları uyarıp uyarmadığınız ve akıllı telefonunuzun iÅŸletim sistemine iliÅŸkin verilerdir. Ayrıca baÅŸka bilgiler de verdiyseniz (bölge, yaÅŸ grubu gibi), bunlar da RKI’ye aktarılır.\n\nRKI, Uygulamanın etki gücünü ve iÅŸlevselliÄŸini deÄŸerlendirmek ve pandemi hakkında yeni çıkarımlar elde etmek için, bu verileri birleÅŸtirecek ve istatistikler olarak deÄŸerlendirecektir. Bu süreçte edinilen bulgular, Uygulamanın iÅŸlevlerini ve kullanım kolaylığını iyileÅŸtirmenin yanı sıra pandemiye karşı mücadele için diÄŸer önlemlerin yönlendirilmesine yardımcı olmaktadır.\n\nVerilerinizin deÄŸerlendirilmesinden önce, veri bağışına katılan her Uygulamanın yalnızca bir kez sayıma alındığı ve istatistiklerin tahrif edilmediÄŸi kontrol edilir. Bu baÄŸlamda Uygulamanızın orijinal olduÄŸunun incelenmesi gerekmektedir. Bunun için, akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve Google’ın Uygulamanızın orijinal ürün olduÄŸunu RKI’ye doÄŸrulaması için ABD’deki veya diÄŸer bir üçüncü ülkeye Google’e aktarılır. Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Google, böylece kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik kontrolünün yapıldığını anlayabilir. Google, Uygulamadan daha fazla bilgi almaz.\n\nYukarıdaki “Veri bağışı†seçeneÄŸini devre dışı bırakarak, verdiÄŸiniz rıza beyanını geri alabilirsiniz."</string> - <!-- XHED: Title for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_title" translatable="false">"Prüfung der Echtheit und Drittlandsübermittlung"</string> - <!-- YTXT: First section for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_first_section" translatable="false">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um sicherzustellen, dass nur Nutzer Daten auf diesem Weg an den technischen Support übersenden, die tatsächlich die Corona-Warn-App nutzen und nicht manipulierte Fehlerberichte bereitstellen. Die Kennung wird dafür einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die Angaben aus Ihrem Fehlerbericht erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat."</string> - <!-- YTXT: Second section for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_second_section" translatable="false">"Wenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden und Fehlerbericht senden“ an. Sie können die App weiterhin nutzen, eine Ãœbersendung des Fehlerberichtes über die App ist dann jedoch nicht möglich."</string> - <!-- XHED: Title for debug upload screen privacy card --> <string name="debugging_debuglog_privacy_card_title" translatable="false">"Veri gizliliÄŸi ve veri güvenliÄŸi"</string> <!-- YTXT: First bullet point for debug upload screen privacy card --> diff --git a/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml index c29b757bb969656b1dc0b0e1663ac93110bd1386..6a75176cfdb4ecc1c98f0bf00c5dfeef5d2dbc3d 100644 --- a/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml @@ -12,38 +12,30 @@ <!-- XBUT: Continue button for the release info screen --> <string name="release_info_continue_button">"Sonraki"</string> <!-- XTXT: New release info footer --> - <string name="new_release_bottom">"Bu sürümdeki deÄŸiÅŸiklikleri uygulama ayarlarında “Yeni Özellikler†bölümünde bulabilirsiniz."</string> + <string name="new_release_bottom">"Bu sürümdeki deÄŸiÅŸiklikleri uygulama bilgilerinde “Yeni Özellikler†bölümünde bulabilirsiniz."</string> <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> - <item>"Temas Güncesindeki Ek Özellikler"</item> - <item>"Temas Güncesine DoÄŸrudan EriÅŸim"</item> - <item>"Risk Durumunuz Hakkında Daha Ayrıntılı Bilgi"</item> - <item>"Corona-Warn-App Ekran Görüntüleri"</item> + <item>"Uluslararası Maruz Kalma Günlüğüne Ä°sviçre Eklendi"</item> + <item>"Hata Raporlarının OluÅŸturulması"</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"Artık her bir karşılaÅŸma için belirli koÅŸullar girebilirsiniz. Önceden tanımlanmış seçenekler (örneÄŸin karşılaÅŸmanın süresi, maske takılı olup olmadığı) arasından seçim yapabilir ve yüksek riske neden olabilecek diÄŸer koÅŸulları kaydetmek için kısa bir not da ekleyebilirsiniz. Enfekte olmanız halinde bu bilgiler kamu saÄŸlığı yetkilisinin potansiyel enfeksiyon zincirlerini takip etmesine yardımcı olabilir."</item> - <item>"Artık önce uygulamayı açmanız gerekmeden doÄŸrudan temas güncenize giriÅŸ ekleyebilirsiniz. Bunun için, bir menü görüntülenene dek yaklaşık 2 saniye süreyle Corona-Warn-App simgesine basılı tutun ve ardından “Bugün için Günce GiriÅŸi Ekle†seçeneÄŸine dokunun."</item> - <item>"Uygulama, yüksek risk altında olduÄŸunuzu belirtiyorsa artık risk durumunun, yüksek riskli bir veya daha fazla maruz kalmaya göre mi yoksa düşük riskli bir veya daha fazla maruz kalmaya göre mi görüntülendiÄŸini görebilirsiniz."</item> - <item>"https://www.coronawarn.app adresindeki web sitesinde uygulamanın tüm ekran görüntüleri sunulmaktadır. Bu sayede, planlanan özellikler gibi çeÅŸitli bilgileri edinebilirsiniz."</item> + <item>"Corona-Warn-App kullanıcıları artık ÅŸifrelenmiÅŸ rastgele kimlikleri, Ä°sviçre’de resmi uyarı uygulamasının kullanıcıları ile deÄŸiÅŸtirebilir. Buna göre, artık Ä°sviçre’deki uygulama kullanıcıları ile uyarı alışveriÅŸi yapılabilir."</item> + <item>"Artık teknik destek talebi üzerine bir hata raporu oluÅŸturabilir ve uygulamada gerçekleÅŸtirdiÄŸiniz adımları kaydedebilirsiniz. Bu sayede teknik hataların analiz edilmesi ve daha kısa süre içinde düzeltilmesi kolaylaÅŸtırılacaktır."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> <string-array name="new_release_linkified_labels"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app"</item> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> <item/> - <item/> - <item>"https://www.coronawarn.app/en/screenshots"</item> </string-array> </resources> \ No newline at end of file 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 11ca6f8de7fd5ed2bd5a42f2133bd2315962832d..f408e7698649008c23232c2964420735064486cf 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -1,47 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?><resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> - - <!-- #################################### - Preference Keys - ###################################### - TODO: Check what is needed --> - - <!-- NOTR --> - <string name="preference_name"><xliff:g id="preference">"shared_preferences_cwa"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed"><xliff:g id="preference">"preference_onboarding_completed"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed_timestamp"><xliff:g id="preference">"preference_onboarding_completed_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_check_done"><xliff:g id="preference">"preference_background_check_done"</xliff:g></string> - <!-- NOTR --> - <string name="preference_reset_app"><xliff:g id="preference">"preference_reset_app"</xliff:g></string> - <!-- NOTR --> - <string name="preference_only_wifi"><xliff:g id="preference">"preference_only_wifi"</xliff:g></string> - <!-- NOTR --> - <string name="preference_tracing"><xliff:g id="preference">"preference_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> - <!-- NOTR --> - <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_result_received_time"><xliff:g id="preference">"preference_initial_result_received_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_is_allowed_to_submit_diagnosis_keys"><xliff:g id="preference">"preference_is_allowed_to_submit_diagnosis_keys"</xliff:g></string> - <!-- NOTR --> - <string name="preference_database_password"><xliff:g id="preference">"preference_database_password"</xliff:g></string> - <!-- NOTR --> - <string name="preference_total_non_active_tracing"><xliff:g id="preference">"preference_total_non_active_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_last_non_active_tracing_timestamp"><xliff:g id="preference">"preference_last_non_active_tracing_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_number_successful_submissions"><xliff:g id="preference">"preference_number_successful_submissions"</xliff:g></string> - <!-- NOTR --> - <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string> - <!-- NOTR --> - <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string> - <!-- #################################### Generics ###################################### --> @@ -105,6 +62,12 @@ Risk Card ###################################### --> + <!-- XTXT: risk card - Days since installation if < 14 days --> + <string name="risk_card_body_days_since_installation">"%s gün önce yüklendi"</string> + <!-- XTXT: risk card - tracing active for x out of 14 days --> + <string name="risk_card_body_saved_days">"Maruz kalma günlüğü son 14 günde %1$s gün etkindi"</string> + <!-- XTXT: risk card- tracing active for 14 out of 14 days --> + <string name="risk_card_body_saved_days_full">"Maruz kalma günlüğü sürekli etkin"</string> <!-- XTXT; risk card - no update done yet --> <string name="risk_card_body_not_yet_fetched">"KarşılaÅŸmalar henüz kontrol edilmedi."</string> <!-- XTXT: risk card - last successful update --> @@ -307,7 +270,7 @@ <!-- XMSG: risk details - go/stay home, something like a bullet point --> <string name="risk_details_behavior_body_stay_home">"Mümkünse lütfen eve gidin ve evde kalın."</string> <!-- XMSG: risk details - get in touch with the corresponding people, something like a bullet point --> - <string name="risk_details_behavior_body_contact_doctor">"Belirtiler, test kullanılabilirliÄŸi veya bireysel izolasyon hakkında sorularınız varsa lütfen aÅŸağıdakilerden biri ile iletiÅŸime geçin:"</string> + <string name="risk_details_behavior_body_contact_doctor">"Belirtiler, test kullanılabilirliÄŸi veya karantina önlemleri hakkında sorularınız varsa lütfen aÅŸağıdakilerden biri ile iletiÅŸime geçin:"</string> <!-- XMSG: risk details - wash your hands, something like a bullet point --> <string name="risk_details_behavior_body_wash_hands">"Ellerinizi düzenli olarak 20 saniye sabunla yıkayın."</string> <!-- XMSG: risk details - wear a face mask, something like a bullet point --> @@ -322,6 +285,8 @@ <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> + <!-- XMSG: risk details - ventilation bullet point --> + <string name="risk_details_behavior_body_ventilation">"Kapalı alanları günde birkaç kez havalandırın. Bunun için, pencereleri birkaç dakika süreyle sonuna dek açın (“şok havalandırmaâ€)."</string> <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"Öksürürken ya da hapşırırken dirseÄŸinizin iç kısmına dönün veya bir mendil kullanın."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -337,7 +302,13 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Bu dönem hesaplamaya dahil edildi."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> - <string name="risk_details_information_body_period_logged">"Enfeksiyon riskiniz yalnızca maruz kalma günlüğünün etkin olduÄŸu dönemler için hesaplanabilir. Bu nedenle günlüğe kaydetme özelliÄŸinin sürekli etkin kalması gerekir."</string> + <string name="risk_details_information_body_period_logged">"Enfeksiyon riskiniz yalnızca maruz kalma günlüğünün etkin olduÄŸu dönemler için hesaplanabilir. Bu nedenle günlüğe kaydetme özelliÄŸinin sürekli etkin kalması gerekir. Maruz kalma günlüğü son 14 günü kapsar."</string> + <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Corona-Warn-App %s gün önce yüklendi. Enfeksiyon riskiniz, maruz kalma günlüğünün etkin olduÄŸu dönemler için hesaplanır. BaÅŸka insanlarla karşılaÅŸmışsanız ve bu sırada maruz kalma günlüğü etkindiyse enfeksiyon riskiniz hesaplanır."</string> + <!-- XTXT: risk details - infection period logged information body, over 14 days --> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"Maruz kalma günlüğü baÅŸka insanlarla karşılaÅŸtığınız sırada etkindiyse bu dönem için enfeksiyon riskiniz hesaplanabilir."</string> + <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <string name="risk_details_information_body_period_logged_assessment">"Maruz kalma günlüğü son 14 günü kapsar. Bu süre boyunca akıllı telefonunuzdaki günlüğe kaydetme özelliÄŸi %1$s gün etkindi. Uygulama, enfeksiyondan korunma için artık ilgili olmadığından daha eski kayıtları otomatik olarak siler."</string> <!-- XTXT: risk details - infection period days logged/14 --> <string name="risk_details_information_active_tracing_days_circle_progress">"%s/14"</string> <!-- XHED: risk details - how your risk level was calculated, below behaviors --> @@ -350,8 +321,12 @@ <string name="risk_details_information_body_low_risk">"Daha sonra COVID-19 tanısı konan kiÅŸilere maruz kaldığınıza dair bir günlük kaydı oluÅŸturulmadığı veya bu kiÅŸilerle yalnızca kısa süreyle ve uzak mesafeden karşılaÅŸtığınız için enfeksiyon riskiniz düşüktür."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"Enfeksiyon riskiniz, maruz kalma günlüğünün verileri kullanılarak yerel olarak akıllı telefonunuzda hesaplanır. Hesaplamada koronavirüs tanısı konan kiÅŸilere maruz kalma mesafesi ve süresinin yanında potansiyel enfeksiyon bulaÅŸtırma durumu da göz önünde bulundurulur. Enfeksiyon riskiniz bir baÅŸkası tarafından görüntülenemez ya da bir baÅŸkasına aktarılamaz."</string> + <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> + <string name="risk_details_explanation_dialog_faq_body">"Daha fazla bilgi için lütfen SSS sayfamıza bakın."</string> + <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> + <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> - <string name="risk_details_information_body_increased_risk_date">"En son %1$s tarihinde, koronavirüs tanısı konan en az bir kiÅŸiyle daha uzun süreyle ve yakın mesafeden maruz kalma yaÅŸadığınız için enfeksiyon riskiniz daha yüksektir."</string> + <string name="risk_details_information_body_increased_risk_date">"COVID-19 tanısı konan en az bir kiÅŸiyle daha uzun süreyle ve yakın mesafeden maruz kalma yaÅŸadığınız için enfeksiyon riskiniz daha yüksektir."</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">"En son %1$s gün önce, koronavirüs tanısı konan en az bir kiÅŸiyle daha uzun süreyle ve yakın mesafeden maruz kalma yaÅŸadığınız için enfeksiyon riskiniz daha yüksektir."</item> @@ -486,7 +461,7 @@ <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> <string name="onboarding_tracing_location_headline">"Konum Ayarını EtkinleÅŸtir"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Uygulama, cihazınızın konumunu belirleyemiyor ancak bazı Android sürümlerinde Bluetooth Düşük Enerji iÅŸlevinin kullanılması için cihazın konum ayarının etkinleÅŸtirilmesi gereklidir."</string> + <string name="onboarding_tracing_location_body">"Uygulama, konumunuzu belirleyemiyor. Ancak bazı Android 10 ve daha önceki sürümlerde Bluetooth Düşük Enerji iÅŸlevinin kullanılması için cihazın konum ayarının etkinleÅŸtirilmesi gereklidir."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Cihaz Ayarlarını Aç"</string> <!-- XACT: Onboarding (test) page title --> @@ -599,9 +574,6 @@ <string name="onboarding_ppa_more_info_much_privacy_body">"Bu bilgilerin kiÅŸisel olarak sizinle baÄŸlantısı kurulamaz. KimliÄŸiniz gizli tutulacaktır.\nBilgiler istatistiksel amaçlar doÄŸrultusunda analiz edilecektir. Profil oluÅŸturmak için kaydedilmeyecektir."</string> - - - <!-- #################################### Onboarding sixteen include ###################################### --> @@ -851,26 +823,76 @@ <!-- XACT: describes illustration --> <string name="information_legal_illustration_description">"Bir kiÅŸi ekranında uzun bir metin olan bir akıllı telefon tutuyor. Metnin yanında baskıyı temsil eden bir bölüm bulunuyor."</string> + <!-- #################################### + App Information - Bug Reporting + ###################################### --> + <!-- XHED: Headline for debug log screen --> - <string name="debugging_debuglog_title">"Hata Raporu"</string> - <!-- YTXT: Description for the debug option to record log files --> - <string name="debugging_debuglog_intro_explanation_section_one">"Bu seçenek, uygulama davranışını bir metin dosyasına kaydeder. Bir hata meydana gelmeden önce bu seçenek etkinleÅŸtirilirse geliÅŸtiricilerin sorunu çözmesine yardımcı olmak için bu raporu sunabilirsiniz.\nBu seçeneÄŸi etkinleÅŸtirilmiÅŸ durumda bırakırsanız yüksek depolama alanı gereklilikleri oluÅŸacaktır. SeçeneÄŸi devre dışı bırakırsanız hata raporu silinir."</string> + <string name="debugging_debuglog_title">"Hata Raporları"</string> + <!-- XHED: Headline for current status of debug log screen --> + <string name="debugging_debuglog_current_status_title">"Hata Analizi"</string> + <!-- YTXT: Description one for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_one">"Hata analizi konusunda uygulamanın teknik destek ekibine yardımcı olmak için CWA’da bir hata raporu kaydı oluÅŸturabilirsiniz. OluÅŸturduÄŸunuzda, her bir teknik adım ve uygulama iÅŸlemlerinin sonuçları kaydedilir. Ardından hata raporunu teknik ekibe gönderebilir ve hatanın belirlenip düzeltilmesine yardımcı olabilirsiniz."</string> + <!-- YTXT: Description two for the debug option to record log files --> + <string name="debugging_debuglog_intro_explanation_section_two">"Daha fazla bilgi için lütfen SSS sayfamıza bakın:\nHata raporları için SSS"</string> + <!-- XTXT: Debug Log screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="debugging_debuglog_intro_explanation_section_two_link_label">"Hata raporları için SSS"</string> + <!-- XTXT: Explains user about about debug log: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> + <string name="debugging_debuglog_intro_explanation_section_two_faq_link">"https://www.coronawarn.app/en/faq/#error_log"</string> + <!-- YTXT: Title of ID History --> + <string name="debugging_debuglog_id_history_title">"Tanıtıcı GeçmiÅŸi"</string> + <!-- YTXT: Description of ID History --> + <string name="debugging_debuglog_id_history_body">"Åžimdiye dek paylaşılan hata analizlerinin tanıtıcıları"</string> <!-- YTXT: Warning regarding downsides of recording a log file --> <string name="debugging_debuglog_intro_warning">"Lütfen hata raporlarının hassas veriler içerebileceÄŸini unutmayın (örneÄŸin, test sonucu ya da temas güncesi giriÅŸleri). Bu nedenle bu hata raporlarını herkese açık ÅŸekilde paylaÅŸmamanız gerekir."</string> <!-- XBUT: Button text to start the log recording --> <string name="debugging_debuglog_action_start_recording">"BaÅŸlat"</string> <!-- XBUT: Button text to stop the log recording --> - <string name="debugging_debuglog_action_stop_recording">"Sil"</string> + <string name="debugging_debuglog_action_stop_recording">"Durdur ve Sil"</string> <!-- XBUT: Button text to share the log recording --> - <string name="debugging_debuglog_action_share_log">"PaylaÅŸ"</string> + <string name="debugging_debuglog_action_share_log">"Hata Raporu Gönder"</string> + <!-- XBUT: Button text to locally store the log recording --> + <string name="debugging_debuglog_action_local_log_store">"Yerel Olarak Kaydet ve Devam Et"</string> <!-- YTXT: Status text if a debug log is being recorded --> <string name="debugging_debuglog_status_recording">"Kayıt Etkin"</string> + <!-- YTXT: Status text if a debug log is being recorded but there is not enough free storage space --> + <string name="debugging_debuglog_status_lowstorage">"Yeterli bellek yok"</string> <!-- YTXT: Status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_not_recording">"Etkin DeÄŸil"</string> <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_additional_infos">"Geçerli boyut: %1$s (sıkıştırılmamış)"</string> - <!-- XHED: Title for native sharing dialog --> - <string name="debugging_debuglog_sharing_dialog_title">"CWA Hata Raporunu PaylaÅŸ"</string> + <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_message">"Hata raporu silinmiÅŸ. Hata raporunun yerel bir kopyasını kaydettiyseniz bu kopya silinmemiÅŸtir."</string> + <!-- YTXT: Dialog message if there is not enough free storage to start a debug log --> + <string name="debugging_debuglog_start_low_storage_error">"Hata analizini baÅŸlatmak için en az 200 MB bellek alanınızın olması gerekir. Lütfen belleÄŸi boÅŸaltın."</string> + <!-- XHED: Dialog title if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_title">"Yerel Olarak Kaydedildi"</string> + <!-- YTXT: Dialog message if a user has stored a debug log locally --> + <string name="debugging_debuglog_localexport_message">"Hata analizi yerel olarak kaydedilmiÅŸtir."</string> + <!-- YTXT: Dialog message if local export has failed --> + <string name="debugging_debuglog_localexport_error_message">"Hata raporunu kaydetme denemesi baÅŸarısız oldu. Lütfen yeterli bellek alanı olup olmadığını kontrol edin."</string> + + <!-- XHED: Title for debug legal screen --> + <string name="debugging_debuglog_legal_dialog_title">"Hata Raporlarını Gönderme Konusunda Ayrıntılı Bilgi"</string> + <!-- YTXT: Section Title for debug legal screen --> + <string name="debugging_debuglog_legal_section_title">"RKI, hata analizlerini nasıl analiz eder?"</string> + <!-- YTXT: Section Body for debug legal screen --> + <string name="debugging_debuglog_legal_section_body">"Uygulamanızın orijinalliÄŸi doÄŸrulandıktan sonra hata raporu, güvenli bir baÄŸlantı üzerinden RKI’ye gönderilir. Hata raporları yalnızca gelecekteki uygulama güncellemeleri kapsamında hata düzeltme ve sorun gierme için kullanılacaktır. Hata raporlarına yalnızca teknik destek çalışanları eriÅŸiebilir. Hata raporları, uygulamada tetiklenen çeÅŸitli durum mesajlarını ve olayları içerir ancak RKI’nin kimliÄŸinizi belirlemesini saÄŸlayabilecek herhangi bir bilgi içermez. Yalnızca ilave mesajlarla birlikte hata raporu tanıtıcısını adlandırmanız durumunda mesaj (ve örneÄŸin bu mesajda yer alan adınız) ile hata raporunda bulunan bilgiler (örneÄŸin, maruz kalma günlüğü kapsamında hesaplamaya iliÅŸkin teknik mesajlar, uygulamada görüntülenen bilgiler ve gerçekleÅŸtirilen adımlar ve geçerli durumlarda, alınan test sonucu ve diÄŸer kullanıcıları uyarma sürecinde paylaşılan rastgele kimlikler) arasında baÄŸlantı kurulabilir."</string> + + <!-- XHED: Title for Bugreporting share log screen --> + <string name="debugging_debuglog_share_log_title">"Hata Raporu Gönder"</string> + <!-- YTXT: First body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_one">"Kaydedilen hata raporunu RKI teknik destek birimine gönderebilmeniz için onay vermeniz gerekir."</string> + <!-- YTXT: Second body section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_section_two">"Raporu göndermenizin ardından bir hata raporu tanıtıcısı belirtilecektir. Bu tanıtıcı bilgisini vererek örneÄŸin, teknik destek birimine ek bilgi sunabilir ve hata raporunuza atanmasını saÄŸlayabilirsiniz. Hata raporu tanıtıcısını belirtmezseniz RKI raporu ÅŸahsınıza atayamaz."</string> + <!-- YTXT: Privacy Information section for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_privacy_information">"ABD’de ve DiÄŸer Üçüncü Ãœlkelerde Veri Ä°ÅŸleme ve Veri Koruma Riskleri Hakkında Ayrıntılı Bilgiler"</string> + <!-- XBUT: Button for bugreporting share log screen --> + <string name="debugging_debuglog_share_log_button">"Kabul Et ve Gönder"</string> + <!-- XHED: Title for log upload history --> + <string name="debugging_debuglog_uploadhistory_title">"Tanıtıcı GeçmiÅŸi"</string> + <!-- YTXT: Description for log upload history --> + <string name="debugging_debuglog_uploadhistory_description">"Hata analizi günlüklerinizin tanıtıcıları burada görüntülenir."</string> <!-- #################################### Interoperability @@ -1383,9 +1405,9 @@ <!-- YTXT: Text for consent given --> <string name="submission_test_result_available_text_consent_given">"Test sonucunuzu paylaÅŸmayı kabul ederek diÄŸer kullanıcıların uyarılmasına yardımcı olduÄŸunuz için teÅŸekkür ederiz. \n\n"<b>"Sonraki adımda lütfen “Paylaş†seçeneÄŸine dokunarak test sonucunuzu paylaşın."</b></string> <!-- XHED: Close screen popup title for consent given --> - <string name="submission_test_result_available_close_dialog_title_consent_given">"Ä°ÅŸlemi gerçekten iptal etmek istiyor musunuz?"</string> + <string name="submission_test_result_available_close_dialog_title_consent_given">"Test Sonucunu Görüntüle"</string> <!-- XTXT: Close screen popup text for consent given --> - <string name="submission_test_result_available_close_dialog_body_consent_given">"Önemli:\nBu iÅŸlemi iptal etmek üzeresiniz.\nHenüz diÄŸer kullanıcıları uyarmadınız. Lütfen bu iÅŸlemi tamamlayın ve diÄŸer kullanıcıların korunmasına yardımcı olun."</string> + <string name="submission_test_result_available_close_dialog_body_consent_given">"Test sonucunuzu okuduktan sonra gerekli durumlarda diÄŸer kullanıcı uyarabilir ve enfeksiyon zincirini kırabilirsiniz."</string> <!-- YTXT: Text for consent NOT given --> <string name="submission_test_result_available_text_consent_not_given">"Test sonucunuzu paylaÅŸmamayı seçtiniz. DiÄŸer kullanıcılar uyarılmayacak. \n\nSonraki adımda dilerseniz fikrinizi deÄŸiÅŸtirip test sonucunuzu paylaÅŸarak koronavirüsün yayılmasını durdurmaya ve diÄŸer kullanıcıların korunmasına yardımcı olabilirsiniz."</string> <!-- XHED: Close screen popup title for consent NOT given --> @@ -1393,9 +1415,9 @@ <!-- XTXT: Close screen popup text for consent NOT given --> <string name="submission_test_result_available_close_dialog_body_consent_not_given">"GiriÅŸleriniz kaydedilmeyecektir."</string> <!-- XBUT: Close screen popup cancel button --> - <string name="submission_test_result_available_close_dialog_cancel_button">"Ä°ptal"</string> + <string name="submission_test_result_available_close_dialog_cancel_button">"Görüntüleme"</string> <!-- XBUT: Close screen popup continue button --> - <string name="submission_test_result_available_close_dialog_continue_button">"Devam"</string> + <string name="submission_test_result_available_close_dialog_continue_button">"Görüntüle"</string> <!-- Submission your consent screen --> <!-- XHED: Your consent screen title --> @@ -1558,6 +1580,8 @@ Generic Error Messages ###################################### --> <!-- XHED: error dialog - headline --> + <string name="errors_generic_headline_short">"Hata"</string> + <!-- XHED: error dialog - headline --> <string name="errors_generic_headline">"Bir sorun oluÅŸtu."</string> <!-- XTXT: error dialog - short text for error reason --> <string name="errors_generic_details_headline">"Neden"</string> diff --git a/Corona-Warn-App/src/main/res/values/attrs.xml b/Corona-Warn-App/src/main/res/values/attrs.xml index adaecc5e822c0a653b59294a20e35a87c697e73f..6ff2a5d3b289bf4be15f39c201c06965b41bbf8d 100644 --- a/Corona-Warn-App/src/main/res/values/attrs.xml +++ b/Corona-Warn-App/src/main/res/values/attrs.xml @@ -39,4 +39,11 @@ <attr name="android:backgroundTint" /> <attr name="android:foregroundTint" /> </declare-styleable> + + <declare-styleable name="MoreInformationView"> + <attr name="isTopDividerVisible" format="boolean" /> + <attr name="isBottomDividerVisible" format="boolean" /> + <attr name="titleText" format="string" localization="suggested" /> + <attr name="subtitleText" format="string" /> + </declare-styleable> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml index dad9ca91c2d328f440aaff4ab49e69a25da3637c..83bd6e5955907c1d696fb6e148eb5709a6e2ab80 100644 --- a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml @@ -104,9 +104,9 @@ <!-- XHED: Title for the contact diary dialog to delete all persons --> <string name="contact_diary_delete_persons_title">"Do you really want to remove all people?"</string> <!-- XTXT: Message for the contact diary dialog to delete all locations --> - <string name="contact_diary_delete_locations_message">"If you remove a place, all the entries for that place will be removed from your journal."</string> + <string name="contact_diary_delete_locations_message">"If you remove all places, all the entries for all places will be removed from your journal."</string> <!-- XTXT: Message for the contact diary dialog to delete all persons --> - <string name="contact_diary_delete_persons_message">"If you remove a person, all the entries for that person will be removed from your journal."</string> + <string name="contact_diary_delete_persons_message">"If you remove all people, all the entries for all people will be removed from your journal."</string> <!-- XACT: edit icon description for screen readers --> <string name="contact_diary_edit_icon_content_description">"Edit Entry"</string> <!-- XACT: edit icon description for screen readers --> @@ -116,9 +116,9 @@ <!-- XHED: Title for the contact diary dialog to delete delete a single person --> <string name="contact_diary_delete_person_title">"Do you really want to remove this person?"</string> <!-- XTXT: Message for the contact diary dialog to delete a single location --> - <string name="contact_diary_delete_location_message">"Wenn Sie einen Ort entfernen, werden alle Einträge für diesen Ort aus dem Tagebuch gelöscht."</string> + <string name="contact_diary_delete_location_message">"If you remove a place, all the entries for that place will be removed from your journal."</string> <!-- XTXT: Message for the contact diary dialog to delete a single person --> - <string name="contact_diary_delete_person_message">"Wenn Sie eine Person entfernen, werden alle Einträge für diese Person aus dem Tagebuch gelöscht."</string> + <string name="contact_diary_delete_person_message">"If you remove a person, all the entries for that person will be removed from your journal."</string> <!-- XHED: Title for the contact journal export email subject --> <string name="contact_diary_export_subject" translatable="false">"Mein Kontakt-Tagebuch"</string> diff --git a/Corona-Warn-App/src/main/res/values/legal_strings.xml b/Corona-Warn-App/src/main/res/values/legal_strings.xml index d56a5c9fe018e8b804d3e4e6d35051ae336d6c0c..5d8adace039144459fc2fccab4f3e7d312f035a5 100644 --- a/Corona-Warn-App/src/main/res/values/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values/legal_strings.xml @@ -87,13 +87,6 @@ <!-- XTXT: Body for Privacy-preserving Analytics settings --> <string name="ppa_settings_privacy_information_body" translatable="false">"By enabling “Share Data†above, you consent to the following:\n\nThe app will transmit information it records to the RKI, on a daily basis. The data concerns possible exposures and warnings that have been displayed to you, test results you have retrieved, and whether you have warned other users, and information about your smartphone’s operating system. If you have provided further details above (region, age group), then the RKI will also receive this information.\n\nThe RKI will compile this data into statistics and analyze it to assess the effectiveness and functioning of the app, and draw conclusions regarding the pandemic. The resulting knowledge will help to improve the app’s features and make it more user-friendly, as well as to inform other pandemic response measures.\n\nBefore your data is analyzed, it is necessary to ensure that each app that shares data is only counted once, so as not to distort the statistics. This is why the authenticity of your app needs to be verified. For this purpose, a unique identifier is generated by your smartphone and transmitted to Google in the U.S. or other third countries, so that Google can confirm the authenticity of your app to the RKI. The identifier contains information about the version of your smartphone and the app. On this basis, Google may be able to infer your identity and learn that your smartphone has been authenticated. Google does not receive any other information from the app during this process.\n\n You can withdraw your consent at any time by disabling “Share Data†above."</string> - <!-- XHED: Title for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_title" translatable="false">"Prüfung der Echtheit und Drittlandsübermittlung"</string> - <!-- YTXT: First section for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_first_section" translatable="false">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um sicherzustellen, dass nur Nutzer Daten auf diesem Weg an den technischen Support übersenden, die tatsächlich die Corona-Warn-App nutzen und nicht manipulierte Fehlerberichte bereitstellen. Die Kennung wird dafür einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die Angaben aus Ihrem Fehlerbericht erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat."</string> - <!-- YTXT: Second section for debug legal screen privacy card --> - <string name="debugging_debuglog_legal_privacy_card_second_section" translatable="false">"Wenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden und Fehlerbericht senden“ an. Sie können die App weiterhin nutzen, eine Ãœbersendung des Fehlerberichtes über die App ist dann jedoch nicht möglich."</string> - <!-- XHED: Title for debug upload screen privacy card --> <string name="debugging_debuglog_privacy_card_title" translatable="false">"Data protection and data security"</string> <!-- YTXT: First bullet point for debug upload screen privacy card --> diff --git a/Corona-Warn-App/src/main/res/values/release_info_strings.xml b/Corona-Warn-App/src/main/res/values/release_info_strings.xml index 5e030915b3fb4a6df911d4ab61d373431a99c33f..304fd087ff53e1a48c2f599221162111d9748122 100644 --- a/Corona-Warn-App/src/main/res/values/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values/release_info_strings.xml @@ -13,30 +13,30 @@ <!-- XBUT: Continue button for the release info screen --> <string name="release_info_continue_button">"Next"</string> <!-- XTXT: New release info footer --> - <string name="new_release_bottom">"Changes in this release can be found in the app settings, under “New Featuresâ€."</string> + <string name="new_release_bottom">"Changes in this release can be found in the app information, under “New Featuresâ€."</string> <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> - <item>Länderübergreifende Risiko-Ermittlung um Schweiz erweitert </item> - <item>Erstellung von Fehlerberichten</item> + <item>"Switzerland Added to Transnational Exposure Logging"</item> + <item>"Generation of Error Reports"</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>Die Corona-Warn-App nutzende Personen können nun auch verschlüsselte Zufalls-IDs mit Personen austauschen, die die offizielle Warn-App der Schweiz nutzen. Somit können Warnungen an App nutzende Personen in der Schweiz geschickt sowie von ihnen empfangen werden.</item> - <item>Sie können nun nach Aufforderung des technischen Supports einen Fehlerbericht erstellen und die Schritte aufzeichnen, die Sie in der App ausführen. Mögliche technische Fehler können so besser analysiert und schneller behoben werden. </item> + <item>"Corona-Warn-App users can now exchange encrypted random IDs with users of the official warning app in Switzerland. This means warnings can now be sent to and received from app users in Switzerland."</item> + <item>"You can now generate an error report, upon request by technical support, and record the steps that you perform in the app. This will make it easier to analyze technical errors and correct them more quickly."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> <string-array name="new_release_linkified_labels"> - <item></item> - <item></item> + <item/> + <item/> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> - <item></item> - <item></item> + <item/> + <item/> </string-array> </resources> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index ee5cad7b72738f11b52bc72fd7cc92e503250749..38eb2464b2fe251d64f1b1f0568a845cb2392325 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -1,54 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> - - <!-- #################################### - Preference Keys - ###################################### - TODO: Check what is needed --> - - <!-- NOTR --> - <string name="preference_name"><xliff:g id="preference">"shared_preferences_cwa"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed"><xliff:g id="preference">"preference_onboarding_completed"</xliff:g></string> - <!-- NOTR --> - <string name="preference_onboarding_completed_timestamp"><xliff:g id="preference">"preference_onboarding_completed_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_check_done"><xliff:g id="preference">"preference_background_check_done"</xliff:g></string> - <!-- NOTR --> - <string name="preference_reset_app"><xliff:g id="preference">"preference_reset_app"</xliff:g></string> - <!-- NOTR --> - <string name="preference_only_wifi"><xliff:g id="preference">"preference_only_wifi"</xliff:g></string> - <!-- NOTR --> - <string name="preference_tracing"><xliff:g id="preference">"preference_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> - <!-- NOTR --> - <string name="preference_background_job_allowed"><xliff:g id="preference">"preference_background_job_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_mobile_data_allowed"><xliff:g id="preference">"preference_mobile_data_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_registration_token"><xliff:g id="preference">"preference_registration_token"</xliff:g></string> - <!-- NOTR --> - <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_initial_result_received_time"><xliff:g id="preference">"preference_initial_result_received_time"</xliff:g></string> - <!-- NOTR --> - <string name="preference_is_allowed_to_submit_diagnosis_keys"><xliff:g id="preference">"preference_is_allowed_to_submit_diagnosis_keys"</xliff:g></string> - <!-- NOTR --> - <string name="preference_database_password"><xliff:g id="preference">"preference_database_password"</xliff:g></string> - <!-- NOTR --> - <string name="preference_total_non_active_tracing"><xliff:g id="preference">"preference_total_non_active_tracing"</xliff:g></string> - <!-- NOTR --> - <string name="preference_last_non_active_tracing_timestamp"><xliff:g id="preference">"preference_last_non_active_tracing_timestamp"</xliff:g></string> - <!-- NOTR --> - <string name="preference_number_successful_submissions"><xliff:g id="preference">"preference_number_successful_submissions"</xliff:g></string> - <!-- NOTR --> - <string name="preference_polling_test_result_started"><xliff:g id="preference">"preference_polling_test_result_started"</xliff:g></string> - <!-- NOTR --> - <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string> - <!-- #################################### Generics ###################################### --> @@ -117,9 +68,9 @@ ###################################### --> <!-- XTXT: risk card - Days since installation if < 14 days --> - <string name="risk_card_body_days_since_installation">"Seit %s Tagen installiert"</string> + <string name="risk_card_body_days_since_installation">"Installed %s days ago"</string> <!-- XTXT: risk card - tracing active for x out of 14 days --> - <string name="risk_card_body_saved_days">"Exposure logging was active for %1$s of the past 14 days."</string> + <string name="risk_card_body_saved_days">"Exposure logging was active for %1$s of the past 14 days"</string> <!-- XTXT: risk card- tracing active for 14 out of 14 days --> <string name="risk_card_body_saved_days_full">"Exposure logging permanently active"</string> <!-- XTXT; risk card - no update done yet --> @@ -324,7 +275,7 @@ <!-- XMSG: risk details - go/stay home, something like a bullet point --> <string name="risk_details_behavior_body_stay_home">"If possible, please go home and stay at home."</string> <!-- XMSG: risk details - get in touch with the corresponding people, something like a bullet point --> - <string name="risk_details_behavior_body_contact_doctor">"If you have questions about symptoms, testing availability, or self-isolation, please contact one of the following:"</string> + <string name="risk_details_behavior_body_contact_doctor">"If you have questions about symptoms, testing availability, or quarantine measures, please contact one of the following:"</string> <!-- XMSG: risk details - wash your hands, something like a bullet point --> <string name="risk_details_behavior_body_wash_hands">"Wash your hands regularly, with soap, for 20 seconds."</string> <!-- XMSG: risk details - wear a face mask, something like a bullet point --> @@ -340,7 +291,7 @@ <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> <!-- XMSG: risk details - ventilation bullet point --> - <string name="risk_details_behavior_body_ventilation">"Lüften Sie Innenräume mehrmals täglich. Öffnen Sie dazu die Fenster einige Minuten lang so weit wie möglich (Stoßlüftung)."</string> + <string name="risk_details_behavior_body_ventilation">"Air out indoor spaces several times per day. To do so, open the windows as far as possible for several minutes (“shock ventilationâ€)."</string> <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"Sneeze or cough into your elbow or a tissue."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -356,11 +307,11 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"This period is included in the calculation."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> - <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently."</string> + <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently. Exposure logging covers the last 14 days."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> - <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Exposure logging covers the past 14 days. During this time, the logging feature on your smartphone was active for %1$s days. The app automatically deletes older logs, as these are no longer relevant for infection prevention."</string> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> <!-- XTXT: risk details - infection period logged information body, over 14 days --> - <string name="risk_details_information_body_period_logged_assessment_over_14_days">"Wenn die Risiko-Ermittlung zu Zeiten in denen sie andere Personen getroffen haben aktiv war, kann die Berechnung des Infektionsrisikos für diesen Zeitraum erfolgen."</string> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"If exposure logging was active in times during which you encountered other people, your risk of infection can be calculated for this period."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment">"Exposure logging covers the past 14 days. During this time, the logging feature on your smartphone was active for %1$s days. The app automatically deletes older logs, as these are no longer relevant for infection prevention."</string> <!-- XTXT: risk details - infection period days logged/14 --> @@ -376,7 +327,7 @@ <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"The risk of infection is calculated locally on your smartphone, using exposure logging data. The calculation also takes into account distance and duration of any exposure to persons diagnosed with coronavirus, as well as their potential infectiousness. Your risk of infection cannot be seen by or passed on to anyone else."</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> - <string name="risk_details_information_body_increased_risk_date">"You have an increased risk of infection because you were last exposed on %1$s over a longer period of time and at close proximity to at least one person diagnosed with coronavirus."</string> + <string name="risk_details_information_body_increased_risk_date">"You have an increased risk of infection because you were exposed over a longer period of time and at close proximity to at least one person diagnosed with COVID-19."</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">"You have an increased risk of infection because you were last exposed %1$s days ago over a longer period of time and at close proximity to at least one person diagnosed with coronavirus."</item> @@ -511,7 +462,7 @@ <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> <string name="onboarding_tracing_location_headline">"Activate Location Setting"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Your device location cannot be determined by the app, but the device location setting must be activated to use Bluetooth Low Energy in some Android versions."</string> + <string name="onboarding_tracing_location_body">"The app cannot determine your location. However, the device location setting must be activated to use Bluetooth Low Energy in Android 10 and earlier versions."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Open Device Settings"</string> <!-- XACT: Onboarding (test) page title --> @@ -624,9 +575,6 @@ <string name="onboarding_ppa_more_info_much_privacy_body">"This information cannot be linked with you personally. Your identity will remain confidential.\nThe information will be analyzed for statistical purposes. It will not be saved for a profile."</string> - - - <!-- #################################### Onboarding sixteen include ###################################### --> @@ -691,7 +639,7 @@ <!-- XTXT: settings(tracing) - explains user what to do on card if connection is disabled --> <string name="settings_tracing_status_connection_body">"Exposure logging requires an Internet connection to calculate your risk of infection. Please turn on Wi-Fi or mobile data in your device settings."</string> <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> - <string name="risk_details_explanation_dialog_faq_body">"Weitere Informationen finden Sie in den FAQ."</string> + <string name="risk_details_explanation_dialog_faq_body">"For further information, please see our FAQ page."</string> <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card --> @@ -888,73 +836,85 @@ ###################################### --> <!-- XHED: Headline for debug log screen --> - <string name="debugging_debuglog_title">"Error Report"</string> + <string name="debugging_debuglog_title">"Error Reports"</string> <!-- XHED: Headline for current status of debug log screen --> - <string name="debugging_debuglog_current_status_title">"Fehleranalyse"</string> + <string name="debugging_debuglog_current_status_title">"Error Analysis"</string> <!-- YTXT: Description one for the debug option to record log files --> - <string name="debugging_debuglog_intro_explanation_section_one">"Um den technischen Support der App bei der Fehleranalyse zu unterstützen, können Sie einen Fehlerbericht der CWA aufzeichnen. Hierbei werden die einzelnen technischen Schritte und Ereignisse beim Ablauf der App detailliert aufgezeichnet. Den Fehlerbericht können Sie dann an den technischen Support senden und so helfen, Fehler zu erkennen und zu beheben."</string> + <string name="debugging_debuglog_intro_explanation_section_one">"To help the app technical support team with error analysis, you can record an error report from the CWA. When you do so, the individual technical steps and results of app processes are recorded. You can then send the error report to technical support and help to identify and correct the error."</string> <!-- YTXT: Description two for the debug option to record log files --> - <string name="debugging_debuglog_intro_explanation_section_two">"Weitere Informationen finden Sie in den FAQ:\nFAQ zu den Fehlerberichten"</string> + <string name="debugging_debuglog_intro_explanation_section_two">"For further information, please see our FAQ page:\nFAQ for error reports"</string> <!-- XTXT: Debug Log screen increased risk level link label - HAS TO MATCH the link text above --> - <string name="debugging_debuglog_intro_explanation_section_two_link_label">"FAQ zu den Fehlerberichten"</string> + <string name="debugging_debuglog_intro_explanation_section_two_link_label">"FAQ for error reports"</string> <!-- XTXT: Explains user about about debug log: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="debugging_debuglog_intro_explanation_section_two_faq_link">"https://www.coronawarn.app/en/faq/#error_log"</string> <!-- YTXT: Title of ID History --> <string name="debugging_debuglog_id_history_title">"ID History"</string> <!-- YTXT: Description of ID History --> - <string name="debugging_debuglog_id_history_body">"IDs der bisher geteilten Fehleranalysen"</string> + <string name="debugging_debuglog_id_history_body">"IDs of the error analyses shared so far"</string> <!-- YTXT: Warning regarding downsides of recording a log file --> <string name="debugging_debuglog_intro_warning">"Please note that error reports may contain sensitive data (such as a test result or contact journal entries). Therefore, you should not share these error reports publicly."</string> <!-- XBUT: Button text to start the log recording --> <string name="debugging_debuglog_action_start_recording">"Start"</string> <!-- XBUT: Button text to stop the log recording --> - <string name="debugging_debuglog_action_stop_recording">"Stoppen und löschen"</string> + <string name="debugging_debuglog_action_stop_recording">"Stop and Delete"</string> <!-- XBUT: Button text to share the log recording --> - <string name="debugging_debuglog_action_share_log">"Fehlerbericht senden"</string> + <string name="debugging_debuglog_action_share_log">"Send Error Report"</string> <!-- XBUT: Button text to locally store the log recording --> - <string name="debugging_debuglog_action_local_log_store">"Lokal speichern und fortsetzen"</string> + <string name="debugging_debuglog_action_local_log_store">"Save Locally and Continue"</string> <!-- YTXT: Status text if a debug log is being recorded --> <string name="debugging_debuglog_status_recording">"Recording Active"</string> <!-- YTXT: Status text if a debug log is being recorded but there is not enough free storage space --> - <string name="debugging_debuglog_status_lowstorage">"Zu wenig Speicherplatz"</string> + <string name="debugging_debuglog_status_lowstorage">"Not enough memory"</string> <!-- YTXT: Status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_not_recording">"Inactive"</string> <!-- YTXT: Describtion for current logging status text if a debug log is not being recorded --> <string name="debugging_debuglog_status_additional_infos">"Current size: %1$s (uncompressed)"</string> - <!-- XHED: Title for native sharing dialog --> - <string name="debugging_debuglog_sharing_dialog_title">"Share CWA Error Report"</string> + + <!-- XHED: Title for debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_title">"Prüfung der Echtheit und Drittlandsübermittlung"</string> + <!-- YTXT: First section for debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_first_section">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um sicherzustellen, dass nur Nutzer Daten auf diesem Weg an den technischen Support übersenden, die tatsächlich die Corona-Warn-App nutzen und nicht manipulierte Fehlerberichte bereitstellen. Die Kennung wird dafür einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die Angaben aus Ihrem Fehlerbericht erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat."</string> + <!-- YTXT: Second section for debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_second_section">"Wenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden und senden“ an. Sie können die App weiterhin nutzen, eine Ãœbersendung des Fehlerberichtes über die App ist dann jedoch nicht möglich."</string> + + <!-- YTXT: Dialog title if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_title">"Wollen Sie die Fehleranalyse wirklich stoppen?"</string> <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> - <string name="debugging_debuglog_stop_confirmation_message">"Der Fehlerbericht wurde gelöscht. Wenn Sie eine lokale Kopie des Fehlerberichts gespeichert haben, wurde diese nicht gelöscht."</string> + <string name="debugging_debuglog_stop_confirmation_message">"The error report was deleted. If you have saved a local copy of the error report, it was not deleted."</string> + <!-- YTXT: Dialog confirmation button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Analyse stoppen"</string> + <!-- YTXT: Dialog discard button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Analyse fortführen"</string> <!-- YTXT: Dialog message if there is not enough free storage to start a debug log --> - <string name="debugging_debuglog_start_low_storage_error">Sie brauchen mindestens 200 MB Speicherplatz, um die Fehleranalyse zu starten. Bitte geben Sie Speicherplatz frei.</string> + <string name="debugging_debuglog_start_low_storage_error">"You need at least 200 MB of memory to start the error analysis. Please free up memory."</string> <!-- XHED: Dialog title if a user has stored a debug log locally --> - <string name="debugging_debuglog_localexport_title">"Lokal gespeichert"</string> + <string name="debugging_debuglog_localexport_title">"Saved Locally"</string> <!-- YTXT: Dialog message if a user has stored a debug log locally --> - <string name="debugging_debuglog_localexport_message">"Die Fehleranalyse wurde lokal gespeichert."</string> + <string name="debugging_debuglog_localexport_message">"The error analysis was saved locally."</string> <!-- YTXT: Dialog message if local export has failed --> - <string name="debugging_debuglog_localexport_error_message">"Das Speichern des Fehlerberichts ist fehlgeschlagen. Bitte überprüfen Sie, ob genügend Speicherplatz zur Verfügung steht."</string> + <string name="debugging_debuglog_localexport_error_message">"The attempt to save the error report failed. Please check whether enough memory is available."</string> <!-- XHED: Title for debug legal screen --> - <string name="debugging_debuglog_legal_dialog_title">"Ausführliche Informationen zur Ãœbersendung der Fehlerberichte"</string> + <string name="debugging_debuglog_legal_dialog_title">"Detailed Information on Sending Error Reports"</string> <!-- YTXT: Section Title for debug legal screen --> - <string name="debugging_debuglog_legal_section_title">"Zur Auswertung der Fehlerberichte durch das RKI"</string> + <string name="debugging_debuglog_legal_section_title">"How the RKI analyzes the error reports"</string> <!-- YTXT: Section Body for debug legal screen --> - <string name="debugging_debuglog_legal_section_body">"Nachdem die Echtheit Ihrer App geprüft wurde, wird der Fehlerbericht über eine gesicherte Verbindung an das RKI übermittelt. Die Fehlerberichte werden nur zur Fehleranalyse und Fehlerbehebung im Rahmen zukünftiger Updates der App genutzt. Nur Mitarbeiter des technischen Support können auf die Fehlerberichte zugreifen. Die Fehlerberichte enthalten eine Vielzahl von Statusmeldungen und Ereignissen, die in der App ausgelöst wurden, sie enthalten aber keine Hinweise anhand derer das RKI auf Ihre Identität schließen kann. Erst wenn Sie die Fehlerbericht-ID im Zusammenhang mit weiteren Mitteilungen nennen, kann ein Zusammenhang zwischen der Mitteilung (und z.B. Ihrem dort enthaltenen Namen) und den im Fehlerbericht enthaltenen Angaben (z.B. technische Meldungen zur Berechnung im Rahmen der Risiko-Ermittlung, in der App angezeigte Informationen und durchlaufene Schritte, aber ggf. auch einem abgerufenen Testergebnis und ggf. im Rahmen der Warnung geteilter Zufalls-IDs) hergestellt werden."</string> + <string name="debugging_debuglog_legal_section_body">"After the authenticity of your app has been verified, the error report is sent to the RKI through a secure connection. Error reports will only be used for troubleshooting and error correction within the framework of future app updates. Only technical support employees can access error reports. Error reports contain a variety of status messages and events that are triggered in the app, but do not contain any information that would enable the RKI to determine your identity. Only if you name the error report ID in connection with further messages can a connection be made between the message (and your name contained there, for example) and the information contained in the error report (such as technical messages for calculation as part of exposure logging, information displayed and steps performed in the app, and, where applicable, a retrieved test result and random IDs shared as part of notifying others)."</string> <!-- XHED: Title for Bugreporting share log screen --> - <string name="debugging_debuglog_share_log_title">"Fehlerbericht senden"</string> + <string name="debugging_debuglog_share_log_title">"Send Error Report"</string> <!-- YTXT: First body section for bugreporting share log screen --> - <string name="debugging_debuglog_share_log_section_one">"Bevor Sie den aufgezeichneten Fehlerbericht an den technischen Support des RKI übersenden können, ist Ihr Einverständnis erforderlich."</string> + <string name="debugging_debuglog_share_log_section_one">"Your consent is needed before you can send the recorded error report to RKI technical support."</string> <!-- YTXT: Second body section for bugreporting share log screen --> - <string name="debugging_debuglog_share_log_section_two">"Nach der Ãœbersendung erhalten Sie eine Fehlerbericht-ID. Diese können Sie angeben, um dem technischen Support z.B. weitere Informationen zukommen zu lassen und dabei eine Zuordnung zu Ihrem Fehlerbericht zu ermöglichen. Wenn Sie die Fehlerbericht-ID nicht mitteilen, ist dem RKI eine Zuordnung zu Ihrer Person nicht möglich."</string> + <string name="debugging_debuglog_share_log_section_two">"After you send the report, you will receive an error report ID. You can specify this ID to provide technical support with further information, for example, and enable its assignment to your error report. If you do not provide the error report ID, the RKI cannot assign the report to you as an individual."</string> <!-- YTXT: Privacy Information section for bugreporting share log screen --> - <string name="debugging_debuglog_share_log_privacy_information">"Ausführliche Informationen zu dieser Datenverarbeitung und den Datenschutzrisiken in den USA und anderen Drittländern."</string> + <string name="debugging_debuglog_share_log_privacy_information">"Detailed Information about This Data Processing and Data Protection Risks in the U.S. and Other Third Countries"</string> <!-- XBUT: Button for bugreporting share log screen --> - <string name="debugging_debuglog_share_log_button">"Einverstanden und senden"</string> + <string name="debugging_debuglog_share_log_button">"Agree and Send"</string> <!-- XHED: Title for log upload history --> - <string name="debugging_debuglog_uploadhistory_title">"ID Historie"</string> + <string name="debugging_debuglog_uploadhistory_title">"ID History"</string> <!-- YTXT: Description for log upload history --> - <string name="debugging_debuglog_uploadhistory_description">"Hier sehen Sie die IDs Ihrer Fehleranalyse-Protokolle."</string> + <string name="debugging_debuglog_uploadhistory_description">"You see the IDs of your error analysis logs here."</string> <!-- #################################### Interoperability @@ -1467,9 +1427,9 @@ <!-- YTXT: Text for consent given --> <string name="submission_test_result_available_text_consent_given">"Thank you for agreeing to share your test result and helping to warn others as a result. \n\n"<b>"In the next step, please share your test result by tapping “Shareâ€."</b></string> <!-- XHED: Close screen popup title for consent given --> - <string name="submission_test_result_available_close_dialog_title_consent_given">"Do you really want to cancel the process?"</string> + <string name="submission_test_result_available_close_dialog_title_consent_given">"Display Test Result"</string> <!-- XTXT: Close screen popup text for consent given --> - <string name="submission_test_result_available_close_dialog_body_consent_given">"Important:\nYou are about to cancel this process.\nYou have not warned others yet. Please complete the process and help to protect others."</string> + <string name="submission_test_result_available_close_dialog_body_consent_given">"After you have read your test result, you can notify others if necessary and break the chain of infection."</string> <!-- YTXT: Text for consent NOT given --> <string name="submission_test_result_available_text_consent_not_given">"You have chosen not to share your test result. Others will not be warned. \n\nIn the next step, you have the opportunity to change your mind and share your test result after all, to help stop the spread of coronavirus and protect others."</string> <!-- XHED: Close screen popup title for consent NOT given --> @@ -1477,9 +1437,9 @@ <!-- XTXT: Close screen popup text for consent NOT given --> <string name="submission_test_result_available_close_dialog_body_consent_not_given">"Your entries will not be saved."</string> <!-- XBUT: Close screen popup cancel button --> - <string name="submission_test_result_available_close_dialog_cancel_button">"Cancel"</string> + <string name="submission_test_result_available_close_dialog_cancel_button">"Do Not Display"</string> <!-- XBUT: Close screen popup continue button --> - <string name="submission_test_result_available_close_dialog_continue_button">"Continue"</string> + <string name="submission_test_result_available_close_dialog_continue_button">"Display"</string> <!-- Submission your consent screen --> <!-- XHED: Your consent screen title --> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c0680a991b0dae9908b46274bac5b5bbd4988276 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt @@ -0,0 +1,69 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class BugCensorTest : BaseTest() { + + @Test + fun `name censoring validity`() { + BugCensor.withValidName(null) {} shouldBe false + BugCensor.withValidName("") {} shouldBe false + BugCensor.withValidName(" ") {} shouldBe false + BugCensor.withValidName(" ") {} shouldBe false + BugCensor.withValidName("J") {} shouldBe false + BugCensor.withValidName("Jo") {} shouldBe false + BugCensor.withValidName("Joe") {} shouldBe true + } + + @Test + fun `email censoring validity`() { + BugCensor.withValidEmail(null) {} shouldBe false + BugCensor.withValidEmail("") {} shouldBe false + BugCensor.withValidEmail(" ") {} shouldBe false + BugCensor.withValidEmail(" ") {} shouldBe false + BugCensor.withValidEmail("@") {} shouldBe false + BugCensor.withValidEmail("@.") {} shouldBe false + BugCensor.withValidEmail("@.de") {} shouldBe false + BugCensor.withValidEmail("a@.de") {} shouldBe false + BugCensor.withValidEmail("a@b.de") {} shouldBe true + } + + @Test + fun `phone censoring validity`() { + BugCensor.withValidPhoneNumber(null) {} shouldBe false + BugCensor.withValidPhoneNumber(" ") {} shouldBe false + BugCensor.withValidPhoneNumber(" ") {} shouldBe false + BugCensor.withValidPhoneNumber("0") {} shouldBe false + BugCensor.withValidPhoneNumber("01") {} shouldBe false + BugCensor.withValidPhoneNumber("012") {} shouldBe false + BugCensor.withValidPhoneNumber("0123") {} shouldBe true + } + + @Test + fun `comment censoring validity`() { + BugCensor.withValidComment(null) {} shouldBe false + BugCensor.withValidComment(" ") {} shouldBe false + BugCensor.withValidComment(" ") {} shouldBe false + BugCensor.withValidComment("a") {} shouldBe false + BugCensor.withValidComment("ab") {} shouldBe false + BugCensor.withValidComment("abc") {} shouldBe true + } + + @Test + fun `loglines are only copied if the message is different`() { + val logLine = LogLine( + timestamp = 1, + priority = 3, + message = "Message", + tag = "Tag", + throwable = null + ) + logLine.toNewLogLineIfDifferent("Message") shouldBe null + logLine.toNewLogLineIfDifferent("Message ") shouldNotBe logLine + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..90bcddb7723145acd4131e3d4e6ba14343778a09 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt @@ -0,0 +1,71 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import dagger.Component +import dagger.Module +import dagger.Provides +import de.rki.coronawarnapp.bugreporting.BugReportingSharedModule +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.submission.SubmissionSettings +import io.github.classgraph.ClassGraph +import io.kotest.matchers.collections.shouldContainAll +import io.kotest.matchers.shouldBe +import io.mockk.mockk +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import timber.log.Timber +import javax.inject.Singleton + +class CensorInjectionTest : BaseTest() { + + /** + * Scan our class graph, and compare it against what we inject via Dagger. + * Catch accidentally forgetting to add a bug censor. + */ + @Test + fun `all censors are injected`() { + val component = DaggerBugCensorTestComponent.factory().create() + val bugCensors = component.bugCensors + + Timber.v("We know %d censors.", bugCensors.size) + require(bugCensors.isNotEmpty()) + + val scanResult = ClassGraph() + .acceptPackages("de.rki.coronawarnapp") + .enableClassInfo() + .scan() + + val ourCensors = scanResult + .getClassesImplementing("de.rki.coronawarnapp.bugreporting.censors.BugCensor") + + Timber.v("Our project contains %d censor classes.", ourCensors.size) + + val injectedCensors = bugCensors.map { it.javaClass.name }.toSet() + val existingCensors = ourCensors.map { it.name }.toSet() + existingCensors.isEmpty() shouldBe false + injectedCensors shouldContainAll existingCensors + } +} + +@Singleton +@Component(modules = [MockProvider::class, BugReportingSharedModule::class]) +interface BugCensorTestComponent { + + val bugCensors: Set<BugCensor> + + @Component.Factory + interface Factory { + fun create(): BugCensorTestComponent + } +} + +@Module +class MockProvider { + + @Singleton + @Provides + fun diary(): ContactDiaryRepository = mockk() + + @Singleton + @Provides + fun submissionSettings(): SubmissionSettings = mockk() +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..a00968c428c93e723309761c38e08af1634ce905 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt @@ -0,0 +1,163 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import kotlin.concurrent.thread + +class DiaryEncounterCensorTest : BaseTest() { + + @MockK lateinit var diaryRepo: ContactDiaryRepository + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + } + + private fun createInstance(scope: CoroutineScope) = DiaryEncounterCensor( + debugScope = scope, + diary = diaryRepo + ) + + private fun mockEncounter( + _id: Long, + _circumstances: String + ) = mockk<ContactDiaryPersonEncounter>().apply { + every { id } returns _id + every { circumstances } returns _circumstances + } + + @Test + fun `censoring replaces the logline message`() = runBlockingTest { + every { diaryRepo.personEncounters } returns flowOf( + listOf( + mockEncounter(1, _circumstances = ""), + mockEncounter(2, _circumstances = "A rainy day"), + mockEncounter(3, "Spilled coffee on each others laptops") + ) + ) + + val instance = createInstance(this) + val censorMe = LogLine( + timestamp = 1, + priority = 3, + message = + """ + On A rainy day, + two persons Spilled coffee on each others laptops, + everyone disliked that. + """.trimIndent(), + tag = "I'm a tag", + throwable = null + ) + + instance.checkLog(censorMe) shouldBe censorMe.copy( + message = + """ + On Encounter#2/Circumstances, + two persons Encounter#3/Circumstances, + everyone disliked that. + """.trimIndent() + ) + } + + @Test + fun `censoring returns null if all circumstances are blank`() = runBlockingTest { + every { diaryRepo.personEncounters } returns flowOf(listOf(mockEncounter(1, _circumstances = ""))) + val instance = createInstance(this) + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "That was strange.", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } + + @Test + fun `censoring returns null if there are no locations no match`() = runBlockingTest { + every { diaryRepo.personEncounters } returns flowOf(emptyList()) + + val instance = createInstance(this) + + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "Nothing ever happens.", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } + + @Test + fun `censoring returns null if the message did not change`() = runBlockingTest { + every { diaryRepo.personEncounters } returns flowOf( + listOf( + mockEncounter(1, _circumstances = "March weather"), + mockEncounter(2, _circumstances = "Rainy, cold"), + ) + ) + + val instance = createInstance(this) + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "I like turtles", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } + + // EXPOSUREAPP-5670 / EXPOSUREAPP-5691 + @Test + fun `replacement doesn't cause recursion`() { + every { diaryRepo.personEncounters } returns flowOf( + listOf( + mockEncounter(1, _circumstances = "March weather"), + mockEncounter(2, _circumstances = "Rainy, cold"), + ) + ) + + val logLine = LogLine( + timestamp = 1, + priority = 3, + message = "Lorem ipsum", + tag = "I'm a tag", + throwable = null + ) + + var isFinished = false + + thread { + Thread.sleep(500) + if (isFinished) return@thread + Runtime.getRuntime().exit(1) + } + + runBlocking { + val instance = createInstance(this) + + val processedLine = try { + instance.checkLog(logLine) + } finally { + isFinished = true + } + processedLine shouldBe null + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt index a02d2f73cd405b14179730df07f4083f4d47dd63..0c80fee73ed0eb5e43ee044c80a43214c73047d2 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt @@ -3,20 +3,19 @@ package de.rki.coronawarnapp.bugreporting.censors import de.rki.coronawarnapp.bugreporting.debuglog.LogLine import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository -import de.rki.coronawarnapp.util.CWADebug import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import io.mockk.mockkObject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runBlockingTest -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import kotlin.concurrent.thread class DiaryLocationCensorTest : BaseTest() { @@ -25,14 +24,6 @@ class DiaryLocationCensorTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - - mockkObject(CWADebug) - every { CWADebug.isDeviceForTestersBuild } returns false - } - - @AfterEach - fun teardown() { - QRCodeCensor.lastGUID = null } private fun createInstance(scope: CoroutineScope) = DiaryLocationCensor( @@ -40,31 +31,47 @@ class DiaryLocationCensorTest : BaseTest() { diary = diaryRepo ) - private fun mockLocation(id: Long, name: String) = mockk<ContactDiaryLocation>().apply { + private fun mockLocation( + id: Long, + name: String, + phone: String?, + mail: String? + ) = mockk<ContactDiaryLocation>().apply { every { locationId } returns id every { locationName } returns name + every { phoneNumber } returns phone + every { emailAddress } returns mail } @Test fun `censoring replaces the logline message`() = runBlockingTest { every { diaryRepo.locations } returns flowOf( - listOf(mockLocation(1, "Berlin"), mockLocation(2, "Munich"), mockLocation(3, "Aachen")) + listOf( + mockLocation(1, "Munich", phone = "+49 089 3333", mail = "bürgermeister@münchen.de"), + mockLocation(2, "Bielefeld", phone = null, mail = null), + mockLocation(3, "Aachen", phone = "+49 0241 9999", mail = "karl@aachen.de") + ) ) val instance = createInstance(this) val censorMe = LogLine( timestamp = 1, priority = 3, - message = "Munich is nice, but Aachen is nice too.", + message = + """ + Bürgermeister of Munich (+49 089 3333) and Karl of Aachen [+49 0241 9999] called each other. + Both agreed that their emails (bürgermeister@münchen.de|karl@aachen.de) are awesome, + and that Bielefeld doesn't exist as it has neither phonenumber (null) nor email (null). + """.trimIndent(), tag = "I'm a tag", throwable = null ) instance.checkLog(censorMe) shouldBe censorMe.copy( - message = "Location#2 is nice, but Location#3 is nice too." - ) - - every { CWADebug.isDeviceForTestersBuild } returns true - instance.checkLog(censorMe) shouldBe censorMe.copy( - message = "Munich is nice, but Aachen is nice too." + message = + """ + Bürgermeister of Location#1/Name (Location#1/PhoneNumber) and Karl of Location#3/Name [Location#3/PhoneNumber] called each other. + Both agreed that their emails (Location#1/EMail|Location#3/EMail) are awesome, + and that Location#2/Name doesn't exist as it has neither phonenumber (null) nor email (null). + """.trimIndent() ) } @@ -81,4 +88,63 @@ class DiaryLocationCensorTest : BaseTest() { ) instance.checkLog(notCensored) shouldBe null } + + @Test + fun `if message is the same, don't copy the log line`() = runBlockingTest { + every { diaryRepo.locations } returns flowOf( + listOf( + mockLocation(1, "Test", phone = null, mail = null), + mockLocation(2, "Test", phone = null, mail = null), + mockLocation(3, "Test", phone = null, mail = null) + ) + ) + val instance = createInstance(this) + val logLine = LogLine( + timestamp = 1, + priority = 3, + message = "Lorem ipsum", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(logLine) shouldBe null + } + + // EXPOSUREAPP-5670 / EXPOSUREAPP-5691 + @Test + fun `replacement doesn't cause recursion`() { + every { diaryRepo.locations } returns flowOf( + listOf( + mockLocation(1, "Test", phone = null, mail = null), + mockLocation(2, "Test", phone = null, mail = null), + mockLocation(3, "Test", phone = null, mail = null) + ) + ) + + val logLine = LogLine( + timestamp = 1, + priority = 3, + message = "Lorem ipsum", + tag = "I'm a tag", + throwable = null + ) + + var isFinished = false + + thread { + Thread.sleep(500) + if (isFinished) return@thread + Runtime.getRuntime().exit(1) + } + + runBlocking { + val instance = createInstance(this) + + val processedLine = try { + instance.checkLog(logLine) + } finally { + isFinished = true + } + processedLine shouldBe null + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt index 44b46c3d8685a849216a9287fd01fda88a802d99..e4e5e38c294d6f3b384f6a8e7191f725a29ed441 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt @@ -3,20 +3,20 @@ package de.rki.coronawarnapp.bugreporting.censors import de.rki.coronawarnapp.bugreporting.debuglog.LogLine import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository -import de.rki.coronawarnapp.util.CWADebug import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import io.mockk.mockkObject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runBlockingTest -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import java.lang.Thread.sleep +import kotlin.concurrent.thread class DiaryPersonCensorTest : BaseTest() { @@ -25,14 +25,6 @@ class DiaryPersonCensorTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - - mockkObject(CWADebug) - every { CWADebug.isDeviceForTestersBuild } returns false - } - - @AfterEach - fun teardown() { - QRCodeCensor.lastGUID = null } private fun createInstance(scope: CoroutineScope) = DiaryPersonCensor( @@ -40,31 +32,47 @@ class DiaryPersonCensorTest : BaseTest() { diary = diaryRepo ) - private fun mockPerson(id: Long, name: String) = mockk<ContactDiaryPerson>().apply { + private fun mockPerson( + id: Long, + name: String, + phone: String?, + mail: String? + ) = mockk<ContactDiaryPerson>().apply { every { personId } returns id every { fullName } returns name + every { phoneNumber } returns phone + every { emailAddress } returns mail } @Test fun `censoring replaces the logline message`() = runBlockingTest { every { diaryRepo.people } returns flowOf( - listOf(mockPerson(1, "Luka"), mockPerson(2, "Ralf"), mockPerson(3, "Matthias")) + listOf( + mockPerson(1, "Luka", phone = "+49 1234 7777", mail = "luka@sap.com"), + mockPerson(2, "Ralf", phone = null, mail = null), + mockPerson(3, "Matthias", phone = null, mail = "matthias@sap.com") + ) ) val instance = createInstance(this) val censorMe = LogLine( timestamp = 1, priority = 3, - message = "Ralf needs more coffee, but Matthias has had enough for today.", + message = + """ + Ralf requested more coffee from +49 1234 7777, + but Matthias thought he had enough has had enough for today. + A quick mail to luka@sap.com confirmed this. + """.trimIndent(), tag = "I'm a tag", throwable = null ) instance.checkLog(censorMe) shouldBe censorMe.copy( - message = "Person#2 needs more coffee, but Person#3 has had enough for today." - ) - - every { CWADebug.isDeviceForTestersBuild } returns true - instance.checkLog(censorMe) shouldBe censorMe.copy( - message = "Ralf needs more coffee, but Matthias has had enough for today." + message = + """ + Person#2/Name requested more coffee from Person#1/PhoneNumber, + but Person#3/Name thought he had enough has had enough for today. + A quick mail to Person#1/EMail confirmed this. + """.trimIndent() ) } @@ -81,4 +89,63 @@ class DiaryPersonCensorTest : BaseTest() { ) instance.checkLog(notCensored) shouldBe null } + + @Test + fun `if message is the same, don't copy the log line`() = runBlockingTest { + every { diaryRepo.people } returns flowOf( + listOf( + mockPerson(1, "Test", phone = null, mail = null), + mockPerson(2, "Test", phone = null, mail = null), + mockPerson(3, "Test", phone = null, mail = null) + ) + ) + val instance = createInstance(this) + val logLine = LogLine( + timestamp = 1, + priority = 3, + message = "Lorem ipsum", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(logLine) shouldBe null + } + + // EXPOSUREAPP-5670 / EXPOSUREAPP-5691 + @Test + fun `replacement doesn't cause recursion`() { + every { diaryRepo.people } returns flowOf( + listOf( + mockPerson(1, "Test", phone = "", mail = ""), + mockPerson(2, "Test", phone = "", mail = ""), + mockPerson(3, "Test", phone = "", mail = "") + ) + ) + + val logLine = LogLine( + timestamp = 1, + priority = 3, + message = "Lorem ipsum", + tag = "I'm a tag", + throwable = null + ) + + var isFinished = false + + thread { + sleep(500) + if (isFinished) return@thread + Runtime.getRuntime().exit(1) + } + + runBlocking { + val instance = createInstance(this) + + val processedLine = try { + instance.checkLog(logLine) + } finally { + isFinished = true + } + processedLine shouldBe null + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..cb9405722fde29918cb4d4a03fff8413fd518bea --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt @@ -0,0 +1,158 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import kotlin.concurrent.thread + +class DiaryVisitCensorTest : BaseTest() { + + @MockK lateinit var diaryRepo: ContactDiaryRepository + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + } + + private fun createInstance(scope: CoroutineScope) = DiaryVisitCensor( + debugScope = scope, + diary = diaryRepo + ) + + private fun mockVisit( + _id: Long, + _circumstances: String + ) = mockk<ContactDiaryLocationVisit>().apply { + every { id } returns _id + every { circumstances } returns _circumstances + } + + @Test + fun `censoring replaces the logline message`() = runBlockingTest { + every { diaryRepo.locationVisits } returns flowOf( + listOf( + mockVisit(1, _circumstances = "Döner that was too spicy"), + mockVisit(2, _circumstances = "beard shaved without mask"), + mockVisit(3, _circumstances = "out of toiletpaper") + ) + ) + val instance = createInstance(this) + val censorMe = LogLine( + timestamp = 1, + priority = 3, + message = + """ + After having a Döner that was too spicy, + I got my beard shaved without mask, + only to find out the supermarket was out of toiletpaper. + """.trimIndent(), + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(censorMe) shouldBe censorMe.copy( + message = + """ + After having a Visit#1/Circumstances, + I got my Visit#2/Circumstances, + only to find out the supermarket was Visit#3/Circumstances. + """.trimIndent() + ) + } + + @Test + fun `censoring returns null if all circumstances are blank`() = runBlockingTest { + every { diaryRepo.locationVisits } returns flowOf(listOf(mockVisit(1, _circumstances = ""))) + val instance = createInstance(this) + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "So many places to visit, but no place like home!", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } + + @Test + fun `censoring returns null if there are no visits no match`() = runBlockingTest { + every { diaryRepo.locationVisits } returns flowOf(emptyList()) + val instance = createInstance(this) + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "So many places to visit, but no place like home!", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } + + @Test + fun `censoring returns null if the message didn't change`() = runBlockingTest { + every { diaryRepo.locationVisits } returns flowOf( + listOf( + mockVisit(1, _circumstances = "Coffee"), + mockVisit(2, _circumstances = "fuels the world."), + ) + ) + val instance = createInstance(this) + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "Wakey wakey, eggs and bakey.", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } + + // EXPOSUREAPP-5670 / EXPOSUREAPP-5691 + @Test + fun `replacement doesn't cause recursion`() { + every { diaryRepo.locationVisits } returns flowOf( + listOf( + mockVisit(1, _circumstances = "Coffee"), + mockVisit(2, _circumstances = "fuels the world."), + ) + ) + + val logLine = LogLine( + timestamp = 1, + priority = 3, + message = "Lorem ipsum", + tag = "I'm a tag", + throwable = null + ) + + var isFinished = false + + thread { + Thread.sleep(500) + if (isFinished) return@thread + Runtime.getRuntime().exit(1) + } + + runBlocking { + val instance = createInstance(this) + + val processedLine = try { + instance.checkLog(logLine) + } finally { + isFinished = true + } + processedLine shouldBe null + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensorTest.kt index 617bad4cab1ae22191aafd4b5261b98c8f19ab8b..62bf33f114bc5d0051b5d9fa0fafa92264c01417 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensorTest.kt @@ -48,7 +48,7 @@ class QRCodeCensorTest : BaseTest() { every { CWADebug.isDeviceForTestersBuild } returns true instance.checkLog(censored) shouldBe censored.copy( - message = "I'm a shy qrcode: 63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f" + message = "I'm a shy qrcode: ########-e0de-4bd4-90c1-17c2bb683a2f" ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt index 08978e545feb1e462f8e81f139c65cc52b7e2394..284f8398badbfc1427c82fbfdaebc1bd6b488bc7 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt @@ -1,22 +1,27 @@ package de.rki.coronawarnapp.bugreporting.censors import de.rki.coronawarnapp.bugreporting.debuglog.LogLine -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.CWADebug import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.mockkObject import io.mockk.verify import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference class RegistrationTokenCensorTest : BaseTest() { + @MockK lateinit var submissionSettings: SubmissionSettings private val testToken = "63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f" + private val regtokenPreference = mockFlowPreference<String?>(testToken) + @BeforeEach fun setup() { MockKAnnotations.init(this) @@ -24,11 +29,12 @@ class RegistrationTokenCensorTest : BaseTest() { mockkObject(CWADebug) every { CWADebug.isDeviceForTestersBuild } returns false - mockkObject(LocalData) - every { LocalData.registrationToken() } returns testToken + every { submissionSettings.registrationToken } returns regtokenPreference } - private fun createInstance() = RegistrationTokenCensor() + private fun createInstance() = RegistrationTokenCensor( + submissionSettings = submissionSettings + ) @Test fun `censoring replaces the logline message`() = runBlockingTest { @@ -46,15 +52,15 @@ class RegistrationTokenCensorTest : BaseTest() { every { CWADebug.isDeviceForTestersBuild } returns true instance.checkLog(filterMe) shouldBe filterMe.copy( - message = "I'm a shy registration token: 63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f" + message = "I'm a shy registration token: ########-e0de-4bd4-90c1-17c2bb683a2f" ) - verify { LocalData.registrationToken() } + verify { regtokenPreference.value } } @Test fun `censoring returns null if there is no token`() = runBlockingTest { - every { LocalData.registrationToken() } returns null + every { submissionSettings.registrationToken } returns mockFlowPreference(null) val instance = createInstance() val filterMeNot = LogLine( timestamp = 1, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt index 61dbd80eafbcd892ac79d14ba4b32cd4f0eb0b6a..ace9d7c0a8bf6eacb9e3f12d265f0f0a157d593a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt @@ -49,7 +49,7 @@ class DebugLoggerTest : BaseIOTest() { every { application.cacheDir } returns cacheDir every { component.inject(any<DebugLogger>()) } answers { val logger = arg<DebugLogger>(0) - logger.bugCensors = Lazy { listOf(registrationTokenCensor) } + logger.bugCensors = Lazy { setOf(registrationTokenCensor) } } coEvery { registrationTokenCensor.checkLog(any()) } returns null diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiTest.kt index 46c4a59e9e75e3aca26e6b4b81030744f78e27e1..d4f34fa8717d49a34b76624ed0f4759ca9ab6809 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthApiTest.kt @@ -77,7 +77,7 @@ class LogUploadAuthApiTest : BaseTest() { api.authOTP(requestBody = elsPayload) webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply { - path shouldBe "/version/v1/android/log" + path shouldBe "/version/v1/android/els" body.readByteArray() shouldBe elsPayload.toByteArray() } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt index b2a8b3c9fa39ca76780f3ce666666641dd33f53b..d04943c8dd24e4bde7879594aef7b5f87bed8c2a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt @@ -6,6 +6,8 @@ import de.rki.coronawarnapp.appconfig.LogUploadConfig import de.rki.coronawarnapp.appconfig.SafetyNetRequirements import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid +import de.rki.coronawarnapp.util.CWADebug +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -13,6 +15,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just +import io.mockk.mockkObject import io.mockk.slot import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest @@ -46,6 +49,9 @@ class LogUploadAuthorizerTest : BaseIOTest() { fun setup() { MockKAnnotations.init(this) + mockkObject(CWADebug) + every { CWADebug.isDeviceForTestersBuild } returns true + every { configData.logUpload } returns logUploadConfig every { logUploadConfig.safetyNetRequirements } returns safetyNetRequirements @@ -82,4 +88,16 @@ class LogUploadAuthorizerTest : BaseIOTest() { attestationRequestSlot.captured.configData shouldBe configData attestationRequestSlot.captured.checkDeviceTime shouldBe false } + + @Test + fun `upload is not possible on prod builds`() = runBlockingTest { + every { CWADebug.isDeviceForTestersBuild } returns false + + val expectedOtp = UUID.fromString("15cff19f-af26-41bc-94f2-c1a65075e894") + val instance = createInstance() + + shouldThrow<UnsupportedOperationException> { + instance.getAuthorizedOTP(otp = expectedOtp) + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt index a1b9812796d018fa5e09f415571e7fc4e87f6741..6cc0f482d8e03e279efd2d5565a51fc560f6477f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt @@ -16,7 +16,7 @@ import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaDataRequestAndroid import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.util.TimeStamper import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations @@ -27,7 +27,6 @@ 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.slot import io.mockk.spyk import kotlinx.coroutines.delay @@ -50,6 +49,7 @@ class AnalyticsTest : BaseTest() { @MockK lateinit var exposureRiskMetadataDonor: ExposureRiskMetadataDonor @MockK lateinit var lastAnalyticsSubmissionLogger: LastAnalyticsSubmissionLogger @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var onboardingSettings: OnboardingSettings private val baseTime: Instant = Instant.ofEpochMilli(0) @@ -57,8 +57,6 @@ class AnalyticsTest : BaseTest() { fun setup() { MockKAnnotations.init(this) - mockkObject(LocalData) - coEvery { appConfigProvider.getAppConfig() } returns configData every { configData.analytics } returns analyticsConfig @@ -73,7 +71,7 @@ class AnalyticsTest : BaseTest() { val twoDaysAgo = baseTime.minus(Days.TWO.toStandardDuration()) every { settings.lastSubmittedTimestamp } returns mockFlowPreference(twoDaysAgo) - every { LocalData.onboardingCompletedTimestamp() } returns twoDaysAgo.millis + every { onboardingSettings.onboardingCompletedTimestamp } returns twoDaysAgo every { analyticsConfig.safetyNetRequirements } returns SafetyNetRequirementsContainer() @@ -88,7 +86,8 @@ class AnalyticsTest : BaseTest() { donorModules = modules, settings = settings, logger = lastAnalyticsSubmissionLogger, - timeStamper = timeStamper + timeStamper = timeStamper, + onboardingSettings = onboardingSettings ) ) @@ -196,7 +195,7 @@ class AnalyticsTest : BaseTest() { @Test fun `abort due to time since onboarding`() { - every { LocalData.onboardingCompletedTimestamp() } returns baseTime.millis + every { onboardingSettings.onboardingCompletedTimestamp } returns baseTime val analytics = createInstance() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepositoryTest.kt index 136eaae38cf3cd1b3a7f1955e16bc2d6964e11f4..bbb25be9800a339a9614195855606218a98c33a5 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepositoryTest.kt @@ -26,7 +26,8 @@ class AnalyticsKeySubmissionRepositoryTest : BaseTest() { } private fun createInstance() = AnalyticsKeySubmissionRepository( - storage, riskLevelSettings + storage, + riskLevelSettings ) @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt index 193b4bf5f13c24bcc41501e8bc059c19d28f484e..f128a602166778abbbcae049ec4a8e24a0fefd15 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDonorTest.kt @@ -7,7 +7,7 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettin import de.rki.coronawarnapp.risk.RiskLevelSettings import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.formatter.TestResult import io.kotest.matchers.shouldBe @@ -18,42 +18,43 @@ 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 io.mockk.verify import kotlinx.coroutines.test.runBlockingTest +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 - import testhelpers.preferences.mockFlowPreference -import java.util.concurrent.TimeUnit class TestResultDonorTest : BaseTest() { @MockK lateinit var testResultDonorSettings: TestResultDonorSettings @MockK lateinit var riskLevelSettings: RiskLevelSettings @MockK lateinit var riskLevelStorage: RiskLevelStorage @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var submissionSettings: SubmissionSettings private lateinit var testResultDonor: TestResultDonor + private val baseTime = Instant.ofEpochMilli(101010101) + @BeforeEach fun setUp() { MockKAnnotations.init(this, true) - mockkObject(LocalData) - every { timeStamper.nowUTC } returns Instant.now() - every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp } returns Instant.now() + every { timeStamper.nowUTC } returns baseTime + every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp } returns baseTime every { testResultDonorSettings.riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW) - every { LocalData.initialTestResultReceivedTimestamp() } returns System.currentTimeMillis() + every { submissionSettings.initialTestResultReceivedAt } returns baseTime testResultDonor = TestResultDonor( testResultDonorSettings, riskLevelSettings, riskLevelStorage, timeStamper, + submissionSettings ) } @@ -71,7 +72,7 @@ class TestResultDonorTest : BaseTest() { @Test fun `No donation when timestamp at registration is missing`() = runBlockingTest { every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true) - every { LocalData.initialTestResultReceivedTimestamp() } returns null + every { submissionSettings.initialTestResultReceivedAt } returns null testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution } @@ -105,16 +106,16 @@ class TestResultDonorTest : BaseTest() { every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true) every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.PENDING) - val timeDayBefore = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1) - every { LocalData.initialTestResultReceivedTimestamp() } returns timeDayBefore - every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp } returns Instant.ofEpochMilli(timeDayBefore) + val timeDayBefore = baseTime.minus(Duration.standardDays(1)) + every { submissionSettings.initialTestResultReceivedAt } returns timeDayBefore + every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp } returns timeDayBefore val donation = testResultDonor.beginDonation(TestRequest) donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>() with(donation.testResultMetadata) { riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW testResult shouldBe PpaData.PPATestResult.TEST_RESULT_PENDING - hoursSinceTestRegistration shouldBe 23 + hoursSinceTestRegistration shouldBe 24 hoursSinceHighRiskWarningAtTestRegistration shouldBe -1 daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 0 } @@ -126,7 +127,7 @@ class TestResultDonorTest : BaseTest() { runBlockingTest { every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true) every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE) - every { testResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(Instant.now()) + every { testResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime) val donation = testResultDonor.beginDonation(TestRequest) donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>() @@ -145,7 +146,7 @@ class TestResultDonorTest : BaseTest() { runBlockingTest { every { testResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true) every { testResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE) - every { testResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(Instant.now()) + every { testResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime) val donation = testResultDonor.beginDonation(TestRequest) donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt index 6e4b58f4d948b2a8937bf192edfa96af3c03e410..3201a04a7c24b83bb2c573c35c9982a59ac3c585 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt @@ -8,7 +8,7 @@ import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeStamper import io.mockk.MockKAnnotations import io.mockk.Runs @@ -46,6 +46,7 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() { @MockK lateinit var newKey1: CachedKey @MockK lateinit var latestTrackedDetection: TrackedExposureDetection + @MockK lateinit var submissionSettings: SubmissionSettings @BeforeEach fun setup() { @@ -53,8 +54,7 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() { mockkObject(BuildConfigWrap) every { BuildConfigWrap.VERSION_CODE } returns 1080005 - mockkObject(LocalData) - every { LocalData.isAllowedToSubmitDiagnosisKeys() } returns false + every { submissionSettings.isAllowedToSubmitKeys } returns false availableKey1.apply { every { path } returns File("availableKey1") @@ -101,7 +101,8 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() { appConfigProvider = appConfigProvider, keyPackageSyncTool = keyPackageSyncTool, timeStamper = timeStamper, - settings = downloadSettings + settings = downloadSettings, + submissionSettings = submissionSettings ) @Test @@ -229,7 +230,7 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() { @Test fun `we do not submit keys if user got positive test results`() = runBlockingTest { - every { LocalData.isAllowedToSubmitDiagnosisKeys() } returns true + every { submissionSettings.isAllowedToSubmitKeys } returns true createInstance().run(DownloadDiagnosisKeysTask.Arguments()) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt index 1e15c104b00d5c4d273af764f67e0b857bc9e9f0..aa8bab6568f0480baf3d800c487bcc9eacae1beb 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt @@ -2,7 +2,8 @@ package de.rki.coronawarnapp.main import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.environment.EnvironmentSetup -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.playbook.BackgroundNoise +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.ui.main.MainActivityViewModel import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.device.BackgroundModeStatus @@ -25,15 +26,16 @@ class MainActivityViewModelTest : BaseTest() { @MockK lateinit var environmentSetup: EnvironmentSetup @MockK lateinit var backgroundModeStatus: BackgroundModeStatus @MockK lateinit var diarySettings: ContactDiarySettings + @MockK lateinit var backgroundNoise: BackgroundNoise + @MockK lateinit var onboardingSettings: OnboardingSettings @BeforeEach fun setup() { MockKAnnotations.init(this) - mockkObject(LocalData) mockkObject(CWADebug) - every { LocalData.isBackgroundCheckDone() } returns true + every { onboardingSettings.isOnboarded } returns true every { environmentSetup.currentEnvironment } returns EnvironmentSetup.Type.WRU } @@ -41,7 +43,9 @@ class MainActivityViewModelTest : BaseTest() { dispatcherProvider = TestDispatcherProvider(), environmentSetup = environmentSetup, backgroundModeStatus = backgroundModeStatus, - contactDiarySettings = diarySettings + contactDiarySettings = diarySettings, + backgroundNoise = backgroundNoise, + onboardingSettings = onboardingSettings ) @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt index dbbcda67a7ea6f040e0f790cff68f8b5a95a138e..fe35faf99c0c09ab909c1ce2a86c2edbd7a928b6 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt @@ -7,8 +7,8 @@ import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.statistics.source.StatisticsProvider -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.TracingRepository +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider @@ -64,6 +64,7 @@ class HomeFragmentViewModelTest : BaseTest() { @MockK lateinit var statisticsProvider: StatisticsProvider @MockK lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler @MockK lateinit var appShortcutsHelper: AppShortcutsHelper + @MockK lateinit var tracingSettings: TracingSettings @BeforeEach fun setup() { @@ -95,7 +96,8 @@ class HomeFragmentViewModelTest : BaseTest() { appConfigProvider = appConfigProvider, statisticsProvider = statisticsProvider, deadmanNotificationScheduler = deadmanNotificationScheduler, - appShortcutsHelper = appShortcutsHelper + appShortcutsHelper = appShortcutsHelper, + tracingSettings = tracingSettings ) @Test @@ -176,9 +178,7 @@ class HomeFragmentViewModelTest : BaseTest() { @Test fun `test correct order of displaying delta onboarding, release notes and popups`() { - - mockkObject(LocalData) - every { LocalData.isInteroperabilityShownAtLeastOnce } returns false andThen true + every { cwaSettings.wasInteroperabilityShownAtLeastOnce } returns false andThen true mockkObject(BuildConfigWrap) every { BuildConfigWrap.VERSION_CODE } returns 1120004 diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt index de30f6d1da854d5d9ae23031569e496be0f4f31f..68c76f457bfbd3ea795cc687ba1ea51a02e31a62 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.nearby import android.app.Activity import com.google.android.gms.common.api.Status -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings import io.kotest.matchers.shouldBe import io.mockk.Called import io.mockk.MockKAnnotations @@ -13,7 +13,6 @@ 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.slot import io.mockk.verify import kotlinx.coroutines.CoroutineScope @@ -25,6 +24,7 @@ import testhelpers.BaseTest class TracingPermissionHelperTest : BaseTest() { @MockK lateinit var enfClient: ENFClient + @MockK lateinit var tracingSettings: TracingSettings @BeforeEach fun setup() { @@ -33,14 +33,14 @@ class TracingPermissionHelperTest : BaseTest() { coEvery { enfClient.isTracingEnabled } returns flowOf(false) coEvery { enfClient.setTracing(any(), any(), any(), any()) } just Runs - mockkObject(LocalData) - every { LocalData.initialTracingActivationTimestamp() } returns 123L + every { tracingSettings.isConsentGiven } returns true } fun createInstance(scope: CoroutineScope, callback: TracingPermissionHelper.Callback) = TracingPermissionHelper( callback = callback, scope = scope, - enfClient = enfClient + enfClient = enfClient, + tracingSettings = tracingSettings ) @Test @@ -61,7 +61,7 @@ class TracingPermissionHelperTest : BaseTest() { @Test fun `if consent is missing then we continue after it was given`() = runBlockingTest { - every { LocalData.initialTracingActivationTimestamp() } returns null + every { tracingSettings.isConsentGiven } returns false val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true) val consentCallbackSlot = slot<(Boolean) -> Unit>() @@ -85,7 +85,7 @@ class TracingPermissionHelperTest : BaseTest() { @Test fun `if consent was declined then we do nothing`() = runBlockingTest { - every { LocalData.initialTracingActivationTimestamp() } returns null + every { tracingSettings.isConsentGiven } returns false val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true) val consentCallbackSlot = slot<(Boolean) -> Unit>() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt index a72581ca824c0513f308f8c3833727cb91d604b3..a9a402b39d5b9c41d76cc54c30b46daea87d7e18 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.nearby.modules.tracing import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import de.rki.coronawarnapp.storage.TracingSettings import io.kotest.matchers.shouldBe import io.mockk.Called import io.mockk.MockKAnnotations @@ -22,6 +23,7 @@ import testhelpers.gms.MockGMSTask class DefaultTracingStatusTest : BaseTest() { @MockK lateinit var client: ExposureNotificationClient + @MockK lateinit var tracingSettings: TracingSettings @BeforeEach fun setup() { @@ -32,7 +34,8 @@ class DefaultTracingStatusTest : BaseTest() { private fun createInstance(scope: CoroutineScope): DefaultTracingStatus = DefaultTracingStatus( client = client, - scope = scope + scope = scope, + tracingSettings = tracingSettings ) @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt index 2a50eb7dbe82515e9f01165e50d1a1314fb99dcb..d2fc8112210d32d98c6c86005037f29adff4b63d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt @@ -6,7 +6,7 @@ import android.content.Context import androidx.navigation.NavDeepLinkBuilder import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.util.device.ForegroundState import de.rki.coronawarnapp.util.formatter.TestResult import io.kotest.matchers.shouldBe @@ -35,26 +35,27 @@ class TestResultAvailableNotificationServiceTest : BaseTest() { @MockK lateinit var navDeepLinkBuilderProvider: Provider<NavDeepLinkBuilder> @MockK lateinit var notificationManager: NotificationManager @MockK lateinit var notificationHelper: NotificationHelper + @MockK lateinit var cwaSettings: CWASettings @BeforeEach fun setUp() { MockKAnnotations.init(this) mockkObject(CoronaWarnApplication) - mockkObject(LocalData) every { CoronaWarnApplication.getAppContext() } returns context every { context.getSystemService(Context.NOTIFICATION_SERVICE) } returns notificationManager every { navDeepLinkBuilderProvider.get() } returns navDeepLinkBuilder every { navDeepLinkBuilder.createPendingIntent() } returns pendingIntent - every { LocalData.isNotificationsTestEnabled } returns true + every { cwaSettings.isNotificationsTestEnabled.value } returns true } fun createInstance() = TestResultAvailableNotificationService( context = context, foregroundState = foregroundState, navDeepLinkBuilderProvider = navDeepLinkBuilderProvider, - notificationHelper = notificationHelper + notificationHelper = notificationHelper, + cwaSettings = cwaSettings ) @Test @@ -111,7 +112,7 @@ class TestResultAvailableNotificationServiceTest : BaseTest() { @Test fun `test notification in background disabled`() = runBlockingTest { coEvery { foregroundState.isInForeground } returns flow { emit(false) } - every { LocalData.isNotificationsTestEnabled } returns false + every { cwaSettings.isNotificationsTestEnabled.value } returns false createInstance().apply { showTestResultAvailableNotification(TestResult.POSITIVE) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt index 9ec5698f0d5b0529d2aff62a4fea3b64333d9c6b..13fea4bb9f7b0a2bb24c3848256cfe2431e2ba14 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt @@ -10,7 +10,8 @@ import de.rki.coronawarnapp.risk.RiskState.INCREASED_RISK import de.rki.coronawarnapp.risk.RiskState.LOW_RISK import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.device.ForegroundState import io.kotest.matchers.shouldBe @@ -22,7 +23,6 @@ import io.mockk.coVerifySequence import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just -import io.mockk.mockkObject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest @@ -30,9 +30,9 @@ import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference class RiskLevelChangeDetectorTest : BaseTest() { - @MockK lateinit var context: Context @MockK lateinit var timeStamper: TimeStamper @MockK lateinit var riskLevelStorage: RiskLevelStorage @@ -41,15 +41,15 @@ class RiskLevelChangeDetectorTest : BaseTest() { @MockK lateinit var riskLevelSettings: RiskLevelSettings @MockK lateinit var notificationHelper: NotificationHelper @MockK lateinit var surveys: Surveys + @MockK lateinit var submissionSettings: SubmissionSettings + @MockK lateinit var tracingSettings: TracingSettings @BeforeEach fun setup() { MockKAnnotations.init(this) - mockkObject(LocalData) - - every { LocalData.isUserToBeNotifiedOfLoweredRiskLevel = any() } just Runs - every { LocalData.submissionWasSuccessful() } returns false + every { tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel } returns mockFlowPreference(false) + every { submissionSettings.isSubmissionSuccessful } returns false every { foregroundState.isInForeground } returns flowOf(true) every { notificationManagerCompat.areNotificationsEnabled() } returns true every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp = any() } just Runs @@ -78,7 +78,9 @@ class RiskLevelChangeDetectorTest : BaseTest() { foregroundState = foregroundState, riskLevelSettings = riskLevelSettings, notificationHelper = notificationHelper, - surveys = surveys + surveys = surveys, + submissionSettings = submissionSettings, + tracingSettings = tracingSettings ) @Test @@ -92,7 +94,6 @@ class RiskLevelChangeDetectorTest : BaseTest() { advanceUntilIdle() coVerifySequence { - LocalData wasNot Called notificationManagerCompat wasNot Called surveys wasNot Called } @@ -115,7 +116,6 @@ class RiskLevelChangeDetectorTest : BaseTest() { advanceUntilIdle() coVerifySequence { - LocalData wasNot Called notificationManagerCompat wasNot Called surveys wasNot Called } @@ -138,9 +138,8 @@ class RiskLevelChangeDetectorTest : BaseTest() { advanceUntilIdle() coVerifySequence { - LocalData.submissionWasSuccessful() + submissionSettings.isSubmissionSuccessful foregroundState.isInForeground - LocalData.isUserToBeNotifiedOfLoweredRiskLevel = any() surveys.resetSurvey(Surveys.Type.HIGH_RISK_ENCOUNTER) } } @@ -162,7 +161,7 @@ class RiskLevelChangeDetectorTest : BaseTest() { advanceUntilIdle() coVerifySequence { - LocalData.submissionWasSuccessful() + submissionSettings.isSubmissionSuccessful foregroundState.isInForeground surveys wasNot Called } @@ -186,7 +185,6 @@ class RiskLevelChangeDetectorTest : BaseTest() { advanceUntilIdle() coVerifySequence { - LocalData wasNot Called notificationManagerCompat wasNot Called surveys wasNot Called } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt index 62a8305271e50cc3dd45c4c8014fd28180ec137f..28cf7f0f7f8327649644c8b907bebe7033c87694 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt @@ -13,7 +13,7 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException import de.rki.coronawarnapp.util.TimeStamper @@ -39,7 +39,6 @@ import org.junit.jupiter.api.assertThrows import testhelpers.BaseTest class RiskLevelTaskTest : BaseTest() { - @MockK lateinit var riskLevels: RiskLevels @MockK lateinit var context: Context @MockK lateinit var enfClient: ENFClient @@ -50,6 +49,7 @@ class RiskLevelTaskTest : BaseTest() { @MockK lateinit var appConfigProvider: AppConfigProvider @MockK lateinit var riskLevelStorage: RiskLevelStorage @MockK lateinit var keyCacheRepository: KeyCacheRepository + @MockK lateinit var submissionSettings: SubmissionSettings @MockK lateinit var analyticsExposureWindowCollector: AnalyticsExposureWindowCollector private val arguments: Task.Arguments = object : Task.Arguments {} @@ -59,9 +59,8 @@ class RiskLevelTaskTest : BaseTest() { MockKAnnotations.init(this) mockkObject(TimeVariables) - mockkObject(LocalData) - every { LocalData.isAllowedToSubmitDiagnosisKeys() } returns false + every { submissionSettings.isAllowedToSubmitKeys } returns false every { configData.isDeviceTimeCorrect } returns true every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(true) coEvery { appConfigProvider.getAppConfig() } returns configData @@ -95,6 +94,7 @@ class RiskLevelTaskTest : BaseTest() { appConfigProvider = appConfigProvider, riskLevelStorage = riskLevelStorage, keyCacheRepository = keyCacheRepository, + submissionSettings = submissionSettings, analyticsExposureWindowCollector = analyticsExposureWindowCollector ) @@ -215,7 +215,7 @@ class RiskLevelTaskTest : BaseTest() { coEvery { keyCacheRepository.getAllCachedKeys() } returns listOf(cachedKey) every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(false) every { timeStamper.nowUTC } returns now - every { LocalData.isAllowedToSubmitDiagnosisKeys() } returns true + every { submissionSettings.isAllowedToSubmitKeys } returns true createTask().run(arguments) shouldBe RiskLevelTaskResult( calculatedAt = now, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt index 31d2aaa7c6d0775833f853d5d30f53f923de3dfe..31f62170a700594e6b68beb6e7f3039da3154d4f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt @@ -27,6 +27,7 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockkObject +import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.emptyFlow @@ -53,6 +54,7 @@ class SubmissionRepositoryTest : BaseTest() { @MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool @MockK lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector + @MockK lateinit var tracingSettings: TracingSettings private val guid = "123456-12345678-1234-4DA7-B166-B86D85475064" private val tan = "123456-12345678-1234-4DA7-B166-B86D85475064" @@ -60,6 +62,9 @@ class SubmissionRepositoryTest : BaseTest() { private val testResult = TestResult.PENDING private val registrationData = SubmissionService.RegistrationData(registrationToken, testResult) + private val registrationTokenPreference = mockFlowPreference<String?>(null) + private val resultReceivedTimeStamp = Instant.ofEpochMilli(101010101) + @BeforeEach fun setUp() { MockKAnnotations.init(this) @@ -69,14 +74,12 @@ class SubmissionRepositoryTest : BaseTest() { every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory every { appComponent.errorResetTool } returns encryptionErrorResetTool - mockkObject(BackgroundNoise.Companion) - every { BackgroundNoise.getInstance() } returns backgroundNoise every { backgroundNoise.scheduleDummyPattern() } just Runs - mockkObject(LocalData) - every { LocalData.registrationToken(any()) } just Runs - every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs - every { LocalData.initialTestResultReceivedTimestamp() } returns 1L + every { submissionSettings.registrationToken } returns registrationTokenPreference + + every { submissionSettings.devicePairingSuccessfulAt = any() } just Runs + every { submissionSettings.initialTestResultReceivedAt } returns resultReceivedTimeStamp every { submissionSettings.hasGivenConsent } returns mockFlowPreference(false) every { submissionSettings.hasViewedTestResult } returns mockFlowPreference(false) @@ -97,31 +100,41 @@ class SubmissionRepositoryTest : BaseTest() { timeStamper = timeStamper, tekHistoryStorage = tekHistoryStorage, deadmanNotificationScheduler = deadmanNotificationScheduler, - analyticsKeySubmissionCollector = analyticsKeySubmissionCollector + backgroundNoise = backgroundNoise, + analyticsKeySubmissionCollector = analyticsKeySubmissionCollector, + tracingSettings = tracingSettings ) @Test fun removeTestFromDeviceSucceeds() = runBlockingTest { val submissionRepository = createInstance(scope = this) - every { LocalData.initialPollingForTestResultTimeStamp(any()) } just Runs - every { LocalData.initialTestResultReceivedTimestamp(any()) } just Runs - every { LocalData.isAllowedToSubmitDiagnosisKeys(any()) } just Runs - every { LocalData.isTestResultAvailableNotificationSent(any()) } just Runs - every { LocalData.numberOfSuccessfulSubmissions(any()) } just Runs + val initialPollingForTestResultTimeStampSlot = slot<Long>() + val isTestResultAvailableNotificationSent = slot<Boolean>() + every { + tracingSettings.initialPollingForTestResultTimeStamp = capture(initialPollingForTestResultTimeStampSlot) + } answers {} + every { + tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent) + } answers {} + + every { submissionSettings.initialTestResultReceivedAt = any() } just Runs + every { submissionSettings.isAllowedToSubmitKeys = any() } just Runs + every { submissionSettings.isSubmissionSuccessful = any() } just Runs every { analyticsKeySubmissionCollector.reset() } just Runs submissionRepository.removeTestFromDevice() verify(exactly = 1) { - LocalData.registrationToken(null) - LocalData.devicePairingSuccessfulTimestamp(0L) - LocalData.initialPollingForTestResultTimeStamp(0L) - LocalData.initialTestResultReceivedTimestamp(0L) - LocalData.isAllowedToSubmitDiagnosisKeys(false) - LocalData.isTestResultAvailableNotificationSent(false) - LocalData.numberOfSuccessfulSubmissions(0) + registrationTokenPreference.update(any()) + submissionSettings.devicePairingSuccessfulAt = null + submissionSettings.initialTestResultReceivedAt = null + submissionSettings.isAllowedToSubmitKeys = false + submissionSettings.isSubmissionSuccessful = false } + + initialPollingForTestResultTimeStampSlot.captured shouldBe 0L + isTestResultAvailableNotificationSent.captured shouldBe false } @Test @@ -133,11 +146,13 @@ class SubmissionRepositoryTest : BaseTest() { submissionRepository.asyncRegisterDeviceViaGUID(guid) - verify { - LocalData.devicePairingSuccessfulTimestamp(any()) - LocalData.registrationToken(registrationToken) + registrationTokenPreference.value shouldBe registrationToken + submissionRepository.testResultReceivedDateFlow.first() shouldBe resultReceivedTimeStamp.toDate() + + verify(exactly = 1) { + registrationTokenPreference.update(any()) + submissionSettings.devicePairingSuccessfulAt = any() backgroundNoise.scheduleDummyPattern() - submissionRepository.updateTestResult(testResult) } } @@ -151,11 +166,13 @@ class SubmissionRepositoryTest : BaseTest() { submissionRepository.asyncRegisterDeviceViaTAN(tan) - coVerify { - LocalData.devicePairingSuccessfulTimestamp(any()) - LocalData.registrationToken(registrationToken) + registrationTokenPreference.value shouldBe registrationToken + submissionRepository.testResultReceivedDateFlow.first() shouldBe resultReceivedTimeStamp.toDate() + + verify(exactly = 1) { + registrationTokenPreference.update(any()) + submissionSettings.devicePairingSuccessfulAt = any() backgroundNoise.scheduleDummyPattern() - submissionRepository.updateTestResult(testResult) } } @@ -174,8 +191,10 @@ class SubmissionRepositoryTest : BaseTest() { @Test fun `ui state is SUBMITTED_FINAL when submission was done`() = runBlockingTest { - coEvery { LocalData.submissionWasSuccessful() } returns true + every { submissionSettings.isSubmissionSuccessful } returns true + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() submissionRepository.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL) @@ -183,9 +202,11 @@ class SubmissionRepositoryTest : BaseTest() { @Test fun `ui state is UNPAIRED when no token is present`() = runBlockingTest { - coEvery { LocalData.submissionWasSuccessful() } returns false - coEvery { LocalData.registrationToken() } returns null + every { submissionSettings.isSubmissionSuccessful } returns false + every { submissionSettings.registrationToken } returns mockFlowPreference(null) + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() submissionRepository.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED) @@ -193,10 +214,12 @@ class SubmissionRepositoryTest : BaseTest() { @Test fun `ui state is PAIRED_POSITIVE when allowed to submit`() = runBlockingTest { - coEvery { LocalData.submissionWasSuccessful() } returns false - coEvery { LocalData.registrationToken() } returns "token" - coEvery { LocalData.isAllowedToSubmitDiagnosisKeys() } returns true + every { submissionSettings.isSubmissionSuccessful } returns false + every { submissionSettings.registrationToken } returns mockFlowPreference("token") + coEvery { submissionSettings.isAllowedToSubmitKeys } returns true + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() submissionRepository.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE) @@ -204,40 +227,53 @@ class SubmissionRepositoryTest : BaseTest() { @Test fun `refresh when state is PAIRED_NO_RESULT`() = runBlockingTest { - coEvery { LocalData.submissionWasSuccessful() } returns false - coEvery { LocalData.registrationToken() } returns "token" - coEvery { LocalData.isAllowedToSubmitDiagnosisKeys() } returns false + every { submissionSettings.isSubmissionSuccessful } returns false + every { submissionSettings.registrationToken } returns mockFlowPreference("token") + coEvery { submissionSettings.isAllowedToSubmitKeys } returns false coEvery { submissionService.asyncRequestTestResult(any()) } returns TestResult.PENDING + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() submissionRepository.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT) + coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) } } @Test fun `refresh when state is UNPAIRED`() = runBlockingTest { - coEvery { LocalData.submissionWasSuccessful() } returns false - coEvery { LocalData.registrationToken() } returns null - coEvery { LocalData.isAllowedToSubmitDiagnosisKeys() } returns false + every { submissionSettings.isSubmissionSuccessful } returns false + every { submissionSettings.registrationToken } returns mockFlowPreference(null) + coEvery { submissionSettings.isAllowedToSubmitKeys } returns false coEvery { submissionService.asyncRequestTestResult(any()) } returns TestResult.PENDING + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() submissionRepository.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED) - coEvery { LocalData.registrationToken() } returns "token" + + every { submissionSettings.registrationToken } returns mockFlowPreference("token") + submissionRepository.refreshDeviceUIState() + coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) } } @Test fun `no refresh when state is SUBMITTED_FINAL`() = runBlockingTest { - coEvery { LocalData.submissionWasSuccessful() } returns true + every { submissionSettings.isSubmissionSuccessful } returns true + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() + submissionRepository.deviceUIStateFlow.first() shouldBe NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL) + submissionRepository.refreshDeviceUIState() + coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt index f5e67e1b5f43d10e8739f91da62534ef7b022aaa..f49adc5f87d8e25d5dacaf1dae27cd0ef3396290 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt @@ -9,7 +9,6 @@ import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.submission.auto.AutoSubmission @@ -63,6 +62,7 @@ class SubmissionTaskTest : BaseTest() { private lateinit var settingSymptomsPreference: FlowPreference<Symptoms?> + private val registrationToken: FlowPreference<String?> = mockFlowPreference("regtoken") private val settingHasGivenConsent: FlowPreference<Boolean> = mockFlowPreference(true) private val settingAutoSubmissionAttemptsCount: FlowPreference<Int> = mockFlowPreference(0) private val settingAutoSubmissionAttemptsLast: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH) @@ -73,9 +73,8 @@ class SubmissionTaskTest : BaseTest() { fun setup() { MockKAnnotations.init(this) - mockkObject(LocalData) - every { LocalData.registrationToken() } returns "regtoken" - every { LocalData.numberOfSuccessfulSubmissions(any()) } just Runs + every { submissionSettings.registrationToken } returns registrationToken + every { submissionSettings.isSubmissionSuccessful = any() } just Runs mockkObject(BackgroundWorkScheduler) every { BackgroundWorkScheduler.stopWorkScheduler() } just Runs @@ -133,11 +132,20 @@ class SubmissionTaskTest : BaseTest() { ) coVerifySequence { + submissionSettings.lastSubmissionUserActivityUTC settingLastUserActivityUTC.value + submissionSettings.hasGivenConsent settingHasGivenConsent.value - LocalData.registrationToken() + submissionSettings.autoSubmissionAttemptsCount + submissionSettings.autoSubmissionAttemptsLast + submissionSettings.autoSubmissionAttemptsCount + submissionSettings.autoSubmissionAttemptsLast + submissionSettings.registrationToken + + registrationToken.value tekHistoryStorage.tekData + submissionSettings.symptoms settingSymptomsPreference.value tekHistoryCalculations.transformToKeyHistoryInExternalFormat(listOf(tek), userSymptoms) @@ -156,12 +164,13 @@ class SubmissionTaskTest : BaseTest() { analyticsKeySubmissionCollector.reportSubmittedInBackground() tekHistoryStorage.clear() + submissionSettings.symptoms settingSymptomsPreference.update(match { it.invoke(mockk()) == null }) autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) BackgroundWorkScheduler.stopWorkScheduler() - LocalData.numberOfSuccessfulSubmissions(1) + submissionSettings.isSubmissionSuccessful = true BackgroundWorkScheduler.startWorkScheduler() shareTestResultNotificationService.cancelSharePositiveTestResultNotification() @@ -195,7 +204,7 @@ class SubmissionTaskTest : BaseTest() { coVerifySequence { settingHasGivenConsent.value - LocalData.registrationToken() + registrationToken.value tekHistoryStorage.tekData settingSymptomsPreference.value @@ -222,7 +231,7 @@ class SubmissionTaskTest : BaseTest() { @Test fun `task throws if no registration token is available`() = runBlockingTest { - every { LocalData.registrationToken() } returns null + every { submissionSettings.registrationToken } returns mockFlowPreference(null) val task = createTask() shouldThrow<NoRegistrationTokenSetException> { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt index 8fb93b6e4079ff9662f02ce560a6d10f707afc0e..3bc2c760b50fb3d259a4fde85c58fcd134002d1b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt @@ -32,6 +32,7 @@ internal class LowRiskTest { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 0, + daysSinceInstallation = 4, lastEncounterAt = null ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt index 73be55667a1886353dd13dfc6ba528a61bfe77db..7ca361ae4bfbf43bfd339e3262fadbd93f3c097c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.res.Resources import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.datadonation.survey.Surveys +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.risk.ProtoRiskLevel import de.rki.coronawarnapp.risk.RiskLevelTaskResult import de.rki.coronawarnapp.risk.result.AggregatedRiskResult @@ -39,6 +40,7 @@ class TracingDetailsItemProviderTest : BaseTest() { @MockK lateinit var tracingStatus: GeneralTracingStatus @MockK lateinit var riskLevelStorage: RiskLevelStorage + @MockK lateinit var installTimeProvider: InstallTimeProvider @MockK lateinit var surveys: Surveys @BeforeEach @@ -50,6 +52,7 @@ class TracingDetailsItemProviderTest : BaseTest() { private fun createInstance() = TracingDetailsItemProvider( tracingStatus = tracingStatus, riskLevelStorage = riskLevelStorage, + installTimeProvider = installTimeProvider, surveys = surveys ) @@ -57,10 +60,12 @@ class TracingDetailsItemProviderTest : BaseTest() { status: GeneralTracingStatus.Status, riskLevel: ProtoRiskLevel, matchedKeyCount: Int, + daysSinceInstallation: Long, availableSurveys: List<Surveys.Type> = emptyList() ) { every { tracingStatus.generalStatus } returns flowOf(status) every { aggregatedRiskResult.totalRiskLevel } returns riskLevel + every { installTimeProvider.daysSinceInstallation } returns daysSinceInstallation every { surveys.availableSurveys } returns flowOf(availableSurveys) if (riskLevel == ProtoRiskLevel.LOW) { @@ -86,6 +91,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 1 ) @@ -102,6 +108,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -118,6 +125,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -134,6 +142,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -152,6 +161,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -170,7 +180,8 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, - matchedKeyCount = 0 + daysSinceInstallation = 4, + matchedKeyCount = 0, ) val instance = createInstance() @@ -189,6 +200,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -208,6 +220,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.UNRECOGNIZED, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -227,6 +240,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_INACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -246,6 +260,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_INACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -268,6 +283,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.UNRECOGNIZED, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -290,6 +306,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -312,6 +329,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -335,6 +353,7 @@ class TracingDetailsItemProviderTest : BaseTest() { status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, matchedKeyCount = 0, + daysSinceInstallation = 4, availableSurveys = listOf(Surveys.Type.HIGH_RISK_ENCOUNTER) ) @@ -355,6 +374,7 @@ class TracingDetailsItemProviderTest : BaseTest() { status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, matchedKeyCount = 0, + daysSinceInstallation = 4, availableSurveys = emptyList() ) @@ -375,6 +395,7 @@ class TracingDetailsItemProviderTest : BaseTest() { status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, matchedKeyCount = 0, + daysSinceInstallation = 4, availableSurveys = listOf(Surveys.Type.HIGH_RISK_ENCOUNTER) ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt index 15decd484cd425591face7df7a9faf9995bed521..dee497b2ccdd3f88fa5a838b3480616fd2940ce5 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/homecards/SubmissionStateProviderTest.kt @@ -1,8 +1,8 @@ package de.rki.coronawarnapp.tracing.ui.homecards import android.content.Context -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.ui.homecards.NoTest import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider import de.rki.coronawarnapp.util.DeviceUIState @@ -11,7 +11,6 @@ import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.mockkObject import io.mockk.verifySequence import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow @@ -22,6 +21,7 @@ import org.junit.jupiter.api.extension.ExtendWith import testhelpers.BaseTest import testhelpers.extensions.CoroutinesTestExtension import testhelpers.extensions.InstantExecutorExtension +import testhelpers.preferences.mockFlowPreference import java.util.Date @ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class) @@ -29,21 +29,24 @@ class SubmissionStateProviderTest : BaseTest() { @MockK lateinit var context: Context @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var submissionSettings: SubmissionSettings @BeforeEach fun setup() { MockKAnnotations.init(this) - mockkObject(LocalData) every { submissionRepository.hasViewedTestResult } returns flow { emit(true) } every { submissionRepository.deviceUIStateFlow } returns flow { emit(NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>(DeviceUIState.PAIRED_POSITIVE)) } every { submissionRepository.testResultReceivedDateFlow } returns flow { emit(Date()) } - every { LocalData.registrationToken() } returns null + every { submissionSettings.registrationToken } returns mockFlowPreference(null) } - private fun createInstance() = SubmissionStateProvider(submissionRepository) + private fun createInstance() = SubmissionStateProvider( + submissionRepository = submissionRepository, + submissionSettings = submissionSettings + ) @Test fun `state determination, unregistered test`() = runBlockingTest { @@ -54,7 +57,7 @@ class SubmissionStateProviderTest : BaseTest() { submissionRepository.deviceUIStateFlow submissionRepository.hasViewedTestResult submissionRepository.testResultReceivedDateFlow - LocalData.registrationToken() + submissionSettings.registrationToken } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt index 45a8b3e1ef2b03347324d72f0a8c4ad9ca32b37d..52a0c791b7ad45b9a47eaa8ad202002480ee68bd 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt @@ -4,9 +4,12 @@ import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.ui.Country import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.just import io.mockk.verify import kotlinx.coroutines.flow.flowOf import org.junit.jupiter.api.BeforeEach @@ -25,6 +28,7 @@ class InteroperabilityConfigurationFragmentViewModelTest { fun setupFreshViewModel() { MockKAnnotations.init(this) + coEvery { interoperabilityRepository.refreshCountries() } just Runs every { interoperabilityRepository.countryList } returns flowOf(Country.values().toList()) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt index 14df18a27d310a3266724880134bfb279cee3f18..07a76044a01c0f3840389387debbd865b0bc9202 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.ui.launcher import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.update.UpdateChecker import io.kotest.matchers.shouldBe import io.kotest.matchers.types.instanceOf @@ -26,13 +26,13 @@ class LauncherActivityViewModelTest : BaseTest() { @MockK lateinit var updateChecker: UpdateChecker @MockK lateinit var cwaSettings: CWASettings + @MockK lateinit var onboardingSettings: OnboardingSettings @BeforeEach fun setupFreshViewModel() { MockKAnnotations.init(this) - mockkObject(LocalData) - every { LocalData.isOnboarded() } returns false + every { onboardingSettings.isOnboarded } returns false mockkObject(BuildConfigWrap) every { BuildConfigWrap.VERSION_CODE } returns 10L @@ -43,7 +43,8 @@ class LauncherActivityViewModelTest : BaseTest() { private fun createViewModel() = LauncherActivityViewModel( updateChecker = updateChecker, dispatcherProvider = TestDispatcherProvider(), - cwaSettings = cwaSettings + cwaSettings = cwaSettings, + onboardingSettings = onboardingSettings ) @Test @@ -67,8 +68,8 @@ class LauncherActivityViewModelTest : BaseTest() { @Test fun `onboarding finished`() { - every { LocalData.isOnboarded() } returns true - every { LocalData.isInteroperabilityShownAtLeastOnce } returns true + every { onboardingSettings.isOnboarded } returns true + every { cwaSettings.wasInteroperabilityShownAtLeastOnce } returns true every { cwaSettings.lastChangelogVersion } returns mockFlowPreference(10L) val vm = createViewModel() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/settings/notification/NotificationsSettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/settings/notification/NotificationsSettingsTest.kt index 3bd5a7811a0ab9771756341527dd20c07f0f8b6f..7f0c43618e781d12ee792f90b6c2d6d6c5390b47 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/settings/notification/NotificationsSettingsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/settings/notification/NotificationsSettingsTest.kt @@ -1,18 +1,14 @@ package de.rki.coronawarnapp.ui.settings.notification import androidx.core.app.NotificationManagerCompat -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.ui.settings.notifications.NotificationSettings import de.rki.coronawarnapp.util.device.ForegroundState import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations -import io.mockk.Runs import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.mockkObject -import io.mockk.verify import io.mockk.verifySequence import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow @@ -20,32 +16,28 @@ import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference class NotificationsSettingsTest : BaseTest() { @MockK lateinit var foregroundState: ForegroundState @MockK lateinit var notificationManagerCompat: NotificationManagerCompat + @MockK lateinit var cwaSettings: CWASettings @BeforeEach fun setup() { MockKAnnotations.init(this) - mockkObject(LocalData) - - every { LocalData.isNotificationsRiskEnabledFlow } returns flow { emit(true) } - every { LocalData.isNotificationsRiskEnabled = any() } just Runs - every { LocalData.isNotificationsRiskEnabled } returns true - - every { LocalData.isNotificationsTestEnabledFlow } returns flow { emit(true) } - every { LocalData.isNotificationsTestEnabled = any() } just Runs - every { LocalData.isNotificationsTestEnabled } returns true + every { cwaSettings.isNotificationsRiskEnabled } returns mockFlowPreference(true) + every { cwaSettings.isNotificationsTestEnabled } returns mockFlowPreference(true) every { notificationManagerCompat.areNotificationsEnabled() } returns true coEvery { foregroundState.isInForeground } returns flow { emit(true) } } private fun createInstance() = NotificationSettings( foregroundState = foregroundState, - notificationManagerCompat = notificationManagerCompat + notificationManagerCompat = notificationManagerCompat, + cwaSettings = cwaSettings ) @Test @@ -72,16 +64,4 @@ class NotificationsSettingsTest : BaseTest() { isNotificationsTestEnabled.first() shouldBe true } } - - @Test - fun toggleNotificationsRiskEnabled() { - createInstance().toggleNotificationsRiskEnabled() - verify { LocalData.isNotificationsRiskEnabled = false } - } - - @Test - fun toggleNotificationsTestEnabled() { - createInstance().toggleNotificationsTestEnabled() - verify { LocalData.isNotificationsTestEnabled = false } - } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt index b3d1b9bae194730731f92604c6d2a97b3d22bfc7..87265f9ce82758aecc07abf52db12f7563382062 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.ui.submission.qrcode.scan import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.TestResultDataCollector -import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.service.submission.QRScanResult import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ScanStatus @@ -14,7 +13,6 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import io.mockk.mockkObject import org.junit.Assert import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -25,16 +23,12 @@ import testhelpers.extensions.InstantExecutorExtension @ExtendWith(InstantExecutorExtension::class) class SubmissionQRCodeScanViewModelTest : BaseTest() { - @MockK lateinit var backgroundNoise: BackgroundNoise @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var testResultDataCollector: TestResultDataCollector @BeforeEach fun setUp() { MockKAnnotations.init(this) - - mockkObject(BackgroundNoise.Companion) - every { BackgroundNoise.getInstance() } returns backgroundNoise } private fun createViewModel() = SubmissionQRCodeScanViewModel( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/CWADebugTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/CWADebugTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c29454f47f6d852cee30ccc20efc6dd262b52d6e --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/CWADebugTest.kt @@ -0,0 +1,77 @@ +package de.rki.coronawarnapp.util + +import android.app.Application +import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger +import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.util.di.ApplicationComponent +import io.kotest.matchers.shouldBe +import io.mockk.Called +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import io.mockk.verifySequence +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import java.io.File + +class CWADebugTest : BaseTest() { + + @MockK lateinit var application: Application + @MockK lateinit var appComponent: ApplicationComponent + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { application.cacheDir } returns File("cache") + + mockkObject(BuildConfigWrap) + every { BuildConfigWrap.FLAVOR } returns "device" + } + + @Test + fun `flavor check`() { + CWADebug.isDeviceForTestersBuild shouldBe false + + every { BuildConfigWrap.FLAVOR } returns "deviceForTesters" + CWADebug.isDeviceForTestersBuild shouldBe true + CWADebug.buildFlavor shouldBe CWADebug.BuildFlavor.DEVICE_FOR_TESTERS + + every { BuildConfigWrap.FLAVOR } returns "device" + CWADebug.buildFlavor shouldBe CWADebug.BuildFlavor.DEVICE + CWADebug.isDeviceForTestersBuild shouldBe false + } + + @Test + fun `logging is only initialized on tester builds`() { + val debugLogger: DebugLogger = mockk() + CWADebug.debugLoggerFactory = { debugLogger } + CWADebug.init(application) + CWADebug.initAfterInjection(appComponent) + verify { debugLogger wasNot Called } + } + + @Test + fun `logging is initialized on deviceForTester builds`() { + every { BuildConfigWrap.FLAVOR } returns "deviceForTesters" + + val debugLogger = mockk<DebugLogger>().apply { + every { init() } just Runs + every { setInjectionIsReady(appComponent) } just Runs + } + + CWADebug.debugLoggerFactory = { debugLogger } + CWADebug.init(application) + CWADebug.initAfterInjection(appComponent) + verifySequence { + debugLogger.init() + debugLogger.setInjectionIsReady(appComponent) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptionResetToolTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptionResetToolTest.kt deleted file mode 100644 index 574b1a009ce5b50486fd72da511a89e6488463fc..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptionResetToolTest.kt +++ /dev/null @@ -1,339 +0,0 @@ -package de.rki.coronawarnapp.util.security - -import android.content.Context -import androidx.core.content.edit -import de.rki.coronawarnapp.exception.CwaSecurityException -import de.rki.coronawarnapp.util.TimeStamper -import io.kotest.matchers.shouldBe -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -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.BaseIOTest -import testhelpers.preferences.MockSharedPreferences -import java.io.File -import java.io.IOException -import java.security.GeneralSecurityException -import java.security.KeyException -import java.security.KeyStoreException - -class EncryptionResetToolTest : BaseIOTest() { - - @MockK lateinit var context: Context - @MockK lateinit var timeStamper: TimeStamper - private lateinit var mockPreferences: MockSharedPreferences - - private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!) - private val privateFilesDir = File(testDir, "files") - private val encryptedPrefsFile = File(testDir, "shared_prefs/shared_preferences_cwa.xml") - - @BeforeEach - fun setup() { - MockKAnnotations.init(this) - - every { context.filesDir } returns privateFilesDir - - mockPreferences = MockSharedPreferences() - every { - context.getSharedPreferences( - "encryption_error_reset_tool", - Context.MODE_PRIVATE - ) - } returns mockPreferences - - every { timeStamper.nowUTC } returns Instant.ofEpochMilli(1234567890L) - } - - @AfterEach - fun teardown() { - testDir.deleteRecursively() - } - - private fun createInstance() = EncryptionErrorResetTool( - context = context, - timeStamper = timeStamper - ) - - private fun createMockFiles() { - encryptedPrefsFile.apply { - parentFile!!.mkdirs() - createNewFile() - exists() shouldBe true - } - } - - @Test - fun `initialiation is sideeffect free`() { - createMockFiles() - - createInstance() - - encryptedPrefsFile.exists() shouldBe true - mockPreferences.dataMapPeek shouldBe emptyMap() - } - - @Test - fun `reset dialog show flag is writable and persisted`() { - val instance = createInstance() - mockPreferences.dataMapPeek["ea1851.reset.shownotice"] shouldBe null - instance.isResetNoticeToBeShown shouldBe false - - instance.isResetNoticeToBeShown = true - mockPreferences.dataMapPeek["ea1851.reset.shownotice"] shouldBe true - instance.isResetNoticeToBeShown shouldBe true - - createInstance().isResetNoticeToBeShown shouldBe true - - instance.isResetNoticeToBeShown = false - mockPreferences.dataMapPeek["ea1851.reset.shownotice"] shouldBe false - instance.isResetNoticeToBeShown shouldBe false - } - - @Test - fun `reset is not warranted by default`() { - createMockFiles() - - createInstance().tryResetIfNecessary(Exception()) - - encryptedPrefsFile.exists() shouldBe true - } - - /** - Based on https://github.com/corona-warn-app/cwa-app-android/issues/642#issuecomment-650199424 - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: java.lang.SecurityException: Could not decrypt value. decryption failed - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at androidx.security.crypto.EncryptedSharedPreferences.getDecryptedObject(EncryptedSharedPreferences.java:33) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at androidx.security.crypto.EncryptedSharedPreferences.getBoolean(EncryptedSharedPreferences.java:1) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at de.rki.coronawarnapp.update.UpdateChecker.checkForUpdate(UpdateChecker.kt:23) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at de.rki.coronawarnapp.update.UpdateChecker$checkForUpdate$1.invokeSuspend(Unknown Source:11) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:2) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:18) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:809) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:102) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at android.os.Looper.loop(Looper.java:166) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7377) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: Caused by: java.security.GeneralSecurityException: decryption failed - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at com.google.crypto.tink.aead.AeadWrapper$WrappedAead.decrypt(AeadWrapper.java:15) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: at androidx.security.crypto.EncryptedSharedPreferences.getDecryptedObject(EncryptedSharedPreferences.java:5) - 06-23 21:52:51.681 10311 17331 17331 E AndroidRuntime: ... 12 more - */ - @Test - fun `reset is warranted if the first exception after upgrade was a GeneralSecurityException`() { - // We only perform the reset for users who encounter it the first time after the upgrade - createMockFiles() - - createInstance().tryResetIfNecessary( - GeneralSecurityException("decryption failed") - ) shouldBe true - - createInstance().tryResetIfNecessary( - GeneralSecurityException("decryption failed") - ) shouldBe false - - encryptedPrefsFile.exists() shouldBe false - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe 1234567890L - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe true - } - } - - @Test - fun `the previous reset attempt from 1_5_0 is ignored`() { - mockPreferences.edit { putBoolean("ea1851.reset.windowconsumed", true) } - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe null - this["ea1851.reset.windowconsumed"] shouldBe true - this["ea1851.reset.windowconsumed.160"] shouldBe null - this["ea1851.reset.shownotice"] shouldBe null - } - - createMockFiles() - - createInstance().tryResetIfNecessary( - GeneralSecurityException("decryption failed") - ) shouldBe true - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe 1234567890L - this["ea1851.reset.windowconsumed"] shouldBe true - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe true - } - } - - @Test - fun `reset is also warranted if the exception has our desired exception as cause`() { - // We only perform the reset for users who encounter it the first time after the upgrade - createMockFiles() - - createInstance().tryResetIfNecessary( - CwaSecurityException(RuntimeException(GeneralSecurityException("decryption failed"))) - ) shouldBe true - - encryptedPrefsFile.exists() shouldBe false - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe 1234567890L - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe true - } - } - - @Test - fun `nested exception may have the same base exception type, ie GeneralSecurityException`() { - // https://github.com/corona-warn-app/cwa-app-android/issues/642#issuecomment-712188157 - createMockFiles() - - createInstance().tryResetIfNecessary( - KeyException( // subclass of GeneralSecurityException - "Permantly failed to instantiate encrypted preferences", - SecurityException( - "Could not decrypt key. decryption failed", - GeneralSecurityException("decryption failed") - ) - ) - ) shouldBe true - - encryptedPrefsFile.exists() shouldBe false - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe 1234567890L - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe true - } - } - - @Test - fun `exception check does not care about the first exception type`() { - createMockFiles() - - createInstance().tryResetIfNecessary( - CwaSecurityException( - KeyException( // subclass of GeneralSecurityException - "Permantly failed to instantiate encrypted preferences", - SecurityException( - "Could not decrypt key. decryption failed", - GeneralSecurityException("decryption failed") - ) - ) - ) - ) shouldBe true - - encryptedPrefsFile.exists() shouldBe false - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe 1234567890L - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe true - } - } - - @Test - fun `exception check DOES care about the most nested exception`() { - createMockFiles() - - createInstance().tryResetIfNecessary( - CwaSecurityException( - KeyException( // subclass of GeneralSecurityException - "Permantly failed to instantiate encrypted preferences", - SecurityException( - "Could not decrypt key. decryption failed", - GeneralSecurityException( - "decryption failed", - IOException("I am unexpeted") - ) - ) - ) - ) - ) shouldBe false - - encryptedPrefsFile.exists() shouldBe true - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe null - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe null - } - } - - @Test - fun `we want only a specific type of GeneralSecurityException`() { - createMockFiles() - - createInstance().tryResetIfNecessary( - GeneralSecurityException("2020 failed") - ) shouldBe false - - encryptedPrefsFile.exists() shouldBe true - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe null - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe null - } - } - - @Test - fun `reset is not warranted for GeneralSecurityException that happened later`() { - createMockFiles() - - createInstance().tryResetIfNecessary(KeyStoreException()) shouldBe false - - createInstance().tryResetIfNecessary( - GeneralSecurityException("decryption failed") - ) shouldBe false - - encryptedPrefsFile.exists() shouldBe true - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe null - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe null - } - } - - @Test - fun `reset is not warranted if the error fits, but there is no existing preference file`() { - encryptedPrefsFile.exists() shouldBe false - - createInstance().tryResetIfNecessary( - GeneralSecurityException("decryption failed") - ) shouldBe false - - encryptedPrefsFile.exists() shouldBe false - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe null - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe null - } - } - - @Test - fun `the reset is considered failed if the preferences can not be deleted`() { - createMockFiles() - encryptedPrefsFile.delete() - encryptedPrefsFile.mkdir() // Can't delete directories with children via `delete()` - File(encryptedPrefsFile, "prevent deletion").createNewFile() - - createInstance().tryResetIfNecessary( - GeneralSecurityException("decryption failed") - ) shouldBe false - - encryptedPrefsFile.exists() shouldBe true - - mockPreferences.dataMapPeek.apply { - this["ea1851.reset.performedAt"] shouldBe null - this["ea1851.reset.windowconsumed.160"] shouldBe true - this["ea1851.reset.shownotice"] shouldBe null - } - } -} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/SecurityHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/SecurityHelperTest.kt deleted file mode 100644 index cb3ce3fdd240f7fd67e74f109957c3b789355709..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/SecurityHelperTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -package de.rki.coronawarnapp.util.security - -import android.content.SharedPreferences -import de.rki.coronawarnapp.util.di.ApplicationComponent -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.verify -import io.mockk.verifySequence -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import testhelpers.BaseTest - -class SecurityHelperTest : BaseTest() { - @MockK - lateinit var appComponent: ApplicationComponent - - @MockK - lateinit var errorResetTool: EncryptionErrorResetTool - - @MockK - lateinit var preferenceFactory: EncryptedPreferencesFactory - - @BeforeEach - fun setup() { - MockKAnnotations.init(this) - - every { appComponent.errorResetTool } returns errorResetTool - every { appComponent.encryptedPreferencesFactory } returns preferenceFactory - } - - @Test - fun `error free case is sideeffect free`() { - val sharedPreferences: SharedPreferences = mockk() - every { preferenceFactory.create("shared_preferences_cwa") } returns sharedPreferences - - SecurityHelper.encryptedPreferencesProvider(appComponent) shouldBe sharedPreferences - verify(exactly = 0) { errorResetTool.tryResetIfNecessary(any()) } - } - - @Test - fun `positive reset tool results cause a retry`() { - val ourPreferences: SharedPreferences = mockk() - var ourException: Exception? = null - every { preferenceFactory.create("shared_preferences_cwa") } answers { - if (ourException == null) { - ourException = Exception("99 bugs") - throw ourException!! - } else { - ourPreferences - } - } - every { errorResetTool.tryResetIfNecessary(any()) } returns true - - SecurityHelper.encryptedPreferencesProvider(appComponent) shouldBe ourPreferences - - verifySequence { - preferenceFactory.create(any()) - errorResetTool.tryResetIfNecessary(ourException!!) - preferenceFactory.create(any()) - } - } - - @Test - fun `negative reset tool results rethrow the exception`() { - val ourPreferences: SharedPreferences = mockk() - var ourException: Exception? = null - every { preferenceFactory.create("shared_preferences_cwa") } answers { - if (ourException == null) { - ourException = Exception("99 bugs") - throw ourException!! - } else { - ourPreferences - } - } - every { errorResetTool.tryResetIfNecessary(any()) } returns false - - shouldThrow<Exception> { - SecurityHelper.encryptedPreferencesProvider(appComponent) shouldBe ourPreferences - }.cause shouldBe ourException - - verifySequence { - preferenceFactory.create(any()) - errorResetTool.tryResetIfNecessary(ourException!!) - } - } -} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt index 9de1965602d2351a0fc09046ce922b777f8d790a..2547fd7543ba824ef0c7cb14acfe1036a2ae4343 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.notification.NotificationConstants import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService import de.rki.coronawarnapp.service.submission.SubmissionService -import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeAndDateExtensions.daysToMilliseconds import de.rki.coronawarnapp.util.TimeStamper @@ -29,12 +29,14 @@ import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.RelaxedMockK import io.mockk.just import io.mockk.mockkObject +import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.test.runBlockingTest import org.joda.time.Instant import org.junit.Before import org.junit.Test import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { @MockK lateinit var context: Context @@ -48,6 +50,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { @MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool @MockK lateinit var operation: Operation @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var tracingSettings: TracingSettings @RelaxedMockK lateinit var workerParams: WorkerParameters private val currentInstant = Instant.ofEpochSecond(1611764225) private val registrationToken = "test token" @@ -57,18 +60,17 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { MockKAnnotations.init(this) every { submissionSettings.hasViewedTestResult.value } returns false every { timeStamper.nowUTC } returns currentInstant + every { tracingSettings.initialPollingForTestResultTimeStamp } returns currentInstant.millis + every { tracingSettings.isTestResultAvailableNotificationSent } returns false + every { tracingSettings.initialPollingForTestResultTimeStamp = capture(slot()) } answers {} + every { tracingSettings.isTestResultAvailableNotificationSent = capture(slot()) } answers {} mockkObject(AppInjector) every { AppInjector.component } returns appComponent every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory every { appComponent.errorResetTool } returns encryptionErrorResetTool - mockkObject(LocalData) - every { LocalData.registrationToken() } returns registrationToken - every { LocalData.isTestResultAvailableNotificationSent() } returns false - every { LocalData.initialPollingForTestResultTimeStamp() } returns currentInstant.millis - every { LocalData.initialPollingForTestResultTimeStamp(any()) } just Runs - every { LocalData.isTestResultAvailableNotificationSent(any()) } just Runs + every { submissionSettings.registrationToken } returns mockFlowPreference(registrationToken) mockkObject(BackgroundWorkScheduler) every { BackgroundWorkScheduler.WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.stop() } returns operation @@ -82,19 +84,19 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val result = worker.doWork() coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) } verify(exactly = 1) { BackgroundWorkScheduler.WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.stop() } - assert(result is ListenableWorker.Result.Success) + result shouldBe ListenableWorker.Result.success() } } @Test fun testStopWorkerWhenNotificationSent() { runBlockingTest { - every { LocalData.isTestResultAvailableNotificationSent() } returns true + every { tracingSettings.isTestResultAvailableNotificationSent } returns true val worker = createWorker() val result = worker.doWork() coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) } verify(exactly = 1) { BackgroundWorkScheduler.WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.stop() } - assert(result is ListenableWorker.Result.Success) + result shouldBe ListenableWorker.Result.success() } } @@ -103,7 +105,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { runBlockingTest { val past = currentInstant - (BackgroundConstants.POLLING_VALIDITY_MAX_DAYS.toLong() + 1).daysToMilliseconds() - every { LocalData.initialPollingForTestResultTimeStamp() } returns past.millis + every { tracingSettings.initialPollingForTestResultTimeStamp } returns past.millis val worker = createWorker() val result = worker.doWork() coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) } @@ -114,6 +116,11 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { @Test fun testSendNotificationWhenPositive() { + val isTestResultAvailableNotificationSent = slot<Boolean>() + every { + tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent) + } answers {} + runBlockingTest { val testResult = TestResult.POSITIVE coEvery { submissionService.asyncRequestTestResult(registrationToken) } returns testResult @@ -126,19 +133,24 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val worker = createWorker() val result = worker.doWork() coVerify { submissionService.asyncRequestTestResult(registrationToken) } - coVerify { LocalData.isTestResultAvailableNotificationSent(true) } coVerify { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } coVerify { notificationHelper.cancelCurrentNotification( NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID ) } - assert(result is ListenableWorker.Result.Success) + result shouldBe ListenableWorker.Result.success() + isTestResultAvailableNotificationSent.captured shouldBe true } } @Test fun testSendNotificationWhenNegative() { + val isTestResultAvailableNotificationSent = slot<Boolean>() + every { + tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent) + } answers {} + runBlockingTest { val testResult = TestResult.NEGATIVE coEvery { submissionService.asyncRequestTestResult(registrationToken) } returns testResult @@ -151,19 +163,24 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val worker = createWorker() val result = worker.doWork() coVerify { submissionService.asyncRequestTestResult(registrationToken) } - coVerify { LocalData.isTestResultAvailableNotificationSent(true) } coVerify { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } coVerify { notificationHelper.cancelCurrentNotification( NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID ) } - assert(result is ListenableWorker.Result.Success) + result shouldBe ListenableWorker.Result.success() + isTestResultAvailableNotificationSent.captured shouldBe true } } @Test fun testSendNotificationWhenInvalid() { + val isTestResultAvailableNotificationSent = slot<Boolean>() + every { + tracingSettings.isTestResultAvailableNotificationSent = capture(isTestResultAvailableNotificationSent) + } answers {} + runBlockingTest { val testResult = TestResult.INVALID coEvery { submissionService.asyncRequestTestResult(registrationToken) } returns testResult @@ -176,14 +193,14 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val worker = createWorker() val result = worker.doWork() coVerify { submissionService.asyncRequestTestResult(registrationToken) } - coVerify { LocalData.isTestResultAvailableNotificationSent(true) } coVerify { testResultAvailableNotificationService.showTestResultAvailableNotification(testResult) } coVerify { notificationHelper.cancelCurrentNotification( NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID ) } - assert(result is ListenableWorker.Result.Success) + result shouldBe ListenableWorker.Result.success() + isTestResultAvailableNotificationSent.captured shouldBe true } } @@ -201,7 +218,6 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val worker = createWorker() val result = worker.doWork() coVerify { submissionService.asyncRequestTestResult(registrationToken) } - coVerify(exactly = 0) { LocalData.isTestResultAvailableNotificationSent(true) } coVerify(exactly = 0) { testResultAvailableNotificationService.showTestResultAvailableNotification( testResult @@ -213,7 +229,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ) } coVerify(exactly = 0) { BackgroundWorkScheduler.WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.stop() } - assert(result is ListenableWorker.Result.Success) + result shouldBe ListenableWorker.Result.success() } } @@ -236,6 +252,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { notificationHelper, submissionSettings, submissionService, - timeStamper + timeStamper, + tracingSettings ) } diff --git a/Corona-Warn-App/src/test/java/testhelpers/preferences/MockFlowPreference.kt b/Corona-Warn-App/src/testShared/java/testhelpers/preferences/MockFlowPreference.kt similarity index 100% rename from Corona-Warn-App/src/test/java/testhelpers/preferences/MockFlowPreference.kt rename to Corona-Warn-App/src/testShared/java/testhelpers/preferences/MockFlowPreference.kt diff --git a/Corona-Warn-App/src/test/java/testhelpers/preferences/MockSharedPreferences.kt b/Corona-Warn-App/src/testShared/java/testhelpers/preferences/MockSharedPreferences.kt similarity index 100% rename from Corona-Warn-App/src/test/java/testhelpers/preferences/MockSharedPreferences.kt rename to Corona-Warn-App/src/testShared/java/testhelpers/preferences/MockSharedPreferences.kt diff --git a/gradle.properties b/gradle.properties index d0d56db18283aea77c47d32b57ae7a3801a9f70d..82f75208619d3a5c6483f25e43fdf31ab4b5bb68 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ org.gradle.dependency.verification.console=verbose VERSION_MAJOR=2 VERSION_MINOR=0 VERSION_PATCH=0 -VERSION_BUILD=0 +VERSION_BUILD=2 diff --git a/prod_environments.json b/prod_environments.json index 9cd0aaa9c3de612c39e495bb2f90885b167fb7cc..f500312f51d59c251fd51267c5ed8ff6f2713fbf 100644 --- a/prod_environments.json +++ b/prod_environments.json @@ -5,7 +5,7 @@ "DOWNLOAD_CDN_URL": "https://svc90.main.px.t-online.de", "VERIFICATION_CDN_URL": "https://verification.coronawarn.app", "DATA_DONATION_CDN_URL": "https://data.coronawarn.app", - "LOG_UPLOAD_SERVER_URL": "https://placeholder", + "LOG_UPLOAD_SERVER_URL": "https://logupload.coronawarn.app", "SAFETYNET_API_KEY": "placeholder", "PUB_KEYS_SIGNATURE_VERIFICATION": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==" }