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 5f2f3a274e85e9c145fb7c1cab7cf55586869160..383ad820a7bc2d2ce99f9951c6d4e9cf23da06cb 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 @@ -30,7 +30,7 @@ import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState import de.rki.coronawarnapp.ui.main.home.items.FAQCard import de.rki.coronawarnapp.ui.main.home.items.HomeItem import de.rki.coronawarnapp.ui.statistics.Statistics -import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.ui.SingleLiveEvent import io.mockk.MockKAnnotations 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 d57407ef4e48d7f63255544a925d6fdae4e261a8..1424f07424a203095617263ea68c00219f6c235d 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,9 +21,8 @@ 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.submission.SubmissionSettings import de.rki.coronawarnapp.storage.OnboardingSettings -import de.rki.coronawarnapp.storage.preferences.EncryptedPreferencesMigration +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.util.CWADebug @@ -62,7 +61,6 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { @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 @@ -71,12 +69,15 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { super.onCreate() CWADebug.init(this) - Timber.v("onCreate(): Initializing Dagger") - AppInjector.init(this) + AppInjector.init(this).let { compPreview -> + Timber.v("Calling EncryptedPreferencesMigration.doMigration()") + compPreview.encryptedMigration.doMigration() - CWADebug.initAfterInjection(component) + CWADebug.initAfterInjection(compPreview) - encryptedPreferencesMigration.doMigration() + Timber.v("Completing application injection") + compPreview.inject(this) + } BackgroundWorkScheduler.init(component) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt index d2685422c75f1161e171c2fc4a5316c7ffd85542..c37acddbee5b455fb54e3b5a1f9f689f6f164d21 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/ContactDiaryRepository.kt @@ -22,7 +22,11 @@ interface ContactDiaryRepository { val locationVisits: Flow<List<ContactDiaryLocationVisit>> fun locationVisitsForDate(date: LocalDate): Flow<List<ContactDiaryLocationVisit>> suspend fun addLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit) - suspend fun updateLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit) + suspend fun updateLocationVisit( + visitId: Long, + update: (ContactDiaryLocationVisit) -> ContactDiaryLocationVisit + ) + suspend fun deleteLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit) suspend fun deleteLocationVisits(contactDiaryLocationVisits: List<ContactDiaryLocationVisit>) suspend fun deleteAllLocationVisits() @@ -39,7 +43,11 @@ interface ContactDiaryRepository { val personEncounters: Flow<List<ContactDiaryPersonEncounter>> fun personEncountersForDate(date: LocalDate): Flow<List<ContactDiaryPersonEncounter>> suspend fun addPersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter) - suspend fun updatePersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter) + suspend fun updatePersonEncounter( + encounterId: Long, + update: (ContactDiaryPersonEncounter) -> ContactDiaryPersonEncounter + ) + suspend fun deletePersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter) suspend fun deletePersonEncounters(contactDiaryPersonEncounters: List<ContactDiaryPersonEncounter>) suspend fun deleteAllPersonEncounters() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt index b4a1e1efa058075f393ef3fd6c18b11110ea7bbf..e94102ed82addadc6217416651ab874248d45ae0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt @@ -11,8 +11,10 @@ import de.rki.coronawarnapp.contactdiary.storage.dao.ContactDiaryLocationVisitDa import de.rki.coronawarnapp.contactdiary.storage.dao.ContactDiaryPersonDao import de.rki.coronawarnapp.contactdiary.storage.dao.ContactDiaryPersonEncounterDao import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationEntity +import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationVisit import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationVisitEntity import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationVisitSortedList +import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryPersonEncounter import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryPersonEncounterEntity import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryPersonEncounterSortedList import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryPersonEntity @@ -108,10 +110,14 @@ class DefaultContactDiaryRepository @Inject constructor( contactDiaryLocationVisitDao.insert(contactDiaryLocationVisitEntity) } - override suspend fun updateLocationVisit(contactDiaryLocationVisit: ContactDiaryLocationVisit) { - executeWhenIdNotDefault(contactDiaryLocationVisit.id) { - val contactDiaryLocationVisitEntity = contactDiaryLocationVisit.toContactDiaryLocationVisitEntity() - contactDiaryLocationVisitDao.update(contactDiaryLocationVisitEntity) + override suspend fun updateLocationVisit( + visitId: Long, + update: (ContactDiaryLocationVisit) -> ContactDiaryLocationVisit + ) { + executeWhenIdNotDefault(visitId) { + val original = contactDiaryLocationVisitDao.entityForId(visitId) + val updatedVisit = update(original.toContactDiaryLocationVisit()) + contactDiaryLocationVisitDao.update(updatedVisit.toContactDiaryLocationVisitEntity()) } } @@ -202,10 +208,14 @@ class DefaultContactDiaryRepository @Inject constructor( contactDiaryPersonEncounterDao.insert(contactDiaryPersonEncounterEntity) } - override suspend fun updatePersonEncounter(contactDiaryPersonEncounter: ContactDiaryPersonEncounter) { - executeWhenIdNotDefault(contactDiaryPersonEncounter.id) { - val contactDiaryPersonEncounterEntity = contactDiaryPersonEncounter.toContactDiaryPersonEncounterEntity() - contactDiaryPersonEncounterDao.update(contactDiaryPersonEncounterEntity) + override suspend fun updatePersonEncounter( + encounterId: Long, + update: (ContactDiaryPersonEncounter) -> ContactDiaryPersonEncounter + ) { + executeWhenIdNotDefault(encounterId) { + val original = contactDiaryPersonEncounterDao.entityForId(encounterId) + val updatedEncounter = update(original.toContactDiaryPersonEncounter()) + contactDiaryPersonEncounterDao.update(updatedEncounter.toContactDiaryPersonEncounterEntity()) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt index 76950a4124614d5c14863ba6f63aa49d33c7e12c..874d073130c504d9620a5a079d587899e9c66c59 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/ContactDiaryLocationListViewModel.kt @@ -99,9 +99,11 @@ class ContactDiaryLocationListViewModel @AssistedInject constructor( item: DiaryLocationListItem, duration: Duration? ) { - val visit = item.visit?.toEditableVariant() ?: return + val visit = item.visit ?: return launchOnAppScope { - contactDiaryRepository.updateLocationVisit(visit.copy(duration = duration)) + contactDiaryRepository.updateLocationVisit(visit.id) { + it.toEditableVariant().copy(duration = duration) + } } } @@ -109,10 +111,12 @@ class ContactDiaryLocationListViewModel @AssistedInject constructor( item: DiaryLocationListItem, circumstances: String ) { - val visit = item.visit?.toEditableVariant() ?: return + val visit = item.visit ?: return val sanitized = circumstances.trim().trimToLength(250) launchOnAppScope { - contactDiaryRepository.updateLocationVisit(visit.copy(circumstances = sanitized)) + contactDiaryRepository.updateLocationVisit(visit.id) { + it.toEditableVariant().copy(circumstances = sanitized) + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt index a0add62e4ef1b7334a00817de8a6f1397651cea3..1e60f3f349564e6d5334ad8d515f94f4ae856c04 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/person/ContactDiaryPersonListViewModel.kt @@ -95,9 +95,11 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor( duration: ContactDiaryPersonEncounter.DurationClassification? ) { Timber.d("onDurationChanged(item=%s, duration=%s)", item, duration) - val encounter = item.personEncounter?.toEditableVariant() ?: return + val encounter = item.personEncounter ?: return launchOnAppScope { - contactDiaryRepository.updatePersonEncounter(encounter.copy(durationClassification = duration)) + contactDiaryRepository.updatePersonEncounter(encounter.id) { + it.toEditableVariant().copy(durationClassification = duration) + } } } @@ -112,9 +114,11 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor( withMask: Boolean? ) { Timber.d("onWithmaskChanged(item=%s, withMask=%s)", item, withMask) - val encounter = item.personEncounter?.toEditableVariant() ?: return + val encounter = item.personEncounter ?: return launchOnAppScope { - contactDiaryRepository.updatePersonEncounter(encounter.copy(withMask = withMask)) + contactDiaryRepository.updatePersonEncounter(encounter.id) { + it.toEditableVariant().copy(withMask = withMask) + } } } @@ -123,9 +127,11 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor( wasOutside: Boolean? ) { Timber.d("onWasOutsideChanged(item=%s, onWasOutside=%s)", item, wasOutside) - val encounter = item.personEncounter?.toEditableVariant() ?: return + val encounter = item.personEncounter ?: return launchOnAppScope { - contactDiaryRepository.updatePersonEncounter(encounter.copy(wasOutside = wasOutside)) + contactDiaryRepository.updatePersonEncounter(encounter.id) { + it.toEditableVariant().copy(wasOutside = wasOutside) + } } } @@ -134,10 +140,12 @@ class ContactDiaryPersonListViewModel @AssistedInject constructor( circumstances: String ) { Timber.d("onCircumstancesChanged(item=%s, circumstances=%s)", item, circumstances) - val encounter = item.personEncounter?.toEditableVariant() ?: return + val encounter = item.personEncounter ?: return launchOnAppScope { val sanitized = circumstances.trim().trimToLength(250) - contactDiaryRepository.updatePersonEncounter(encounter.copy(circumstances = sanitized)) + contactDiaryRepository.updatePersonEncounter(encounter.id) { + it.toEditableVariant().copy(circumstances = sanitized) + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/day/DayDataNestedAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/day/DayDataNestedAdapter.kt index b0cc6fb871c794021930ae919214725af1a11d9b..6de2a0cceec2ec82c2952544f8bd7bf982f35115 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/day/DayDataNestedAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/day/DayDataNestedAdapter.kt @@ -53,7 +53,7 @@ class DayDataNestedAdapter : BaseAdapter<DayDataNestedAdapter.NestedItemViewHold mutableListOf<String>().apply { duration?.run { if (duration != Duration.ZERO) { - val durationSuffix = context.getString(R.string.contact_diary_overview_location_duration_suffix) + val durationSuffix = context.getString(R.string.contact_diary_location_visit_duration_hour) add(toReadableDuration(suffix = durationSuffix)) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt index 25531761c63d7553637cce94d1418271e1f927d3..e4bf062aff616dfd3b93d89885cc04ee79dfc14c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt @@ -8,8 +8,9 @@ fun calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( lastChangeCheckedRiskLevelTimestamp: Instant?, testRegisteredAt: Instant? ): Int { - val lastChangeCheckedRiskLevelDate = lastChangeCheckedRiskLevelTimestamp?.toLocalDate() ?: return 0 - val testRegisteredAtDate = testRegisteredAt?.toLocalDate() ?: return 0 + val lastChangeCheckedRiskLevelDate = lastChangeCheckedRiskLevelTimestamp?.toLocalDate() ?: return -1 + val testRegisteredAtDate = testRegisteredAt?.toLocalDate() ?: return -1 + if (lastChangeCheckedRiskLevelDate.isAfter(testRegisteredAtDate)) return -1 return Days.daysBetween( lastChangeCheckedRiskLevelDate, testRegisteredAtDate diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt index 138e26fa4fd7b0076569ae63527f2ca5d0479487..81529146337c67d83d97488a976bd00f81ebb0cb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission +import de.rki.coronawarnapp.datadonation.analytics.common.calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration import de.rki.coronawarnapp.datadonation.analytics.common.toMetadataRiskLevel import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.risk.RiskLevelSettings @@ -54,6 +55,13 @@ class AnalyticsKeySubmissionCollector @Inject constructor( } } } + + analyticsKeySubmissionStorage.daysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { + calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( + riskLevelSettings.lastChangeCheckedRiskLevelTimestamp, + testRegisteredAt + ) + } } fun reportSubmitted() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt index b2d639381ce9edda8d010567061a65ffbf3a8d16..cad4c220a7ee0a8d45a97ceef7786642a5758b4e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt @@ -1,15 +1,10 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission -import de.rki.coronawarnapp.datadonation.analytics.common.calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration -import de.rki.coronawarnapp.risk.RiskLevelSettings import org.joda.time.Duration -import org.joda.time.Instant import javax.inject.Inject -import kotlin.math.max class AnalyticsKeySubmissionRepository @Inject constructor( - private val storage: AnalyticsKeySubmissionStorage, - private val riskLevelSettings: RiskLevelSettings + private val storage: AnalyticsKeySubmissionStorage ) { val testResultReceivedAt: Long get() = storage.testResultReceivedAt.value @@ -42,16 +37,23 @@ class AnalyticsKeySubmissionRepository @Inject constructor( get() = storage.advancedConsentGiven.value val hoursSinceTestResult: Int - get() = Duration.millis(max(submittedAt - testResultReceivedAt, 0)).toStandardHours().hours + get() { + if (submittedAt <= 0) return -1 + if (testResultReceivedAt <= 0) return -1 + if (submittedAt < testResultReceivedAt) return -1 + return Duration.millis(submittedAt - testResultReceivedAt).toStandardHours().hours + } val hoursSinceTestRegistration: Int - get() = Duration.millis(max(submittedAt - testRegisteredAt, 0L)).toStandardHours().hours + get() { + if (submittedAt <= 0) return -1 + if (testRegisteredAt <= 0) return -1 + if (submittedAt < testRegisteredAt) return -1 + return Duration.millis(submittedAt - testRegisteredAt).toStandardHours().hours + } val daysSinceMostRecentDateAtRiskLevelAtTestRegistration: Int - get() = calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( - riskLevelSettings.lastChangeCheckedRiskLevelTimestamp, - Instant.ofEpochMilli(testRegisteredAt) - ) + get() = storage.daysSinceMostRecentDateAtRiskLevelAtTestRegistration.value val hoursSinceHighRiskWarningAtTestRegistration: Int get() = storage.hoursSinceHighRiskWarningAtTestRegistration.value diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt index 085c5fe727216fee0a3f963e2548ac568412cf70..48fb47afea4097015b1594ba611ac4f935b588e9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt @@ -73,6 +73,11 @@ class AnalyticsKeySubmissionStorage @Inject constructor( defaultValue = -1 ) + val daysSinceMostRecentDateAtRiskLevelAtTestRegistration = prefs.createFlowPreference( + key = "analytics_key_submission_daysSinceMostRecentDateAtRiskLevelAtTestRegistration", + defaultValue = -1 + ) + fun clear() { prefs.clearAndNotify() } 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 34f8be0bb2a6df2029de72f0de6a2888206da592..4cf07f10e528732e5e7c71c6fc973c3cfb792983 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 @@ -3,13 +3,10 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest import de.rki.coronawarnapp.datadonation.analytics.common.calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings -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.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.formatter.TestResult -import kotlinx.coroutines.flow.first import org.joda.time.Duration import org.joda.time.Instant import timber.log.Timber @@ -19,8 +16,6 @@ import javax.inject.Singleton @Singleton class TestResultDonor @Inject constructor( private val testResultDonorSettings: TestResultDonorSettings, - private val riskLevelSettings: RiskLevelSettings, - private val riskLevelStorage: RiskLevelStorage, private val timeStamper: TimeStamper, private val submissionSettings: SubmissionSettings ) : DonorModule { @@ -28,42 +23,66 @@ class TestResultDonor @Inject constructor( override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution { val scannedAfterConsent = testResultDonorSettings.testScannedAfterConsent.value if (!scannedAfterConsent) { - Timber.d("Skipping TestResultMetadata donation (testScannedAfterConsent=%s)", scannedAfterConsent) + Timber.d("Skipping TestResultMetadata donation (scannedAfterConsent=%s)", scannedAfterConsent) return TestResultMetadataNoContribution } val timestampAtRegistration = submissionSettings.initialTestResultReceivedAt - if (timestampAtRegistration == null) { - Timber.d("Skipping TestResultMetadata donation timestampAtRegistration isn't found") + Timber.d("Skipping TestResultMetadata donation (timestampAtRegistration is missing)") return TestResultMetadataNoContribution } - val configHours = request - .currentConfig - .analytics - .hoursSinceTestRegistrationToSubmitTestResultMetadata - - val hoursSinceTestRegistrationTime = Duration(timestampAtRegistration, timeStamper.nowUTC).standardHours.toInt() - val isDiffHoursMoreThanConfigHoursForPendingTest = hoursSinceTestRegistrationTime >= configHours + val testResultAtRegistration = testResultDonorSettings.testResultAtRegistration.value + if (testResultAtRegistration == null) { + Timber.d("Skipping TestResultMetadata donation (testResultAtRegistration is missing)") + return TestResultMetadataNoContribution + } - val testResultAtRegistration = - testResultDonorSettings.testResultAtRegistration.value ?: return TestResultMetadataNoContribution + val lastChangeCheckedRiskLevelTimestamp = testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel.value + if (lastChangeCheckedRiskLevelTimestamp == null) { + Timber.d("Skipping TestResultMetadata donation (lastChangeCheckedRiskLevelTimestamp is missing)") + return TestResultMetadataNoContribution + } val daysSinceMostRecentDateAtRiskLevelAtTestRegistration = calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( - riskLevelSettings.lastChangeCheckedRiskLevelTimestamp, + lastChangeCheckedRiskLevelTimestamp, timestampAtRegistration ) + Timber.i( + "daysSinceMostRecentDateAtRiskLevelAtTestRegistration: %s", + daysSinceMostRecentDateAtRiskLevelAtTestRegistration + ) + val riskLevelAtRegistration = testResultDonorSettings.riskLevelAtTestRegistration.value + val highRiskResultCalculatedAt = testResultDonorSettings.riskLevelTurnedRedTime.value val hoursSinceHighRiskWarningAtTestRegistration = if (riskLevelAtRegistration == PpaData.PPARiskLevel.RISK_LEVEL_LOW) { DEFAULT_HOURS_SINCE_HIGH_RISK_WARNING } else { - calculatedHoursSinceHighRiskWarning(timestampAtRegistration) + if (highRiskResultCalculatedAt == null) { + Timber.d("Skipping TestResultMetadata donation (highRiskResultCalculatedAt is missing)") + return TestResultMetadataNoContribution + } + + Timber.i( + "highRiskResultCalculatedAt: %s, timestampAtRegistration: %s", + highRiskResultCalculatedAt, + timestampAtRegistration + ) + calculatedHoursSinceHighRiskWarning(highRiskResultCalculatedAt, timestampAtRegistration) } + Timber.i( + "hoursSinceHighRiskWarningAtTestRegistration: %s", + hoursSinceHighRiskWarningAtTestRegistration + ) + + val configHours = request.currentConfig.analytics.hoursSinceTestRegistrationToSubmitTestResultMetadata + val hoursSinceTestRegistrationTime = Duration(timestampAtRegistration, timeStamper.nowUTC).standardHours.toInt() + val isDiffHoursMoreThanConfigHoursForPendingTest = hoursSinceTestRegistrationTime >= configHours return when { /** @@ -134,8 +153,13 @@ class TestResultDonor @Inject constructor( ): DonorModule.Contribution { val finalTestResultReceivedAt = testResultDonorSettings.finalTestResultReceivedAt.value val hoursSinceTestRegistrationTime = if (finalTestResultReceivedAt != null) { - Duration(registrationTime, finalTestResultReceivedAt).standardHours.toInt() + Timber.i("finalTestResultReceivedAt: %s", finalTestResultReceivedAt) + Timber.i("registrationTime: %s", registrationTime) + Duration(registrationTime, finalTestResultReceivedAt).standardHours.toInt().also { + Timber.i("Calculated hoursSinceTestRegistrationTime: %s", it) + } } else { + Timber.i("Default hoursSinceTestRegistrationTime") DEFAULT_HOURS_SINCE_TEST_REGISTRATION_TIME } @@ -153,14 +177,10 @@ class TestResultDonor @Inject constructor( return TestResultMetadataContribution(testResultMetaData, ::cleanUp) } - private suspend fun calculatedHoursSinceHighRiskWarning(registrationTime: Instant): Int { - val highRiskResultCalculatedAt = riskLevelStorage - .latestAndLastSuccessful - .first() - .filter { it.isIncreasedRisk } - .minByOrNull { it.calculatedAt } - ?.calculatedAt ?: return DEFAULT_HOURS_SINCE_HIGH_RISK_WARNING - + private fun calculatedHoursSinceHighRiskWarning( + highRiskResultCalculatedAt: Instant, + registrationTime: Instant + ): Int { return Duration( highRiskResultCalculatedAt, registrationTime diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt index 11b76710c64c003c3c22715ce996e2afaee6d2f3..ccc15cb891da84070ffc49410ffcb03cc6acafff 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt @@ -65,6 +65,34 @@ class TestResultDonorSettings @Inject constructor( } ) + val mostRecentDateWithHighOrLowRiskLevel = prefs.createFlowPreference( + key = PREFS_KEY_MOST_RECENT_WITH_HIGH_OR_LOW_RISK_LEVEL, + reader = { key -> + getLong(key, 0L).let { + if (it != 0L) { + Instant.ofEpochMilli(it) + } else null + } + }, + writer = { key, value -> + putLong(key, value?.millis ?: 0L) + } + ) + + val riskLevelTurnedRedTime = prefs.createFlowPreference( + key = PREFS_KEY_RISK_LEVEL_TURNED_RED_TIME, + reader = { key -> + getLong(key, 0L).let { + if (it != 0L) { + Instant.ofEpochMilli(it) + } else null + } + }, + writer = { key, value -> + putLong(key, value?.millis ?: 0L) + } + ) + fun saveTestResultDonorDataAtRegistration(testResult: TestResult, lastRiskResult: RiskLevelResult) { testScannedAfterConsent.update { true } testResultAtRegistration.update { testResult } @@ -82,5 +110,8 @@ class TestResultDonorSettings @Inject constructor( private const val PREFS_KEY_TEST_RESULT_AT_REGISTRATION = "testResultDonor.testResultAtRegistration" private const val PREFS_KEY_RISK_LEVEL_AT_REGISTRATION = "testResultDonor.riskLevelAtRegistration" private const val PREFS_KEY_FINAL_TEST_RESULT_RECEIVED_AT = "testResultDonor.finalTestResultReceivedAt" + private const val PREFS_KEY_RISK_LEVEL_TURNED_RED_TIME = "testResultDonor.riskLevelTurnedRedTime" + private const val PREFS_KEY_MOST_RECENT_WITH_HIGH_OR_LOW_RISK_LEVEL = + "testResultDonor.mostRecentWithHighOrLowRiskLevel" } } 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 e335d18c8d841cfbacf6813521731ef5a3908e3f..86a3b42668eb0f9d689b5b70307a3403d991a863 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 @@ -25,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 wasTracingExplanationDialogShown: Boolean + get() = prefs.getBoolean(PKEY_TRACING_DIALOG_SHOWN, false) + set(value) = prefs.edit { putBoolean(PKEY_TRACING_DIALOG_SHOWN, 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) } @@ -50,12 +54,12 @@ class CWASettings @Inject constructor( val isNotificationsRiskEnabled = prefs.createFlowPreference( key = PKEY_NOTIFICATIONS_RISK_ENABLED, - defaultValue = false + defaultValue = true ) val isNotificationsTestEnabled = prefs.createFlowPreference( key = PKEY_NOTIFICATIONS_TEST_ENABLED, - defaultValue = false + defaultValue = true ) val lastChangelogVersion = prefs.createFlowPreference( @@ -69,6 +73,7 @@ class CWASettings @Inject constructor( companion object { private const val PKEY_DEVICE_TIME_INCORRECT_ACK = "devicetime.incorrect.acknowledged" + private const val PKEY_TRACING_DIALOG_SHOWN = "tracing.dialog.shown" 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" 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 8a6fe31799602ee50ff2aabe8522054b497c6d00..fbee2b6d4a04294dee741b7565c18047c0cb5631 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 @@ -4,6 +4,7 @@ import android.content.Context import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationManagerCompat import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings 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 @@ -34,7 +35,8 @@ class RiskLevelChangeDetector @Inject constructor( private val notificationHelper: NotificationHelper, private val surveys: Surveys, private val submissionSettings: SubmissionSettings, - private val tracingSettings: TracingSettings + private val tracingSettings: TracingSettings, + private val testResultDonorSettings: TestResultDonorSettings ) { fun launch() { @@ -65,9 +67,54 @@ class RiskLevelChangeDetector @Inject constructor( val oldRiskState = oldResult.riskState val newRiskState = newResult.riskState - Timber.d("Last state was $oldRiskState and current state is $newRiskState") + // Check sending a notification when risk level changes + checkSendingNotification(oldRiskState, newRiskState) + + // Save Survey related data based on the risk state + saveSurveyRiskState(oldRiskState, newRiskState, newResult) + + // Save TestDonor risk level timestamps + saveTestDonorRiskLevelAnalytics(newResult) + } + + private fun saveTestDonorRiskLevelAnalytics( + newRiskState: RiskLevelResult + ) { + // Save riskLevelTurnedRedTime if not already set before for high risk detection + Timber.i("riskLevelTurnedRedTime=%s", testResultDonorSettings.riskLevelTurnedRedTime.value) + if (testResultDonorSettings.riskLevelTurnedRedTime.value == null) { + if (newRiskState.isIncreasedRisk) { + testResultDonorSettings.riskLevelTurnedRedTime.update { + newRiskState.calculatedAt + } + Timber.i( + "riskLevelTurnedRedTime: newRiskState=%s, riskLevelTurnedRedTime=%s", + newRiskState.riskState, + newRiskState.calculatedAt + ) + } + } + + // Save most recent date of high or low risks + if (newRiskState.riskState in listOf(RiskState.INCREASED_RISK, RiskState.LOW_RISK)) { + Timber.i( + "mostRecentDateWithHighOrLowRiskLevel: newRiskState=%s, lastRiskEncounterAt=%s", + newRiskState.riskState, + newRiskState.lastRiskEncounterAt + ) + + testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel.update { + newRiskState.lastRiskEncounterAt + } + } + } + + private suspend fun checkSendingNotification( + oldRiskState: RiskState, + newRiskState: RiskState + ) { if (hasHighLowLevelChanged(oldRiskState, newRiskState) && !submissionSettings.isSubmissionSuccessful) { Timber.d("Notification Permission = ${notificationManagerCompat.areNotificationsEnabled()}") @@ -82,7 +129,13 @@ class RiskLevelChangeDetector @Inject constructor( Timber.d("Risk level changed and notification sent. Current Risk level is $newRiskState") } + } + private fun saveSurveyRiskState( + oldRiskState: RiskState, + newRiskState: RiskState, + newResult: RiskLevelResult + ) { if (oldRiskState == RiskState.INCREASED_RISK && newRiskState == RiskState.LOW_RISK) { tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel.update { true } Timber.d("Risk level changed LocalData is updated. Current Risk level is $newRiskState") 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 0bdfeb13edd4bfc6c51bd722fbc0ebd21d0cdaf5..e9ab8acd4b9393d7fadcb47c61347a08d1ea3c2d 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 @@ -83,8 +83,8 @@ class RiskLevelTask @Inject constructor( Timber.d("The current time is %s", it) } - if (submissionSettings.isAllowedToSubmitKeys) { - Timber.i("Positive test result, skip risk calculation") + if (submissionSettings.isAllowedToSubmitKeys && submissionSettings.hasViewedTestResult.value) { + Timber.i("Positive test result and user has seen it, skip risk calculation") return RiskLevelTaskResult( calculatedAt = nowUTC, failureReason = FailureReason.POSITIVE_TEST_RESULT 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 deleted file mode 100644 index 45b15c4d4b07e2cfc3fe60a6c82221d300dfbca1..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesHelper.kt +++ /dev/null @@ -1,51 +0,0 @@ -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/submission/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt index 392fcd548e782a4f8cbd0eef0e9ee28c12e931fb..38edfe9df56b54a02f9e8f0f580a80090e5a8b34 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 @@ -124,6 +124,7 @@ class SubmissionRepository @Inject constructor( } suspend fun asyncRegisterDeviceViaTAN(tan: String) { + analyticsKeySubmissionCollector.reset() val registrationData = submissionService.asyncRegisterDeviceViaTAN(tan) submissionSettings.registrationToken.update { registrationData.registrationToken @@ -136,6 +137,7 @@ class SubmissionRepository @Inject constructor( } suspend fun asyncRegisterDeviceViaGUID(guid: String): TestResult { + analyticsKeySubmissionCollector.reset() val registrationData = submissionService.asyncRegisterDeviceViaGUID(guid) submissionSettings.registrationToken.update { registrationData.registrationToken @@ -163,6 +165,16 @@ class SubmissionRepository @Inject constructor( deadmanNotificationScheduler.cancelScheduledWork() } + // https://jira-ibs.wbs.net.sap/browse/EXPOSUREAPP-4484 + // User removed a test before 1.11 where due to a bug the timestamp was not removed. + if (submissionSettings.initialTestResultReceivedAt != null && + submissionSettings.registrationToken.value != null && + submissionSettings.devicePairingSuccessfulAt == null + ) { + Timber.tag(TAG).w("User has stale initialTestResultReceivedAt, fixing EXPOSUREAPP-4484.") + submissionSettings.initialTestResultReceivedAt = null + } + val initialTestResultReceivedTimestamp = submissionSettings.initialTestResultReceivedAt if (initialTestResultReceivedTimestamp == null) { @@ -188,7 +200,6 @@ class SubmissionRepository @Inject constructor( fun removeTestFromDevice() { submissionSettings.hasViewedTestResult.update { false } submissionSettings.hasGivenConsent.update { false } - analyticsKeySubmissionCollector.reset() revokeConsentToSubmission() submissionSettings.registrationToken.update { null } submissionSettings.devicePairingSuccessfulAt = null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingExplanationDialog.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingExplanationDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..7ebebc73a22e6aef5ae2753807fa505df2e42540 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingExplanationDialog.kt @@ -0,0 +1,35 @@ +package de.rki.coronawarnapp.tracing.ui + +import android.content.Context +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.ui.main.home.HomeFragment +import de.rki.coronawarnapp.util.DialogHelper +import javax.inject.Inject + +class TracingExplanationDialog @Inject constructor( + private val homeFragment: HomeFragment +) { + private val context: Context + get() = homeFragment.requireContext() + + fun show(onPositive: () -> Unit) { + + val infoPeriodLogged = + context.getString(R.string.risk_details_information_body_period_logged) + val infoPeriodLoggedAssessment = + context.getString(R.string.risk_details_information_body_period_logged_assessment) + val infoFAQ = context.getString(R.string.risk_details_explanation_dialog_faq_body) + + val data = DialogHelper.DialogInstance( + context = context, + title = context.getString(R.string.risk_details_explanation_dialog_title), + message = "$infoPeriodLogged\n$infoPeriodLoggedAssessment\n\n$infoFAQ", + positiveButton = context.getString(R.string.errors_generic_button_positive), + negativeButton = null, + cancelable = null, + positiveButtonFunction = onPositive, + negativeButtonFunction = {} + ) + DialogHelper.showDialog(data) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt index d08a82cd6f66f888f36a0a9a95cdcc59b78b44a7..3c6852ad0031d6b01c70d7df4cc03e7b0b0510ee 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt @@ -9,6 +9,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.HomeFragmentLayoutBinding +import de.rki.coronawarnapp.tracing.ui.TracingExplanationDialog import de.rki.coronawarnapp.ui.main.home.popups.DeviceTimeIncorrectDialog import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat import de.rki.coronawarnapp.util.DialogHelper @@ -40,6 +41,7 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { val binding: HomeFragmentLayoutBinding by viewBindingLazy() @Inject lateinit var homeMenu: HomeMenu + @Inject lateinit var tracingExplanationDialog: TracingExplanationDialog @Inject lateinit var deviceTimeIncorrectDialog: DeviceTimeIncorrectDialog private val homeAdapter = HomeAdapter() @@ -91,6 +93,11 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { HomeFragmentEvents.ShowReactivateRiskCheckDialog -> { showReactivateRiskCheckDialog() } + HomeFragmentEvents.ShowTracingExplanation -> { + tracingExplanationDialog.show { + vm.tracingExplanationWasShown() + } + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt index c99db2506808087fa7d0f2a9a658bee8b84aafd7..45f581002f78709b070aa97ea686d628b72f9188 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt @@ -2,6 +2,8 @@ package de.rki.coronawarnapp.ui.main.home sealed class HomeFragmentEvents { + object ShowTracingExplanation : HomeFragmentEvents() + object ShowErrorResetDialog : HomeFragmentEvents() object ShowDeleteTestDialog : HomeFragmentEvents() 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 0c72da282baddaed96138f6756ab8620763cc3b6..53da0dee2c310489f588b9cf8cfa137d1dcd7026 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 @@ -48,13 +48,14 @@ import de.rki.coronawarnapp.tracing.ui.homecards.TracingProgressCard import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState import de.rki.coronawarnapp.tracing.ui.statusbar.toHeaderState import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowErrorResetDialog +import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowTracingExplanation import de.rki.coronawarnapp.ui.main.home.items.FAQCard import de.rki.coronawarnapp.ui.main.home.items.HomeItem import de.rki.coronawarnapp.ui.main.home.items.ReenableRiskCard import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess import de.rki.coronawarnapp.util.coroutine.DispatcherProvider -import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -99,6 +100,9 @@ class HomeFragmentViewModel @AssistedInject constructor( if (errorResetTool.isResetNoticeToBeShown) { popupEvents.postValue(ShowErrorResetDialog) } + if (!cwaSettings.wasTracingExplanationDialogShown) { + popupEvents.postValue(ShowTracingExplanation) + } } } @@ -311,6 +315,10 @@ class HomeFragmentViewModel @AssistedInject constructor( cwaSettings.wasDeviceTimeIncorrectAcknowledged = true } + fun tracingExplanationWasShown() { + cwaSettings.wasTracingExplanationDialogShown = true + } + @AssistedFactory interface Factory : SimpleCWAViewModelFactory<HomeFragmentViewModel> } 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 9cfc42f4eb2fac80e98b96c934975aeb17e90f36..a008516956732dc82157556906387f2e4206b736 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 @@ -36,6 +36,7 @@ object CWADebug { } fun initAfterInjection(component: ApplicationComponent) { + Timber.v("initAfterInjection(%s)", component) // TODO ¯\_(ツ)_/¯ if (isDeviceForTestersBuild) { debugLogger.setInjectionIsReady(component) 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 f7557924b1dada96fa221eb94ec53a28a6396af8..b7a8db6c2d8f80d67a13c55363314b968873102b 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 @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.util.debug +import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.util.CWADebug import timber.log.Timber @@ -12,15 +13,24 @@ class UncaughtExceptionLogger( } override fun uncaughtException(thread: Thread, error: Throwable) { - Timber.tag(thread.name).e(error, "Uncaught exception!") + val sourceTag = thread.name + Timber.tag(sourceTag).e(error, "Uncaught exception!") + + try { + error.reportProblem(tag = sourceTag, "Uncaught exception!") + } catch (e: Exception) { + Timber.e(e, "reportProblem() failed!?") + } + 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.") + Timber.w(e, "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/AppInjector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppInjector.kt index 2c63e6eff976b62809dffa04ffacb214778e37c2..12e80b7d71a2160007838ec7639c1c8136769b47 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppInjector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppInjector.kt @@ -13,9 +13,11 @@ import timber.log.Timber object AppInjector { lateinit var component: ApplicationComponent - fun init(app: CoronaWarnApplication) { - component = DaggerApplicationComponent.factory().create(app) - component.inject(app) + fun init(app: CoronaWarnApplication): ApplicationComponent { + Timber.v("Initializing Dagger (%s)", app) + return DaggerApplicationComponent.factory().create(app).also { + component = it + } } fun setup(activity: Activity) { 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 a2c6ec3d39243cc97bae7269e18dbe1bb09fbde2..dbc57fbeb57043aa52ef75185797fdaae410f5ed 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 @@ -36,8 +36,9 @@ import de.rki.coronawarnapp.util.coroutine.AppCoroutineScope import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.coroutine.CoroutineModule import de.rki.coronawarnapp.util.device.DeviceModule -import de.rki.coronawarnapp.util.security.EncryptedPreferencesFactory -import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.encryptionmigration.EncryptedPreferencesFactory +import de.rki.coronawarnapp.util.encryptionmigration.EncryptedPreferencesMigration +import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool import de.rki.coronawarnapp.util.security.SecurityModule import de.rki.coronawarnapp.util.serialization.SerializationModule import de.rki.coronawarnapp.util.worker.WorkerBinder @@ -102,6 +103,8 @@ interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { fun inject(backgroundWorkScheduler: BackgroundWorkScheduler) + val encryptedMigration: EncryptedPreferencesMigration + @Component.Factory interface Factory { fun create(@BindsInstance app: CoronaWarnApplication): ApplicationComponent diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesFactory.kt similarity index 96% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactory.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesFactory.kt index 317e3e765f7dd4fe0c60c33f9761ca9e4e2756fc..cd3ec57e86f27d00201f8815f2600f70622c58c1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactory.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesFactory.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.util.security +package de.rki.coronawarnapp.util.encryptionmigration import android.content.Context import android.content.SharedPreferences diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..b585815035c1daba44401b83b28b9f471a8572f3 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesHelper.kt @@ -0,0 +1,53 @@ +package de.rki.coronawarnapp.util.encryptionmigration + +import android.content.SharedPreferences +import android.content.pm.ApplicationInfo +import androidx.core.content.edit +import timber.log.Timber +import java.io.File +import javax.inject.Inject + +class EncryptedPreferencesHelper @Inject constructor( + private val applicationInfo: ApplicationInfo, + factory: EncryptedPreferencesFactory +) { + + private val preferenceFile by lazy { + File(applicationInfo.dataDir) + .resolve("shared_prefs/$ENCRYPTED_SHARED_PREFERENCES_FILE.xml") + } + + val instance: SharedPreferences? by lazy { + if (preferenceFile.exists()) { + factory.create(ENCRYPTED_SHARED_PREFERENCES_FILE) + } else { + null + } + } + + fun clean() { + try { + instance?.edit(true) { + Timber.d("Clearing all encrypted preference values.") + clear() + Timber.d("Preference values have been cleared.") + } + } catch (e: Exception) { + Timber.w("Failed to clear encrypted preferences.") + } + + if (preferenceFile.exists()) { + if (preferenceFile.delete()) { + Timber.i("Encrypted preference file deleted.") + } else { + Timber.e("Encrypted preference could not be deleted.") + } + } else { + Timber.d("Encrypted preference file did not exist.") + } + } + + companion object { + private const val ENCRYPTED_SHARED_PREFERENCES_FILE = "shared_preferences_cwa" + } +} 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/util/encryptionmigration/EncryptedPreferencesMigration.kt similarity index 51% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesMigration.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt index cda45aade8aba1a42b5211b52bbfab33d807f92e..64929458b059e6c40a2ea3e1705d438707916f80 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/preferences/EncryptedPreferencesMigration.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt @@ -1,8 +1,10 @@ -package de.rki.coronawarnapp.storage.preferences +package de.rki.coronawarnapp.util.encryptionmigration import android.content.Context import android.content.SharedPreferences import android.database.sqlite.SQLiteDatabase +import androidx.annotation.VisibleForTesting +import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.storage.TracingSettings @@ -15,34 +17,41 @@ import javax.inject.Inject class EncryptedPreferencesMigration @Inject constructor( @AppContext private val context: Context, - private val encryptedPreferencesHelper: EncryptedPreferencesHelper, + private val encryptedPreferences: EncryptedPreferencesHelper, private val cwaSettings: CWASettings, private val submissionSettings: SubmissionSettings, private val tracingSettings: TracingSettings, - private val onboardingSettings: OnboardingSettings + private val onboardingSettings: OnboardingSettings, + private val errorResetTool: EncryptionErrorResetTool ) { fun doMigration() { Timber.d("Migration start") try { - copyData() - cleanData() + encryptedPreferences.instance?.let { copyData(it) } } catch (e: Exception) { - Timber.e(e, "Migration was not successful") + e.reportProblem(tag = this::class.simpleName, info = "Migration failed") + errorResetTool.isResetNoticeToBeShown = true + } finally { + try { + encryptedPreferences.clean() + } catch (e: Exception) { + e.reportProblem(tag = this::class.simpleName, info = "Encryption data clean up failed") + } } try { dropDatabase() } catch (e: Exception) { - Timber.e(e, "Database removing was not successful") + e.reportProblem(tag = this::class.simpleName, info = "Database removing failed") } Timber.d("Migration finish") } - private fun copyData() { - val encryptedSharedPreferences = encryptedPreferencesHelper.encryptedSharedPreferencesInstance ?: return - Timber.d("EncryptedPreferences are available") + private fun copyData(encryptedSharedPreferences: SharedPreferences) { + Timber.i("copyData(): EncryptedPreferences are available") SettingsLocalData(encryptedSharedPreferences).apply { cwaSettings.wasInteroperabilityShownAtLeastOnce = wasInteroperabilityShown() + cwaSettings.wasTracingExplanationDialogShown = wasTracingExplanationDialogShown() cwaSettings.isNotificationsRiskEnabled.update { isNotificationsRiskEnabled() } cwaSettings.isNotificationsTestEnabled.update { isNotificationsTestEnabled() } cwaSettings.numberOfRemainingSharePositiveTestResultReminders = @@ -72,23 +81,30 @@ class EncryptedPreferencesMigration @Inject constructor( submissionSettings.isSubmissionSuccessful = numberOfSuccessfulSubmissions() >= 1 submissionSettings.isAllowedToSubmitKeys = isAllowedToSubmitDiagnosisKeys() } - } - - private fun cleanData() { - encryptedPreferencesHelper.clean() + Timber.i("copyData(): EncryptedPreferences have been copied.") } private fun dropDatabase() { val file = context.getDatabasePath("coronawarnapp-db") - if (file.exists()) { - Timber.d("Removing database $file") - SQLiteDatabase.deleteDatabase(file) + if (!file.exists()) { + Timber.d("Encrypted database does not exist.") + return + } + + Timber.i("Removing database $file") + if (SQLiteDatabase.deleteDatabase(file)) { + Timber.i("Legacy encrypted database was deleted.") + } else { + Timber.e("Legacy encrypted database could not be deleted.") } } - private class SettingsLocalData(private val sharedPreferences: SharedPreferences) { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + class SettingsLocalData(private val sharedPreferences: SharedPreferences) { - fun wasInteroperabilityShown() = sharedPreferences.getBoolean(PREFERENCE_INTEROPERABILITY_WAS_USED, false) + fun wasInteroperabilityShown() = sharedPreferences.getBoolean(PKEY_INTEROPERABILITY_WAS_USED, false) + + fun wasTracingExplanationDialogShown() = sharedPreferences.getBoolean(PKEY_TRACING_EXPLANATION_WAS_SHOWN, false) fun isNotificationsRiskEnabled(): Boolean = sharedPreferences.getBoolean(PKEY_NOTIFICATIONS_RISK_ENABLED, true) @@ -98,15 +114,25 @@ class EncryptedPreferencesMigration @Inject constructor( 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" + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_INTEROPERABILITY_WAS_USED = "preference_interoperability_is_used_at_least_once" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_TRACING_EXPLANATION_WAS_SHOWN = "preference_risk_days_explanation_shown" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_NOTIFICATIONS_RISK_ENABLED = "preference_notifications_risk_enabled" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_NOTIFICATIONS_TEST_ENABLED = "preference_notifications_test_enabled" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT = "preference_positive_test_result_reminder_count" } } - private class OnboardingLocalData(private val sharedPreferences: SharedPreferences) { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + class OnboardingLocalData(private val sharedPreferences: SharedPreferences) { fun onboardingCompletedTimestamp(): Long? { val timestamp = sharedPreferences.getLong(PKEY_ONBOARDING_COMPLETED_TIMESTAMP, 0L) @@ -117,12 +143,16 @@ class EncryptedPreferencesMigration @Inject constructor( 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" + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_ONBOARDING_COMPLETED_TIMESTAMP = "preference_onboarding_completed_timestamp" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_BACKGROUND_CHECK_DONE = "preference_background_check_done" } } - private class TracingLocalData(private val sharedPreferences: SharedPreferences) { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + class TracingLocalData(private val sharedPreferences: SharedPreferences) { fun initialPollingForTestResultTimeStamp() = sharedPreferences.getLong(PKEY_POOLING_TEST_RESULT_STARTED, 0L) @@ -133,14 +163,22 @@ class EncryptedPreferencesMigration @Inject constructor( 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" + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_POOLING_TEST_RESULT_STARTED = "preference_polling_test_result_started" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_TEST_RESULT_NOTIFICATION = "preference_test_result_notification" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_HAS_RISK_STATUS_LOWERED = "preference_has_risk_status_lowered" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_TRACING_ACTIVATION_TIME = "preference_initial_tracing_activation_time" } } - private class SubmissionLocalData(private val sharedPreferences: SharedPreferences) { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + class SubmissionLocalData(private val sharedPreferences: SharedPreferences) { fun registrationToken(): String? = sharedPreferences.getString(PKEY_REGISTRATION_TOKEN, null) fun initialTestResultReceivedTimestamp(): Long? { @@ -157,11 +195,20 @@ class EncryptedPreferencesMigration @Inject constructor( 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" + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_REGISTRATION_TOKEN = "preference_registration_token" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_INITIAL_RESULT_RECEIVED_TIME = "preference_initial_result_received_time" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_DEVICE_PARING_SUCCESSFUL_TIME = "preference_device_pairing_successful_time" + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val PKEY_NUMBER_SUCCESSFUL_SUBMISSIONS = "preference_number_successful_submissions" + + @VisibleForTesting(otherwise = VisibleForTesting.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/util/security/EncryptionErrorResetTool.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptionErrorResetTool.kt similarity index 69% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptionErrorResetTool.kt index fd87b8a0ab525bacf7f0e9640088e7e87aedc36d..fd432a8b6191c943c72bf1af70ff82d1e29c3214 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/encryptionmigration/EncryptionErrorResetTool.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.util.security +package de.rki.coronawarnapp.util.encryptionmigration import android.content.Context import android.content.SharedPreferences @@ -16,12 +16,12 @@ class EncryptionErrorResetTool @Inject constructor( } var isResetNoticeToBeShown: Boolean - get() = prefs.getBoolean(PKEY_EA1851_SHOW_RESET_NOTICE, false) + get() = prefs.getBoolean(PKEY_EA2850_SHOW_RESET_NOTICE, false) set(value) = prefs.edit { - putBoolean(PKEY_EA1851_SHOW_RESET_NOTICE, value) + putBoolean(PKEY_EA2850_SHOW_RESET_NOTICE, value) } companion object { - private const val PKEY_EA1851_SHOW_RESET_NOTICE = "ea1851.reset.shownotice" + private const val PKEY_EA2850_SHOW_RESET_NOTICE = "ea2850.reset.shownotice" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityConstants.kt index 03bffdb2867296613d131f024afafeb9ffbe440d..61ec34895b73e83eb1400d283c0920d69ae7ce04 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityConstants.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityConstants.kt @@ -2,6 +2,5 @@ package de.rki.coronawarnapp.util.security object SecurityConstants { const val DIGEST_ALGORITHM = "SHA-256" - const val ENCRYPTED_SHARED_PREFERENCES_FILE = "shared_preferences_cwa" const val EXPORT_FILE_SIGNATURE_VERIFICATION_ALGORITHM = "SHA256withECDSA" } diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml index 12835781db094e25a966de1c3d5b4c89f9f50675..ed7070c5e2c0ae5249f7db018303815e1fe79676 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_nested_list_item.xml @@ -34,7 +34,6 @@ android:ellipsize="end" android:focusable="true" android:maxLines="3" - app:layout_constraintBottom_toBottomOf="@+id/contact_diary_overview_element_image" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/contact_diary_overview_element_image" app:layout_constraintTop_toTopOf="@+id/contact_diary_overview_element_image" 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 f5bac17c00372220f5e49c9b49af495e3c1aa3a9..58eeab260a9ff94589579c7c9cfbab9b29ac2b73 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Ð’Ñе още нÑма въведени лица"</string> <string name="contact_diary_person_list_no_items_subtitle">"Създайте лице и го добавете към Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº на контактите."</string> <string name="contact_diary_add_person_title">"Лице"</string> - <string name="contact_diary_add_person_text_input_name_hint">"Име, фамилиÑ"</string> + <string name="contact_diary_add_person_text_input_name_hint">"Име"</string> <string name="contact_diary_add_text_input_phone_hint">"Телефон"</string> <string name="contact_diary_add_text_input_email_hint">"Имейл"</string> <string name="contact_diary_add_person_save_button">"Запазване"</string> @@ -171,6 +171,8 @@ <!-- XBUT: Option - person encounter - inside--> <string name="contact_diary_location_visit_duration_label">"ПродължителноÑÑ‚"</string> + <!-- XTXT: Option - location encounter - duration in hours label--> + <string name="contact_diary_location_visit_duration_hour">"ч"</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">"Забележка (напр. препълнено)"</string> </resources> \ No newline at end of file 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 2b1c8215a61f296bdabe9ccd3f4b739573b5737d..66499d99ee2cc8250d19f113bbb8ba576658e2d8 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 @@ -17,25 +17,21 @@ <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> <item>"Ð¨Ð²ÐµÐ¹Ñ†Ð°Ñ€Ð¸Ñ Ðµ добавена към международното региÑтриране на излаганиÑта на риÑк"</item> - <item>"Генериране на отчети за грешки"</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> <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/> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> - <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 498b4a70adc6927a2bfcbac7324d23829ba114a6..bb2c2492c76fa29fd6c60622c01eefd48983f54c 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -302,13 +302,14 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Този период Ñе включва в изчиÑлението."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> +<!-- Dialog part 1--> <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> + <string name="risk_details_information_body_period_logged_assessment">"Приложението изтрива автоматично по-Ñтарите региÑтри, тъй като те вече не могат да Ñлужат за предотвратÑване на заразÑването."</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 --> @@ -318,15 +319,13 @@ <!-- XMSG: risk details - risk calculation wasn't possible for 24h, below behaviors --> <string name="risk_details_information_body_outdated_risk">"РегиÑтърът на излаганиÑта на риÑк не е обновÑван повече от 24 чаÑа."</string> <!-- YTXT: risk details - low risk explanation text --> - <string name="risk_details_information_body_low_risk">"Вашето ниво на риÑк от заразÑване е ниÑко, защото нÑмате региÑтрирани контакти Ñ Ð»Ð¸Ñ†Ð°, които впоÑледÑтвие Ñа били диагноÑтицирани Ñ COVID-19, или ако Ñте имали такива, те Ñа били краткотрайни и от по-голÑмо разÑтоÑние."</string> + <string name="risk_details_information_body_low_risk">"Вашето ниво на риÑк от заразÑване е ниÑко, защото нÑмате региÑтрирани контакти Ñ Ð»Ð¸Ñ†Ð°, които впоÑледÑтвие Ñа били диагноÑтицирани Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ, или ако Ñте имали такива, те Ñа били краткотрайни и от по-голÑмо разÑтоÑние."</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">"Ðивото на риÑка Ви от заразÑване е повишено, защото Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ COVID-19."</string> + <string name="risk_details_information_body_increased_risk_date">"Ðивото на риÑка Ви от заразÑване е повишено, защото Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ."</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> @@ -347,6 +346,10 @@ <!-- XACT: risk details page title --> <string name="risk_details_accessibility_title">"ВашиÑÑ‚ ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° риÑк"</string> + <!-- XHED: one time risk explanation dialog title --> + <string name="risk_details_explanation_dialog_title">"Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно функционалноÑтта за региÑтриране на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк"</string> + <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> + <string name="risk_details_explanation_dialog_faq_body">"За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð²Ð¸Ð¶Ñ‚Ðµ Ñтраницата „ЧЗВ“."</string> <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"ВашиÑÑ‚ ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° риÑк"</string> <!-- YTXT: risk details - deadman notification text --> @@ -861,8 +864,22 @@ <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 debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_title">"УдоÑтоверÑване на автентичноÑтта и Ð¿Ñ€ÐµÐ½Ð¾Ñ Ð½Ð° данни в други държави"</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">"За да бъде потвърдена автентичноÑтта на приложението Ви, Вашето уÑтройÑтво генерира уникален идентификатор Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° верÑиÑта на уÑтройÑтвото и приложението. Това гарантира, че Ñамо лицата, които дейÑтвително използват приложението Corona-Warn-App, могат да изпращат данни до техничеÑката поддръжка и изключва възможноÑтта за манипулиране на отчетите. За тази цел идентификаторът Ñе изпраща до Google. При този Ð¿Ñ€Ð¾Ñ†ÐµÑ Ðµ възможно да бъдат изпратени данни до СÐЩ или други държави, чието ниво на защита на данните може да не ÑъответÑтва на европейÑкото. По тази причина е възможно да не можете да упражните Ñвоите права отноÑно защитата на данните, предвидени в европейÑкото законодателÑтво. По конкретно, Ñлужбите за ÑигурноÑÑ‚ в Ñъответната държава биха могли да оÑъщеÑтвÑÑ‚ доÑтъп до данните и да ги анализират Ñ Google, незавиÑимо от липÑата на оÑнование за това, както и да Ñвържат тези данни Ñ Ð´Ñ€ÑƒÐ³Ð° информациÑ. Това каÑае Ñамо идентификатора, който Ñе изпраща до Google. Google не получава информациÑта от Ð’Ð°ÑˆÐ¸Ñ Ð¾Ñ‚Ñ‡ÐµÑ‚ за грешките, но е възможно да определи Вашата ÑамоличноÑÑ‚ на база на идентификатора и чрез проÑледÑване да уÑтанови, че проверката за автентичноÑÑ‚ е извършена на Вашето уÑтройÑтво."</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">"Ðко не Ñте ÑъглаÑни данните Ви да бъдат пренаÑÑни в други държави, Ð¼Ð¾Ð»Ñ Ð½Ðµ докоÑвайте бутона “Приемане и изпращанеâ€. Ð’ този Ñлучай ще можете да използвате приложението, но не и да изпращате отчети за грешки."</string> + + <!-- YTXT: Dialog title if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_title">"Желаете ли да Ñпрете анализа на грешките?"</string> <!-- YTXT: Dialog message if the log recording is stopped, and thus deleted. --> - <string name="debugging_debuglog_stop_confirmation_message">"Отчетът за грешките е изтрит. Вашето локално копие на отчета, ако Ñте запазили такова, не е изтрито."</string> + <string name="debugging_debuglog_stop_confirmation_message">"Ðко потвърдите, вÑички запиÑани данни ще бъдат изтрити. Вашето локално копие на отчета за грешките, ако Ñте запазили такова, нÑма да бъде изтрито."</string> + <!-- YTXT: Dialog confirmation button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Спиране на анализа"</string> + <!-- YTXT: Dialog discard button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Продължаване на анализа"</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 --> @@ -871,7 +888,6 @@ <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 --> diff --git a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml index 14b896c15e64f5f50dbff1c922fc46ebbe75be0f..5b15ac7a2a8af86ac0c7dde49ebabc1eebcdc3f2 100644 --- a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml @@ -12,7 +12,7 @@ <string name="contact_diary_person_list_no_items_title">"Noch keine Personen vorhanden"</string> <string name="contact_diary_person_list_no_items_subtitle">"Legen Sie eine Person an und fügen Sie sie in Ihrem Kontakt-Tagebuch hinzu."</string> <string name="contact_diary_add_person_title">"Person"</string> - <string name="contact_diary_add_person_text_input_name_hint">"Vorname, Nachname"</string> + <string name="contact_diary_add_person_text_input_name_hint">"Name"</string> <string name="contact_diary_add_text_input_phone_hint">"Telefon"</string> <string name="contact_diary_add_text_input_email_hint">"E-Mail"</string> <string name="contact_diary_add_person_save_button">"Speichern"</string> @@ -172,6 +172,8 @@ <!-- XBUT: Option - person encounter - inside--> <string name="contact_diary_location_visit_duration_label">Dauer</string> + <!-- XTXT: Option - location encounter - duration in hours label--> + <string name="contact_diary_location_visit_duration_hour">"Std."</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">Notiz (z.B. sehr voll)</string> </resources> diff --git a/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml index 55e4acd124fa1a726e68920c53a1a10419efc773..fc43df339c62d57793aa4c2bfbbde19252c350ef 100644 --- a/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml @@ -17,14 +17,14 @@ <!-- 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>Länderübergreifende Risiko-Ermittlung um Schweiz erweitert</item> + <item>Änderung der Risikokarten</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>Auf den Risikokarten entfällt die Anzeige der Anzahl aktiver Tage bzw. dass die App dauerhaft aktiv ist.</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> 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 964add2daa2f2a0cec4d6479af6c654e9a58cb26..7e1eea15b050b7302ec1a6e26ecc91b6be3094b2 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -267,7 +267,7 @@ <!-- XHED: risk details - headline, how a user should act --> <string name="risk_details_headline_behavior">"Verhalten"</string> <!-- XHED: risk details - multiline headline, bold, how to act correct --> - <string name="risk_details_subtitle_behavior">"So verhalten Sie sich richtig:"</string> + <string name="risk_details_subtitle_behavior">"So verhalten Sie sich richtig."</string> <!-- XMSG: risk details - go/stay home, something like a bullet point --> <string name="risk_details_behavior_body_stay_home">"Begeben Sie sich, wenn möglich, nach Hause bzw. bleiben Sie zu Hause."</string> <!-- XMSG: risk details - get in touch with the corresponding people, something like a bullet point --> @@ -303,13 +303,14 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Dieser Zeitraum wird berücksichtigt."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> +<!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Die Berechnung des Infektionsrisikos kann nur für die Zeiträume erfolgen, an denen die Risiko-Ermittlung aktiv war. Die Risiko- Ermittlung sollte daher dauerhaft aktiv sein. Für Ihre Risiko-Ermittlung wird der Zeitraum der letzten 14 Tage betrachtet."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Die Corona-Warn-App ist seit %s Tagen installiert. Das Infektionsrisiko wird für Zeiträume berechnet, in denen die Risiko-Ermittlung aktiv ist. Wenn Sie andere Personen getroffen haben und die Risiko-Ermittlung aktiv war, wird Ihr Infektions-Risiko berechnet."</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> <!-- 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">"Für Ihre Risiko-Ermittlung wird nur der Zeitraum der letzten 14 Tage betrachtet. In diesem Zeitraum war Ihre Risiko-Ermittlung für eine Gesamtdauer von %1$s Tagen aktiv. Ältere Tage werden automatisch gelöscht, da sie aus Sicht des Infektionsschutzes nicht mehr relevant sind."</string> + <string name="risk_details_information_body_period_logged_assessment">"Ältere Tage werden automatisch gelöscht, da sie aus Sicht des Infektionsschutzes nicht mehr relevant sind."</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 --> @@ -322,8 +323,6 @@ <string name="risk_details_information_body_low_risk">"Sie haben ein niedriges Infektionsrisiko, da keine Begegnung mit nachweislich Corona-positiv getesteten Personen aufgezeichnet wurde oder sich Ihre Begegnung auf kurze Zeit und einen größeren Abstand beschränkt hat."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"Das Infektionsrisiko wird anhand der Daten der Risiko-Ermittlung unter Berücksichtigung des Abstands und der Dauer von Begegnungen mit nachweislich Corona-positiv getesteten Personen sowie deren vermutlicher Infektiosität lokal auf Ihrem Smartphone berechnet. Ihr Infektionsrisiko ist für niemanden einsehbar und wird nicht weitergegeben."</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> <!-- 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/de/faq/#encounter_but_green"</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> @@ -348,6 +347,10 @@ <!-- XACT: risk details page title --> <string name="risk_details_accessibility_title">"Ihr Risikostatus"</string> + <!-- XHED: one time risk explanation dialog title --> + <string name="risk_details_explanation_dialog_title">"Information zur Funktionsweise der Risiko-Ermittlung"</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> <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Ihr Risikostatus"</string> <!-- YTXT: risk details - deadman notification text --> @@ -1916,7 +1919,7 @@ <!-- XHED: Duration dialog title --> <string name="duration_dialog_title">Dauer</string> <!-- XBUT: Duration dialog cancel button --> - <string name="duration_dialog_cancel_button">Cancel</string> + <string name="duration_dialog_cancel_button">Abbrechen</string> <!-- XBUT: Duration dialog ok button --> <string name="duration_dialog_ok_button">OK</string> <!-- NOTR --> 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 a22bfd48628ca00e49482eb0232fe4069fe7cac8..04adc0cbda65628fae2abfe1344b441960330109 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"No people defined yet"</string> <string name="contact_diary_person_list_no_items_subtitle">"Create a person and add them to your contact journal."</string> <string name="contact_diary_add_person_title">"Person"</string> - <string name="contact_diary_add_person_text_input_name_hint">"First name, last name"</string> + <string name="contact_diary_add_person_text_input_name_hint">"Name"</string> <string name="contact_diary_add_text_input_phone_hint">"Phone"</string> <string name="contact_diary_add_text_input_email_hint">"e-mail"</string> <string name="contact_diary_add_person_save_button">"Save"</string> @@ -171,6 +171,8 @@ <!-- XBUT: Option - person encounter - inside--> <string name="contact_diary_location_visit_duration_label">"Duration"</string> + <!-- XTXT: Option - location encounter - duration in hours label--> + <string name="contact_diary_location_visit_duration_hour">"hrs"</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">"Note (e.g. very full)"</string> </resources> \ No newline at end of file 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 a38294582a252128ce8c879e7f0876c1d713e2b3..36c5c5426805e97d153731b2a688da698836ee23 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 @@ -17,25 +17,21 @@ <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> <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>"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/> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> - <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 d21b9aeb089913ffc7d1e701b4293a6ab9c9c404..e5206e48b57afe72226a41661833b2acc1457844 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -302,13 +302,14 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"This period is included in the calculation."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> +<!-- Dialog part 1--> <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> + <string name="risk_details_information_body_period_logged_assessment">"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 --> @@ -318,15 +319,13 @@ <!-- XMSG: risk details - risk calculation wasn't possible for 24h, below behaviors --> <string name="risk_details_information_body_outdated_risk">"Your exposure logging could not be updated for more than 24 hours."</string> <!-- YTXT: risk details - low risk explanation text --> - <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> + <string name="risk_details_information_body_low_risk">"You have a low risk of infection because no exposure to people later diagnosed with coronavirus 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 exposed over a longer period of time and at close proximity to at least one person diagnosed with COVID-19."</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 coronavirus."</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> @@ -347,6 +346,10 @@ <!-- XACT: risk details page title --> <string name="risk_details_accessibility_title">"Your Risk Status"</string> + <!-- XHED: one time risk explanation dialog title --> + <string name="risk_details_explanation_dialog_title">"Information about exposure logging functionality"</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> <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Your Risk Status"</string> <!-- YTXT: risk details - deadman notification text --> @@ -861,8 +864,22 @@ <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 debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_title">"Verification of Authenticity and Data Transfer to Third Countries"</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">"To confirm the authenticity of your app, your smartphone generates a unique identifier that contains information about the version of your smartphone and the app. This is necessary to ensure that only people who actually use the Corona-Warn-App can send data to technical support and rule out manipulated error reports. To do so, the identifier is sent to Google. In this process, data may be transferred to the U.S. or other third countries, where the level of data privacy may not correspond to European law and you may not be able to enforce your European rights to data privacy. In particular, security services in the third country could access and analyze the data with Google, even if there is no grounds for suspicion, and link this data with other information, for example. This only involves the identifier that is sent to Google. Google does not receive the information from your error report, however, Google may be able to determine your identity based on the identifier and track that the authenticity check was performed on your smartphone."</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">"If you do not consent to this transfer of your data to a third country, please do not tap on “Accept and Sendâ€. You will still be able to use the app, but you cannot send error reports."</string> + + <!-- YTXT: Dialog title if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_title">"Stop the error analysis?"</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> + <string name="debugging_debuglog_stop_confirmation_message">"If you do so, all recorded data will be deleted. If you have saved a local copy of the error report, it will not be deleted."</string> + <!-- YTXT: Dialog confirmation button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Stop Analysis"</string> + <!-- YTXT: Dialog discard button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Continue Analysis"</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 --> @@ -871,7 +888,6 @@ <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 --> 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 e5e2c254fd356e1a863dc83095273658a237032e..6311de577d870a2f9caf2eb764b960133e8ee37c 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Nie zdefiniowano jeszcze żadnych osób"</string> <string name="contact_diary_person_list_no_items_subtitle">"Utwórz osobÄ™ i dodaj jÄ… do dziennika kontaktów."</string> <string name="contact_diary_add_person_title">"Osoba"</string> - <string name="contact_diary_add_person_text_input_name_hint">"ImiÄ™, nazwisko"</string> + <string name="contact_diary_add_person_text_input_name_hint">"ImiÄ™ i nazwisko"</string> <string name="contact_diary_add_text_input_phone_hint">"Telefon"</string> <string name="contact_diary_add_text_input_email_hint">"E-mail"</string> <string name="contact_diary_add_person_save_button">"Zapisz"</string> @@ -171,6 +171,8 @@ <!-- XBUT: Option - person encounter - inside--> <string name="contact_diary_location_visit_duration_label">"Czas trwania"</string> + <!-- XTXT: Option - location encounter - duration in hours label--> + <string name="contact_diary_location_visit_duration_hour">"godz."</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">"Uwaga (np. bardzo peÅ‚ny)"</string> </resources> \ No newline at end of file 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 5bc9b0023e7e19071f869c456e2e5f2844c2b34e..1c75cff3d82b6ecbf567a85f0e9b94c1c245fdc9 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 @@ -17,25 +17,21 @@ <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> <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>"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/> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> - <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 37ffe028420b7ee05fc4b06555231732833cf717..c6761ed9046e131ba3baa8af40abac8c6c5138f5 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -302,13 +302,14 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Ten okres jest uwzglÄ™dniony w obliczeniu."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> +<!-- Dialog part 1--> <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> + <string name="risk_details_information_body_period_logged_assessment">"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 --> @@ -318,15 +319,13 @@ <!-- XMSG: risk details - risk calculation wasn't possible for 24h, below behaviors --> <string name="risk_details_information_body_outdated_risk">"Rejestrowanie narażenia nie mogÅ‚o zostać zaktualizowane przez okres dÅ‚uższy niż 24 godziny."</string> <!-- YTXT: risk details - low risk explanation text --> - <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> + <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 koronawirusa, 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ż byÅ‚eÅ›(-aÅ›) narażony(-a) na dÅ‚uższy, bliski kontakt z co najmniej jednÄ… osobÄ…, u której zdiagnozowano COVID-19."</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 koronawirusa."</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> @@ -347,6 +346,10 @@ <!-- XACT: risk details page title --> <string name="risk_details_accessibility_title">"Twój status ryzyka"</string> + <!-- XHED: one time risk explanation dialog title --> + <string name="risk_details_explanation_dialog_title">"Informacje o funkcjonalnoÅ›ci rejestrowania narażenia"</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> <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Twój status ryzyka"</string> <!-- YTXT: risk details - deadman notification text --> @@ -861,8 +864,22 @@ <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 debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_title">"Weryfikacja autentycznoÅ›ci i przekazywanie danych do krajów trzecich"</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">"Aby potwierdzić autentyczność aplikacji, smartfon generuje unikalny identyfikator zawierajÄ…cy informacje o wersji smartfona i aplikacji. Jest to konieczne w celu zapewnienia, by tylko osoby, które faktycznie używajÄ… aplikacji Corona-Warn-App mogÅ‚y wysyÅ‚ać dane do pomocy technicznej, i wykluczenia zmanipulowanych raportów o bÅ‚Ä™dach. W tym celu identyfikator jest wysyÅ‚any do Google. W tym procesie dane mogÄ… być przekazywane do Stanów Zjednoczonych lub innych krajów trzecich, gdzie poziom prywatnoÅ›ci danych może nie odpowiadać prawu europejskiemu i możesz nie być w stanie wyegzekwować swoich europejskich praw do prywatnoÅ›ci danych. W szczególnoÅ›ci organy ds. bezpieczeÅ„stwa w kraju trzecim mogÅ‚yby uzyskać dostÄ™p do danych i analizować je przy pomocy Google, nawet jeÅ›li nie ma podstaw do podejrzeÅ„, i na przykÅ‚ad Å‚Ä…czyć te dane z innymi informacjami. Dotyczy to tylko identyfikatora wysyÅ‚anego do Google. Google nie otrzymuje informacji z Twojego raportu o bÅ‚Ä™dzie, może jednak okreÅ›lić TwojÄ… tożsamość na podstawie identyfikatora i poprzez przeÅ›ledzenie danych ustalić, że sprawdzanie autentycznoÅ›ci zostaÅ‚o przeprowadzone na Twoim smartfonie."</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">"JeÅ›li nie wyrażasz zgody na przekazanie swoich danych do kraju trzeciego, nie klikaj opcji „Akceptuj i wyÅ›lijâ€. Nadal bÄ™dziesz mieć możliwość korzystania z aplikacji, ale wysyÅ‚anie raportów o bÅ‚Ä™dach bÄ™dzie niemożliwe."</string> + + <!-- YTXT: Dialog title if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_title">"Zatrzymać analizÄ™ bÅ‚Ä™du?"</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> + <string name="debugging_debuglog_stop_confirmation_message">"JeÅ›li to zrobisz, wszystkie zapisane dane zostanÄ… usuniÄ™te. JeÅ›li zapisano lokalnÄ… kopiÄ™ raportu o bÅ‚Ä™dzie, nie zostanie ona usuniÄ™ta."</string> + <!-- YTXT: Dialog confirmation button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Zatrzymaj analizÄ™"</string> + <!-- YTXT: Dialog discard button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Kontynuuj analizÄ™"</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 --> @@ -871,7 +888,6 @@ <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 --> 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 c20ccc9263e7b6b1efc1b3f84ab5e9454564b60a..9f9b2d09386e71ee7441376f754ce609fc69ec49 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Nicio persoană definită deocamdată"</string> <string name="contact_diary_person_list_no_items_subtitle">"CreaÈ›i o persoană È™i adăugaÈ›i-o la jurnalul dvs. de contacte."</string> <string name="contact_diary_add_person_title">"Persoană"</string> - <string name="contact_diary_add_person_text_input_name_hint">"Prenume, nume"</string> + <string name="contact_diary_add_person_text_input_name_hint">"Nume"</string> <string name="contact_diary_add_text_input_phone_hint">"Telefon"</string> <string name="contact_diary_add_text_input_email_hint">"e-mail"</string> <string name="contact_diary_add_person_save_button">"Salvare"</string> @@ -171,6 +171,8 @@ <!-- XBUT: Option - person encounter - inside--> <string name="contact_diary_location_visit_duration_label">"Durată"</string> + <!-- XTXT: Option - location encounter - duration in hours label--> + <string name="contact_diary_location_visit_duration_hour">"ore"</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">"Notă (de ex., foarte plin)"</string> </resources> \ No newline at end of file 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 b94156bacd13e0489712e0d481d712c0f3b60e90..0229a5b41b90a83927a9fedaf274d2eb05ca7f40 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 @@ -17,25 +17,21 @@ <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> <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>"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/> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> - <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 ce6862eda5e9ecc090476dddc2648c28bec826a7..3510d8122bc8d3faf590e06b8a16bb5a93ac0077 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -302,13 +302,14 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Această perioadă este inclusă în calcul."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> +<!-- Dialog part 1--> <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> + <string name="risk_details_information_body_period_logged_assessment">"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 --> @@ -318,15 +319,13 @@ <!-- XMSG: risk details - risk calculation wasn't possible for 24h, below behaviors --> <string name="risk_details_information_body_outdated_risk">"ÃŽnregistrarea în jurnal a expunerilor dvs. nu a putut fi actualizată timp de peste 24 de ore."</string> <!-- YTXT: risk details - low risk explanation text --> - <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> + <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 coronavirus 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 o perioadă mai lungă de timp È™i în strânsă proximitate cu cel puÈ›in o persoană diagnosticată cu COVID-19."</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 coronavirus."</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> @@ -347,6 +346,10 @@ <!-- XACT: risk details page title --> <string name="risk_details_accessibility_title">"Starea riscului dvs."</string> + <!-- XHED: one time risk explanation dialog title --> + <string name="risk_details_explanation_dialog_title">"InformaÈ›ii despre funcÈ›ionalitatea de înregistrare în jurnal a expunerilor"</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> <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Starea riscului dvs."</string> <!-- YTXT: risk details - deadman notification text --> @@ -861,8 +864,22 @@ <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 debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_title">"Verificarea autenticității È™i a transferului de date către țări terÈ›e"</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">"Pentru a confirma autenticitatea aplicaÈ›iei dvs., smartphone-ul dvs. generează un identificator unic ce conÈ›ine informaÈ›ii despre versiunea smartphone-ului È™i a aplicaÈ›iei. Acest lucru este necesar pentru a ne asigura că doar persoanele care utilizează efectiv aplicaÈ›ia Corona-Warn pot trimite date la suportul tehnic È™i să excludem rapoartele de erori manipulate. ÃŽn acest scop, identificatorul este trimis la Google. ÃŽn acest proces, pot fi transferate date în SUA sau în alte țări terÈ›e, unde nivelul de confidenÈ›ialitate a datelor poate să nu corespundă cu legislaÈ›ia europeană È™i se poate să nu vă puteÈ›i exercita drepturile europene în ceea ce priveÈ™te confidenÈ›ialitatea datelor. ÃŽn special, serviciile de securitate din țările terÈ›e ar putea să vă acceseze È™i să vă analizeze datele de la Google, chiar dacă nu există temei de suspiciune, È™i pot, de exemplu, lega aceste date de alte informaÈ›ii. Acest lucru se referă doar la identificatorul care este trimis la Google. Google nu primeÈ™te informaÈ›iile din raportul dvs. de erori, însă Google ar putea determina identitatea dvs. pe baza identificatorului È™i ar putea urmări că a fost efectuată verificarea autenticității pe smartphone-ul dvs."</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">"Dacă nu sunteÈ›i de acord cu acest transfer al datelor dvs. către o È›ară terță, nu apăsaÈ›i pe „Sunt de acord È™i trimitereâ€. VeÈ›i putea utiliza aplicaÈ›ia în continuare, dar nu veÈ›i putea trimite rapoarte de erori."</string> + + <!-- YTXT: Dialog title if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_title">"OpriÈ›i analiza erorilor?"</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> + <string name="debugging_debuglog_stop_confirmation_message">"Dacă faceÈ›i acest lucru, toate datele înregistrate vor fi È™terse. Dacă aÈ›i salvat o copie locală a raportului de erori, aceasta nu va fi È™tearsă."</string> + <!-- YTXT: Dialog confirmation button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Oprire analiză"</string> + <!-- YTXT: Dialog discard button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Continuare analiză"</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 --> @@ -871,7 +888,6 @@ <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 --> 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 2f3e132f5c7cb581c4766568271ba070be369230..1507f027e54926a40bbf899ee12e63f249d00b2c 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Henüz hiçbir kiÅŸi tanımlanmadı"</string> <string name="contact_diary_person_list_no_items_subtitle">"Bir kiÅŸi oluÅŸturun ve temas güncenize ekleyin."</string> <string name="contact_diary_add_person_title">"KiÅŸi"</string> - <string name="contact_diary_add_person_text_input_name_hint">"Ad, soyadı"</string> + <string name="contact_diary_add_person_text_input_name_hint">"Ad"</string> <string name="contact_diary_add_text_input_phone_hint">"Telefon"</string> <string name="contact_diary_add_text_input_email_hint">"e-posta"</string> <string name="contact_diary_add_person_save_button">"Kaydet"</string> @@ -171,6 +171,8 @@ <!-- XBUT: Option - person encounter - inside--> <string name="contact_diary_location_visit_duration_label">"Süre"</string> + <!-- XTXT: Option - location encounter - duration in hours label--> + <string name="contact_diary_location_visit_duration_hour">"sa"</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">"Not (ör. çok dolu)"</string> </resources> \ No newline at end of file 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 6a75176cfdb4ecc1c98f0bf00c5dfeef5d2dbc3d..eccf0f24383e3914eddc7c32f54b5c1bd057d595 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 @@ -17,25 +17,21 @@ <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> <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>"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/> </string-array> <!-- XTXT: URL destinations for the lables in new_release_linkified_labels --> <string-array name="new_release_target_urls"> <item/> - <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 f408e7698649008c23232c2964420735064486cf..a9d5088a21fe8c4c03457109785beb8af9e9f1f9 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -302,13 +302,14 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Bu dönem hesaplamaya dahil edildi."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> +<!-- Dialog part 1--> <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> + <string name="risk_details_information_body_period_logged_assessment">"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 --> @@ -318,15 +319,13 @@ <!-- XMSG: risk details - risk calculation wasn't possible for 24h, below behaviors --> <string name="risk_details_information_body_outdated_risk">"Maruz kalma günlüğünüz 24 saatten uzun süre için güncellenemedi."</string> <!-- YTXT: risk details - low risk explanation text --> - <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> + <string name="risk_details_information_body_low_risk">"Daha sonra koronavirüs 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">"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> + <string name="risk_details_information_body_increased_risk_date">"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> <!-- 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> @@ -347,6 +346,10 @@ <!-- XACT: risk details page title --> <string name="risk_details_accessibility_title">"Risk Durumunuz"</string> + <!-- XHED: one time risk explanation dialog title --> + <string name="risk_details_explanation_dialog_title">"Maruz kalma günlüğü iÅŸlevi hakkında bilgi"</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> <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Risk Durumunuz"</string> <!-- YTXT: risk details - deadman notification text --> @@ -861,8 +864,22 @@ <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 debug legal screen privacy card - LEGAL STRING MOVED TO THIS FILE ON PURPOSE FOR TRANSLATION --> + <string name="debugging_debuglog_legal_privacy_card_title">"Orijinallik DoÄŸrulaması ve Üçüncü Ãœlkelere Veri Aktarımı"</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">"Akıllı telefonunuz, uygulamanızın orijinalliÄŸini teyit etmek için akıllı telefonunuzun ve uygulamanızın sürümüne iliÅŸkin bilgileri içeren bir benzersiz tanıtıcı oluÅŸturur. Bu, teknik destek birimine yalnızca Corona-Warn-App’i gerçekten kullanan kiÅŸilerin veri aktarabilmesini ve üzerinde oynanmış hata raporlarının ortadan kaldırılabilmesini saÄŸlamak için gereklidir. Bunun için, tanıtıcı Google’a gönderilir. Bu iÅŸlem kapsamında veriler, ABD’ye veya veri gizliliÄŸi düzeyinin Avrupa kanunlarına uygun olmadığı ve veri gizliliÄŸi konusunda Avrupa’daki haklarınızı kullanamayabileceÄŸiniz baÅŸka üçüncü ülkelere aktarılabilir. Özellikle, üçüncü ülkedeki güvenlik servisleri herhangi bir şüphe sebebi olmasa bile verilere eriÅŸebilir, Google ile verileri analiz edebilir ve bu verileri örneÄŸin baÅŸka bilgilerle baÄŸlantılandırabilir. Buna yalnızca Google’a gönderilen tanıtıcı dahildir. Google hata raporunuzda yer alan bilgileri almaz ancak tanıtıcıya göre kimliÄŸinizi belirleyebilir ve akıllı telefonunuzda orijinallik kontrolünün yapıldığını takip edebilir."</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">"Verilerinizin üçüncü bir ülkeye aktarılmasına izin vermiyorsanız lütfen “Kabul Et ve Gönder†öğesine dokunmayın. Uygulamayı kullanmaya devam edebileceksiniz ancak hata raporlarını gönderemeyeceksiniz."</string> + + <!-- YTXT: Dialog title if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_title">"Hata analizi durdurulsun mu?"</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> + <string name="debugging_debuglog_stop_confirmation_message">"Bunu seçerseniz kaydedilen tüm veriler silinir. Hata raporunun yerel bir kopyasını kaydettiyseniz bu kopya silinmez."</string> + <!-- YTXT: Dialog confirmation button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Analizi Durdur"</string> + <!-- YTXT: Dialog discard button if the log recording is stopped, and thus deleted. --> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Analizi Devam Ettir"</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 --> @@ -871,7 +888,6 @@ <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 --> 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 83bd6e5955907c1d696fb6e148eb5709a6e2ab80..cb4764e53467542422f6af1f59ab458ce1268a68 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 @@ -12,7 +12,7 @@ <string name="contact_diary_person_list_no_items_title">"No people defined yet"</string> <string name="contact_diary_person_list_no_items_subtitle">"Create a person and add them to your contact journal."</string> <string name="contact_diary_add_person_title">"Person"</string> - <string name="contact_diary_add_person_text_input_name_hint">"First name, last name"</string> + <string name="contact_diary_add_person_text_input_name_hint">"Name"</string> <string name="contact_diary_add_text_input_phone_hint">"Phone"</string> <string name="contact_diary_add_text_input_email_hint">"e-mail"</string> <string name="contact_diary_add_person_save_button">"Save"</string> @@ -146,8 +146,6 @@ <string name="contact_diary_export_location_duration_prefix" translatable="false">"Dauer"</string> <!-- XTXT: Location duration postfix in the contact journal export--> <string name="contact_diary_export_location_duration_suffix" translatable="false">"h"</string> - <!-- XTXT: Location duration prefix in the contact journal overview--> - <string name="contact_diary_overview_location_duration_suffix" translatable="false">"Std."</string> <!-- XHED: Title for the contact diary comment info screen --> <string name="contact_diary_comment_info_screen_title">"Note"</string> @@ -201,6 +199,8 @@ <!-- XBUT: Option - person encounter - inside--> <string name="contact_diary_location_visit_duration_label">"Duration"</string> + <!-- XTXT: Option - location encounter - duration in hours label--> + <string name="contact_diary_location_visit_duration_hour">"hrs"</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">"Note (e.g. very full)"</string> </resources> 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 304fd087ff53e1a48c2f599221162111d9748122..b9eb2ce9a87edcd502e4b15a48d15bbcbf705dbc 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 @@ -18,25 +18,25 @@ <!-- XHED: Titles for the release info screen bullet points --> <string-array name="new_release_title"> <item>"Switzerland Added to Transnational Exposure Logging"</item> - <item>"Generation of Error Reports"</item> + <item>Änderung der Risikokarten</item> </string-array> <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> <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> + <item>Auf den Risikokarten entfällt die Anzeige der Anzahl aktiver Tage bzw. dass die App dauerhaft aktiv ist.</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> <string-array name="new_release_linkified_labels"> <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 /> </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 ab3d6252c0e7a00591e69b2fcabac9f9b3c87283..15f0e3a8057ecd49476685ca1868a67f94a7d14c 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -307,13 +307,14 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"This period is included in the calculation."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> +<!-- Dialog part 1--> <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> + <string name="risk_details_information_body_period_logged_assessment">"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 --> @@ -323,11 +324,11 @@ <!-- XMSG: risk details - risk calculation wasn't possible for 24h, below behaviors --> <string name="risk_details_information_body_outdated_risk">"Your exposure logging could not be updated for more than 24 hours."</string> <!-- YTXT: risk details - low risk explanation text --> - <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> + <string name="risk_details_information_body_low_risk">"You have a low risk of infection because no exposure to people later diagnosed with coronavirus 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: 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 exposed over a longer period of time and at close proximity to at least one person diagnosed with COVID-19."</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 coronavirus."</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> @@ -348,6 +349,11 @@ <!-- XACT: risk details page title --> <string name="risk_details_accessibility_title">"Your Risk Status"</string> + <!-- XHED: one time risk explanation dialog title --> + <string name="risk_details_explanation_dialog_title">"Information about exposure logging functionality"</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> + <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Your Risk Status"</string> <!-- YTXT: risk details - deadman notification text --> @@ -638,8 +644,6 @@ <string name="settings_tracing_status_connection_headline">"Open Internet connection"</string> <!-- 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">"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 --> @@ -871,20 +875,20 @@ <string name="debugging_debuglog_status_additional_infos">"Current size: %1$s (uncompressed)"</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> + <string name="debugging_debuglog_legal_privacy_card_title">"Verification of Authenticity and Data Transfer to Third Countries"</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> + <string name="debugging_debuglog_legal_privacy_card_first_section">"To confirm the authenticity of your app, your smartphone generates a unique identifier that contains information about the version of your smartphone and the app. This is necessary to ensure that only people who actually use the Corona-Warn-App can send data to technical support and rule out manipulated error reports. To do so, the identifier is sent to Google. In this process, data may be transferred to the U.S. or other third countries, where the level of data privacy may not correspond to European law and you may not be able to enforce your European rights to data privacy. In particular, security services in the third country could access and analyze the data with Google, even if there is no grounds for suspicion, and link this data with other information, for example. This only involves the identifier that is sent to Google. Google does not receive the information from your error report, however, Google may be able to determine your identity based on the identifier and track that the authenticity check was performed on your smartphone."</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> + <string name="debugging_debuglog_legal_privacy_card_second_section">"If you do not consent to this transfer of your data to a third country, please do not tap on “Accept and Sendâ€. You will still be able to use the app, but you cannot send error reports."</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> + <string name="debugging_debuglog_stop_confirmation_title">"Stop the error analysis?"</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> + <string name="debugging_debuglog_stop_confirmation_message">"If you do so, all recorded data will be deleted. If you have saved a local copy of the error report, it will not be 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> + <string name="debugging_debuglog_stop_confirmation_confirmation_button">"Stop Analysis"</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> + <string name="debugging_debuglog_stop_confirmation_discard_button">"Continue Analysis"</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 --> @@ -893,7 +897,6 @@ <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 --> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt index e189bf3933a15a736213cf6371c8dd9ec56c5904..14295ba7d88a14545b343306437daba3cc41f5ad 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.datadonation.analytics.common import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import io.kotest.matchers.shouldBe +import org.joda.time.Instant import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -26,4 +27,60 @@ class PpaDataExtensionsTest : BaseTest() { PpaData.PPAFederalState.FEDERAL_STATE_SH.federalStateShortName shouldBe "SH" PpaData.PPAFederalState.FEDERAL_STATE_TH.federalStateShortName shouldBe "TH" } + + @Test + fun `days since most recent date at risk level at test registration are calculated correctly`() { + val march15At2200 = Instant.parse("2021-03-15T22:00:00.000Z") + val march17At0500 = Instant.parse("2021-03-17T05:00:00.000Z") + calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( + lastChangeCheckedRiskLevelTimestamp = march15At2200, + testRegisteredAt = march17At0500 + ) shouldBe 2 + } + + @Test + fun `days between most recent risk level change and test registration should be 0 if on same day`() { + val march15At0500 = Instant.parse("2021-03-15T05:00:00.000Z") + val march15At2200 = Instant.parse("2021-03-15T22:00:00.000Z") + calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( + lastChangeCheckedRiskLevelTimestamp = march15At0500, + testRegisteredAt = march15At2200 + ) shouldBe 0 + } + + @Test + fun `days should be -1 if lastChangeCheckedRiskLevelTimestamp is null`() { + val march15At0500 = Instant.parse("2021-03-15T05:00:00.000Z") + calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( + lastChangeCheckedRiskLevelTimestamp = null, + testRegisteredAt = march15At0500 + ) shouldBe -1 + } + + @Test + fun `days should be -1 if testRegisteredAt is null`() { + val march15At0500 = Instant.parse("2021-03-15T05:00:00.000Z") + calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( + lastChangeCheckedRiskLevelTimestamp = march15At0500, + testRegisteredAt = null + ) shouldBe -1 + } + + @Test + fun `days should be -1 if both are null`() { + calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( + lastChangeCheckedRiskLevelTimestamp = null, + testRegisteredAt = null + ) shouldBe -1 + } + + @Test + fun `days should be -1 if order is reversed`() { + val march20At2200 = Instant.parse("2021-03-20T22:00:00.000Z") + val march10At0500 = Instant.parse("2021-03-10T05:00:00.000Z") + calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( + lastChangeCheckedRiskLevelTimestamp = march20At2200, + testRegisteredAt = march10At0500 + ) shouldBe -1 + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt index fe9666112e81d20e74876399cd76ef22b47b0ada..859cf662132c0e10231b12ede552934534fe0a2b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt @@ -7,14 +7,18 @@ import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.util.TimeStamper 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.verify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest +import org.joda.time.Days import org.joda.time.Hours import org.joda.time.Instant +import org.joda.time.LocalTime import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -53,13 +57,24 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { val riskLevelAtTestRegistration = mockFlowPreference(-1) every { analyticsKeySubmissionStorage.riskLevelAtTestRegistration } returns riskLevelAtTestRegistration val hoursSinceHighRiskWarningAtTestRegistration = mockFlowPreference(-1) - every { analyticsKeySubmissionStorage.hoursSinceHighRiskWarningAtTestRegistration } returns hoursSinceHighRiskWarningAtTestRegistration + every { analyticsKeySubmissionStorage.hoursSinceHighRiskWarningAtTestRegistration } returns + hoursSinceHighRiskWarningAtTestRegistration + coEvery { + riskLevelSettings.lastChangeCheckedRiskLevelTimestamp + } returns now + .minus(Days.days(2).toStandardDuration()).toDateTime().toLocalDate() + .toDateTime(LocalTime(22, 0)).toInstant() + val daysSinceMostRecentDateAtRiskLevelAtTestRegistration = mockFlowPreference(0) + every { analyticsKeySubmissionStorage.daysSinceMostRecentDateAtRiskLevelAtTestRegistration } returns + daysSinceMostRecentDateAtRiskLevelAtTestRegistration + every { analyticsKeySubmissionStorage.clear() } just Runs runBlockingTest { val collector = createInstance() collector.reportTestRegistered() verify { testRegisteredAt.update(any()) } verify { riskLevelAtTestRegistration.update(any()) } verify { hoursSinceHighRiskWarningAtTestRegistration.update(any()) } + verify { daysSinceMostRecentDateAtRiskLevelAtTestRegistration.update(any()) } } } @@ -119,6 +134,7 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { coEvery { analyticsSettings.analyticsEnabled.value } returns true val flow = mockFlowPreference(now.millis) every { analyticsKeySubmissionStorage.testResultReceivedAt } returns flow + runBlockingTest { val collector = createInstance() collector.reportPositiveTestResultReceived() 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 bbb25be9800a339a9614195855606218a98c33a5..c9f23258a33df6779e3f12989a9dd4f05436fc1b 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 @@ -1,14 +1,11 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission -import de.rki.coronawarnapp.risk.RiskLevelSettings import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.impl.annotations.MockK -import org.joda.time.Days import org.joda.time.Hours import org.joda.time.Instant -import org.joda.time.LocalTime import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -16,7 +13,6 @@ import testhelpers.BaseTest class AnalyticsKeySubmissionRepositoryTest : BaseTest() { @MockK lateinit var storage: AnalyticsKeySubmissionStorage - @MockK lateinit var riskLevelSettings: RiskLevelSettings private val now = Instant.now() @@ -26,8 +22,7 @@ class AnalyticsKeySubmissionRepositoryTest : BaseTest() { } private fun createInstance() = AnalyticsKeySubmissionRepository( - storage, - riskLevelSettings + storage ) @Test @@ -39,27 +34,35 @@ class AnalyticsKeySubmissionRepositoryTest : BaseTest() { } @Test - fun `hours since test result when not submitted should be 0`() { + fun `hours since test result when not submitted should be -1`() { coEvery { storage.submittedAt.value } returns -1 coEvery { storage.testResultReceivedAt.value } returns now.minus(Hours.hours(5).toStandardDuration()).millis val repository = createInstance() - repository.hoursSinceTestResult shouldBe 0 + repository.hoursSinceTestResult shouldBe -1 } @Test - fun `hours since test result when not received or submitted should be 0`() { + fun `hours since test result should be -1 when testResultReceivedAt is missing`() { + coEvery { storage.submittedAt.value } returns now.minus(Hours.hours(5).toStandardDuration()).millis + coEvery { storage.testResultReceivedAt.value } returns -1 + val repository = createInstance() + repository.hoursSinceTestResult shouldBe -1 + } + + @Test + fun `hours since test result when not received or submitted should be -1`() { coEvery { storage.submittedAt.value } returns -1 coEvery { storage.testResultReceivedAt.value } returns -1 val repository = createInstance() - repository.hoursSinceTestResult shouldBe 0 + repository.hoursSinceTestResult shouldBe -1 } @Test - fun `hours since test result should be 0 when dates have been manipulated`() { + fun `hours since test result should be -1 when dates have been manipulated`() { coEvery { storage.submittedAt.value } returns now.minus(Hours.hours(5).toStandardDuration()).millis coEvery { storage.testResultReceivedAt.value } returns now.millis val repository = createInstance() - repository.hoursSinceTestResult shouldBe 0 + repository.hoursSinceTestResult shouldBe -1 } @Test @@ -71,47 +74,18 @@ class AnalyticsKeySubmissionRepositoryTest : BaseTest() { } @Test - fun `hours since test registration should be 0 if not submitted`() { + fun `hours since test registration should be -1 if not submitted`() { coEvery { storage.submittedAt.value } returns -1 coEvery { storage.testRegisteredAt.value } returns now.minus(Hours.hours(5).toStandardDuration()).millis val repository = createInstance() - repository.hoursSinceTestRegistration shouldBe 0 + repository.hoursSinceTestRegistration shouldBe -1 } @Test - fun `days since most recent date at risk level at test registration are calculated correctly`() { - coEvery { - riskLevelSettings.lastChangeCheckedRiskLevelTimestamp - } returns now - .minus(Days.days(2).toStandardDuration()).toDateTime().toLocalDate() - .toDateTime(LocalTime(22, 0)).toInstant() - coEvery { storage.testRegisteredAt.value } returns - now.toDateTime().toLocalDate().toDateTime(LocalTime(5, 0)).millis - val repository = createInstance() - repository.daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 2 - } - - @Test - fun `days between most recent risk level change and test registration should be 0 if on same day`() { - coEvery { - riskLevelSettings.lastChangeCheckedRiskLevelTimestamp - } returns now - .toDateTime().toLocalDate() - .toDateTime(LocalTime(13, 0)).toInstant() - coEvery { storage.testRegisteredAt.value } returns - now.toDateTime().toLocalDate().toDateTime(LocalTime(14, 0)).millis - val repository = createInstance() - repository.daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 0 - } - - @Test - fun `days should be 0 if lastChangeCheckedRiskLevelTimestamp is null`() { - coEvery { - riskLevelSettings.lastChangeCheckedRiskLevelTimestamp - } returns null - coEvery { storage.testRegisteredAt.value } returns - now.toDateTime().toLocalDate().toDateTime(LocalTime(14, 0)).millis + fun `hours since test registration should be -1 if testRegisteredAt is missing`() { + coEvery { storage.submittedAt.value } returns now.minus(Hours.hours(5).toStandardDuration()).millis + coEvery { storage.testRegisteredAt.value } returns -1 val repository = createInstance() - repository.daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 0 + repository.hoursSinceTestRegistration shouldBe -1 } } 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 f128a602166778abbbcae049ec4a8e24a0fefd15..ac97cc321836a06795df3bb79985366634134468 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 @@ -4,8 +4,6 @@ import de.rki.coronawarnapp.appconfig.AnalyticsConfig import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings -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.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeStamper @@ -31,8 +29,6 @@ import testhelpers.preferences.mockFlowPreference 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 @@ -43,16 +39,16 @@ class TestResultDonorTest : BaseTest() { @BeforeEach fun setUp() { MockKAnnotations.init(this, true) + with(testResultDonorSettings) { + every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(baseTime) + every { riskLevelTurnedRedTime } returns mockFlowPreference(baseTime) + every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW) + } every { timeStamper.nowUTC } returns baseTime - every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp } returns baseTime - every { testResultDonorSettings.riskLevelAtTestRegistration } returns - mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW) every { submissionSettings.initialTestResultReceivedAt } returns baseTime testResultDonor = TestResultDonor( testResultDonorSettings, - riskLevelSettings, - riskLevelStorage, timeStamper, submissionSettings ) @@ -108,7 +104,9 @@ class TestResultDonorTest : BaseTest() { val timeDayBefore = baseTime.minus(Duration.standardDays(1)) every { submissionSettings.initialTestResultReceivedAt } returns timeDayBefore - every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp } returns timeDayBefore + every { testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference( + timeDayBefore + ) val donation = testResultDonor.beginDonation(TestRequest) donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>() @@ -141,6 +139,86 @@ class TestResultDonorTest : BaseTest() { } } + @Test + fun `No donation when test is POSITIVE and HighRisk but riskLevelTurnedRedTime is missing`() = + runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime) + every { riskLevelTurnedRedTime } returns mockFlowPreference(null) + every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) + } + testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution + } + + @Test + fun `No donation when test is NEGATIVE and HighRisk but riskLevelTurnedRedTime is missing`() = + runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime) + every { riskLevelTurnedRedTime } returns mockFlowPreference(null) + every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) + } + testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution + } + + @Test + fun `No donation when test is POSITIVE and HighRisk but mostRecentDateWithHighOrLowRiskLevel is missing`() = + runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime) + every { riskLevelTurnedRedTime } returns mockFlowPreference(baseTime) + every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null) + every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) + } + testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution + } + + @Test + fun `No donation when test is NEGATIVE and HighRisk but mostRecentDateWithHighOrLowRiskLevel is missing`() = + runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime) + every { riskLevelTurnedRedTime } returns mockFlowPreference(baseTime) + every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null) + every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) + } + testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution + } + + @Test + fun `No donation when test is POSITIVE and LowRisk but mostRecentDateWithHighOrLowRiskLevel is missing`() = + runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime) + every { riskLevelTurnedRedTime } returns mockFlowPreference(null) + every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null) + } + testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution + } + + @Test + fun `No donation when test is NEGATIVE and LowRisk but mostRecentDateWithHighOrLowRiskLevel is missing`() = + runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference(baseTime) + every { riskLevelTurnedRedTime } returns mockFlowPreference(null) + every { mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null) + } + testResultDonor.beginDonation(TestRequest) shouldBe TestResultDonor.TestResultMetadataNoContribution + } + @Test fun `Donation is collected when test result is NEGATIVE`() { runBlockingTest { @@ -160,6 +238,61 @@ class TestResultDonorTest : BaseTest() { } } + @Test + fun `Scenario 1 LowRisk`() = runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.NEGATIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference( + Instant.parse("2021-03-20T20:00:00Z") + ) + every { riskLevelTurnedRedTime } returns mockFlowPreference(null) // No High risk + every { mostRecentDateWithHighOrLowRiskLevel } returns + mockFlowPreference(Instant.parse("2021-03-18T00:00:00Z")) + every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW) + } + every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z") + every { submissionSettings.initialTestResultReceivedAt } returns Instant.parse("2021-03-20T00:00:00Z") + + val donation = testResultDonor.beginDonation(TestRequest) + donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>() + with(donation.testResultMetadata) { + testResult shouldBe PpaData.PPATestResult.TEST_RESULT_NEGATIVE + hoursSinceTestRegistration shouldBe 20 // hours + riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW + hoursSinceHighRiskWarningAtTestRegistration shouldBe -1 // expected for low risk + daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 2 // days + } + } + + @Test + fun `Scenario 2 HighRisk`() = runBlockingTest { + with(testResultDonorSettings) { + every { testScannedAfterConsent } returns mockFlowPreference(true) + every { testResultAtRegistration } returns mockFlowPreference(TestResult.POSITIVE) + every { finalTestResultReceivedAt } returns mockFlowPreference( + Instant.parse("2021-03-20T20:00:00Z") + ) + every { riskLevelTurnedRedTime } returns mockFlowPreference(Instant.parse("2021-03-01T00:00:00Z")) + every { mostRecentDateWithHighOrLowRiskLevel } returns + mockFlowPreference(Instant.parse("2021-03-18T00:00:00Z")) + every { riskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) + } + + every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z") + every { submissionSettings.initialTestResultReceivedAt } returns Instant.parse("2021-03-20T00:00:00Z") + + val donation = testResultDonor.beginDonation(TestRequest) + donation.shouldBeInstanceOf<TestResultDonor.TestResultMetadataContribution>() + with(donation.testResultMetadata) { + testResult shouldBe PpaData.PPATestResult.TEST_RESULT_POSITIVE + hoursSinceTestRegistration shouldBe 20 // hours + riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_HIGH + hoursSinceHighRiskWarningAtTestRegistration shouldBe 456 // 19 days in hours + daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 2 // days + } + } + @Test fun deleteData() = runBlockingTest { every { testResultDonorSettings.clear() } just Runs 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 fe35faf99c0c09ab909c1ce2a86c2edbd7a928b6..2dfc0688e88b7f1cc9f70dcf67b7b2da182359a6 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 @@ -22,7 +22,7 @@ import de.rki.coronawarnapp.ui.main.home.HomeFragmentViewModel import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE_TELETAN import de.rki.coronawarnapp.util.NetworkRequestWrapper -import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations 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 7840e75c702b5a957a8c5d2689f819849551791f..786c13b0c1fe916c2b248b5e8860c34d5cf922c1 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 @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.risk import android.content.Context import androidx.core.app.NotificationManagerCompat import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings import de.rki.coronawarnapp.datadonation.survey.Surveys import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.risk.RiskState.CALCULATION_FAILED @@ -23,6 +24,7 @@ import io.mockk.coVerifySequence import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just +import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest @@ -43,6 +45,7 @@ class RiskLevelChangeDetectorTest : BaseTest() { @MockK lateinit var surveys: Surveys @MockK lateinit var submissionSettings: SubmissionSettings @MockK lateinit var tracingSettings: TracingSettings + @MockK lateinit var testResultDonorSettings: TestResultDonorSettings @BeforeEach fun setup() { @@ -52,18 +55,27 @@ class RiskLevelChangeDetectorTest : BaseTest() { every { submissionSettings.isSubmissionSuccessful } returns false every { foregroundState.isInForeground } returns flowOf(true) every { notificationManagerCompat.areNotificationsEnabled() } returns true + every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp = any() } just Runs every { riskLevelSettings.lastChangeCheckedRiskLevelTimestamp } returns null + + every { riskLevelSettings.lastChangeToHighRiskLevelTimestamp = any() } just Runs + every { riskLevelSettings.lastChangeToHighRiskLevelTimestamp } returns null + coEvery { surveys.resetSurvey(Surveys.Type.HIGH_RISK_ENCOUNTER) } just Runs + + every { testResultDonorSettings.riskLevelTurnedRedTime } returns mockFlowPreference(null) + every { testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null) } private fun createRiskLevel( riskState: RiskState, - calculatedAt: Instant = Instant.EPOCH + calculatedAt: Instant = Instant.EPOCH, + aggregatedRiskResult: AggregatedRiskResult? = null ): RiskLevelResult = object : RiskLevelResult { override val riskState: RiskState = riskState override val calculatedAt: Instant = calculatedAt - override val aggregatedRiskResult: AggregatedRiskResult? = null + override val aggregatedRiskResult: AggregatedRiskResult? = aggregatedRiskResult override val failureReason: RiskLevelResult.FailureReason? = null override val exposureWindows: List<ExposureWindow>? = null override val matchedKeyCount: Int = 0 @@ -82,7 +94,8 @@ class RiskLevelChangeDetectorTest : BaseTest() { notificationHelper = notificationHelper, surveys = surveys, submissionSettings = submissionSettings, - tracingSettings = tracingSettings + tracingSettings = tracingSettings, + testResultDonorSettings = testResultDonorSettings ) @Test @@ -193,6 +206,89 @@ class RiskLevelChangeDetectorTest : BaseTest() { } } + @Test + fun `riskLevelTurnedRedTime is only set once`() { + testResultDonorSettings.riskLevelTurnedRedTime.update { Instant.EPOCH.plus(1) } + + every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + listOf( + createRiskLevel( + INCREASED_RISK, + calculatedAt = Instant.EPOCH.plus(2), + aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + every { isIncreasedRisk() } returns true + } + ), + createRiskLevel(LOW_RISK, calculatedAt = Instant.EPOCH) + ) + ) + + runBlockingTest { + val instance = createInstance(scope = this) + instance.launch() + advanceUntilIdle() + } + + testResultDonorSettings.riskLevelTurnedRedTime.value shouldBe Instant.EPOCH.plus(1) + + testResultDonorSettings.riskLevelTurnedRedTime.update { null } + + runBlockingTest { + val instance = createInstance(scope = this) + instance.launch() + advanceUntilIdle() + } + + testResultDonorSettings.riskLevelTurnedRedTime.value shouldBe Instant.EPOCH.plus(2) + } + + @Test + fun `mostRecentDateWithHighOrLowRiskLevel is updated every time`() { + every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + listOf( + createRiskLevel( + INCREASED_RISK, + calculatedAt = Instant.EPOCH.plus(1), + aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + every { mostRecentDateWithHighRisk } returns Instant.EPOCH.plus(10) + every { isIncreasedRisk() } returns true + } + ), + createRiskLevel(LOW_RISK, calculatedAt = Instant.EPOCH) + ) + ) + + runBlockingTest { + val instance = createInstance(scope = this) + instance.launch() + advanceUntilIdle() + } + + testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel.value shouldBe Instant.EPOCH.plus(10) + + every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + listOf( + createRiskLevel( + INCREASED_RISK, + calculatedAt = Instant.EPOCH.plus(1), + aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + every { mostRecentDateWithLowRisk } returns Instant.EPOCH.plus(20) + every { isIncreasedRisk() } returns false + } + ), + createRiskLevel(LOW_RISK, calculatedAt = Instant.EPOCH) + ) + ) + + runBlockingTest { + val instance = createInstance(scope = this) + instance.launch() + advanceUntilIdle() + } + + testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel.value shouldBe Instant.EPOCH.plus(20) + } + @Test fun `evaluate risk level change detection function`() { RiskLevelChangeDetector.hasHighLowLevelChanged(CALCULATION_FAILED, CALCULATION_FAILED) shouldBe false 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 28cf7f0f7f8327649644c8b907bebe7033c87694..aa31b2ba8774a2eac7d20f7a6f51e57ea1d62e42 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 @@ -204,7 +204,7 @@ class RiskLevelTaskTest : BaseTest() { } @Test - fun `risk calculation is skipped if positive test is registered`() = runBlockingTest { + fun `risk calculation is skipped if positive test is registered and viewed`() = runBlockingTest { val cachedKey = mockk<CachedKey>().apply { every { info } returns mockk<CachedKeyInfo>().apply { every { toDateTime() } returns DateTime.parse("2020-12-28").minusDays(1) @@ -216,6 +216,7 @@ class RiskLevelTaskTest : BaseTest() { every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(false) every { timeStamper.nowUTC } returns now every { submissionSettings.isAllowedToSubmitKeys } returns true + every { submissionSettings.hasViewedTestResult.value } returns true createTask().run(arguments) shouldBe RiskLevelTaskResult( calculatedAt = now, @@ -223,6 +224,35 @@ class RiskLevelTaskTest : BaseTest() { ) } + @Test + fun `risk calculation is not skipped if positive test is registered and not viewed`() = runBlockingTest { + val cachedKey = mockk<CachedKey>().apply { + every { info } returns mockk<CachedKeyInfo>().apply { + every { toDateTime() } returns DateTime.parse("2020-12-28").minusDays(1) + } + } + val now = Instant.parse("2020-12-28") + val aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + every { isIncreasedRisk() } returns true + } + + coEvery { keyCacheRepository.getAllCachedKeys() } returns listOf(cachedKey) + coEvery { enfClient.exposureWindows() } returns listOf() + every { riskLevels.calculateRisk(any(), any()) } returns null + every { riskLevels.aggregateResults(any(), any()) } returns aggregatedRiskResult + every { timeStamper.nowUTC } returns now + coEvery { analyticsExposureWindowCollector.reportRiskResultsPerWindow(any()) } just Runs + every { submissionSettings.isAllowedToSubmitKeys } returns true + every { submissionSettings.hasViewedTestResult.value } returns false + + createTask().run(arguments) shouldBe RiskLevelTaskResult( + calculatedAt = now, + failureReason = null, + aggregatedRiskResult = aggregatedRiskResult, + listOf() + ) + } + @Test fun `risk calculation returns aggregated risk result`() = runBlockingTest { val cachedKey = mockk<CachedKey>().apply { 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 31f62170a700594e6b68beb6e7f3039da3154d4f..119b38c92ecfddec8871cc31c2bd86136e767960 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 @@ -14,9 +14,9 @@ import de.rki.coronawarnapp.util.NetworkRequestWrapper import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent +import de.rki.coronawarnapp.util.encryptionmigration.EncryptedPreferencesFactory +import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool import de.rki.coronawarnapp.util.formatter.TestResult -import de.rki.coronawarnapp.util.security.EncryptedPreferencesFactory -import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -80,12 +80,15 @@ class SubmissionRepositoryTest : BaseTest() { every { submissionSettings.devicePairingSuccessfulAt = any() } just Runs every { submissionSettings.initialTestResultReceivedAt } returns resultReceivedTimeStamp + every { submissionSettings.initialTestResultReceivedAt = any() } just Runs every { submissionSettings.hasGivenConsent } returns mockFlowPreference(false) every { submissionSettings.hasViewedTestResult } returns mockFlowPreference(false) every { submissionSettings.symptoms } returns mockFlowPreference(Symptoms.NO_INFO_GIVEN) every { submissionSettings.clear() } just Runs + every { submissionSettings.devicePairingSuccessfulAt } returns null + every { taskController.tasks } returns emptyFlow() coEvery { tekHistoryStorage.clear() } just Runs @@ -118,10 +121,8 @@ class SubmissionRepositoryTest : BaseTest() { 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() @@ -141,6 +142,7 @@ class SubmissionRepositoryTest : BaseTest() { fun registrationWithGUIDSucceeds() = runBlockingTest { coEvery { submissionService.asyncRegisterDeviceViaGUID(guid) } returns registrationData coEvery { analyticsKeySubmissionCollector.reportTestRegistered() } just Runs + every { analyticsKeySubmissionCollector.reset() } just Runs val submissionRepository = createInstance(scope = this) @@ -161,6 +163,7 @@ class SubmissionRepositoryTest : BaseTest() { coEvery { submissionService.asyncRegisterDeviceViaTAN(tan) } returns registrationData coEvery { analyticsKeySubmissionCollector.reportTestRegistered() } just Runs every { analyticsKeySubmissionCollector.reportRegisteredWithTeleTAN() } just Runs + every { analyticsKeySubmissionCollector.reset() } just Runs val submissionRepository = createInstance(scope = this) @@ -276,4 +279,55 @@ class SubmissionRepositoryTest : BaseTest() { coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) } } + + @Test + fun `EXPOSUREAPP-4484 is fixed`() = runBlockingTest { + every { timeStamper.nowUTC } returns Instant.EPOCH + + var initialTimeStamp = Instant.EPOCH.plus(9999) + every { submissionSettings.initialTestResultReceivedAt } answers { initialTimeStamp } + every { submissionSettings.initialTestResultReceivedAt = any() } answers { initialTimeStamp = arg(0) } + + every { submissionSettings.registrationToken } returns mockFlowPreference("token") + every { submissionSettings.devicePairingSuccessfulAt } returns null + + val submissionRepository = createInstance(scope = this) + + submissionRepository.updateTestResult(TestResult.NEGATIVE) + + verify { + submissionSettings.initialTestResultReceivedAt = null + submissionSettings.initialTestResultReceivedAt = Instant.EPOCH + } + + initialTimeStamp shouldBe Instant.EPOCH + } + + @Test + fun `EXPOSUREAPP-4484 has specific conditions`() = runBlockingTest { + val submissionRepository = createInstance(scope = this) + + every { submissionSettings.initialTestResultReceivedAt } returns Instant.ofEpochMilli(1234) + every { submissionSettings.registrationToken } returns mockFlowPreference("token") + // This needs to be null to trigger the fix + every { submissionSettings.devicePairingSuccessfulAt } returns Instant.ofEpochMilli(5678) + + submissionRepository.updateTestResult(TestResult.NEGATIVE) + + every { submissionSettings.initialTestResultReceivedAt } returns Instant.ofEpochMilli(1234) + // This needs to be non null to trigger the fix + every { submissionSettings.registrationToken } returns mockFlowPreference(null) + every { submissionSettings.devicePairingSuccessfulAt } returns null + + submissionRepository.updateTestResult(TestResult.NEGATIVE) + + // This needs to be non null to trigger the fix + every { submissionSettings.initialTestResultReceivedAt } returns null + every { submissionSettings.registrationToken } returns mockFlowPreference("token") + every { submissionSettings.devicePairingSuccessfulAt } returns null + + submissionRepository.updateTestResult(TestResult.NEGATIVE) + + verify(exactly = 0) { submissionSettings.initialTestResultReceivedAt = null } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..bc18a0cf95c56e371fc70394f66f767ce40f307d --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt @@ -0,0 +1,165 @@ +package de.rki.coronawarnapp.util.encryptionmigration + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +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 io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.matchers.shouldBe +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 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 testhelpers.preferences.mockFlowPreference +import java.io.File + +class EncryptedPreferencesMigrationTest : BaseIOTest() { + @MockK lateinit var context: Context + @MockK lateinit var encryptedPreferencesHelper: EncryptedPreferencesHelper + @MockK lateinit var cwaSettings: CWASettings + @MockK lateinit var submissionSettings: SubmissionSettings + @MockK lateinit var tracingSettings: TracingSettings + @MockK lateinit var onboardingSettings: OnboardingSettings + @MockK lateinit var encryptedErrorResetTool: EncryptionErrorResetTool + + private val testDir = File(IO_TEST_BASEDIR, this::class.java.simpleName) + private val dbFile = File(testDir, "database.sql") + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + testDir.mkdirs() + } + + @AfterEach + fun teardown() { + testDir.deleteRecursively() + } + + private fun createInstance() = EncryptedPreferencesMigration( + context = context, + encryptedPreferences = encryptedPreferencesHelper, + cwaSettings = cwaSettings, + submissionSettings = submissionSettings, + tracingSettings = tracingSettings, + onboardingSettings = onboardingSettings, + errorResetTool = encryptedErrorResetTool + ) + + private fun createOldPreferences() = MockSharedPreferences().also { + it.edit { + // SettingsLocalData + putBoolean(EncryptedPreferencesMigration.SettingsLocalData.PKEY_INTEROPERABILITY_WAS_USED, true) + putBoolean(EncryptedPreferencesMigration.SettingsLocalData.PKEY_TRACING_EXPLANATION_WAS_SHOWN, true) + putBoolean(EncryptedPreferencesMigration.SettingsLocalData.PKEY_NOTIFICATIONS_RISK_ENABLED, false) + putBoolean(EncryptedPreferencesMigration.SettingsLocalData.PKEY_NOTIFICATIONS_TEST_ENABLED, false) + putInt( + EncryptedPreferencesMigration.SettingsLocalData.PKEY_POSITIVE_TEST_RESULT_REMINDER_COUNT, + Int.MAX_VALUE + ) + + // OnboardingLocalData + putLong(EncryptedPreferencesMigration.OnboardingLocalData.PKEY_ONBOARDING_COMPLETED_TIMESTAMP, 10101010L) + putBoolean(EncryptedPreferencesMigration.OnboardingLocalData.PKEY_BACKGROUND_CHECK_DONE, true) + + // TracingLocalData + putLong(EncryptedPreferencesMigration.TracingLocalData.PKEY_POOLING_TEST_RESULT_STARTED, 10101010L) + putBoolean(EncryptedPreferencesMigration.TracingLocalData.PKEY_TEST_RESULT_NOTIFICATION, true) + putBoolean(EncryptedPreferencesMigration.TracingLocalData.PKEY_HAS_RISK_STATUS_LOWERED, true) + putLong(EncryptedPreferencesMigration.TracingLocalData.PKEY_TRACING_ACTIVATION_TIME, 10101010L) + + // SubmissionLocalData + putString(EncryptedPreferencesMigration.SubmissionLocalData.PKEY_REGISTRATION_TOKEN, "super_secret_token") + putLong(EncryptedPreferencesMigration.SubmissionLocalData.PKEY_INITIAL_RESULT_RECEIVED_TIME, 10101010L) + putLong(EncryptedPreferencesMigration.SubmissionLocalData.PKEY_DEVICE_PARING_SUCCESSFUL_TIME, 10101010L) + putInt(EncryptedPreferencesMigration.SubmissionLocalData.PKEY_NUMBER_SUCCESSFUL_SUBMISSIONS, 1) + putBoolean(EncryptedPreferencesMigration.SubmissionLocalData.PKEY_IS_ALLOWED_TO_SUBMIT, true) + } + } + + @Test + fun `is migration successful`() { + every { context.getDatabasePath("coronawarnapp-db") } returns dbFile + every { encryptedPreferencesHelper.clean() } just Runs + + val oldPreferences = createOldPreferences() + every { encryptedPreferencesHelper.instance } returns oldPreferences + + // SettingsLocalData + every { cwaSettings.wasInteroperabilityShownAtLeastOnce = true } just Runs + every { cwaSettings.wasTracingExplanationDialogShown = true } just Runs + val mockRiskPreference = mockFlowPreference(true) + every { cwaSettings.isNotificationsRiskEnabled } returns mockRiskPreference + val mockTestPreference = mockFlowPreference(true) + every { cwaSettings.isNotificationsTestEnabled } returns mockTestPreference + every { cwaSettings.numberOfRemainingSharePositiveTestResultReminders = Int.MAX_VALUE } just Runs + + // OnboardingLocalData + every { onboardingSettings.onboardingCompletedTimestamp = Instant.ofEpochMilli(10101010L) } just Runs + every { onboardingSettings.isBackgroundCheckDone = true } just Runs + + // TracingLocalData + every { tracingSettings.initialPollingForTestResultTimeStamp = 10101010L } just Runs + every { tracingSettings.isTestResultAvailableNotificationSent = true } just Runs + val mockNotificationPreference = mockFlowPreference(false) + every { tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel } returns mockNotificationPreference + every { tracingSettings.isConsentGiven = true } just Runs + + // SubmissionLocalData + val mockRegtokenPreference = mockFlowPreference<String?>(null) + every { submissionSettings.registrationToken } returns mockRegtokenPreference + every { submissionSettings.initialTestResultReceivedAt = Instant.ofEpochMilli(10101010L) } just Runs + every { submissionSettings.devicePairingSuccessfulAt = Instant.ofEpochMilli(10101010L) } just Runs + every { submissionSettings.isSubmissionSuccessful = true } just Runs + every { submissionSettings.isAllowedToSubmitKeys = true } just Runs + + val migrationInstance = createInstance() + + migrationInstance.doMigration() + + // SettingsLocalData + mockRiskPreference.value shouldBe false + mockTestPreference.value shouldBe false + + // TracingLocalData + mockNotificationPreference.value shouldBe true + + // SubmissionLocalData + mockRegtokenPreference.value shouldBe "super_secret_token" + } + + @Test + fun `error during migration will be caught`() { + every { context.getDatabasePath("coronawarnapp-db") } returns dbFile + + val mockPrefs = mockk<SharedPreferences>() + every { + mockPrefs.getBoolean( + EncryptedPreferencesMigration.SettingsLocalData.PKEY_INTEROPERABILITY_WAS_USED, + false + ) + } throws Exception("No one expects the spanish inquisition") + + every { encryptedPreferencesHelper.instance } returns mockPrefs + every { encryptedPreferencesHelper.clean() } just Runs + + every { encryptedErrorResetTool.isResetNoticeToBeShown = true } just Runs + + shouldNotThrowAny { + val instance = createInstance() + instance.doMigration() + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactoryTest.kt index dc37e11dfe7312d08cf5ae20eb12c9d8a6c1a77a..8ca6a803feab0998a0010866b371caa29c6ac1b2 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactoryTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.util.security import android.content.Context +import de.rki.coronawarnapp.util.encryptionmigration.EncryptedPreferencesFactory import io.kotest.assertions.throwables.shouldNotThrowAny import io.mockk.Called import io.mockk.MockKAnnotations 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 2547fd7543ba824ef0c7cb14acfe1036a2ae4343..6a9c9d4a7777a3e330104b772f6fba992888eeef 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 @@ -15,9 +15,9 @@ import de.rki.coronawarnapp.util.TimeAndDateExtensions.daysToMilliseconds import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent +import de.rki.coronawarnapp.util.encryptionmigration.EncryptedPreferencesFactory +import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool import de.rki.coronawarnapp.util.formatter.TestResult -import de.rki.coronawarnapp.util.security.EncryptedPreferencesFactory -import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool import de.rki.coronawarnapp.worker.BackgroundWorkScheduler.stop import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations diff --git a/README.md b/README.md index 178eb8b25c72280aa477f8a895f8481d0e634a58..d87a39c44dabe90de7707a91137f5df9ba8af0a5 100644 --- a/README.md +++ b/README.md @@ -78,18 +78,20 @@ The German government has asked SAP and Deutsche Telekom to develop the Corona-W ## Repositories -| Repository | Description | -| ------------------- | --------------------------------------------------------------------- | -| [cwa-documentation] | Project overview, general documentation, and white papers. | -| [cwa-app-ios] | Native iOS app using the Apple/Google exposure notification API. | -| [cwa-app-android] | Native Android app using the Apple/Google exposure notification API. | -| [cwa-wishlist] | Community feature requests. | -| [cwa-website] | The official website for the Corona-Warn-App | -| [cwa-server] | Backend implementation for the Apple/Google exposure notification API.| -| [cwa-verification-server] | Backend implementation of the verification process. | -| [cwa-verification-portal] | The portal to interact with the verification server | -| [cwa-verification-iam] | The identity and access management to interact with the verification server | -| [cwa-testresult-server] | Receives the test results from connected laboratories | +| Repository | Description | +| ------------------------- | ---------------------------------------------------------------------------- | +| [cwa-documentation] | Project overview, general documentation, and white papers. | +| [cwa-app-ios] | Native iOS app using the Apple/Google exposure notification API. | +| [cwa-app-android] | Native Android app using the Apple/Google exposure notification API. | +| [cwa-wishlist] | Community feature requests. | +| [cwa-website] | The official website for the Corona-Warn-App. | +| [cwa-server] | Backend implementation for the Apple/Google exposure notification API. | +| [cwa-ppa-server] | Backend implementation for the privacy-preserving analytics server. | +| [cwa-verification-server] | Backend implementation of the verification process. | +| [cwa-verification-portal] | The portal to interact with the verification server. | +| [cwa-verification-iam] | The identity and access management to interact with the verification server. | +| [cwa-testresult-server] | Receives the test results from connected laboratories. | +| [cwa-log-upload] | The log upload service is the counterpart of the log upload in the app. | [cwa-documentation]: https://github.com/corona-warn-app/cwa-documentation [cwa-app-ios]: https://github.com/corona-warn-app/cwa-app-ios @@ -97,10 +99,13 @@ The German government has asked SAP and Deutsche Telekom to develop the Corona-W [cwa-wishlist]: https://github.com/corona-warn-app/cwa-wishlist [cwa-website]: https://github.com/corona-warn-app/cwa-website [cwa-server]: https://github.com/corona-warn-app/cwa-server +[cwa-ppa-server]: https://github.com/corona-warn-app/cwa-ppa-server [cwa-verification-server]: https://github.com/corona-warn-app/cwa-verification-server [cwa-verification-portal]: https://github.com/corona-warn-app/cwa-verification-portal [cwa-verification-iam]: https://github.com/corona-warn-app/cwa-verification-iam [cwa-testresult-server]: https://github.com/corona-warn-app/cwa-testresult-server +[cwa-log-upload]: https://github.com/corona-warn-app/cwa-log-upload + ## Licensing