diff --git a/.circleci/config.yml b/.circleci/config.yml index 939ec23a96d88b817af35289015454d878139d38..c2f4e39956f230b2ccb2b7f43529e1165981f391 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -508,7 +508,7 @@ jobs: - store_artifacts: path: /tmp/instrumentation_tests_device.zip destination: zips/instrumentation_tests_device.zip - + # Keep it until Firebase Test Lab proves reliability device_screenshots: macos: xcode: "12.3.0" @@ -550,6 +550,65 @@ jobs: path: /tmp/device_screenshots.zip destination: zips/device_screenshots.zip + firebase_screenshots: + resource_class: xlarge + docker: + - image: circleci/android:api-28 + steps: + - skip-for-external-pull-requests + - setup-google-cloud + - checkout + - restore-gradle-cache + - restore-android-build-cache + - run-gradle-cmd: + desc: Build APKs for screenshots + cmd: > + :Corona-Warn-App:assembleDebug + :Corona-Warn-App:assembleAndroidTest + - run: + name: Setup Testlab environment + command: | + echo "export BUCKETDIR=\"`date "+%Y-%m-%d-%H:%M:%S:%3N"`-${RANDOM}\"" >> $BASH_ENV + source $BASH_ENV + echo "$BUCKETDIR is setup." + - run: + name: Test with Firebase Test Lab + command: | + echo "Using bucketdir $BUCKETDIR" + sudo gcloud firebase test android run \ + --type instrumentation \ + --app Corona-Warn-App/build/outputs/apk/deviceForTesters/debug/*.apk \ + --test Corona-Warn-App/build/outputs/apk/androidTest/deviceForTesters/debug/*.apk \ + --results-dir ${BUCKETDIR} \ + --results-bucket ${GOOGLE_PROJECT_ID}-circleci-android \ + --environment-variables clearPackageData=true \ + --test-targets "annotation testhelpers.Screenshot" \ + --timeout 20m \ + --device-ids flame \ + --os-version-ids 29 \ + --locales de_DE,en_US \ + --orientations portrait \ + --no-record-video + no_output_timeout: 30m + - run: + name: Create directory to store test results + command: mkdir firebase-screenshots + when: always + - run: + name: Install gsutil dependency and copy test results data + command: | + sudo pip install -U crcmod + sudo gsutil -m cp -R -U gs://${GOOGLE_PROJECT_ID}-circleci-android/${BUCKETDIR}/flame* firebase-screenshots + when: always + - store_test_results: + path: ./firebase-screenshots + - compress-path: + input: ./firebase-screenshots + output: /tmp/screenshots.zip + - store_artifacts: + path: /tmp/screenshots.zip + destination: zips/screenshots.zip + ####################### # Workflow section # Job execution orders @@ -588,7 +647,7 @@ workflows: - /^v.*/ branches: ignore: /.*/ - - device_screenshots: + - firebase_screenshots: filters: tags: only: diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt index 647ff840aaa8c4dbe467e13d4ea26dc7ecf7a2b9..d457ee7eb3f591ca3d179da0f3dfe81b0e627c4b 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt @@ -17,6 +17,7 @@ import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragment import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewViewModel import de.rki.coronawarnapp.contactdiary.ui.overview.adapter.ListItem +import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.main.CWASettings @@ -440,4 +441,10 @@ class MainProviderModule { mockk<ContactDiaryWorkScheduler>(relaxed = true).apply { every { schedulePeriodic() } just Runs } + + @Provides + fun dataDonationAnalyticsScheduler(): DataDonationAnalyticsScheduler = + mockk<DataDonationAnalyticsScheduler>(relaxed = true).apply { + every { schedulePeriodic() } just Runs + } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt index fcb9c09305419dfade9a40174ccf8a3d09c049e1..37e5a4d49af8069975cbe544e3fd203ad12f2cca 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt @@ -39,8 +39,10 @@ class OnboardingFragmentTest : BaseUITest() { launchFragmentInContainer2<OnboardingFragment>() takeScreenshot<OnboardingFragment>() - onView(withId(R.id.onboarding_easy_language)).perform(scrollTo()) - takeScreenshot<OnboardingFragment>("2") + if (showEasyLanguageLink()) { + onView(withId(R.id.onboarding_easy_language)).perform(scrollTo()) + takeScreenshot<OnboardingFragment>("2") + } } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt index cd1ae371d21040b73673b24272918215fea7f634..b9da670a20ca57ebbddf6ba6492d4687e788f5dc 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.datadonation.survey.Surveys import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.storage.TracingRepository import de.rki.coronawarnapp.tracing.GeneralTracingStatus @@ -24,12 +25,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 +import testhelpers.takeScreenshot import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -41,6 +42,7 @@ class TracingDetailsFragmentTest : BaseUITest() { @MockK lateinit var tracingDetailsItemProvider: TracingDetailsItemProvider @MockK lateinit var tracingStateProviderFactory: TracingStateProvider.Factory @MockK lateinit var tracingRepository: TracingRepository + @MockK lateinit var surveys: Surveys private lateinit var viewModel: TracingDetailsFragmentViewModel @@ -63,7 +65,8 @@ class TracingDetailsFragmentTest : BaseUITest() { riskLevelStorage = riskLevelStorage, tracingDetailsItemProvider = tracingDetailsItemProvider, tracingStateProviderFactory = tracingStateProviderFactory, - tracingRepository = tracingRepository + tracingRepository = tracingRepository, + surveys = surveys ) ) diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt index a8c26e949e888c86e9a9f2e60fb7fd1f1a8176e4..ad347acdb141a78f9fa9e3132e25fe99dc894bcf 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt @@ -1,11 +1,20 @@ package testhelpers +import android.Manifest +import androidx.test.rule.GrantPermissionRule import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import org.junit.Rule import testhelpers.viewmodels.MockViewModelModule abstract class BaseUITest : BaseTest() { + @get:Rule + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + inline fun <reified T : CWAViewModel> setupMockViewModel(factory: CWAViewModelFactory<T>) { MockViewModelModule.CREATORS[T::class.java] = factory } diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenShotter.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenShotter.kt index e7076a69636b134b9e8fb5a545b19836c801fea5..4d9f4cea6269d3119a667f9373c88bac80d0c3db 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenShotter.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenShotter.kt @@ -1,13 +1,23 @@ package testhelpers import android.app.Activity +import android.graphics.Bitmap import android.os.Bundle +import android.provider.Settings +import android.util.Log import androidx.annotation.StyleRes import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory import androidx.test.espresso.ViewAction +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import de.rki.coronawarnapp.R import tools.fastlane.screengrab.Screengrab +import tools.fastlane.screengrab.ScreenshotCallback +import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy +import tools.fastlane.screengrab.file.Chmod +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream /** * Waits for 2 sec and captures a screenshot @@ -18,7 +28,18 @@ inline fun <reified T> takeScreenshot(suffix: String = "", delay: Long = SCREENS Thread.sleep(delay) val simpleName = T::class.simpleName val name = if (suffix.isEmpty()) simpleName else simpleName.plus("_$suffix") - Screengrab.screenshot(name) + + val contentResolver = getInstrumentation().targetContext.contentResolver + val testLabSetting = Settings.System.getString(contentResolver, "firebase.test.lab") + if ("true" == testLabSetting) { + Screengrab.screenshot( + name, + UiAutomatorScreenshotStrategy(), + SDCardCallback + ) + } else { + Screengrab.screenshot(name) + } } /** @@ -36,3 +57,35 @@ inline fun <reified F : Fragment> captureScreenshot( launchFragmentInContainer2<F>(fragmentArgs, themeResId, factory) takeScreenshot<F>(suffix) } + +/** + * Saves screenshots on the device's sdcard + */ +object SDCardCallback : ScreenshotCallback { + private const val ROOT_DIRECTORY = "/sdcard" + private const val SCREENSHOTS_DIRECTORY = "screenshots" + private const val SCREENSHOT_FORMAT = ".png" + private const val IMAGE_QUALITY = 100 + + override fun screenshotCaptured(screenshotName: String, screenshot: Bitmap) { + try { + val directory = File(ROOT_DIRECTORY, SCREENSHOTS_DIRECTORY) + if (!directory.exists()) { + directory.mkdirs() + } + val screenshotFile = File(directory, screenshotName + SCREENSHOT_FORMAT) + if (!screenshotFile.exists()) { + screenshotFile.createNewFile() + } + + BufferedOutputStream(FileOutputStream(screenshotFile)).use { + screenshot.compress(Bitmap.CompressFormat.PNG, IMAGE_QUALITY, it) + Chmod.chmodPlusR(screenshotFile) + screenshot.recycle() + } + Log.d("Screengrab", "Captured screenshot \"${screenshotFile.name}\"") + } catch (e: Exception) { + throw RuntimeException("Unable to capture screenshot.", e) + } + } +} diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenshotUnderTest.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenshotUnderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..7d1de56b8f35ddf027530b819add8a7bccd228d7 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenshotUnderTest.kt @@ -0,0 +1,15 @@ +package testhelpers + +/** + * Similar to [Screenshot]. it is helpful during development and testing process to filter + * the test currently being implemented. + * In fastlane folder. replace `Screenshot` with `ScreenshotUnderTest` in Screengrabfile + * + * Note: this is only for testing purposes and should NOT be used in final tests + */ +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.CLASS +) +annotation class ScreenshotUnderTest diff --git a/Corona-Warn-App/src/debug/AndroidManifest.xml b/Corona-Warn-App/src/debug/AndroidManifest.xml index 26a521d64d1103520a4dd6c6130a7f248e939d2e..97058d1d3ae50643a01ff82ebd99e98b1322b9ed 100644 --- a/Corona-Warn-App/src/debug/AndroidManifest.xml +++ b/Corona-Warn-App/src/debug/AndroidManifest.xml @@ -15,4 +15,8 @@ android:name="android.permission.CHANGE_CONFIGURATION" tools:ignore="ProtectedPermissions" /> + <application + android:requestLegacyExternalStorage="true" + tools:node="merge" /> + </manifest> diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/reporter/DefaultBugReporter.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/reporter/DefaultBugReporter.kt index 50bf3b51d4bdd295fc4b0d231ddbf9b0d86d5a86..c195019c58bf43968777d327f9ea4013f25ee389 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/reporter/DefaultBugReporter.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/bugreporting/reporter/DefaultBugReporter.kt @@ -20,7 +20,7 @@ class DefaultBugReporter @Inject constructor( ) : BugReporter { override fun report(throwable: Throwable, tag: String?, info: String?) { - Timber.e(throwable, "Processing reported bug (info=$info) from $tag.") + Timber.v("Processing reported bug (info=$info) from $tag: $throwable") scope.launch(context = dispatcherProvider.IO) { val event = processor.processor(throwable, tag, info) repository.save(event) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/CrashReportAdapter.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/CrashReportAdapter.kt index afdfb4358aa590da76289fb57cf592c9b0efc2f1..6577ece9d20679eadf04e6505c23e2b930b2a6f3 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/CrashReportAdapter.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/CrashReportAdapter.kt @@ -37,8 +37,8 @@ class CrashReportAdapter(private val itemClickListener: (bugEvent: BugEvent) -> class CrashHolder(private val binding: ViewCrashreportListItemBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(bugEvent: BugEvent, pos: Int) { - binding.crashReportTitle = "Error #${pos + 1}" - binding.message = bugEvent.exceptionMessage + binding.crashReportTitle = "#${pos + 1} ${bugEvent.exceptionClass}" + binding.message = bugEvent.info ?: bugEvent.exceptionMessage binding.crashReportDateFormatted = bugEvent.createdAt.toDateTime(DateTimeZone.getDefault()).toString() .replace("T", " ") diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragment.kt index fa6cd1d6b836870396f0b3f16c5c30e2179c100c..26659a70227711ac3798bacb43f4e89b198f3212 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragment.kt @@ -6,7 +6,6 @@ import android.view.View import android.widget.RadioButton import android.widget.RadioGroup import android.widget.Toast -import androidx.annotation.StringRes import androidx.core.app.ShareCompat import androidx.core.view.ViewCompat import androidx.core.view.children @@ -18,6 +17,7 @@ import de.rki.coronawarnapp.datadonation.survey.SurveyException import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.tryHumanReadableError import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider @@ -111,7 +111,15 @@ class DataDonationTestFragment : Fragment(R.layout.fragment_test_datadonation), } vm.showErrorDialog.observe2(this) { - showErrorDialog(it) + val humanReadableError = it.tryHumanReadableError(requireContext()) + val dialog = DialogHelper.DialogInstance( + context = requireContext(), + title = R.string.datadonation_details_survey_consent_error_dialog_title, + message = humanReadableError.description, + positiveButton = R.string.datadonation_details_survey_consent_error_dialog_pos_button, + cancelable = false + ) + DialogHelper.showDialog(dialog) } vm.currentSafetyNetExceptionType.observe2(this) { type -> @@ -153,6 +161,14 @@ class DataDonationTestFragment : Fragment(R.layout.fragment_test_datadonation), surveyExceptionSimulationButton.setOnClickListener { vm.showSurveyErrorDialog() } } + + vm.isSafetyNetTimeCheckSkipped.observe2(this) { + binding.disableSafetynetToggle.isChecked = it + } + + binding.disableSafetynetToggle.setOnClickListener { + vm.toggleSkipSafetyNetTimeCheck() + } } private fun RadioGroup.addRadioButton(text: String) { @@ -170,19 +186,6 @@ class DataDonationTestFragment : Fragment(R.layout.fragment_test_datadonation), } } - private fun showErrorDialog(@StringRes stringRes: Int) { - context?.let { - val dialog = DialogHelper.DialogInstance( - context = it, - title = R.string.datadonation_details_survey_consent_error_dialog_title, - message = stringRes, - positiveButton = R.string.datadonation_details_survey_consent_error_dialog_pos_button, - cancelable = false - ) - DialogHelper.showDialog(dialog) - } - } - companion object { val MENU_ITEM = TestMenuItem( title = "Data Donation", diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentViewModel.kt index e209b4fe84fe3eee00b9c28ce24567490d187023..711cb5f5bd7adcba37acf7a786f23f1fe9ad42be 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentViewModel.kt @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.test.datadonation.ui -import androidx.annotation.StringRes import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -13,11 +12,10 @@ import de.rki.coronawarnapp.datadonation.safetynet.CWASafetyNet import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetClientWrapper import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException -import de.rki.coronawarnapp.datadonation.safetynet.errorMsgRes import de.rki.coronawarnapp.datadonation.storage.OTPRepository import de.rki.coronawarnapp.datadonation.survey.SurveyException -import de.rki.coronawarnapp.datadonation.survey.errorMsgRes import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData +import de.rki.coronawarnapp.storage.TestSettings import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -35,7 +33,8 @@ class DataDonationTestFragmentViewModel @AssistedInject constructor( private val lastAnalyticsSubmissionLogger: LastAnalyticsSubmissionLogger, private val cwaSafetyNet: CWASafetyNet, otpRepository: OTPRepository, - private val appConfigProvider: AppConfigProvider + private val appConfigProvider: AppConfigProvider, + private val testSettings: TestSettings ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val infoEvents = SingleLiveEvent<String>() @@ -55,6 +54,9 @@ class DataDonationTestFragmentViewModel @AssistedInject constructor( private val lastAnalyticsDataInternal = MutableStateFlow<LastAnalyticsSubmission?>(null) val lastAnalyticsData = lastAnalyticsDataInternal.asLiveData(context = dispatcherProvider.Default) + val isSafetyNetTimeCheckSkipped = testSettings.skipSafetyNetTimeCheck.flow + .asLiveData(context = dispatcherProvider.Default) + val otp: String = otpRepository.otpAuthorizationResult?.toString() ?: "No OTP generated and authorized yet" val surveyConfig = appConfigProvider.currentConfig @@ -68,7 +70,7 @@ class DataDonationTestFragmentViewModel @AssistedInject constructor( private val currentSurveyExceptionTypeInternal = MutableStateFlow(SurveyException.Type.values().first()) val currentSurveyExceptionType = currentSurveyExceptionTypeInternal.asLiveData(context = dispatcherProvider.Default) - val showErrorDialog = SingleLiveEvent<@StringRes Int>() + val showErrorDialog = SingleLiveEvent<Exception>() fun createSafetyNetReport() { launch { @@ -158,14 +160,21 @@ class DataDonationTestFragmentViewModel @AssistedInject constructor( } } + fun toggleSkipSafetyNetTimeCheck() { + testSettings.skipSafetyNetTimeCheck.update { !it } + } + fun selectSafetyNetExceptionType(type: SafetyNetException.Type) { currentSafetyNetExceptionTypeInternal.value = type } fun showSafetyNetErrorDialog() { - currentSafetyNetExceptionTypeInternal.value.run { - SafetyNetException(this, "simulated") - }.also { showErrorDialog.postValue(it.errorMsgRes()) } + showErrorDialog.postValue( + SafetyNetException( + type = currentSafetyNetExceptionTypeInternal.value, + message = "simulated" + ) + ) } fun selectSurveyExceptionType(type: SurveyException.Type) { @@ -173,9 +182,9 @@ class DataDonationTestFragmentViewModel @AssistedInject constructor( } fun showSurveyErrorDialog() { - currentSurveyExceptionTypeInternal.value.run { - SurveyException(this) - }.also { showErrorDialog.postValue(it.errorMsgRes()) } + showErrorDialog.postValue( + SurveyException(type = currentSurveyExceptionTypeInternal.value) + ) } @AssistedFactory diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt index 07bfd4404b3f7c606c6a15b1c64b415fd6f15063..45d0b05c577a8851b7c938e2c7de45edc36cc89b 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt @@ -69,6 +69,7 @@ class DebugOptionsFragment : Fragment(R.layout.fragment_test_debugoptions), Auto environmentCdnurlDownload.text = "Download CDN:\n${state.urlDownload}" environmentCdnurlSubmission.text = "Submission CDN:\n${state.urlSubmission}" environmentCdnurlVerification.text = "Verification CDN:\n${state.urlVerification}" + environmentUrlDatadonation.text = "DataDonation:\n${state.urlDataDonation}" } } vm.environmentChangeEvent.observe2(this) { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt index c25c26364c059bf220127e4f89a04f43fbe4c2bf..fb3a8ded80765be7fe43f2487775ff3bfcfa9b2a 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt @@ -7,7 +7,8 @@ data class EnvironmentState( val available: List<EnvironmentSetup.Type>, val urlSubmission: String, val urlDownload: String, - val urlVerification: String + val urlVerification: String, + val urlDataDonation: String ) { companion object { internal fun EnvironmentSetup.toEnvironmentState() = EnvironmentState( @@ -15,7 +16,8 @@ data class EnvironmentState( available = EnvironmentSetup.Type.values().toList(), urlSubmission = submissionCdnUrl, urlDownload = downloadCdnUrl, - urlVerification = verificationCdnUrl + urlVerification = verificationCdnUrl, + urlDataDonation = dataDonationCdnUrl ) } } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_appconfig.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_appconfig.xml index e150e870a96e3ee9a4f0fa60b80860e97b29955a..176b70daeb0aa28c41453addb0bf861b5d3ab1a1 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_appconfig.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_appconfig.xml @@ -4,7 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - xmlns:app="http://schemas.android.com/apk/res-auto" tools:ignore="HardcodedText"> <LinearLayout @@ -84,7 +83,6 @@ <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/fake_correct_devicetime_toggle" android:layout_width="match_parent" - app:thumbTint="@color/colorAccent" android:layout_height="0dp" android:layout_marginTop="@dimen/spacing_tiny" android:layout_weight="1" diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_datadonation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_datadonation.xml index 39be37917e3c0b7bf881565b0a5c9f0fdba64875..077d1f73f03a61ed5acceb7a6acfeb0c8c22e9e3 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_datadonation.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_datadonation.xml @@ -257,6 +257,29 @@ app:layout_constraintTop_toBottomOf="@id/survey_exception_simulation_radio_group" /> </androidx.constraintlayout.widget.ConstraintLayout> + <LinearLayout + style="@style/Card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny" + android:orientation="vertical"> + + <com.google.android.material.switchmaterial.SwitchMaterial + android:id="@+id/disable_safetynet_toggle" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginTop="@dimen/spacing_tiny" + android:layout_weight="1" + android:text="Skip 24H SafetyNet Check" /> + + <TextView + style="@style/TextAppearance.AppCompat.Caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="This disables the 24H time since valid time check for SafetyNet attestation" /> + </LinearLayout> + <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/last_analytics_container" style="@style/Card" diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml index cca12450d9fee3eafa2d51795b793186606cf4b7..cedba81d312970d03e1c16c12dbdb69b669439e3 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml @@ -104,6 +104,17 @@ app:layout_constraintTop_toBottomOf="@+id/environment_cdnurl_submission" tools:text="Verification: ?" /> + <TextView + android:id="@+id/environment_url_datadonation" + style="@style/body2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/environment_cdnurl_verification" + tools:text="DataDonation: ?" /> + <RadioGroup android:id="@+id/environment_toggle_group" android:layout_width="match_parent" @@ -113,7 +124,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/environment_cdnurl_verification" /> + app:layout_constraintTop_toBottomOf="@+id/environment_url_datadonation" /> </androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout> diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/view_crashreport_list_item.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/view_crashreport_list_item.xml index a301fc4ec15a1ed76dc32162324ebe40725e5777..6181e924cf9c9998e78d6ffd53ff64a554c6ef88 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/view_crashreport_list_item.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/view_crashreport_list_item.xml @@ -57,13 +57,13 @@ <TextView android:id="@+id/textViewCrashReportShortMessage" - style="@style/body1" + style="@style/TextAppearance.MaterialComponents.Caption" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="24dp" + android:layout_marginTop="4dp" android:ellipsize="end" android:inputType="none" - android:maxLines="1" + android:maxLines="4" android:text="@{message}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/Corona-Warn-App/src/main/assets/default_app_config_android.bin b/Corona-Warn-App/src/main/assets/default_app_config_android.bin index e94bea0e57dec2d532f9642e5f52f9426e52d028..71db85788c2beff255f07410e14f0ec2bbea4bbb 100644 Binary files a/Corona-Warn-App/src/main/assets/default_app_config_android.bin and b/Corona-Warn-App/src/main/assets/default_app_config_android.bin differ diff --git a/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 b/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 index f352a96630405e9b9b44ae0e7a5d3fb960223c66..dcbdbc7b85dbed0619623f041c6f02d32ae7ede1 100644 --- a/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 +++ b/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 @@ -1 +1 @@ -827fb746a1128e465d65ec77030fdf38c823dec593ae18aed55195069cf8b701 \ No newline at end of file +12d0b93c0c02c6870ef75c173a53a8ffb9cab6828fbf22e751053329c425eef2 \ No newline at end of file diff --git a/Corona-Warn-App/src/main/assets/privacy_de.html b/Corona-Warn-App/src/main/assets/privacy_de.html index d17edfff0e4758ab0ca38941c5fad244524c5d8b..5c74854411dfcaa60bedc13db1ddf09989400b2d 100644 --- a/Corona-Warn-App/src/main/assets/privacy_de.html +++ b/Corona-Warn-App/src/main/assets/privacy_de.html @@ -114,7 +114,10 @@ das System bei der Risiko-Ermittlung, der Warnung anderer und dem Abruf des Testergebnisses keine Daten erfasst, die es dem RKI oder anderen Nutzern ermöglichen, auf Ihre Identität, Ihren Namen, Ihren Standort oder andere - persönliche Details zu schließen. Die App verzichtet daher auch grundsätzlich auf + persönliche Details zu schließen. +</p> +<p> + Die App verzichtet daher auch grundsätzlich auf jegliche Auswertung Ihres Nutzungsverhaltens durch Analyse-Tools. Nur wenn Sie ausdrücklich der freiwilligen Datenspende zustimmen, werden bestimmte Daten über Ihre Nutzung der App an das RKI übermittelt (siehe hierzu Punkt 5 e.). @@ -381,11 +384,13 @@ Ihr Smartphone erzeugt hierbei eine eindeutige Kennung und sendet diese an den Anbieter Ihres Betriebssystems (wenn Sie ein Android-Smartphone verwenden, werden Daten an Google übermittelt; wenn Sie ein iPhone verwenden, werden Daten an Apple übermittelt). Die Kennung enthält - Informationen über die Version Ihres Smartphones und die Version der App. Weitere Angaben aus - der App, z.B. Begegnungsdaten, erhält der Anbieter Ihres Betriebssystems nicht. Die Anbieter des - Betriebssystems nutzen die Kennung, um die Echtheit Ihrer App gegenüber dem RKI zu bestätigen. - Die Nutzung der Funktion zur Bestätigung der Echtheit ist freiwillig. Wenn Sie mit der - Bestätigung der Echtheit Ihrer App nicht einverstanden sind, kann es jedoch sein, dass Ihnen + Informationen über die Version Ihres Smartphones und die Version der App. Möglicherweise kann + der Anbieter Ihres Betriebssystems anhand der Kennung auf Ihre Identität schließen und + nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat. Weitere Angaben + aus der App, z.B. Begegnungsdaten, erhält der Anbieter Ihres Betriebssystems nicht. Die Anbieter + des Betriebssystems nutzen die Kennung, um die Echtheit Ihrer App gegenüber dem RKI zu + bestätigen. Die Nutzung der Funktion zur Bestätigung der Echtheit ist freiwillig. Wenn Sie mit + der Bestätigung der Echtheit Ihrer App nicht einverstanden sind, kann es jedoch sein, dass Ihnen andere Funktionen der App nicht zur Verfügung stehen. </p> <h1> @@ -674,6 +679,12 @@ Befragungen teilnehmen, deren App ordnungsgemäß funktioniert. Auf diese Weise wird sichergestellt, dass die Statistiken und Befragungsergebnisse nicht verfälscht werden. </p> +<p> + Bitte beachten Sie, dass der Anbieter Ihres Betriebssystems möglicherweise nachvollziehen kann, + dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat und dadurch auf Ihre Identität + schließen kann. Weiteren Angaben über Ihre Nutzung der Corona-Warn-App erhält der Anbieter Ihres + Betriebssystems jedoch nicht. +</p> <h1> 7. Wie funktioniert das länderübergreifende Warnsystem? </h1> @@ -905,17 +916,17 @@ Geschäftsreise) abgerufen werden. </p> <p> - Zudem kann es Rahmen der Bestätigung der Echtheit Ihrer App zu einer Ãœbermittlung von Daten in - ein Land außerhalb der EU kommen. Die von Ihrem Smartphone erzeugte Kennung, die Informationen - über die Version Ihres Smartphones und der App enthält, wird einmalig an den + Zudem kann es im Rahmen der Bestätigung der Echtheit Ihrer App zu einer Ãœbermittlung von Daten + in ein Land außerhalb der EU kommen. Die von Ihrem Smartphone erzeugte Kennung, die + Informationen über die Version Ihres Smartphones und der App enthält, wird an den Betriebssystemanbieter Ihres Smartphones (Apple oder Google) übermittelt. Dabei kann es auch zu - einer Datenübermittlung in die USA kommen. Dort besteht kein dem europäischen Recht - entsprechendes angemessenes Datenschutzniveau und Ihre europäischen Datenschutzrechte können - eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass - US-Sicherheitsbehörden auf die übermittelten Daten beim Betriebssystemanbieter zugreifen und - diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies - betrifft jedoch nur die übermittelte Kennung. Weitere Angaben aus der App, beispielsweise - Begegnungsdaten, sind davon nicht erfasst. + einer Datenübermittlung in Drittländer, insbesondere in die USA, kommen. Dort besteht + möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre + europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht + die Möglichkeit, dass Sicherheitsbehörden im Drittland auf die übermittelten Daten beim + Betriebssystemanbieter zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen + Informationen verknüpfen. Dies betrifft jedoch nur die übermittelte Kennung. Weitere Angaben aus + der App, beispielsweise Begegnungsdaten, sind davon nicht erfasst. </p> <p> Im Ãœbrigen werden die von der App @@ -1078,5 +1089,5 @@ datenschutz@rki.de. </p> <p> - Stand: 24.02.2021 + Stand: 01.03.2021 </p> diff --git a/Corona-Warn-App/src/main/assets/privacy_en.html b/Corona-Warn-App/src/main/assets/privacy_en.html index 089c8366c71b139592c7fd9f4a875edba5d96670..379e081aabfe86cd3b2b0672daa6c3597c40ddbf 100644 --- a/Corona-Warn-App/src/main/assets/privacy_en.html +++ b/Corona-Warn-App/src/main/assets/privacy_en.html @@ -276,7 +276,7 @@ tracing purposes, and how you can provide it. </p> <h2> - e. Share Data + e. Data sharing </h2> <p> If you enable the data sharing feature, the app will transmit various data about your use of @@ -370,7 +370,9 @@ system. Your smartphone generates a unique identifier and sends it to your operating system provider (if you use an Android smartphone, data is transmitted to Google; if you use an iPhone, data is transmitted to Apple). The identifier contains information about the version of your - smartphone and the version of the app. Your operating system provider does not receive any other + smartphone and the version of the app. It is possible for your operating system provider to + infer your identity from the identifier and learn that your smartphone has been authenticated. + Your operating system provider does not receive any other information, such as exposure data, from the app. Your operating system provider will use the identifier to confirm the authenticity of your app to the RKI. Using the feature for confirming the authenticity of your app is voluntary. However, if you do not agree to having the @@ -598,7 +600,7 @@ the risk of causing undetected infections. </p> <h2> - f. Share Data + f. Data sharing </h2> <p> Share Data is an additional feature of the app. The usage data and other voluntary @@ -651,9 +653,14 @@ </h2> <p> A feature of your smartphone’s operating system is used to confirm the authenticity of your app. - This ensures that only app users whose app is functioning properly can donate their data or + This ensures that only app users whose app is functioning properly can share their data or participate in surveys. This prevents the statistics and survey results from being distorted. </p> +<p> + Please note that your operating system provider may be able to tell that your smartphone has + been authenticated and may therefore be able to infer your identity. However, your operating + system provider will not receive any further information about your use of the Corona-Warn-App. +</p> <h1> 7. How does the transnational warning system work? </h1> @@ -880,23 +887,25 @@ <p> If you activate the warning feature, please note that users can retrieve the latest positive lists regardless of where they are (even if they are - abroad on holiday or on a business trip, for example). Otherwise, the data - transmitted by the app is processed exclusively on servers in Germany or in - another country in the EU (or the European Economic Area), which are - therefore subject to the strict requirements of the General Data Protection - Regulation (GDPR). + abroad on holiday or on a business trip, for example). </p> <p> In addition, the confirmation of the authenticity of your app may involve the transfer of data to a country outside the EU. The identifier generated by your smartphone, which contains - information about the version of your smartphone and the app, will be transmitted once to the + information about the version of your smartphone and the app, will be transmitted to the provider of your smartphone’s operating system (Apple or Google). This may result in data being - transferred to the US. There, the level of data protection is not considered adequate under - European law and it may not be possible to enforce your European data protection rights. In - particular, there is a possibility that once the transmitted data reaches the operating system - provider, it may be accessed and analysed by US security authorities, for example by linking the - data with other information. However, this only concerns the submitted identifier. It does not - concern other information from the app, such as exposure data. + transferred to third countries, in particular the US. There, the level of data protection may + not be considered equivalent under European law and it may not be possible to enforce your + European data protection rights. In particular, there is a possibility that once the transmitted + data reaches the operating system provider, it may be accessed and analysed by security + authorities in the third country, for example by linking the data with other information. + However, this only concerns the submitted identifier. It does not concern other information from + the app, such as exposure data. +</p> +<p> + Otherwise, the data transmitted by the app is processed exclusively on servers in Germany or in + another country in the EU (or the European Economic Area), which are therefore subject to the + strict requirements of the General Data Protection Regulation (GDPR). </p> <h1> 12. How can you withdraw your consent? @@ -1040,5 +1049,5 @@ 13353 Berlin, or by emailing datenschutz@rki.de. </p> <p> - Last amended: 24 February 2021 + Last amended: 01 March 2021 </p> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/assets/privacy_tr.html b/Corona-Warn-App/src/main/assets/privacy_tr.html index e9a984ffa8eb406001ae62fe00327bd2bc7cd828..31ca70cf54ecdba84986828ae501ae4a07808ff6 100644 --- a/Corona-Warn-App/src/main/assets/privacy_tr.html +++ b/Corona-Warn-App/src/main/assets/privacy_tr.html @@ -367,12 +367,13 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g yararlanılır. Bu baÄŸlamda akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve bunu iÅŸletim sisteminizin saÄŸlayıcısına gönderir (bir Android akıllı telefon kullanıyorsanız, bu veriler Google’a, iPhone kullanıyorsanız Apple’a aktarılır). Bu kimlik kodu, akıllı - telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Uygulamadan karşılaÅŸma verileri gibi - baÅŸka bilgiler Ä°ÅŸletim sisteminizin saÄŸlayıcısına gönderilmez. Ä°ÅŸletim sisteminin saÄŸlayıcısı, - Uygulamanızın orijinal olduÄŸunu RKI’ye doÄŸrulamak için bu kimlik kodunu kullanır. - OrijinalliÄŸinin doÄŸrulanmasını saÄŸlayan bu iÅŸlevin kullanılması isteÄŸe baÄŸlıdır. Ancak - Uygulamanızın orijinalliÄŸinin doÄŸrulanmasını kabul etmezseniz, Uygulamanın baÅŸka bazı iÅŸlevleri - sizin için sunulmayabilir. + telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Ä°ÅŸletim sisteminizin saÄŸlayıcısı, + kimlik kodunuz üzerinden kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik + kontrolünün yapıldığını anlayabilir. Uygulamadan karşılaÅŸma verileri gibi baÅŸka bilgiler Ä°ÅŸletim + sisteminizin saÄŸlayıcısına gönderilmez. Ä°ÅŸletim sisteminin saÄŸlayıcısı, Uygulamanızın orijinal + olduÄŸunu RKI’ye doÄŸrulamak için bu kimlik kodunu kullanır. OrijinalliÄŸinin doÄŸrulanmasını + saÄŸlayan bu iÅŸlevin kullanılması isteÄŸe baÄŸlıdır. Ancak Uygulamanızın orijinalliÄŸinin + doÄŸrulanmasını kabul etmezseniz, Uygulamanın baÅŸka bazı iÅŸlevleri sizin için sunulmayabilir. </p> <h1> 6. Verileriniz niçin iÅŸleniyor? @@ -651,7 +652,10 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g Uygulamanızın orijinalliÄŸinin doÄŸrulanması için akıllı telefonunuzun iÅŸletim sisteminin bir iÅŸlevi kullanılır. Bu sayede sadece uygulaması düzgün çalışan Uygulama kullanıcılarının veri bağışına veya anketlere katılması mümkün kılınır. Böylece istatistiklerin ve anket sonuçlarının - tahrif edilmesinin önüne geçilir. + tahrif edilmesinin önüne geçilir. Lütfen iÅŸletim sisteminizin saÄŸlayıcısının muhtemelen akıllı + telefonunuzun orijinallik kontrolünün yapıldığını anlayabileceÄŸini ve bu nedenle kimliÄŸinizi + ortaya çıkarabileceÄŸini unutmayın. Ancak, iÅŸletim sisteminizin saÄŸlayıcısı Corona-Warn-App + kullanımınız hakkında daha fazla bilgi almaz. </p> <h1> 7. Sınır ötesi uyarı sistemi nasıl çalışır? @@ -881,13 +885,13 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g Ayrıca, Uygulamanızın orijinalliÄŸinin doÄŸrulanması kapsamında verilerin AB dışındaki bir ülkeye aktarılması söz konusu olabilir. Akıllı telefonunuz tarafından oluÅŸturulan ve akıllı telefonunuzun ve Uygulamanın sürümüyle ilgili bilgileri içeren kimlik kodu, akıllı telefonunuzun - iÅŸletim sisteminin saÄŸlayıcısına (Apple veya Google) bir kez aktarılır. Bu süreçte verilerin - ABD’ye aktarılması da söz konusu olabilir. Orada Avrupa hukukuna uygun kiÅŸisel verilerin koruma - seviyesi bulunmamaktadır ve Avrupa’daki veri koruma haklarınız uygulanmayabilir. Bu baÄŸlamda - özellikle ABD güvenlik makamlarının iÅŸletim sistemi saÄŸlayıcısına aktarılan bu verilere eriÅŸme - ve örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları deÄŸerlendirmeye alma olasılığı - bulunmaktadır. Ancak bu, yalnızca aktarılan kimlik kodlarını etkiler. KarşılaÅŸma verileri gibi - Uygulamadaki diÄŸer veriler bu prosedüre dahil edilmez. + iÅŸletim sisteminin saÄŸlayıcısına (Apple veya Google) aktarılır. Bu süreçte verilerin üçüncü bir + ülkeye, özellikle ABD’ye aktarılması da söz konusu olabilir. Orada Avrupa hukukuna karşılık + gelen kiÅŸisel verilerin koruma seviyesi bulunmayabilir ve Avrupa’daki veri koruma haklarınız + uygulanmayabilir. Bu baÄŸlamda özellikle güvenlik makamlarının iÅŸletim sistemi saÄŸlayıcısına + aktarılan bu verilere eriÅŸme ve örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları + deÄŸerlendirmeye alma olasılığı bulunmaktadır. Ancak bu, yalnızca aktarılan kimlik kodlarını + etkiler. KarşılaÅŸma verileri gibi Uygulamadaki diÄŸer veriler bu prosedüre dahil edilmez. </p> <p> Ancak Uygulama tarafından aktarılan veriler, sadece Almanya’daki sunucularda veya bir diÄŸer AB (veya Avrupa Ekonomik Alanı) ülkesindeki sunucularda iÅŸlenir, @@ -1038,5 +1042,5 @@ Temas güncesinin kullanılması isteÄŸe baÄŸlıdır. Veri giriÅŸlerinin temas g veya e-posta yoluyla: datenschutz@rki.de. </p> <p> - Baskı 24.02.2021 + Baskı 01.03.2021 </p> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt index 92b10563715bedf2b8562702e87df6219bd57bc7..f797cf5b89cd738c7f47d106b854ef1f2ec08064 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 @@ -92,7 +92,6 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { deadmanNotificationScheduler.schedulePeriodic() } contactDiaryWorkScheduler.schedulePeriodic() - dataDonationAnalyticsScheduler.schedulePeriodic() } deviceTimeHandler.launch() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReporter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReporter.kt index 31913f27d014b3340462b4271e205d07c03b9c90..a831191bb91c4857d39f7876770c8e26219bb3b6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReporter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReporter.kt @@ -9,7 +9,7 @@ interface BugReporter { } fun Throwable.reportProblem(tag: String? = null, info: String? = null) { - Timber.tag("BugReporter").v(this, "report(tag=$tag, info=$info)") + Timber.tag(tag ?: "BugReporter").e(this, info) if (CWADebug.isAUnitTest) return val reporter = AppInjector.component.bugReporter diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryWorkScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryWorkScheduler.kt index d989e8146ceca7b951e43f8b005ad9c3a20e1108..15bbc81bdc3ca0ba7302bdecb7440d01772035d9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryWorkScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryWorkScheduler.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.contactdiary.retention import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.WorkManager import dagger.Reusable +import timber.log.Timber import javax.inject.Inject @Reusable @@ -16,6 +17,7 @@ class ContactDiaryWorkScheduler @Inject constructor( * Replace with new if older work exists. */ fun schedulePeriodic() { + Timber.d("ContactDiaryWorkScheduler schedulePeriodic()") // Create unique work and enqueue workManager.enqueueUniquePeriodicWork( PERIODIC_WORK_NAME, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt index 4cf4427828dafc0c9653f0862e2dc6f2fcb18b31..f99851e7b28f6d8ea18481acdde8f7f5cee9dc32 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt @@ -12,5 +12,10 @@ data class OTPAuthorizationResult( @SerializedName("authorized") val authorized: Boolean, @SerializedName("redeemedAt") - val redeemedAt: Instant -) + val redeemedAt: Instant, + @SerializedName("invalidated") + val invalidated: Boolean = false +) { + + fun toInvalidatedInstance() = copy(invalidated = true) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt index bdd5fef0303cef83dc74709ae1d5f549c7de593a..8bf382bdc68a73c396037fd39004d87c93451e11 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.datadonation import androidx.annotation.Keep import com.google.gson.annotations.SerializedName +import de.rki.coronawarnapp.server.protocols.internal.ppdd.EdusOtp import org.joda.time.Instant import java.util.UUID @@ -14,5 +15,10 @@ data class OneTimePassword( ) { @Transient - val payloadForRequest = uuid.toString().toByteArray() + val edusOneTimePassword: EdusOtp.EDUSOneTimePassword = EdusOtp.EDUSOneTimePassword.newBuilder() + .setOtp(uuid.toString()) + .build() + + @Transient + val payloadForRequest: ByteArray = edusOneTimePassword.toByteArray() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt index 151e182bff636c866c915766c776e9f001bc7380..dab8333ca7fc18e204efa713891a1fad97f1b70f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt @@ -3,18 +3,22 @@ package de.rki.coronawarnapp.datadonation.analytics import androidx.annotation.VisibleForTesting import de.rki.coronawarnapp.appconfig.AnalyticsConfig import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.datadonation.analytics.server.DataDonationAnalyticsServer import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.datadonation.analytics.storage.LastAnalyticsSubmissionLogger import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation +import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaDataRequestAndroid import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.TimeStamper +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withTimeout import org.joda.time.Hours import org.joda.time.Instant import timber.log.Timber @@ -35,57 +39,69 @@ class Analytics @Inject constructor( ) { private val submissionLockoutMutex = Mutex() - private suspend fun trySubmission(analyticsConfig: AnalyticsConfig, ppaData: PpaData.PPADataAndroid): Boolean { - try { + private suspend fun trySubmission(analyticsConfig: AnalyticsConfig, ppaData: PpaData.PPADataAndroid): Result { + return try { val ppaAttestationRequest = PPADeviceAttestationRequest( ppaData = ppaData ) - Timber.d("Starting safety net device attestation") + Timber.tag(TAG).d("Starting safety net device attestation") val attestation = deviceAttestation.attest(ppaAttestationRequest) attestation.requirePass(analyticsConfig.safetyNetRequirements) - Timber.d("Safety net attestation passed") + Timber.tag(TAG).d("Safety net attestation passed") val ppaContainer = PpaDataRequestAndroid.PPADataRequestAndroid.newBuilder() .setAuthentication(attestation.accessControlProtoBuf) .setPayload(ppaData) .build() - Timber.d("Starting analytics upload") + Timber.tag(TAG).d("Starting analytics upload") dataDonationAnalyticsServer.uploadAnalyticsData(ppaContainer) - Timber.d("Analytics upload finished") + Timber.tag(TAG).d("Analytics upload finished") - return true + Result(successful = true) } catch (err: Exception) { - Timber.e(err, "Error during analytics submission") - return false + err.reportProblem(tag = TAG, info = "An error occurred during analytics submission") + val retry = err is SafetyNetException && err.type == SafetyNetException.Type.ATTESTATION_REQUEST_FAILED + Result(successful = false, shouldRetry = retry) } } suspend fun collectContributions(ppaDataBuilder: PpaData.PPADataAndroid.Builder): List<DonorModule.Contribution> { val request: DonorModule.Request = object : DonorModule.Request {} - val contributions = donorModules.map { - Timber.d("Beginning donation for module: %s", it::class.simpleName) - it.beginDonation(request) + val contributions = donorModules.mapNotNull { + val moduleName = it::class.simpleName + Timber.tag(TAG).d("Beginning donation for module: %s", moduleName) + try { + it.beginDonation(request) + } catch (e: Exception) { + e.reportProblem(TAG, "beginDonation($request): $moduleName failed") + null + } } contributions.forEach { - Timber.d("Injecting contribution: %s", it::class.simpleName) - it.injectData(ppaDataBuilder) + val moduleName = it::class.simpleName + Timber.tag(TAG).d("Injecting contribution: %s", moduleName) + try { + it.injectData(ppaDataBuilder) + } catch (e: Exception) { + e.reportProblem(TAG, "injectData($ppaDataBuilder): $moduleName failed") + } } return contributions } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - suspend fun submitAnalyticsData(analyticsConfig: AnalyticsConfig) { - Timber.d("Starting analytics submission") + suspend fun submitAnalyticsData(analyticsConfig: AnalyticsConfig): Result { + Timber.tag(TAG).d("Starting analytics submission") val ppaDataBuilder = PpaData.PPADataAndroid.newBuilder() @@ -93,14 +109,28 @@ class Analytics @Inject constructor( val analyticsProto = ppaDataBuilder.build() - val success = trySubmission(analyticsConfig, analyticsProto) + val result = try { + // 6min, if attestation and/or submission takes longer than that, + // then we want to give modules still time to cleanup and get into a consistent state. + withTimeout(360_000) { + trySubmission(analyticsConfig, analyticsProto) + } + } catch (e: TimeoutCancellationException) { + Timber.tag(TAG).e(e, "trySubmission() timed out after 360s.") + Result(successful = false, shouldRetry = true) + } contributions.forEach { - Timber.d("Finishing contribution: %s", it::class.simpleName) - it.finishDonation(success) + val moduleName = it::class.simpleName + Timber.tag(TAG).d("Finishing contribution($result) for %s", moduleName) + try { + it.finishDonation(result.successful) + } catch (e: Exception) { + e.reportProblem(TAG, "finishDonation($result): $moduleName failed") + } } - if (success) { + if (result.successful) { settings.lastSubmittedTimestamp.update { timeStamper.nowUTC } @@ -108,11 +138,12 @@ class Analytics @Inject constructor( logger.storeAnalyticsData(analyticsProto) } - Timber.d("Finished analytics submission success=%s", success) + Timber.tag(TAG).d("Finished analytics submission result=%s", result) + return result } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - suspend fun stopDueToNoAnalyticsConfig(analyticsConfig: AnalyticsConfig): Boolean { + fun stopDueToNoAnalyticsConfig(analyticsConfig: AnalyticsConfig): Boolean { return !analyticsConfig.analyticsEnabled } @@ -122,7 +153,7 @@ class Analytics @Inject constructor( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - suspend fun stopDueToProbabilityToSubmit(analyticsConfig: AnalyticsConfig): Boolean { + fun stopDueToProbabilityToSubmit(analyticsConfig: AnalyticsConfig): Boolean { val submitRoll = Random.nextDouble(0.0, 1.0) return submitRoll > analyticsConfig.probabilityToSubmit } @@ -137,40 +168,39 @@ class Analytics @Inject constructor( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun stopDueToTimeSinceOnboarding(): Boolean { val onboarding = LocalData.onboardingCompletedTimestamp()?.let { Instant.ofEpochMilli(it) } ?: return true - return onboarding.plus(Hours.hours(ONBOARDING_DELAY_HOURS).toStandardDuration()) - .isAfter(timeStamper.nowUTC) + return onboarding.plus(Hours.hours(ONBOARDING_DELAY_HOURS).toStandardDuration()).isAfter(timeStamper.nowUTC) } - suspend fun submitIfWanted() = submissionLockoutMutex.withLock { - Timber.d("Checking analytics conditions") + suspend fun submitIfWanted(): Result = submissionLockoutMutex.withLock { + Timber.tag(TAG).d("Checking analytics conditions") val analyticsConfig: AnalyticsConfig = appConfigProvider.getAppConfig().analytics if (stopDueToNoAnalyticsConfig(analyticsConfig)) { - Timber.w("Aborting Analytics submission due to noAnalyticsConfig") - return + Timber.tag(TAG).w("Aborting Analytics submission due to noAnalyticsConfig") + return@withLock Result(successful = false) } if (stopDueToNoUserConsent()) { - Timber.w("Aborting Analytics submission due to noUserConsent") - return + Timber.tag(TAG).w("Aborting Analytics submission due to noUserConsent") + return@withLock Result(successful = false) } if (stopDueToProbabilityToSubmit(analyticsConfig)) { - Timber.w("Aborting Analytics submission due to probabilityToSubmit") - return + Timber.tag(TAG).w("Aborting Analytics submission due to probabilityToSubmit") + return@withLock Result(successful = false) } if (stopDueToLastSubmittedTimestamp()) { - Timber.w("Aborting Analytics submission due to lastSubmittedTimestamp") - return + Timber.tag(TAG).w("Aborting Analytics submission due to lastSubmittedTimestamp") + return@withLock Result(successful = false) } if (stopDueToTimeSinceOnboarding()) { - Timber.w("Aborting Analytics submission due to timeSinceOnboarding") - return + Timber.tag(TAG).w("Aborting Analytics submission due to timeSinceOnboarding") + return@withLock Result(successful = false) } - submitAnalyticsData(analyticsConfig) + return@withLock submitAnalyticsData(analyticsConfig) } private suspend fun deleteAllData() = submissionLockoutMutex.withLock { @@ -192,7 +222,13 @@ class Analytics @Inject constructor( fun isAnalyticsEnabledFlow(): Flow<Boolean> = settings.analyticsEnabled.flow + data class Result( + val successful: Boolean, + val shouldRetry: Boolean = false + ) + companion object { + private val TAG = Analytics::class.java.simpleName private const val LAST_SUBMISSION_MIN_AGE_HOURS = 23 private const val ONBOARDING_DELAY_HOURS = 24 diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt index f37919da329465cc397752371df1aa7eb0719ee3..47b17821aaff71159b1d46c699fe23f0b25a7ac3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData +import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds import kotlinx.coroutines.flow.first import javax.inject.Inject import javax.inject.Singleton @@ -27,13 +28,14 @@ class ExposureRiskMetadataDonor @Inject constructor( .lastCalculated val riskLevelForMetadata = lastRiskResult.toMetadataRiskLevel() + val mostRecentDateAtRiskLevel = lastRiskResult.lastRiskEncounterAt?.seconds ?: -1 val newMetadata = PpaData.ExposureRiskMetadata.newBuilder() .setRiskLevel(riskLevelForMetadata) .setRiskLevelChangedComparedToPreviousSubmission(previousMetadata?.riskLevel != riskLevelForMetadata) - .setMostRecentDateAtRiskLevel(lastRiskResult.calculatedAt.millis) + .setMostRecentDateAtRiskLevel(mostRecentDateAtRiskLevel) .setDateChangedComparedToPreviousSubmission( - previousMetadata?.mostRecentDateAtRiskLevel != lastRiskResult.calculatedAt.millis + previousMetadata?.mostRecentDateAtRiskLevel != mostRecentDateAtRiskLevel ) .build() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsApiV1.kt index d0b2ac212bcabccd5fd19da54abc13ac2252f771..7343e9808f039422bebdc8f4a13e5eec3760bb72 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsApiV1.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsApiV1.kt @@ -9,7 +9,7 @@ import retrofit2.http.POST interface DataDonationAnalyticsApiV1 { data class DataDonationAnalyticsResponse( - @SerializedName("errorState") val errorState: String? + @SerializedName("errorCode") val errorCode: String? ) @POST("/version/v1/android/dat") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsServer.kt index ae2269d961e4fd3105a0689b7a773f8ca290d0c4..ef23e998bfcf5e06b624128737f15893e5918c87 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/server/DataDonationAnalyticsServer.kt @@ -22,7 +22,7 @@ class DataDonationAnalyticsServer @Inject constructor( return when (code) { 204 -> Timber.d("Analytics upload completed successfully") 400, 401, 403 -> { - val explanation = response.body()?.errorState ?: "Unknown clientside error" + val explanation = response.body()?.errorCode ?: "Unknown clientside error" throw AnalyticsException(message = explanation).also { Timber.w(it, "Analytics upload failed with 40X") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt index 7edd80d04f8fafeaa6235afa576bf3f438ce5a73..9c240c16e7269c78546b020de46897865fa7d329 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt @@ -4,6 +4,7 @@ import android.content.Context import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData.ExposureRiskMetadata import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.preferences.clearAndNotify import de.rki.coronawarnapp.util.preferences.createFlowPreference import okio.ByteString.Companion.decodeBase64 import okio.ByteString.Companion.toByteString @@ -89,6 +90,8 @@ class AnalyticsSettings @Inject constructor( defaultValue = false ) + fun clear() = prefs.clearAndNotify() + companion object { private const val PREVIOUS_EXPOSURE_RISK_METADATA = "exposurerisk.metadata.previous" private const val PKEY_USERINFO_AGEGROUP = "userinfo.agegroup" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsPeriodicWorker.kt index c964f618cc1c50400fa516c0a7a6abc646df42c6..0920f50625cd7cc07b9d2262d701b3e7a82b5716 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsPeriodicWorker.kt @@ -20,8 +20,7 @@ class DataDonationAnalyticsPeriodicWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, private val analytics: Analytics -) : - CoroutineWorker(context, workerParams) { +) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { Timber.tag(TAG).d("Background job started. Run attempt: $runAttemptCount") @@ -31,15 +30,19 @@ class DataDonationAnalyticsPeriodicWorker @AssistedInject constructor( return Result.failure() } - var result = Result.success() - try { - analytics.submitIfWanted() + + return try { + val analyticsResult = analytics.submitIfWanted() + Timber.tag(TAG).d("submitIfWanted() finished: %s", analyticsResult) + when { + analyticsResult.successful -> Result.success() + analyticsResult.shouldRetry -> Result.retry() + else -> Result.failure() + } } catch (e: Exception) { - Timber.tag(TAG).d(e) - result = Result.retry() + Timber.tag(TAG).w(e, "submitIfWanted() failed unexpectedly") + Result.failure() } - - return result } @AssistedFactory diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsScheduler.kt index 7483d2c46e6c76ca358ca12932b834a45aa33f2a..d4442e6890d606c91786ee01a9673fff21a141a1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsScheduler.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.datadonation.analytics.worker import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.WorkManager import dagger.Reusable +import timber.log.Timber import javax.inject.Inject /** @@ -22,12 +23,14 @@ class DataDonationAnalyticsScheduler @Inject constructor( * Enqueue background analytics submission periodic work */ fun schedulePeriodic() { - val additionalDelay = timeCalculation.getDelay() + val initialDelay = timeCalculation.getDelay() + // TODO Replace with logic that checks if already scheduled workManager. workManager.getWorkInfosByTag() + Timber.d("schedulePeriodic() initialDelay(if not yet scheduled)=%s", initialDelay) // Create unique work and enqueue workManager.enqueueUniquePeriodicWork( PERIODIC_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, - workBuilder.buildPeriodicWork(additionalDelay) + workBuilder.buildPeriodicWork(initialDelay) ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt index 5b5e92550c4c675ba438037de8f4023da37918e0..4360da23c6755b223a8891b5fdc9faa809537839 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt @@ -1,6 +1,8 @@ package de.rki.coronawarnapp.datadonation.analytics.worker import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.NetworkType import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequestBuilder import dagger.Reusable @@ -12,13 +14,13 @@ import javax.inject.Inject @Reusable class DataDonationAnalyticsWorkBuilder @Inject constructor() { - fun buildPeriodicWork(additionalDelay: Duration): PeriodicWorkRequest = + fun buildPeriodicWork(initialDelay: Duration): PeriodicWorkRequest = PeriodicWorkRequestBuilder<DataDonationAnalyticsPeriodicWorker>( DateTimeConstants.HOURS_PER_DAY.toLong(), TimeUnit.HOURS ) .setInitialDelay( - DateTimeConstants.HOURS_PER_DAY.toLong() + additionalDelay.standardHours, + initialDelay.standardHours, TimeUnit.HOURS ) .setBackoffCriteria( @@ -26,5 +28,11 @@ class DataDonationAnalyticsWorkBuilder @Inject constructor() { BackgroundConstants.BACKOFF_INITIAL_DELAY, TimeUnit.MINUTES ) + .setConstraints(buildConstraints()) + .build() + + private fun buildConstraints() = + Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) .build() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt index bd5e04ec1aefc5f52b1b64856bdf00029aa0d2b5..e29f43cc5c8ea7baca9a7cd4b3f4a409ce4e2b83 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt @@ -6,11 +6,14 @@ import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException.Type import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.util.HashExtensions +import de.rki.coronawarnapp.storage.TestSettings +import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.HashExtensions.toSHA256 import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.gplay.GoogleApiVersion +import okio.ByteString +import okio.ByteString.Companion.decodeHex import okio.ByteString.Companion.toByteString import org.joda.time.Duration import org.joda.time.Instant @@ -27,7 +30,8 @@ class CWASafetyNet @Inject constructor( private val appConfigProvider: AppConfigProvider, private val googleApiVersion: GoogleApiVersion, private val cwaSettings: CWASettings, - private val timeStamper: TimeStamper + private val timeStamper: TimeStamper, + private val testSettings: TestSettings ) : DeviceAttestation { @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -36,9 +40,10 @@ class CWASafetyNet @Inject constructor( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun calculateNonce(salt: ByteArray, payload: ByteArray): String { + fun calculateNonce(salt: ByteArray, payload: ByteArray): ByteString { val concat = salt + payload - return concat.toSHA256(format = HashExtensions.Format.BASE64) + // Default format is hex. + return concat.toSHA256().decodeHex() } override suspend fun attest(request: DeviceAttestation.Request): DeviceAttestation.Result { @@ -55,10 +60,16 @@ class CWASafetyNet @Inject constructor( } } + val skip24hCheck = CWADebug.isDeviceForTestersBuild && testSettings.skipSafetyNetTimeCheck.value + val nowUTC = timeStamper.nowUTC val firstReliableTimeStamp = cwaSettings.firstReliableDeviceTime + val timeSinceOnboarding = Duration(firstReliableTimeStamp, nowUTC) + Timber.d("firstReliableTimeStamp=%s, now=%s", firstReliableTimeStamp, nowUTC) + Timber.d("skip24hCheck=%b, timeSinceOnboarding=%dh", skip24hCheck, timeSinceOnboarding.standardHours) + if (firstReliableTimeStamp == Instant.EPOCH) { throw SafetyNetException(Type.TIME_SINCE_ONBOARDING_UNVERIFIED, "No first reliable timestamp available") - } else if (Duration(firstReliableTimeStamp, timeStamper.nowUTC) < Duration.standardHours(24)) { + } else if (!skip24hCheck && timeSinceOnboarding < Duration.standardHours(24)) { throw SafetyNetException(Type.TIME_SINCE_ONBOARDING_UNVERIFIED, "Time since first reliable timestamp <24h") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapper.kt index 6a36b54dc86562968267dda427fcb2bc1950ea9a..f1b732d2640e529df3d79d7667492bf8c58081a9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapper.kt @@ -10,13 +10,14 @@ import dagger.Reusable import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException.Type import de.rki.coronawarnapp.environment.EnvironmentSetup import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout +import okio.ByteString import okio.ByteString.Companion.decodeBase64 import timber.log.Timber import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine @Reusable class SafetyNetClientWrapper @Inject constructor( @@ -26,9 +27,9 @@ class SafetyNetClientWrapper @Inject constructor( suspend fun attest(nonce: ByteArray): Report { val response = try { - withTimeout(30 * 1000L) { callClient(nonce) } + withTimeout(180_000) { callClient(nonce) } } catch (e: TimeoutCancellationException) { - throw SafetyNetException(Type.ATTESTATION_FAILED, "Attestation timeout.", e) + throw SafetyNetException(Type.ATTESTATION_REQUEST_FAILED, "Attestation timeout (us).", e) } val jwsResult = response.jwsResult ?: throw SafetyNetException( @@ -72,22 +73,46 @@ class SafetyNetClientWrapper @Inject constructor( return JsonParser.parseString(rawJson).asJsonObject } - private suspend fun callClient(nonce: ByteArray): SafetyNetApi.AttestationResponse = suspendCoroutine { cont -> - safetyNetClient.attest(nonce, environmentSetup.safetyNetApiKey) - .addOnSuccessListener { - Timber.tag(TAG).v("Attestation finished with %s", it) - cont.resume(it) - } - .addOnFailureListener { - Timber.tag(TAG).w(it, "Attestation failed.") - val wrappedError = if (it is ApiException && it.statusCode == CommonStatusCodes.NETWORK_ERROR) { - SafetyNetException(Type.ATTESTATION_REQUEST_FAILED, "Network error", it) - } else { - SafetyNetException(Type.ATTESTATION_FAILED, "SafetyNet client returned an error.", it) + private suspend fun callClient(nonce: ByteArray): SafetyNetApi.AttestationResponse = + suspendCancellableCoroutine { cont -> + safetyNetClient.attest(nonce, environmentSetup.safetyNetApiKey) + .addOnSuccessListener { + Timber.tag(TAG).v("Attestation finished with %s", it) + cont.resume(it) } - cont.resumeWithException(wrappedError) - } - } + .addOnFailureListener { + Timber.tag(TAG).w(it, "Attestation failed.") + val defaultError = + SafetyNetException(Type.ATTESTATION_FAILED, "SafetyNet client returned an error.", it) + + if (it !is ApiException) { + cont.resumeWithException(defaultError) + return@addOnFailureListener + } + + val apiError = when (it.statusCode) { + CommonStatusCodes.TIMEOUT -> SafetyNetException( + Type.ATTESTATION_REQUEST_FAILED, + "Timeout (them)", + it + ) + // com.google.android.gms.common.api.ApiException: 20: + // The connection to Google Play services was lost due to service disconnection. + // Last reason for disconnect: Timing out service connection. + CommonStatusCodes.CONNECTION_SUSPENDED_DURING_CALL, + CommonStatusCodes.RECONNECTION_TIMED_OUT_DURING_UPDATE, + CommonStatusCodes.RECONNECTION_TIMED_OUT, + CommonStatusCodes.NETWORK_ERROR -> SafetyNetException( + Type.ATTESTATION_REQUEST_FAILED, + "Network error (${it.statusCode})", + it + ) + else -> defaultError + } + + cont.resumeWithException(apiError) + } + } data class Report( val jwsResult: String, @@ -95,7 +120,7 @@ class SafetyNetClientWrapper @Inject constructor( val body: JsonObject, val signature: ByteArray ) { - val nonce: String? = body.get("nonce")?.asString?.decodeBase64()?.utf8() + val nonce: ByteString? = body.get("nonce")?.asString?.decodeBase64() val apkPackageName: String? = body.get("apkPackageName")?.asString diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt index 083c7626612af2bfaeac77bc082319b69cf12680..f75598ff0c050fd6a32ce4c51eca99aef13ed7a8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt @@ -1,12 +1,38 @@ package de.rki.coronawarnapp.datadonation.safetynet +import android.content.Context import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.util.HasHumanReadableError +import de.rki.coronawarnapp.util.HumanReadableError class SafetyNetException constructor( val type: Type, message: String? = null, cause: Throwable? = null -) : Exception("$type: $message", cause) { +) : Exception("$type: $message", cause), HasHumanReadableError { + + override fun toHumanReadableError(context: Context): HumanReadableError { + val messageRes = when (type) { + Type.APK_PACKAGE_NAME_MISMATCH, + Type.ATTESTATION_FAILED, + Type.ATTESTATION_REQUEST_FAILED, + Type.DEVICE_TIME_UNVERIFIED, + Type.NONCE_MISMATCH -> + R.string.datadonation_details_survey_consent_error_TRY_AGAIN_LATER + Type.BASIC_INTEGRITY_REQUIRED, + Type.CTS_PROFILE_MATCH_REQUIRED, + Type.EVALUATION_TYPE_BASIC_REQUIRED, + Type.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED -> + R.string.datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED + Type.DEVICE_TIME_INCORRECT -> + R.string.datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME + Type.PLAY_SERVICES_VERSION_MISMATCH -> + R.string.datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES + Type.TIME_SINCE_ONBOARDING_UNVERIFIED -> + R.string.datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED + } + return HumanReadableError(description = context.getString(messageRes, type)) + } enum class Type { // TRY_AGAIN_LATER (Text Key) @@ -32,23 +58,3 @@ class SafetyNetException constructor( TIME_SINCE_ONBOARDING_UNVERIFIED } } - -fun SafetyNetException.errorMsgRes(): Int = when (type) { - SafetyNetException.Type.APK_PACKAGE_NAME_MISMATCH, - SafetyNetException.Type.ATTESTATION_FAILED, - SafetyNetException.Type.ATTESTATION_REQUEST_FAILED, - SafetyNetException.Type.DEVICE_TIME_UNVERIFIED, - SafetyNetException.Type.NONCE_MISMATCH -> - R.string.datadonation_details_survey_consent_error_TRY_AGAIN_LATER - SafetyNetException.Type.BASIC_INTEGRITY_REQUIRED, - SafetyNetException.Type.CTS_PROFILE_MATCH_REQUIRED, - SafetyNetException.Type.EVALUATION_TYPE_BASIC_REQUIRED, - SafetyNetException.Type.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED -> - R.string.datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED - SafetyNetException.Type.DEVICE_TIME_INCORRECT -> - R.string.datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME - SafetyNetException.Type.PLAY_SERVICES_VERSION_MISMATCH -> - R.string.datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES - SafetyNetException.Type.TIME_SINCE_ONBOARDING_UNVERIFIED -> - R.string.datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyException.kt index e7967fd5d4765621da9797e06e7b2f36cc9ab8f4..884715f69fd62aa69e5926964454589630439f1e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyException.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyException.kt @@ -1,21 +1,27 @@ package de.rki.coronawarnapp.datadonation.survey +import android.content.Context import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.util.HasHumanReadableError +import de.rki.coronawarnapp.util.HumanReadableError class SurveyException constructor( val type: Type, message: String? = null, cause: Throwable? = null -) : Exception("$type: $message", cause) { +) : Exception("$type: $message", cause), HasHumanReadableError { + + override fun toHumanReadableError(context: Context): HumanReadableError { + val messageRes = when (type) { + Type.ALREADY_PARTICIPATED_THIS_MONTH -> + R.string.datadonation_details_survey_consent_error_ALREADY_PARTICIPATED + Type.OTP_NOT_AUTHORIZED -> R.string.datadonation_details_survey_consent_error_TRY_AGAIN_LATER + } + return HumanReadableError(description = context.getString(messageRes, type)) + } enum class Type { ALREADY_PARTICIPATED_THIS_MONTH, OTP_NOT_AUTHORIZED } } - -fun SurveyException.errorMsgRes(): Int = when (type) { - SurveyException.Type.ALREADY_PARTICIPATED_THIS_MONTH -> - R.string.datadonation_details_survey_consent_error_ALREADY_PARTICIPATED - SurveyException.Type.OTP_NOT_AUTHORIZED -> R.string.datadonation_details_survey_consent_error_TRY_AGAIN_LATER -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt index 228b75aa90a94823144c1946038ef3666e1bd64e..bf804128e1815ec72818c5c59e28707197ea04db 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt @@ -52,6 +52,7 @@ class SurveySettings @Inject constructor( requireNotNull(result.uuid) requireNotNull(result.authorized) requireNotNull(result.redeemedAt) + requireNotNull(result.invalidated) return result } return null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt index bf2bf4f0b4deaf919a6cc3b44784dd787e790b0f..f792b7d75942f553f9d3bc45ea68bb245f466017 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt @@ -37,6 +37,36 @@ class Surveys @Inject constructor( } } + suspend fun isConsentNeeded(type: Type): ConsentResult { + + when (type) { + Type.HIGH_RISK_ENCOUNTER -> { + + // If no OTP was ever authorized, we need a consent. + val authResult = oneTimePasswordRepo.otpAuthorizationResult ?: return ConsentResult.Needed + + // If we already have an authorized OTP for this high-risk state + // we can skip the consent and directly show the url in the browser. + // We know that the otp belongs to the current high-risk state, because the authResult gets + // invalidated on high to low risk state transitions. + if (authResult.authorized && !authResult.invalidated) { + val surveyLink = urlProvider.provideUrl(type, authResult.uuid) + return ConsentResult.AlreadyGiven(surveyLink) + } + + // Finally, we need a consent for stored OTPs where the authorization failed (authorized == false) + // or when the app shows a new high-risk card (and therefore authResult was previously invalidated when the + // risk changed from high to low) + return ConsentResult.Needed + } + } + } + + sealed class ConsentResult { + object Needed : ConsentResult() + data class AlreadyGiven(val surveyLink: String) : ConsentResult() + } + suspend fun requestDetails(type: Type): Survey { val config = appConfigProvider.getAppConfig().survey Timber.v("Requested survey: %s", config) @@ -62,7 +92,11 @@ class Surveys @Inject constructor( // request validation from server val errorCode = surveyServer.authOTP(oneTimePassword, attestationResult).errorCode - val result = OTPAuthorizationResult(oneTimePassword.uuid, errorCode == null, now) + val result = OTPAuthorizationResult( + uuid = oneTimePassword.uuid, + authorized = errorCode == null, + redeemedAt = now + ) oneTimePasswordRepo.otpAuthorizationResult = result if (result.authorized) { @@ -76,9 +110,10 @@ class Surveys @Inject constructor( } fun resetSurvey(type: Type) { - if (type == Type.HIGH_RISK_ENCOUNTER) { - Timber.d("Discarding one time password for survey about previous high-risk state.") - oneTimePasswordRepo.clear() + val authResult = oneTimePasswordRepo.otpAuthorizationResult + if (type == Type.HIGH_RISK_ENCOUNTER && authResult != null) { + Timber.d("Invalidating one time password for survey about previous high-risk state.") + oneTimePasswordRepo.otpAuthorizationResult = authResult.toInvalidatedInstance() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentFragment.kt index 28f400fe95db220e74aac93356877d6c8a2e49b9..2042fb57bd8372625789e06181417b388d08dea8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentFragment.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.datadonation.survey.consent import android.os.Bundle import android.view.View -import androidx.annotation.StringRes import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs @@ -73,16 +72,16 @@ class SurveyConsentFragment : Fragment(R.layout.survey_consent_fragment), AutoIn } vm.showErrorDialog.observe2(this) { - showErrorDialog(it.msgRes) + showErrorDialog(it.errorMessage.get(requireContext())) } } - private fun showErrorDialog(@StringRes stringRes: Int) { + private fun showErrorDialog(message: String) { context?.let { val dialog = DialogHelper.DialogInstance( context = it, title = R.string.datadonation_details_survey_consent_error_dialog_title, - message = stringRes, + message = message, positiveButton = R.string.datadonation_details_survey_consent_error_dialog_pos_button, cancelable = false ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentViewModel.kt index d4a5e4c3a482322e7d576af051e41c3745d8d168..e3ccb2df950ec596e066f5c63a46ba3d3f95596b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentViewModel.kt @@ -1,19 +1,18 @@ package de.rki.coronawarnapp.datadonation.survey.consent -import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException -import de.rki.coronawarnapp.datadonation.safetynet.errorMsgRes -import de.rki.coronawarnapp.datadonation.survey.SurveyException import de.rki.coronawarnapp.datadonation.survey.Surveys -import de.rki.coronawarnapp.datadonation.survey.errorMsgRes +import de.rki.coronawarnapp.util.HasHumanReadableError import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.toResolvingString +import de.rki.coronawarnapp.util.ui.LazyString import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.ui.toResolvingString import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import kotlinx.coroutines.flow.MutableStateFlow @@ -52,9 +51,11 @@ class SurveyConsentViewModel @AssistedInject constructor( State.Success(survey) } catch (e: Exception) { val errorMsg = when (e) { - is SafetyNetException -> e.errorMsgRes() - is SurveyException -> e.errorMsgRes() - else -> R.string.datadonation_details_survey_consent_error_TRY_AGAIN_LATER + is HasHumanReadableError -> e.toResolvingString() + else -> { + R.string.datadonation_details_survey_consent_error_TRY_AGAIN_LATER + .toResolvingString(e.javaClass.simpleName) + } } Timber.e(e) State.Error(errorMsg) @@ -76,7 +77,7 @@ class SurveyConsentViewModel @AssistedInject constructor( object Loading : State() data class Error( - @StringRes val msgRes: Int + val errorMessage: LazyString ) : State() data class Success( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/server/SurveyServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/server/SurveyServer.kt index e345914ef6cc453ebd5fed21a12844a1dd06f86f..3feba5a87b53142e47e7a422e5d25ff06271d53b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/server/SurveyServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/server/SurveyServer.kt @@ -1,10 +1,8 @@ package de.rki.coronawarnapp.datadonation.survey.server -import com.google.protobuf.ByteString import dagger.Lazy import de.rki.coronawarnapp.datadonation.OneTimePassword import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation -import de.rki.coronawarnapp.server.protocols.internal.ppdd.EdusOtp import de.rki.coronawarnapp.server.protocols.internal.ppdd.EdusOtpRequestAndroid import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import kotlinx.coroutines.withContext @@ -28,11 +26,7 @@ class SurveyServer @Inject constructor( Timber.d("authOTP()") val dataDonationPayload = EdusOtpRequestAndroid.EDUSOneTimePasswordRequestAndroid.newBuilder() - .setPayload( - EdusOtp.EDUSOneTimePassword.newBuilder() - .setOtp(data.uuid.toString()) - .setOtpBytes(ByteString.copyFrom(data.payloadForRequest)) - ) + .setPayload(data.edusOneTimePassword) .setAuthentication(deviceAttestation.accessControlProtoBuf) .build() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt index db52d23dcd0803b00a9500e8e4efec8c017b756a..b545c807ec7d29864850a5c770e6ffd08100da77 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.risk.RollbackItem +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException import de.rki.coronawarnapp.task.TaskFactory @@ -25,6 +26,7 @@ import java.util.Date import javax.inject.Inject import javax.inject.Provider +@Suppress("ReturnCount") class DownloadDiagnosisKeysTask @Inject constructor( private val enfClient: ENFClient, private val environmentSetup: EnvironmentSetup, @@ -111,6 +113,11 @@ class DownloadDiagnosisKeysTask @Inject constructor( // remember version code of this execution for next time settings.updateLastVersionCodeToCurrent() + if (LocalData.isAllowedToSubmitDiagnosisKeys()) { + Timber.tag(TAG).i("task aborted, positive test result") + return object : Task.Result {} + } + Timber.tag(TAG).d("Attempting submission to ENF") val isSubmissionSuccessful = enfClient.provideDiagnosisKeys( availableKeyFiles, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt index b54b8d76836122d6620b318e14c22a7632f27e71..ba9ca0de7a51413d35f153e6f23a8b16029e36c4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt @@ -29,55 +29,81 @@ import okhttp3.Response import timber.log.Timber import java.net.SocketTimeoutException import java.net.UnknownHostException -import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.HttpsURLConnection.HTTP_ACCEPTED +import javax.net.ssl.HttpsURLConnection.HTTP_BAD_GATEWAY +import javax.net.ssl.HttpsURLConnection.HTTP_BAD_REQUEST +import javax.net.ssl.HttpsURLConnection.HTTP_CONFLICT +import javax.net.ssl.HttpsURLConnection.HTTP_CREATED +import javax.net.ssl.HttpsURLConnection.HTTP_FORBIDDEN +import javax.net.ssl.HttpsURLConnection.HTTP_GATEWAY_TIMEOUT +import javax.net.ssl.HttpsURLConnection.HTTP_GONE +import javax.net.ssl.HttpsURLConnection.HTTP_INTERNAL_ERROR +import javax.net.ssl.HttpsURLConnection.HTTP_NOT_FOUND +import javax.net.ssl.HttpsURLConnection.HTTP_NOT_IMPLEMENTED +import javax.net.ssl.HttpsURLConnection.HTTP_NO_CONTENT +import javax.net.ssl.HttpsURLConnection.HTTP_OK +import javax.net.ssl.HttpsURLConnection.HTTP_UNAUTHORIZED +import javax.net.ssl.HttpsURLConnection.HTTP_UNAVAILABLE +import javax.net.ssl.HttpsURLConnection.HTTP_UNSUPPORTED_TYPE +import javax.net.ssl.HttpsURLConnection.HTTP_VERSION class HttpErrorParser : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { try { val response = chain.proceed(chain.request()) - val message: String? = try { - if (response.isSuccessful) { - null - } else { - response.message - } + if (response.isSuccessful) { + return response + } + + val statusMessage: String? = try { + response.message } catch (e: Exception) { - Timber.w("Failed to get http error message.") + Timber.w("Failed to get http status-message.") null } + + val body: String? = try { + response.peekBody(2048).string() + } catch (e: Exception) { + Timber.w("Failed to get http error body.") + null + } + + val errorDetails = "$statusMessage body=$body'" + return when (val code = response.code) { - HttpsURLConnection.HTTP_OK -> response - HttpsURLConnection.HTTP_CREATED -> response - HttpsURLConnection.HTTP_ACCEPTED -> response - HttpsURLConnection.HTTP_NO_CONTENT -> response - HttpsURLConnection.HTTP_BAD_REQUEST -> throw BadRequestException(message) - HttpsURLConnection.HTTP_UNAUTHORIZED -> throw UnauthorizedException(message) - HttpsURLConnection.HTTP_FORBIDDEN -> throw ForbiddenException(message) - HttpsURLConnection.HTTP_NOT_FOUND -> throw NotFoundException(message) - HttpsURLConnection.HTTP_CONFLICT -> throw ConflictException(message) - HttpsURLConnection.HTTP_GONE -> throw GoneException(message) - HttpsURLConnection.HTTP_UNSUPPORTED_TYPE -> throw UnsupportedMediaTypeException(message) - 429 -> throw TooManyRequestsException(message) - HttpsURLConnection.HTTP_INTERNAL_ERROR -> throw InternalServerErrorException(message) - HttpsURLConnection.HTTP_NOT_IMPLEMENTED -> throw NotImplementedException(message) - HttpsURLConnection.HTTP_BAD_GATEWAY -> throw BadGatewayException(message) - HttpsURLConnection.HTTP_UNAVAILABLE -> throw ServiceUnavailableException(message) - HttpsURLConnection.HTTP_GATEWAY_TIMEOUT -> throw GatewayTimeoutException(message) - HttpsURLConnection.HTTP_VERSION -> throw HTTPVersionNotSupported(message) - 511 -> throw NetworkAuthenticationRequiredException(message) - 598 -> throw NetworkReadTimeoutException(message) - 599 -> throw NetworkConnectTimeoutException(message) + HTTP_OK -> response + HTTP_CREATED -> response + HTTP_ACCEPTED -> response + HTTP_NO_CONTENT -> response + HTTP_BAD_REQUEST -> throw BadRequestException(errorDetails) + HTTP_UNAUTHORIZED -> throw UnauthorizedException(errorDetails) + HTTP_FORBIDDEN -> throw ForbiddenException(errorDetails) + HTTP_NOT_FOUND -> throw NotFoundException(errorDetails) + HTTP_CONFLICT -> throw ConflictException(errorDetails) + HTTP_GONE -> throw GoneException(errorDetails) + HTTP_UNSUPPORTED_TYPE -> throw UnsupportedMediaTypeException(errorDetails) + 429 -> throw TooManyRequestsException(errorDetails) + HTTP_INTERNAL_ERROR -> throw InternalServerErrorException(errorDetails) + HTTP_NOT_IMPLEMENTED -> throw NotImplementedException(errorDetails) + HTTP_BAD_GATEWAY -> throw BadGatewayException(errorDetails) + HTTP_UNAVAILABLE -> throw ServiceUnavailableException(errorDetails) + HTTP_GATEWAY_TIMEOUT -> throw GatewayTimeoutException(errorDetails) + HTTP_VERSION -> throw HTTPVersionNotSupported(errorDetails) + 511 -> throw NetworkAuthenticationRequiredException(errorDetails) + 598 -> throw NetworkReadTimeoutException(errorDetails) + 599 -> throw NetworkConnectTimeoutException(errorDetails) else -> { - if (code in 100..199) throw CwaInformationalNotSupportedError(code, message) + if (code in 100..199) throw CwaInformationalNotSupportedError(code, errorDetails) if (code in 200..299) throw CwaSuccessResponseWithCodeMismatchNotSupportedError( code, - message + errorDetails ) - if (code in 300..399) throw CwaRedirectNotSupportedError(code, message) - if (code in 400..499) throw CwaClientError(code, message) - if (code in 500..599) throw CwaServerError(code, message) - throw CwaWebException(code, message) + if (code in 300..399) throw CwaRedirectNotSupportedError(code, errorDetails) + if (code in 400..499) throw CwaClientError(code, errorDetails) + if (code in 500..599) throw CwaServerError(code, errorDetails) + throw CwaWebException(code, errorDetails) } } } catch (err: SocketTimeoutException) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProvider.kt index bb0241ab522a1d6772c9543051210eab935a7372..2452ac44b62c3ab3b127e9e083ff96a803bcd373 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProvider.kt @@ -157,16 +157,21 @@ class DefaultTEKHistoryProvider @Inject constructor( } } - // Timeout after 20 sec if receiver did not get called + // Timeout after 5 sec if receiver did not get called @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal suspend fun getPreAuthorizedExposureKeys(): List<TemporaryExposureKey> = withTimeout(20_000) { + internal suspend fun getPreAuthorizedExposureKeys(): List<TemporaryExposureKey> = withTimeout(5_000) { coroutineScope { // Register receiver before hitting the API to avoid race conditions val deferredIntent = async { awaitReceivedBroadcast() } client.requestPreAuthorizedTemporaryExposureKeyRelease().await() - Timber.i("requestPreAuthorizedTemporaryExposureKeyRelease is done") + Timber.i("Pre-Auth requestPreAuthorizedTemporaryExposureKeyRelease is done") + val startTime = System.currentTimeMillis() + Timber.i("Pre-Auth Receiver StartTime:$startTime") val intent = deferredIntent.await() - Timber.d("getPreAuthorizedExposureKeys():intent=%s", intent) + val endTime = System.currentTimeMillis() + Timber.i("Pre-Auth Receiver EndTime:$endTime") + Timber.i("Pre-Auth Receiver WaitingTime:${endTime - startTime}") + Timber.d("Pre-Auth getPreAuthorizedExposureKeys():intent=%s", intent) intent.getParcelableArrayListExtra(EXTRA_TEMPORARY_EXPOSURE_KEY_LIST) ?: emptyList() } } @@ -175,7 +180,7 @@ class DefaultTEKHistoryProvider @Inject constructor( val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { cont.resume(intent) - Timber.d("unregisterReceiver") + Timber.d("Pre-Auth unregisterReceiver") context.unregisterReceiver(this) } } @@ -184,7 +189,7 @@ class DefaultTEKHistoryProvider @Inject constructor( IntentFilter(ACTION_PRE_AUTHORIZE_RELEASE_PHONE_UNLOCKED) ) cont.invokeOnCancellation { - Timber.d(it, "unregisterReceiver") + Timber.d(it, "Pre-Auth unregisterReceiver") context.unregisterReceiver(receiver) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt index e9bb38c8485ad88263fb1fbdbabf6551036a3010..c0099d3a3f1550da855c969ff4c5e8ce6b0a289e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt @@ -35,6 +35,11 @@ class TestSettings @Inject constructor( writer = FlowPreference.gsonWriter(gson) ) + val skipSafetyNetTimeCheck = prefs.createFlowPreference( + key = "safetynet.skip.timecheck", + defaultValue = false + ) + enum class FakeExposureWindowTypes { @SerializedName("DISABLED") DISABLED, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt index b21bf698e80c4186fd840cca67e761700809b76f..7732c39381124be8d7f5fa910e53c814247d6d56 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TracingDetailsFragmentLayoutBinding +import de.rki.coronawarnapp.util.ExternalActionHelper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.lists.diffutil.update import de.rki.coronawarnapp.util.ui.doNavigate @@ -55,6 +56,10 @@ class TracingDetailsFragment : Fragment(R.layout.tracing_details_fragment_layout is TracingDetailsNavigationEvents.NavigateToSurveyConsentFragment -> doNavigate( TracingDetailsFragmentDirections.actionRiskDetailsFragmentToSurveyConsentFragment(it.type) ) + is TracingDetailsNavigationEvents.NavigateToSurveyUrlInBrowser -> ExternalActionHelper.openUrl( + this, + it.url + ) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt index df6725509fbc286e8108d1f01abe82866a2596c0..e7fa208798eaa4d03d3ace92c00d141d499ae932 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt @@ -4,6 +4,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.datadonation.survey.Surveys +import de.rki.coronawarnapp.datadonation.survey.Surveys.ConsentResult.AlreadyGiven +import de.rki.coronawarnapp.datadonation.survey.Surveys.ConsentResult.Needed import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults @@ -42,7 +45,8 @@ class TracingDetailsFragmentViewModel @AssistedInject constructor( riskLevelStorage: RiskLevelStorage, tracingDetailsItemProvider: TracingDetailsItemProvider, tracingStateProviderFactory: TracingStateProvider.Factory, - private val tracingRepository: TracingRepository + private val tracingRepository: TracingRepository, + private val surveys: Surveys ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { private val tracingStateProvider by lazy { tracingStateProviderFactory.create(isDetailsMode = true) } @@ -108,7 +112,20 @@ class TracingDetailsFragmentViewModel @AssistedInject constructor( fun onItemClicked(item: DetailsItem) { when (item) { is UserSurveyBox.Item -> - routeToScreen.postValue(TracingDetailsNavigationEvents.NavigateToSurveyConsentFragment(item.type)) + launch { + when (val consentResult = surveys.isConsentNeeded(Surveys.Type.HIGH_RISK_ENCOUNTER)) { + is Needed -> routeToScreen.postValue( + TracingDetailsNavigationEvents.NavigateToSurveyConsentFragment( + item.type + ) + ) + is AlreadyGiven -> routeToScreen.postValue( + TracingDetailsNavigationEvents.NavigateToSurveyUrlInBrowser( + consentResult.surveyLink + ) + ) + } + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsNavigationEvents.kt index 221ebc91e8e9ebd767706a140276e2647c3f1749..9ba870a63c02d6048d225a6d84114e3061e8e730 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsNavigationEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsNavigationEvents.kt @@ -3,5 +3,6 @@ package de.rki.coronawarnapp.tracing.ui.details import de.rki.coronawarnapp.datadonation.survey.Surveys sealed class TracingDetailsNavigationEvents { - class NavigateToSurveyConsentFragment(val type: Surveys.Type) : TracingDetailsNavigationEvents() + data class NavigateToSurveyConsentFragment(val type: Surveys.Type) : TracingDetailsNavigationEvents() + data class NavigateToSurveyUrlInBrowser(val url: String) : TracingDetailsNavigationEvents() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt index c9204ce988c03634c8bab244d36cdc4f818f0c0a..a0c2547e99906dc956149bebca5d47edf4e317a5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt @@ -17,6 +17,7 @@ import dagger.android.HasAndroidInjector import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryWorkScheduler import de.rki.coronawarnapp.databinding.ActivityMainBinding +import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.ui.base.startActivitySafely @@ -61,6 +62,7 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { @Inject lateinit var powerManagement: PowerManagement @Inject lateinit var deadmanScheduler: DeadmanNotificationScheduler @Inject lateinit var contactDiaryWorkScheduler: ContactDiaryWorkScheduler + @Inject lateinit var dataDonationAnalyticsScheduler: DataDonationAnalyticsScheduler override fun onCreate(savedInstanceState: Bundle?) { AppInjector.setup(this) @@ -108,6 +110,7 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { scheduleWork() vm.doBackgroundNoiseCheck() contactDiaryWorkScheduler.schedulePeriodic() + dataDonationAnalyticsScheduler.schedulePeriodic() if (!LocalData.isAllowedToSubmitDiagnosisKeys()) { deadmanScheduler.schedulePeriodic() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt index 1bceda1f85eca90f326d9e60b62821ae39104f5b..6a265ce44d9184285e656b5dc74af6a4e4076ccc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt @@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultAvailableBinding import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog +import de.rki.coronawarnapp.ui.submission.SubmissionBlockingDialog import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.doNavigate @@ -16,6 +17,7 @@ import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import timber.log.Timber import javax.inject.Inject /** @@ -27,10 +29,13 @@ class SubmissionTestResultAvailableFragment : Fragment(R.layout.fragment_submiss @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory private val vm: SubmissionTestResultAvailableViewModel by cwaViewModels { viewModelFactory } private val binding: FragmentSubmissionTestResultAvailableBinding by viewBindingLazy() + private lateinit var keyRetrievalProgress: SubmissionBlockingDialog override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + keyRetrievalProgress = SubmissionBlockingDialog(requireContext()) + val backCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() = vm.goBack() } @@ -49,6 +54,12 @@ class SubmissionTestResultAvailableFragment : Fragment(R.layout.fragment_submiss binding.submissionTestResultAvailableConsentStatus.consent = it } + vm.showKeyRetrievalProgress.observe2(this) { show -> + Timber.i("SubmissionTestResult:showKeyRetrievalProgress:$show") + keyRetrievalProgress.setState(show) + binding.submissionTestResultAvailableProceedButton.isEnabled = !show + } + binding.apply { submissionTestResultAvailableProceedButton.setOnClickListener { vm.proceed() } submissionTestResultAvailableConsentStatus.setOnClickListener { vm.goConsent() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt index 380d7a84cdd912fe61dfc35392a6c9897977e9e6..9d122d70c945df4436468b3246aba2727db44107 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt @@ -32,6 +32,7 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor( val consent = consentFlow.asLiveData(dispatcherProvider.Default) val showPermissionRequest = SingleLiveEvent<(Activity) -> Unit>() val showCloseDialog = SingleLiveEvent<Unit>() + val showKeyRetrievalProgress = SingleLiveEvent<Boolean>() val showTracingConsentDialog = SingleLiveEvent<(Boolean) -> Unit>() private val tekHistoryUpdater = tekHistoryUpdaterFactory.create( @@ -44,6 +45,8 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor( SubmissionTestResultAvailableFragmentDirections .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultConsentGivenFragment() ) + + showKeyRetrievalProgress.postValue(false) } override fun onTEKPermissionDeclined() { @@ -51,14 +54,17 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor( SubmissionTestResultAvailableFragmentDirections .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultNoConsentFragment() ) + showKeyRetrievalProgress.postValue(false) } override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) { showTracingConsentDialog.postValue(onConsentResult) + showKeyRetrievalProgress.postValue(false) } override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) { showPermissionRequest.postValue(permissionRequest) + showKeyRetrievalProgress.postValue(false) } override fun onError(error: Throwable) { @@ -67,6 +73,7 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor( exceptionCategory = ExceptionCategory.EXPOSURENOTIFICATION, prefix = "SubmissionTestResultAvailableViewModel" ) + showKeyRetrievalProgress.postValue(false) } } ) @@ -96,6 +103,7 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor( } fun proceed() { + showKeyRetrievalProgress.value = true launch { if (consentFlow.first()) { tekHistoryUpdater.updateTEKHistoryOrRequestPermission() @@ -109,6 +117,7 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor( } fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + showKeyRetrievalProgress.value = true tekHistoryUpdater.handleActivityResult(requestCode, resultCode, data) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt index bf7306dae8e383771de71a6040e67caba25b5268..85af09cf324159ace2456eb156beb62d47331314 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt @@ -1,5 +1,7 @@ package de.rki.coronawarnapp.ui.submission.symptoms.calendar +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import dagger.assisted.Assisted @@ -29,8 +31,17 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( val routeToScreen = SingleLiveEvent<NavDirections>() val showCancelDialog = SingleLiveEvent<Unit>() - val showUploadDialog = autoSubmission.isSubmissionRunning - .asLiveData(context = dispatcherProvider.Default) + private val mediatorShowUploadDialog = MediatorLiveData<Boolean>() + + init { + mediatorShowUploadDialog.addSource( + autoSubmission.isSubmissionRunning.asLiveData(context = dispatcherProvider.Default) + ) { show -> + mediatorShowUploadDialog.postValue(show) + } + } + + val showUploadDialog: LiveData<Boolean> = mediatorShowUploadDialog fun onLastSevenDaysStart() { updateSymptomStart(Symptoms.StartOf.LastSevenDays) @@ -88,6 +99,8 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( } catch (e: Exception) { Timber.tag(TAG).e(e, "performSubmission() failed.") } finally { + Timber.i("Hide uploading progress and navigate to HomeFragment") + mediatorShowUploadDialog.postValue(false) routeToScreen.postValue( SubmissionSymptomCalendarFragmentDirections.actionSubmissionSymptomCalendarFragmentToMainFragment() ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt index 5aef9891bc0113c36ed32cd435026028d1a7db30..2fd6b41f222745ebcd9ab224706efd87d14d5a27 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionNoConsentPositiveOtherWarningBinding import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog +import de.rki.coronawarnapp.ui.submission.SubmissionBlockingDialog import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.doNavigate @@ -38,6 +39,8 @@ class SubmissionResultPositiveOtherWarningNoConsentFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val keysRetrievalProgress = SubmissionBlockingDialog(requireContext()) + binding.submissionPositiveOtherWarningNoConsentButtonNext.setOnClickListener { viewModel.onConsentButtonClicked() } @@ -49,6 +52,11 @@ class SubmissionResultPositiveOtherWarningNoConsentFragment : doNavigate(it) } + viewModel.keysRetrievalProgress.observe2(this) { show -> + keysRetrievalProgress.setState(show) + binding.submissionPositiveOtherWarningNoConsentButtonNext.isEnabled = !show + } + viewModel.showPermissionRequest.observe2(this) { permissionRequest -> permissionRequest.invoke(requireActivity()) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt index 08289e0cd776ee1e9a98e45f15a6571811e9064f..4fe9eb0c0a6f39d578dca6e962211c4e5eca8f02 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt @@ -32,6 +32,8 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con val routeToScreen = SingleLiveEvent<NavDirections>() + val keysRetrievalProgress = SingleLiveEvent<Boolean>() + val showPermissionRequest = SingleLiveEvent<(Activity) -> Unit>() val showEnableTracingEvent = SingleLiveEvent<Unit>() @@ -46,7 +48,7 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con override fun onTEKAvailable(teks: List<TemporaryExposureKey>) { Timber.d("onTEKAvailable(tek.size=%d)", teks.size) autoSubmission.updateMode(AutoSubmission.Mode.MONITOR) - + keysRetrievalProgress.postValue(false) routeToScreen.postValue( SubmissionResultPositiveOtherWarningNoConsentFragmentDirections .actionSubmissionResultPositiveOtherWarningNoConsentFragmentToSubmissionResultReadyFragment() @@ -54,18 +56,22 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con } override fun onTEKPermissionDeclined() { + keysRetrievalProgress.postValue(false) // stay on screen } override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) { + keysRetrievalProgress.postValue(false) showTracingConsentDialog.postValue(onConsentResult) } override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) { + keysRetrievalProgress.postValue(false) showPermissionRequest.postValue(permissionRequest) } override fun onError(error: Throwable) { + keysRetrievalProgress.postValue(false) Timber.e(error, "Couldn't access temporary exposure key history.") error.report(ExceptionCategory.EXPOSURENOTIFICATION, "Failed to obtain TEKs.") } @@ -80,6 +86,7 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con } fun onConsentButtonClicked() { + keysRetrievalProgress.value = true submissionRepository.giveConsentToSubmission() launch { if (enfClient.isTracingEnabled.first()) { @@ -98,6 +105,7 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con } fun handleActivityRersult(requestCode: Int, resultCode: Int, data: Intent?) { + keysRetrievalProgress.value = true tekHistoryUpdater.handleActivityResult(requestCode, resultCode, data) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt index b2c0555a70b90f928b90d414379154c12adcc23e..28805584195cbb5958de45501a85d5321d82a8e2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt @@ -5,6 +5,8 @@ import android.content.Context import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.contactdiary.storage.ContactDiaryPreferences import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.datadonation.analytics.Analytics +import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.datadonation.survey.SurveySettings import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysSettings import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository @@ -40,7 +42,9 @@ class DataReset @Inject constructor( private var contactDiaryPreferences: ContactDiaryPreferences, private val cwaSettings: CWASettings, private val statisticsProvider: StatisticsProvider, - private val surveySettings: SurveySettings + private val surveySettings: SurveySettings, + private val analyticsSettings: AnalyticsSettings, + private val analytics: Analytics ) { private val mutex = Mutex() @@ -59,6 +63,9 @@ class DataReset @Inject constructor( // Shared Preferences Reset SecurityHelper.resetSharedPrefs() + // Triggers deletion of all analytics contributed data + analytics.setAnalyticsEnabled(false) + // Reset the current states stored in LiveData submissionRepository.reset() keyCacheRepository.clear() @@ -69,6 +76,7 @@ class DataReset @Inject constructor( contactDiaryPreferences.clear() cwaSettings.clear() surveySettings.clear() + analyticsSettings.clear() // Clear contact diary database contactDiaryRepository.clear() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/HasHumanReadableError.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/HasHumanReadableError.kt index 82f66fda53b9599b39dd156620343d52cdb97de6..37eebfda6663da7d3587233005b48d36899a5d7e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/HasHumanReadableError.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/HasHumanReadableError.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.util import android.content.Context +import de.rki.coronawarnapp.util.ui.LazyString interface HasHumanReadableError { fun toHumanReadableError(context: Context): HumanReadableError @@ -19,3 +20,7 @@ fun Throwable.tryHumanReadableError(context: Context): HumanReadableError = when ) } } + +fun HasHumanReadableError.toResolvingString() = object : LazyString { + override fun get(context: Context): String = toHumanReadableError(context).description +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt index d529dc873981f17a7beedcedbf4be6725215beaa..05090cb511b29a6a47f37ce59ce1af4b6cd49a7b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt @@ -85,4 +85,6 @@ object TimeAndDateExtensions { fun Instant.toLocalDate(): LocalDate = this.toDateTime(DateTimeZone.UTC).toLocalDate() fun Instant.toLocalTime(): LocalTime = this.toDateTime(DateTimeZone.UTC).toLocalTime() + + val Instant.seconds get() = TimeUnit.MILLISECONDS.toSeconds(millis) } diff --git a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_delta_ppa.xml b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_delta_ppa.xml index c3027b02364029dc3b6d420aeb2df1f0616c32fd..464d8c50f8ee5babb217b25ac47cb0f9f474a57e 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_delta_ppa.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_delta_ppa.xml @@ -62,42 +62,30 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - <androidx.appcompat.widget.AppCompatImageView - android:id="@+id/onboarding_illustration" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="18dp" - android:contentDescription="@string/onboarding_ppa_illustration_description" - android:focusable="true" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_illustration_ppa" /> - <TextView - android:id="@+id/onboarding_body" + android:id="@+id/onboarding_body_short" style="@style/body1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/guideline_start" - android:layout_marginTop="35dp" + android:layout_marginTop="14dp" android:layout_marginEnd="@dimen/guideline_end" - android:contentDescription="@string/onboarding_ppa_body" + android:contentDescription="@string/onboarding_ppa_body_short" android:focusable="true" - android:text="@string/onboarding_ppa_body" - app:layout_constraintEnd_toEndOf="@id/body_end" - app:layout_constraintStart_toStartOf="@id/body_start" - app:layout_constraintTop_toBottomOf="@+id/onboarding_illustration" /> + android:text="@string/onboarding_ppa_body_short" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/federal_state_row" style="@style/row" + android:layout_marginTop="28dp" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="28dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/onboarding_body"> + app:layout_constraintTop_toBottomOf="@+id/onboarding_body_short"> <TextView android:id="@+id/federal_state_row_subtitle" @@ -228,16 +216,31 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/age_group_row" /> + <TextView + android:id="@+id/onboarding_body_long" + style="@style/body1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/guideline_start" + android:layout_marginTop="31dp" + android:layout_marginEnd="@dimen/guideline_end" + android:contentDescription="@string/onboarding_ppa_body" + android:focusable="true" + android:text="@string/onboarding_ppa_body" + app:layout_constraintEnd_toEndOf="@id/body_end" + app:layout_constraintStart_toStartOf="@id/body_start" + app:layout_constraintTop_toBottomOf="@+id/age_group_row" /> + <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/consent_layout" style="@style/cardTracing" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="42dp" + android:layout_marginTop="32dp" android:orientation="vertical" app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" app:layout_constraintStart_toStartOf="@+id/guideline_card_start" - app:layout_constraintTop_toBottomOf="@+id/age_group_row"> + app:layout_constraintTop_toBottomOf="@+id/onboarding_body_long"> <TextView android:id="@+id/legal_title" diff --git a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_ppa.xml b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_ppa.xml index b0600543c04b6fef6f7b61dd53e687cd15f8d307..bd4f247b1a02d8edb40a9dec2995681519711807 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_ppa.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_ppa.xml @@ -53,18 +53,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - <androidx.appcompat.widget.AppCompatImageView - android:id="@+id/onboarding_illustration" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="18dp" - android:contentDescription="@string/onboarding_ppa_illustration_description" - android:focusable="true" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_illustration_ppa" /> - <TextView android:id="@+id/onboarding_headline" style="@style/headline4" @@ -76,21 +64,21 @@ android:contentDescription="@string/onboarding_ppa_headline" android:focusable="true" android:text="@string/onboarding_ppa_headline" - app:layout_constraintEnd_toEndOf="@id/body_end" - app:layout_constraintStart_toStartOf="@id/body_start" - app:layout_constraintTop_toBottomOf="@+id/onboarding_illustration" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> <TextView - android:id="@+id/onboarding_body" + android:id="@+id/onboarding_body_short" style="@style/body1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/guideline_start" android:layout_marginTop="35dp" android:layout_marginEnd="@dimen/guideline_end" - android:contentDescription="@string/onboarding_ppa_body" + android:contentDescription="@string/onboarding_ppa_body_short" android:focusable="true" - android:text="@string/onboarding_ppa_body" + android:text="@string/onboarding_ppa_body_short" app:layout_constraintEnd_toEndOf="@id/body_end" app:layout_constraintStart_toStartOf="@id/body_start" app:layout_constraintTop_toBottomOf="@+id/onboarding_headline" /> @@ -103,7 +91,7 @@ android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/onboarding_body"> + app:layout_constraintTop_toBottomOf="@+id/onboarding_body_short"> <TextView android:id="@+id/federal_state_row_subtitle" @@ -234,16 +222,31 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/age_group_row" /> + <TextView + android:id="@+id/onboarding_body_long" + style="@style/body1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/guideline_start" + android:layout_marginTop="47dp" + android:layout_marginEnd="@dimen/guideline_end" + android:contentDescription="@string/onboarding_ppa_body" + android:focusable="true" + android:text="@string/onboarding_ppa_body" + app:layout_constraintEnd_toEndOf="@id/body_end" + app:layout_constraintStart_toStartOf="@id/body_start" + app:layout_constraintTop_toBottomOf="@+id/age_group_row" /> + <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/consent_layout" style="@style/cardTracing" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="42dp" + android:layout_marginTop="34dp" android:orientation="vertical" app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" app:layout_constraintStart_toStartOf="@+id/guideline_card_start" - app:layout_constraintTop_toBottomOf="@+id/age_group_row"> + app:layout_constraintTop_toBottomOf="@+id/onboarding_body_long"> <TextView android:id="@+id/legal_title" diff --git a/Corona-Warn-App/src/main/res/layout/fragment_settings_privacy_preserving_analytics.xml b/Corona-Warn-App/src/main/res/layout/fragment_settings_privacy_preserving_analytics.xml index 366ae243143786dcfccecbb55812dde07a48c16f..21731d367ee1d3f4849102e6fe107334e371ba31 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_settings_privacy_preserving_analytics.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_settings_privacy_preserving_analytics.xml @@ -48,21 +48,6 @@ app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_illustration_ppa" /> - <TextView - android:id="@+id/onboarding_headline" - style="@style/headline4" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/guideline_start" - android:layout_marginTop="@dimen/spacing_small" - android:layout_marginEnd="@dimen/guideline_end" - android:contentDescription="@string/onboarding_ppa_headline" - android:focusable="true" - android:text="@string/onboarding_ppa_headline" - app:layout_constraintEnd_toEndOf="@id/body_end" - app:layout_constraintStart_toStartOf="@id/body_start" - app:layout_constraintTop_toBottomOf="@+id/onboarding_illustration" /> - <TextView android:id="@+id/onboarding_body" style="@style/body1" @@ -76,7 +61,7 @@ android:text="@string/onboarding_ppa_body" app:layout_constraintEnd_toEndOf="@id/body_end" app:layout_constraintStart_toStartOf="@id/body_start" - app:layout_constraintTop_toBottomOf="@+id/onboarding_headline" /> + app:layout_constraintTop_toBottomOf="@+id/onboarding_illustration" /> <include android:id="@+id/settings_ppa_switch_row" @@ -237,7 +222,7 @@ style="@style/cardTracing" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_large" + android:layout_marginTop="32dp" android:orientation="vertical" app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" app:layout_constraintStart_toStartOf="@+id/guideline_card_start" @@ -249,9 +234,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/spacing_small" - android:contentDescription="@string/onboarding_ppa_consent_title" + android:contentDescription="@string/ppa_onboarding_privacy_information_title" android:focusable="true" - android:text="@string/onboarding_ppa_consent_title" + android:text="@string/ppa_onboarding_privacy_information_title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml index 972f77a2523ffb55b703ce6181129c8f05576f8b..430e1f7fa7a4cacd6fd4e419c02a6c5d03ab2196 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml @@ -17,8 +17,8 @@ <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/settings_tracing_container" + invisible="@{loggedPeriod == null || settingsTracingState == null}" android:layout_width="match_parent" - invisible="@{loggedPeriod == null || settingsTracingState== null}" android:layout_height="match_parent" android:contentDescription="@string/settings_tracing_title" android:focusable="true"> @@ -28,9 +28,9 @@ style="@style/CWAToolbar.Close" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" app:title="@string/settings_tracing_title" /> <ScrollView @@ -88,6 +88,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/information_details_header_headline" app:showDivider="@{true}" + app:status="@{settingsTracingState.isTracingSwitchChecked()}" app:statusText="@{settingsTracingState.getTracingStatusText(context)}" app:subtitle="@{@string/settings_tracing_title}" /> @@ -141,10 +142,10 @@ <include android:id="@+id/settings_tracing_status_location" + gone="@{!settingsTracingState.isLocationCardVisible()}" layout="@layout/include_tracing_status_card_location" android:layout_width="0dp" android:layout_height="wrap_content" - gone="@{!settingsTracingState.isLocationCardVisible()}" app:buttonText="@{@string/settings_tracing_status_location_button}" app:headline="@{@string/settings_tracing_status_location_headline}" app:icon="@{@drawable/ic_location}" @@ -154,10 +155,10 @@ <include android:id="@+id/settings_tracing_status_bluetooth" + gone="@{!settingsTracingState.isBluetoothCardVisible()}" layout="@layout/include_tracing_status_card" android:layout_width="0dp" android:layout_height="wrap_content" - gone="@{!settingsTracingState.isBluetoothCardVisible()}" app:body="@{@string/settings_tracing_status_bluetooth_body}" app:buttonText="@{@string/settings_tracing_status_bluetooth_button}" app:headline="@{@string/settings_tracing_status_bluetooth_headline}" @@ -169,42 +170,42 @@ <TextView android:id="@+id/risk_details_period_logged_body_notice" style="@style/subtitleMedium" + gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_tiny" android:focusable="true" - gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:text="@string/risk_details_information_body_period_logged" - app:layout_constraintStart_toStartOf="@+id/guideline_start" app:layout_constraintEnd_toStartOf="@+id/guideline_end" + app:layout_constraintStart_toStartOf="@+id/guideline_start" app:layout_constraintTop_toBottomOf="@+id/settings_tracing_status_bluetooth" /> <TextView android:id="@+id/risk_details_period_logged_subtitle" style="@style/subtitle" + gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_normal" android:layout_marginStart="@dimen/spacing_small" + android:layout_marginTop="@dimen/spacing_normal" android:focusable="true" - gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:text="@{loggedPeriod.getRiskActiveTracingDaysInRetentionPeriodLogged(context)}" - app:layout_constraintStart_toEndOf="@+id/risk_details_investigation_period_circle_progress" app:layout_constraintEnd_toStartOf="@+id/guideline_end" + app:layout_constraintStart_toEndOf="@+id/risk_details_investigation_period_circle_progress" app:layout_constraintTop_toBottomOf="@+id/risk_details_period_logged_body_notice" /> <de.rki.coronawarnapp.ui.view.CircleProgress android:id="@+id/risk_details_investigation_period_circle_progress" + gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:layout_width="@dimen/spacing_huge" android:layout_height="@dimen/spacing_huge" - gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:importantForAccessibility="no" app:circleWidth="@dimen/circle_large_width" - app:layout_constraintTop_toTopOf="@+id/risk_details_period_logged_subtitle" app:layout_constraintBottom_toBottomOf="@+id/risk_details_period_logged_subtitle" app:layout_constraintStart_toStartOf="@+id/guideline_start" + app:layout_constraintTop_toTopOf="@+id/risk_details_period_logged_subtitle" app:progress="@{loggedPeriod.activeTracingDaysInRetentionPeriod}" - app:progressColor="@{loggedPeriod.getProgressColor(context)}"/> + app:progressColor="@{loggedPeriod.getProgressColor(context)}" /> <include layout="@layout/merge_guidelines_card" /> diff --git a/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml b/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml index a28280e788de0f35b133d912b211aefa4a4de60b..37fffb2c75f60994682f29d81acb6cf8536f53a1 100644 --- a/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml @@ -57,8 +57,7 @@ android:focusable="true" android:text="@string/release_info_version_title" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/new_release_info_illustration" - tools:text="@string/release_info_version_title" /> + app:layout_constraintTop_toBottomOf="@id/new_release_info_illustration" /> <TextView style="@style/subtitleMedium" @@ -72,8 +71,7 @@ android:text="@string/release_info_version_body" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/headline" - app:layout_constraintEnd_toEndOf="parent" - tools:text="@string/release_info_version_body" /> + app:layout_constraintEnd_toEndOf="parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" @@ -95,12 +93,11 @@ android:layout_height="wrap_content" android:layout_margin="@dimen/spacing_normal" android:focusable="true" - android:text="@string/release_info_version_body" + android:text="@string/new_release_bottom" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/recycler_view" - tools:text="@string/new_release_bottom" /> + app:layout_constraintTop_toBottomOf="@id/recycler_view" /> </androidx.constraintlayout.widget.ConstraintLayout> 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 fa9512b0c64f03ff987f4543d2f31c87309af04b..f05ceb616eafe5dea2ba8cb70c7a7557e5b10e56 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -323,8 +323,8 @@ <!-- XMSG: risk details - link to faq, something like a bullet point --> <string name="risk_details_increased_risk_faq_link_text">"Ðко Ñе теÑтвате, ще получите допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно процедурата за теÑтване в ЧЗВ."</string> - <!-- XTXT: Explanation screen increased risk level link label --> - <string name="risk_details_increased_risk_faq_link_label">"ЧЗВ: Процедура за теÑтване"</string> + <!-- XTXT: Explanation screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="risk_details_increased_risk_faq_link_label">"ЧЗВ"</string> <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> @@ -496,9 +496,9 @@ <!-- XACT: onboarding(tracing) - illustraction description, header image --> <string name="onboarding_tracing_illustration_description">"Трима души Ñа активирали региÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк и контактите им един Ñ Ð´Ñ€ÑƒÐ³ ще бъдат запиÑани."</string> <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> - <string name="onboarding_tracing_location_headline">"Разрешаване на доÑтъп до данните за меÑтоположение"</string> + <string name="onboarding_tracing_location_headline">"Ðктивиране на наÑтройките за меÑтоположение"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Ðе може да бъде оÑъщеÑтвен доÑтъп до Вашето меÑтоположение. За да използвате Bluetooth, Google и/или Android изиÑкват от Ð’Ð°Ñ Ð´Ð° предоÑтавите доÑтъп до меÑтоположението на Ñмартфона Ñи."</string> + <string name="onboarding_tracing_location_body">"Приложението не може да определи къде Ñе намирате, но нÑкои верÑии на Android изиÑкват наÑтройката за меÑтоположение да бъде активирана, за да използват режима на Bluetooth Ñ Ð½Ð¸Ñка конÑÑƒÐ¼Ð°Ñ†Ð¸Ñ Ð½Ð° енергиÑ."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Към наÑтройките на уÑтройÑтвото"</string> <!-- XACT: Onboarding (test) page title --> @@ -526,6 +526,8 @@ <string name="onboarding_ppa_illustration_description">"Човек държи Ñмартфон в ръка"</string> <!-- XHED: onboarding privacy preserving analytics (ppa) - headline --> <string name="onboarding_ppa_headline">"СподелÑне на данни"</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - body short text --> + <string name="onboarding_ppa_body_short">"Покажете ни как използвате приложението. Това ще ни помогне да оценим неговата ефективноÑÑ‚."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - body text --> <string name="onboarding_ppa_body">"Можете да ни помогнете да подобрим приложението Corona-Warn-App, като Ñподелите Ñ Ð¸Ð½Ñтитута „Роберт Кох“ данните за Вашето използване на приложението. Това ще помогне на инÑтитута да оцени ефективноÑтта му, както и да подобри функциите и удобÑтвото при неговото използване."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - state title --> @@ -537,7 +539,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - age title --> <string name="onboarding_ppa_age_title">"Вашата възраÑÑ‚ (незадължително)"</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_more_info_title">"Подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно обработката на тези данни и риÑковете отноÑно защитата на данните в СÐЩ"</string> + <string name="onboarding_ppa_more_info_title">"Подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно обработката на тези данни и риÑковете отноÑно защитата на данните в СÐЩ и други държави."</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - donate button --> <string name="onboarding_ppa_consent_donate_button">"СподелÑне на данни"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - dont donate button --> @@ -550,7 +552,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - more info - title --> <string name="onboarding_ppa_more_info_data_processing_title">"Обработка на данните в процеÑа на ÑподелÑне на данни"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing body --> - <string name="onboarding_ppa_more_info_data_processing_body">"Ðко предоÑтавите ÑъглаÑие за ÑподелÑне, приложението изпраща ежедневно различни Ваши данни до инÑтитута „Роберт Кох“. Предадените данни ще бъдат анализирани за различни цели:"</string> + <string name="onboarding_ppa_more_info_data_processing_body">"Ðко Ñе ÑъглаÑите да ÑподелÑте Ñвои данни, приложението изпраща ежедневно такива до инÑтитута „Роберт Кох“. Те ще ни помогнат да оценим ефективноÑтта на приложението, а извършениÑÑ‚ върху Ñ‚ÑÑ… анализ ще подпомогне Ñледните подобрениÑ:"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 1 --> <string name="onboarding_ppa_more_info_data_processing_point_1_text">"ПодобрÑване на региÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ - Бихме иÑкали да подобрим точноÑтта и надеждноÑтта на техничеÑкото изчиÑление на риÑковете от заразÑване. За тази цел ще направим оценка на информациÑта отноÑно излаганиÑта на риÑк и предупреждениÑта, които виждате. Това ще ни даде възможноÑÑ‚ да подобрим метода на изчиÑление."</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 2 --> @@ -1794,21 +1796,21 @@ <string name="datadonation_details_survey_consent_details_title">"Подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно обработката на данни при учаÑтие в анкетата"</string> <!-- XHED: Dialog error title for survey request error informations 1 --> - <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"ЧаÑÑŠÑ‚, показван от Ð’Ð°ÑˆÐ¸Ñ Ñмартфон, не ÑъответÑтва на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ñ‡Ð°Ñ. МолÑ, коригирайте това в наÑтройките на уÑтройÑтвото Ñи. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"ЧаÑÑŠÑ‚, показван от Ð’Ð°ÑˆÐ¸Ñ Ñмартфон, не ÑъответÑтва на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ñ‡Ð°Ñ. МолÑ, коригирайте това в наÑтройките на уÑтройÑтвото. Код на грешката: %1$s."</string> <!-- XHED: Dialog error title for survey request error informations 2--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Ðнкетата не може да бъде извлечена. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Ðнкетата не може да бъде извлечена. Код на грешката: %1$s."</string> <!-- XHED: Dialog error title for survey request error informations 3--> - <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Ðнкетата не може да бъде извлечена в момента. МолÑ, актуализирайте Google Play Services. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Ðнкетата не може да бъде извлечена в момента. МолÑ, актуализирайте Google Play Services. Код на грешката: %1$s."</string> <!-- XHED: Dialog error title for survey request error informations 4--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Ðнкетата не може да бъде извлечена в момента. МолÑ, опитайте по-къÑно. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Ðнкетата не може да бъде извлечена в момента. МолÑ, опитайте по-къÑно. Код на грешката: %1$s."</string> <!-- XHED: Dialog error title for survey request error informations 5--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Ðнкетата не може да бъде извлечена поради причини, Ñвързани ÑÑŠÑ ÑигурноÑтта. Може да попълните анкетата отново през ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€ÐµÐ½ меÑец. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Ðнкетата не може да бъде извлечена поради причини, Ñвързани ÑÑŠÑ ÑигурноÑтта. Може да попълните анкетата отново през ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€ÐµÐ½ меÑец. Код на грешката: %1$s."</string> <!-- XHED: Dialog error title for survey request error informations 6--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Ðнкетата не може да бъде извлечена на Вашето уÑтройÑтво. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Благодарим Ви, че Ñе опитахте да помогнете! За Ñъжаление, използвате уÑтройÑтво, което по техничеÑки причини не поддържа анкетното проучване. Код на грешката: %1$s."</string> <!-- XHED: Dialog error title for survey request error informations 7--> - <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"От ÑÑŠÐ¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð·Ð° ÑигурноÑÑ‚ можете да учаÑтвате в анкетата най-рано 24 чаÑа, Ñлед като инÑталирате или актуализирате приложението. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"От ÑÑŠÐ¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð·Ð° ÑигурноÑÑ‚ можете да учаÑтвате в анкетата най-рано 24 чаÑа, Ñлед като инÑталирате или актуализирате приложението. Код на грешката: %1$s"</string> <!-- XHED: Dialog error title for survey request error informations 8--> - <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"Вече Ñте попълнили анкетата. Може да правите това Ñамо веднъж меÑечно. Код на грешката: {0}."</string> + <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"Вече Ñте попълнили анкетата. Може да правите това Ñамо веднъж меÑечно. Код на грешката: %1$s."</string> <!-- XHED: Dialog error title for survey request error informations dialog--> <string name="datadonation_details_survey_consent_error_dialog_title">"Грешка"</string> diff --git a/Corona-Warn-App/src/main/res/values-de/legal_strings.xml b/Corona-Warn-App/src/main/res/values-de/legal_strings.xml index aae4f6bd89d5973fb6657c6b76e503a0a2f61b08..de6248a18b15efc04b890a2fe7e2beca24aee64d 100644 --- a/Corona-Warn-App/src/main/res/values-de/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/legal_strings.xml @@ -53,7 +53,7 @@ <!-- XHED: Title for the consent displayed above the button in the survey consent screen --> <string name="datadonation_details_survey_consent_info_card_title">"Ihr Einverständnis"</string> <!-- XTXT: Text for the consent body displayed above the button in the survey consent screen --> - <string name="datadonation_details_survey_consent_info_card_body">"Durch Antippen von „Einverstanden“ willigen Sie ein:\n\nBevor Ihnen der Teilnahmelink angezeigt werden kann, wird die Echtheit Ihrer App hierbei einmalig geprüft. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Google in die USA übermittelt, damit Google die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Weitere Angaben aus der App erhält Google hierbei nicht. So wird sichergestellt, dass jeder Nutzer nur einmal an der Befragung teilnehmen kann und die Statistiken nicht verfälscht werden."</string> + <string name="datadonation_details_survey_consent_info_card_body">"Durch Antippen von „Einverstanden“ willigen Sie ein:\n\nBevor Ihnen der Teilnahmelink angezeigt werden kann, wird die Echtheit Ihrer App hierbei einmalig geprüft. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Google in die USA oder andere Drittländer übermittelt, damit Google die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Google kann damit möglicherweise auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat. Weitere Angaben aus der App erhält Google hierbei nicht. So wird sichergestellt, dass jeder Nutzer nur einmal an der Befragung teilnehmen kann und die Statistiken nicht verfälscht werden."</string> <!-- XTXT: First bullet point in the consent body displayed above the button in the survey consent screen --> <string name="datadonation_details_survey_consent_bullet_point_one">"Ihr Einverständnis ist freiwillig. Sie können die App vollständig nutzen, auch wenn Sie Ihr Einverständnis für die Zwecke der Befragung nicht erteilen."</string> <!-- XTXT: Second bullet point in the consent body displayed above the button in the survey consent screen --> @@ -65,23 +65,25 @@ <!-- XHED: Title for the information box in the survey consent detail screen --> <string name="datadonation_survey_consent_details_title">"Prüfung der Echtheit und Drittlandsübermittlung"</string> <!-- XTXT: Text for the information box in the survey consent detail screen --> - <string name="datadonation_survey_consent_details_text">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Nutzer mehrfach der Befragung teilnehmen und so die Ergebnisse der Befragung verfälschen. Die Kennung wird hier einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA kommen. Dort besteht kein dem europäischen Recht angemessenes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass US-Sicherheitsbehörden, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Teilnahme an der Befragung erhält Google nicht. Wenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Teilnahme an dieser Befragung ist dann jedoch nicht möglich."</string> + <string name="datadonation_survey_consent_details_text">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Nutzer mehrfach der Befragung teilnehmen und so die Ergebnisse der Befragung verfälschen. Die Kennung wird hier einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Teilnahme an der Befragung erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\n\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Teilnahme an dieser Befragung ist dann jedoch nicht möglich."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> <string name="ppa_onboarding_consent_title" translatable="false">"Ihr Einverständnis"</string> <!-- XTXT: Body for Privacy-preserving Analytics onboarding --> - <string name="ppa_onboarding_privacy_information_body" translatable="false">"Indem Sie oben „Datenspende“ aktivieren, willigen Sie ein:\n\nDie App übermittelt täglich von ihr erfasste Angaben an das RKI. Die Daten betreffen angezeigte Risiko-Begegnungen und Warnungen, durch Sie abgerufene Testergebnisse, ob Sie andere Nutzer gewarnt haben sowie Angaben über das Betriebssystem Ihres Smartphones. Wenn Sie oben weitere Angaben gemacht haben (Region, Altersgruppe), werden auch diese an das RKI übermittelt.\n\nDas RKI wird diese Daten zu Statistiken zusammenfassen und auswerten, um die Wirksamkeit und Funktionsweise der App zu bewerten und Rückschlüsse auf das Pandemiegeschehen zu ziehen. Die dabei gefundenen Erkenntnisse helfen bei der Verbesserung der Funktionen und Nutzerfreundlichkeit der App sowie bei der Steuerung anderer Maßnahmen der Pandemiebekämpfung.\n\nBevor Ihre Daten ausgewertet werden, muss sichergestellt sein, dass jede an der Datenspende teilnehmende App nur einmal gezählt wird und die Statistiken nicht verfälscht werden. Hierfür muss die Echtheit Ihrer App geprüft werden. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Google in die USA übermittelt, damit Google die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Weitere Angaben aus der App erhält Google hierbei nicht.\n\nSie können Ihr Einverständnis jederzeit zurücknehmen, indem Sie „Daten spenden" in den Einstellungen der App deaktivieren."</string> + <string name="ppa_onboarding_privacy_information_body" translatable="false">"Indem Sie „Datenspende“ aktivieren, willigen Sie ein:\n\nDie App übermittelt täglich von ihr erfasste Angaben an das RKI. Die Daten betreffen angezeigte Risiko-Begegnungen und Warnungen, durch Sie abgerufene Testergebnisse, ob Sie andere Nutzer gewarnt haben sowie Angaben über das Betriebssystem Ihres Smartphones. Wenn Sie oben weitere Angaben gemacht haben (Region, Altersgruppe), werden auch diese an das RKI übermittelt.\n\nDas RKI wird diese Daten zu Statistiken zusammenfassen und auswerten, um die Wirksamkeit und Funktionsweise der App zu bewerten und Rückschlüsse auf das Pandemiegeschehen zu ziehen. Die dabei gefundenen Erkenntnisse helfen bei der Verbesserung der Funktionen und Nutzerfreundlichkeit der App sowie bei der Steuerung anderer Maßnahmen der Pandemiebekämpfung.\n\nBevor Ihre Daten ausgewertet werden, muss sichergestellt sein, dass jede an der Datenspende teilnehmende App nur einmal gezählt wird und die Statistiken nicht verfälscht werden. Hierfür muss die Echtheit Ihrer App geprüft werden. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Google in die USA oder andere Drittländer übermittelt, damit Google die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Google kann damit möglicherweise auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat. Weitere Angaben aus der App erhält Google hierbei nicht.\n\nSie können Ihr Einverständnis jederzeit zurücknehmen, indem Sie „Daten spenden" in den Einstellungen der App deaktivieren."</string> <!-- XTXT: Body point consent for Privacy-preserving Analytics --> - <string name="ppa_onboarding_privacy_information_point_consent" translatable="false">"Ihr Einverständnis ist freiwillig. Sie können die App auch nutzen, wenn Sie Ihr Einverständnis nicht erteilen."</string> + <string name="ppa_onboarding_privacy_information_point_consent" translatable="false">"Ihr Einverständnis ist freiwillig. Sie können die App auch nutzen, wenn Sie Ihr Einverständnis nicht erteilt haben."</string> <!-- XTXT: Body point identity for Privacy-preserving Analytics --> - <string name="ppa_onboarding_privacy_information_point_identity" translatable="false">"Wenn Sie Daten über Ihre App-Nutzung spenden, bleibt Ihre Identität weiterhin geschützt. Das RKI erfährt also nicht, wer Sie sind oder wen Sie getroffen haben. Es werden auch keine Profile gebildet."</string> + <string name="ppa_onboarding_privacy_information_point_identity" translatable="false">"Wenn Sie Daten über Ihre App-Nutzung spenden, bleibt Ihre Identität gegenüber dem RKI weiterhin geschützt. Das RKI erfährt also nicht, wer Sie sind oder wen Sie getroffen haben. Es werden auch keine Profile gebildet."</string> <!-- XTXT: Body point sixteen for Privacy-preserving Analytics --> <string name="ppa_onboarding_privacy_information_point_sixteen" translatable="false">"Sie können Ihr Einverständnis abgeben, wenn Sie mindestens 16 Jahre alt sind."</string> <!-- XHED: Title for Privacy-preserving Analytics additional info --> <string name="ppa_onboarding_more_info_title" translatable="false">"Prüfung der Echtheit und Drittlandsübermittlung"</string> <!-- XTXT: Body for Privacy-preserving Analytics additional info --> - <string name="ppa_onboarding_more_info_body" translatable="false">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Nutzer mehrfach der Befragung teilnehmen und so die Ergebnisse der Befragung verfälschen. Die Kennung wird hier einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA kommen. Dort besteht kein dem europäischen Recht angemessenes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass US-Sicherheitsbehörden, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Teilnahme an der Befragung erhält Google nicht.\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Teilnahme an dieser Befragung ist dann jedoch nicht möglich."</string> + <string name="ppa_onboarding_more_info_body" translatable="false">"Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um zu verhindern, dass Nutzer mehrfach der Befragung teilnehmen und so die Ergebnisse der Befragung verfälschen. Die Kennung wird hier einmalig an Google übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Google zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Google übermittelte Kennung. Die weiteren Angaben über Ihre Teilnahme an der Befragung erhält Google nicht. Möglicherweise kann Google jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\n\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Teilnahme an dieser Befragung ist dann jedoch nicht möglich."</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> + <string name="ppa_onboarding_privacy_information_title" translatable="false">"Ihr Einverständnis"</string> <!-- XTXT: Body for Privacy-preserving Analytics settings --> - <string name="ppa_settings_privacy_information_body" translatable="false">"Indem Sie oben „Datenspende“ aktivieren, willigen Sie ein:\n\nDie App übermittelt täglich von ihr erfasste Angaben an das RKI. Die Daten betreffen angezeigte Risiko-Begegnungen und Warnungen, durch Sie abgerufene Testergebnisse, ob Sie andere Nutzer gewarnt haben sowie Angaben über das Betriebssystem Ihres Smartphones. Wenn Sie oben weitere Angaben gemacht haben (Region, Altersgruppe), werden auch diese an das RKI übermittelt.\n\nDas RKI wird diese Daten zu Statistiken zusammenfassen und auswerten, um die Wirksamkeit und Funktionsweise der App zu bewerten und Rückschlüsse auf das Pandemiegeschehen zu ziehen. Die dabei gefundenen Erkenntnisse helfen bei der Verbesserung der Funktionen und Nutzerfreundlichkeit der App sowie bei der Steuerung anderer Maßnahmen der Pandemiebekämpfung.\n\nBevor Ihre Daten ausgewertet werden, muss sichergestellt sein, dass jede an der Datenspende teilnehmende App nur einmal gezählt wird und die Statistiken nicht verfälscht werden. Hierfür muss die Echtheit Ihrer App geprüft werden. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Google in die USA übermittelt, damit Google die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Weitere Angaben aus der App erhält Google hierbei nicht.\n\nSie können Ihr Einverständnis jederzeit zurücknehmen, indem Sie oben „Datenspende“ deaktivieren."</string> + <string name="ppa_settings_privacy_information_body" translatable="false">"Indem Sie oben „Datenspende“ aktivieren, willigen Sie ein:\n\nDie App übermittelt täglich von ihr erfasste Angaben an das RKI. Die Daten betreffen angezeigte Risiko-Begegnungen und Warnungen, durch Sie abgerufene Testergebnisse, ob Sie andere Nutzer gewarnt haben sowie Angaben über das Betriebssystem Ihres Smartphones. Wenn Sie oben weitere Angaben gemacht haben (Region, Altersgruppe), werden auch diese an das RKI übermittelt.\n\nDas RKI wird diese Daten zu Statistiken zusammenfassen und auswerten, um die Wirksamkeit und Funktionsweise der App zu bewerten und Rückschlüsse auf das Pandemiegeschehen zu ziehen. Die dabei gefundenen Erkenntnisse helfen bei der Verbesserung der Funktionen und Nutzerfreundlichkeit der App sowie bei der Steuerung anderer Maßnahmen der Pandemiebekämpfung.\n\nBevor Ihre Daten ausgewertet werden, muss sichergestellt sein, dass jede an der Datenspende teilnehmende App nur einmal gezählt wird und die Statistiken nicht verfälscht werden. Hierfür muss die Echtheit Ihrer App geprüft werden. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Google in die USA oder andere Drittländer übermittelt, damit Google die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Google kann damit möglicherweise auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat.\nWeitere Angaben aus der App erhält Google hierbei nicht.\n\nSie können Ihr Einverständnis jederzeit zurücknehmen, indem Sie oben „Datenspende“ deaktivieren."</string> </resources> 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 642286debd8608ede12626707e795f8c710fd91d..baa753516352ada5d7007222af7e080782478cba 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -324,7 +324,7 @@ <!-- XMSG: risk details - link to faq, something like a bullet point --> <string name="risk_details_increased_risk_faq_link_text">"Falls Sie sich testen lassen, finden Sie weitere Informationen in der FAQ zum Testablauf."</string> - <!-- XTXT: Explanation screen increased risk level link label --> + <!-- XTXT: Explanation screen increased risk level link label - HAS TO MATCH the link text above --> <string name="risk_details_increased_risk_faq_link_label">"FAQ zum Testablauf"</string> <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/de/faq/#red_card_how_to_test"</string> @@ -523,12 +523,12 @@ <!-- XACT: onboarding(notifications) - illustraction description, header image --> <string name="onboarding_notifications_illustration_description">"Eine Frau erhält eine Mitteilung von ihrer Corona-Warn-App."</string> - <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_consent_title">"Ihr Einverständnis"</string> <!-- XACT: onboarding privacy preserving analytics (ppa) - illustraction description, header image --> <string name="onboarding_ppa_illustration_description">"Darstellung einer Person, die ihr Smartphone in der Hand hält"</string> <!-- XHED: onboarding privacy preserving analytics (ppa) - headline --> <string name="onboarding_ppa_headline">"Datenspende"</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - body short text --> + <string name="onboarding_ppa_body_short">"Teilen Sie Daten über Ihre App-Nutzung mit uns und helfen Sie uns so, die Wirksamkeit der App zu bewerten."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - body text --> <string name="onboarding_ppa_body">"Sie können uns helfen, die Corona-Warn-App zu verbessern. Teilen Sie Daten über Ihre App-Nutzung mit dem RKI. Sie helfen damit dem RKI bei der Bewertung der Wirksamkeit der App. Ihre Daten ermöglichen außerdem, die Funktionen und Nutzerfreundlichkeit der App zu verbessern."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - state title --> @@ -540,7 +540,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - age title --> <string name="onboarding_ppa_age_title">"Ihr Alter (optional)"</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_more_info_title">"Ausführliche Informationen zu dieser Datenverarbeitung und den Datenschutzrisiken in den USA"</string> + <string name="onboarding_ppa_more_info_title">"Ausführliche Informationen zu dieser Datenverarbeitung und den Datenschutzrisiken in den USA und anderen Drittländern"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - donate button --> <string name="onboarding_ppa_consent_donate_button">"Daten spenden"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - dont donate button --> @@ -553,7 +553,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - more info - title --> <string name="onboarding_ppa_more_info_data_processing_title">"Datenverarbeitung im Rahmen der Datenspende"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing body --> - <string name="onboarding_ppa_more_info_data_processing_body">"Im Rahmen der Datenspende übermittelt die App verschiedene Daten täglich an das RKI. Die übermittelten Daten werden zu unterschiedlichen Zwecken ausgewertet:"</string> + <string name="onboarding_ppa_more_info_data_processing_body">"Im Rahmen der Datenspende übermittelt die App verschiedene Daten täglich an das RKI. Die übermittelten Daten dienen der Bewertung der Wirksamkeit der App und sie werden ausgewertet, um folgende Verbesserungen zu ermöglichen:"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 1 --> <string name="onboarding_ppa_more_info_data_processing_point_1_text">"Verbesserung der Risiko-Ermittlung – Die Genauigkeit und Zuverlässigkeit der technischen Berechnung der Ansteckungsrisiken sollen verbessert werden. Hierfür werden Angaben über Risiko-Begegnungen und Ihnen angezeigte Warnungen ausgewertet. In der Folge kann die Berechnungsmethode verfeinert werden."</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 2 --> @@ -1797,21 +1797,21 @@ <string name="datadonation_details_survey_consent_details_title">"Ausführliche Informationen zur Datenverarbeitung bei der Teilnahme an der Befragung"</string> <!-- XHED: Dialog error title for survey request error informations 1 --> - <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Die Uhrzeit Ihres Smartphones stimmt nicht mit der aktuellen Zeit überein. Bitte korrigieren Sie die Uhrzeit in den Einstellungen Ihres Smartphones (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Die Uhrzeit Ihres Smartphones stimmt nicht mit der aktuellen Zeit überein. Bitte korrigieren Sie die Uhrzeit in den Einstellungen Ihres Smartphones (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 2--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Die Befragung kann nicht aufgerufen werden (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Die Befragung kann nicht aufgerufen werden (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 3--> - <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Die Befragung kann aktuell nicht aufgerufen werden. Bitte aktualisieren Sie die Google Play Services (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Die Befragung kann aktuell nicht aufgerufen werden. Bitte aktualisieren Sie die Google Play Services (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 4--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Die Befragung kann aktuell nicht aufgerufen werden. Bitte versuchen Sie es später noch mal (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Die Befragung kann aktuell nicht aufgerufen werden. Bitte versuchen Sie es später noch mal (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 5--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Die Befragung konnte aus Sicherheitsgründen nicht aufgerufen werden. Sie können im nächsten Kalendermonat wieder teilnehmen (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Die Befragung konnte aus Sicherheitsgründen nicht aufgerufen werden. Sie können im nächsten Kalendermonat wieder teilnehmen (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 6--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Danke, dass Sie mithelfen wollen. Leider verwenden Sie ein Smartphone, mit dem Sie aus technischen Gründen nicht an der Umfrage teilnehmen können. (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Danke, dass Sie mithelfen wollen. Leider verwenden Sie ein Smartphone, mit dem Sie aus technischen Gründen nicht an der Umfrage teilnehmen können. (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 7--> - <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Aus Sicherheitsgründen können Sie erst 24 Stunden, nachdem Sie die App installiert oder aktualisiert haben, an der Befragung teilnehmen (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Aus Sicherheitsgründen können Sie erst 24 Stunden, nachdem Sie die App installiert oder aktualisiert haben, an der Befragung teilnehmen (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 8--> - <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"Sie haben bereits an der Befragung teilgenommen. Sie können nur einmal im Monat an der Befragung teilnehmen (Fehlercode {0})."</string> + <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"Sie haben bereits an der Befragung teilgenommen. Sie können nur einmal im Monat an der Befragung teilnehmen (Fehlercode %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations dialog--> <string name="datadonation_details_survey_consent_error_dialog_title">"Fehler"</string> 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 8cf84fc5222ac19b312f61dc30d0f1cf97495abd..0330f3c7b7f0bab25bccdc3ad621f791477b9178 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -323,8 +323,8 @@ <!-- XMSG: risk details - link to faq, something like a bullet point --> <string name="risk_details_increased_risk_faq_link_text">"If you get tested, you will find additional information about the testing procedure in the FAQ."</string> - <!-- XTXT: Explanation screen increased risk level link label --> - <string name="risk_details_increased_risk_faq_link_label">"FAQ: Testing Procedure"</string> + <!-- XTXT: Explanation screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="risk_details_increased_risk_faq_link_label">"FAQ"</string> <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> @@ -339,7 +339,7 @@ <!-- XHED: risk details - infection risk headline, below behaviors --> <string name="risk_details_headline_infection_risk">"Risk of Infection"</string> <!-- XHED: risk details - infection period logged headling, below behaviors --> - <string name="risk_details_headline_period_logged">"Period logged"</string> + <string name="risk_details_headline_period_logged">"Period Logged"</string> <!-- 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 --> @@ -496,9 +496,9 @@ <!-- XACT: onboarding(tracing) - illustraction description, header image --> <string name="onboarding_tracing_illustration_description">"Three persons have activated exposure logging on their smartphones, which will log their encounters with each other."</string> <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> - <string name="onboarding_tracing_location_headline">"Allow location access"</string> + <string name="onboarding_tracing_location_headline">"Activate Location Setting"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Your location cannot be accessed. Google and/or Android requires access to your smartphone’s location to use Bluetooth."</string> + <string name="onboarding_tracing_location_body">"Your device location cannot be determined by the app, but the device location setting must be activated to use Bluetooth Low Energy in some Android versions."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Open Device Settings"</string> <!-- XACT: Onboarding (test) page title --> @@ -526,6 +526,8 @@ <string name="onboarding_ppa_illustration_description">"A person is holding a smartphone in their hand"</string> <!-- XHED: onboarding privacy preserving analytics (ppa) - headline --> <string name="onboarding_ppa_headline">"Share Data"</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - body short text --> + <string name="onboarding_ppa_body_short">"Let us know how you use the app and help us to assess its effectiveness."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - body text --> <string name="onboarding_ppa_body">"You can help us to improve the Corona-Warn-App. Share the data about your app usage with the RKI. This will help the RKI assess the app’s effectiveness. Your data will also help to improve the features and usability of the app."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - state title --> @@ -537,7 +539,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - age title --> <string name="onboarding_ppa_age_title">"Your age (optional)"</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_more_info_title">"Detailed Information about This Data Processing and Data Protection Risks in the U.S."</string> + <string name="onboarding_ppa_more_info_title">"Detailed Information about This Data Processing and Data Protection Risks in the U.S. and Other Third Countries"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - donate button --> <string name="onboarding_ppa_consent_donate_button">"Share Data"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - dont donate button --> @@ -550,7 +552,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - more info - title --> <string name="onboarding_ppa_more_info_data_processing_title">"Data Processing in the Data Sharing Process"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing body --> - <string name="onboarding_ppa_more_info_data_processing_body">"If you consent to share your data, the app sends various data to the RKI each day. The transmitted data will be analyzed for a variety of purposes:"</string> + <string name="onboarding_ppa_more_info_data_processing_body">"If you consent to share your data, the app sends various data to the RKI each day. The transmitted data will help us assess the effectiveness of the app and will be analyzed to support the following improvements:"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 1 --> <string name="onboarding_ppa_more_info_data_processing_point_1_text">"Improved exposure logging – We want to improve the accuracy and reliability of the technical calculation of infection risks. To do so, we will evaluate information about risk exposures and the warnings you see. As a result, we will be able to refine the calculation method."</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 2 --> @@ -1794,21 +1796,21 @@ <string name="datadonation_details_survey_consent_details_title">"Detailed Information on Data Processing Involved in Taking the Survey"</string> <!-- XHED: Dialog error title for survey request error informations 1 --> - <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"The time on your smartphone does not match the current time. Please correct the time in your smartphone settings (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"The time on your smartphone does not match the current time. Please correct the time in your smartphone settings (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 2--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"The survey cannot be retrieved (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"The survey cannot be retrieved (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 3--> - <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"The survey cannot be retrieved right now. Please update the Google Play Services (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"The survey cannot be retrieved right now. Please update the Google Play Services (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 4--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"The survey cannot be retrieved right now. Please try again later (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"The survey cannot be retrieved right now. Please try again later (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 5--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"The survey cannot be retrieved due to security reasons. You can take the survey again in the next calendar month (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"The survey cannot be retrieved due to security reasons. You can take the survey again in the next calendar month (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 6--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"The survey could not be retrieved on your smartphone (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Thank you for wanting to help. Unfortunately, you use a smartphone that does not support the survey for technical reasons (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 7--> - <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"For security reasons, you cannot take the survey less than 24 hours after you install or update the app (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"For security reasons, you cannot take the survey less than 24 hours after you install or update the app (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 8--> - <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"You have already taken the survey. You can only take the survey once per month (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"You have already taken the survey. You can only take the survey once per month (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations dialog--> <string name="datadonation_details_survey_consent_error_dialog_title">"Error"</string> 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 0223e645bd3251a38d72f15a671e15062e66eddd..51bc65f9f9367f7eb5203b610216feb3ef871ec3 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -323,8 +323,8 @@ <!-- XMSG: risk details - link to faq, something like a bullet point --> <string name="risk_details_increased_risk_faq_link_text">"JeÅ›li poddasz siÄ™ testowi, dodatkowe informacje na temat procedury testowania znajdziesz w „CzÄ™sto zadawanych pytaniachâ€."</string> - <!-- XTXT: Explanation screen increased risk level link label --> - <string name="risk_details_increased_risk_faq_link_label">"CzÄ™sto zadawane pytania: Procedura testowania"</string> + <!-- XTXT: Explanation screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="risk_details_increased_risk_faq_link_label">"CzÄ™sto zadawanych pytaniach"</string> <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> @@ -496,9 +496,9 @@ <!-- XACT: onboarding(tracing) - illustraction description, header image --> <string name="onboarding_tracing_illustration_description">"Trzy osoby aktywowaÅ‚y rejestrowanie narażenia na swoich smartfonach, które bÄ™dÄ… rejestrować ich wzajemne kontakty."</string> <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> - <string name="onboarding_tracing_location_headline">"Zezwól na dostÄ™p do lokalizacji"</string> + <string name="onboarding_tracing_location_headline">"Aktywuj ustawienie lokalizacji"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Nie można uzyskać dostÄ™pu do Twojej lokalizacji. Google i/lub Android wymaga dostÄ™pu do lokalizacji Twojego smartfona w celu użycia Bluetooth."</string> + <string name="onboarding_tracing_location_body">"Aplikacja nie może ustalić lokalizacji urzÄ…dzenia, której ustawienie musi być aktywowane w niektórych wersjach Androida, aby możliwe byÅ‚o korzystanie z technologii Bluetooth Low Energy."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Otwórz ustawienia urzÄ…dzenia"</string> <!-- XACT: Onboarding (test) page title --> @@ -526,6 +526,8 @@ <string name="onboarding_ppa_illustration_description">"Osoba trzyma w dÅ‚oni smartfon"</string> <!-- XHED: onboarding privacy preserving analytics (ppa) - headline --> <string name="onboarding_ppa_headline">"UdostÄ™pnij dane"</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - body short text --> + <string name="onboarding_ppa_body_short">"Podziel siÄ™ z nami informacjami na temat korzystania z aplikacji i pomóż nam ocenić jej skuteczność."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - body text --> <string name="onboarding_ppa_body">"Możesz pomóc nam ulepszyć aplikacjÄ™ Corona-Warn-App. UdostÄ™pnij dane o korzystaniu z aplikacji instytutowi RKI, który bÄ™dzie mógÅ‚ wówczas ocenić jej skuteczność. Twoje dane pomogÄ… również ulepszyć funkcje i użyteczność tej aplikacji."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - state title --> @@ -537,7 +539,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - age title --> <string name="onboarding_ppa_age_title">"Twój wiek (opcjonalnie)"</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_more_info_title">"Szczegółowe informacje o ryzyku zwiÄ…zanym z przetwarzaniem i ochronÄ… danych w USA"</string> + <string name="onboarding_ppa_more_info_title">"Szczegółowe informacje o ryzyku zwiÄ…zanym z przetwarzaniem i ochronÄ… danych w USA i innych krajach trzecich"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - donate button --> <string name="onboarding_ppa_consent_donate_button">"UdostÄ™pnij dane"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - dont donate button --> @@ -550,7 +552,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - more info - title --> <string name="onboarding_ppa_more_info_data_processing_title">"Przetwarzanie danych w procesie udostÄ™pniania danych"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing body --> - <string name="onboarding_ppa_more_info_data_processing_body">"JeÅ›li wyrazisz zgodÄ™ na udostÄ™pnienie swoich danych, aplikacja bÄ™dzie codziennie wysyÅ‚ać różne dane do RKI. Przekazane dane bÄ™dÄ… analizowane w różnych celach:"</string> + <string name="onboarding_ppa_more_info_data_processing_body">"JeÅ›li wyrazisz zgodÄ™ na udostÄ™pnienie swoich danych, aplikacja bÄ™dzie codziennie wysyÅ‚ać różne dane do RKI. PrzesÅ‚ane dane pomogÄ… nam ocenić skuteczność aplikacji i bÄ™dÄ… analizowane w celu wprowadzenia nastÄ™pujÄ…cych ulepszeÅ„:"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 1 --> <string name="onboarding_ppa_more_info_data_processing_point_1_text">"Ulepszone rejestrowanie narażenia – Chcemy poprawić dokÅ‚adność i wiarygodność technicznych obliczeÅ„ ryzyka zakażenia. Aby to osiÄ…gnąć, bÄ™dziemy analizować otrzymywane przez Ciebie informacje o narażeniu na ryzyko i ostrzeżenia. Pozwoli to nam udoskonalić metodÄ™ obliczeÅ„."</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 2 --> @@ -1794,21 +1796,21 @@ <string name="datadonation_details_survey_consent_details_title">"Szczegółowe informacje na temat przetwarzania danych zwiÄ…zanego z ankietÄ…"</string> <!-- XHED: Dialog error title for survey request error informations 1 --> - <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Czas na Twoim smartfonie nie zgadza siÄ™ z aktualnym czasem. Popraw czas w ustawieniach swojego smartfona (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Czas na Twoim smartfonie nie zgadza siÄ™ z aktualnym czasem. Popraw czas w ustawieniach swojego smartfona (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 2--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Nie można pobrać ankiety (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Nie można pobrać ankiety (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 3--> - <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"W tej chwili nie można pobrać ankiety. Zaktualizuj usÅ‚ugi Google Play (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"W tej chwili nie można pobrać ankiety. Zaktualizuj usÅ‚ugi Google Play (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 4--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"W tej chwili nie można pobrać ankiety. Spróbuj ponownie później (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"W tej chwili nie można pobrać ankiety. Spróbuj ponownie później (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 5--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Ankiety nie można pobrać ze wzglÄ™dów bezpieczeÅ„stwa. Możesz ponownie wypeÅ‚nić ankietÄ™ w nastÄ™pnym miesiÄ…cu kalendarzowym (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Ankiety nie można pobrać ze wzglÄ™dów bezpieczeÅ„stwa. Możesz ponownie wypeÅ‚nić ankietÄ™ w nastÄ™pnym miesiÄ…cu kalendarzowym (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 6--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Nie udaÅ‚o siÄ™ pobrać ankiety na smartfon (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"DziÄ™kujemy za chęć pomocy. Niestety, korzystasz ze smartfona, który nie obsÅ‚uguje ankiety z powodów technicznych (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 7--> - <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Ze wzglÄ™dów bezpieczeÅ„stwa nie możesz wypeÅ‚nić ankiety wczeÅ›niej niż 24 godziny po zainstalowaniu lub zaktualizowaniu aplikacji (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Ze wzglÄ™dów bezpieczeÅ„stwa nie możesz wypeÅ‚nić ankiety wczeÅ›niej niż po upÅ‚ywie 24 godzin od zainstalowania lub zaktualizowania aplikacji (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 8--> - <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"WypeÅ‚niÅ‚eÅ›(-aÅ›) już ankietÄ™. AnkietÄ™ można wypeÅ‚nić tylko raz w miesiÄ…cu (kod bÅ‚Ä™du {0})."</string> + <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"WypeÅ‚niÅ‚eÅ›(-aÅ›) już ankietÄ™. AnkietÄ™ można wypeÅ‚nić tylko raz w miesiÄ…cu (kod bÅ‚Ä™du %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations dialog--> <string name="datadonation_details_survey_consent_error_dialog_title">"BÅ‚Ä…d"</string> diff --git a/Corona-Warn-App/src/main/res/values-ro/strings.xml b/Corona-Warn-App/src/main/res/values-ro/strings.xml index d67b2dbb535362717b648e39bf3f8601f8f7f018..e29be8d41f483ea294dcf04f97dcbd3e28617c81 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -323,8 +323,8 @@ <!-- XMSG: risk details - link to faq, something like a bullet point --> <string name="risk_details_increased_risk_faq_link_text">"Dacă vă testaÈ›i, veÈ›i găsi mai multe informaÈ›ii despre procedura de testare în ÃŽntrebările frecvente."</string> - <!-- XTXT: Explanation screen increased risk level link label --> - <string name="risk_details_increased_risk_faq_link_label">"ÃŽntrebări frecvente: Procedura de testare"</string> + <!-- XTXT: Explanation screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="risk_details_increased_risk_faq_link_label">"ÃŽntrebările frecvente"</string> <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> @@ -496,9 +496,9 @@ <!-- XACT: onboarding(tracing) - illustraction description, header image --> <string name="onboarding_tracing_illustration_description">"Trei persoane È™i-au activat pe smartphone înregistrarea în jurnal a expunerilor, ceea ce va duce la înregistrarea întâlnirilor lor."</string> <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> - <string name="onboarding_tracing_location_headline">"PermiteÈ›i accesul la locaÈ›ie"</string> + <string name="onboarding_tracing_location_headline">"ActivaÈ›i setarea locaÈ›iei"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"LocaÈ›ia dvs. nu poate fi accesată. Google È™i/sau Android necesită acces la locaÈ›ia smartphone-ului dvs. pentru a utiliza Bluetooth-ul."</string> + <string name="onboarding_tracing_location_body">"LocaÈ›ia dispozitivului dvs. nu poate fi determinată de aplicaÈ›ie, dar setarea locaÈ›iei dispozitivului trebuie să fie activată pentru a utiliza Bluetooth Low Energy pe unele versiuni Android."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"DeschideÈ›i configurările dispozitivului"</string> <!-- XACT: Onboarding (test) page title --> @@ -526,6 +526,8 @@ <string name="onboarding_ppa_illustration_description">"O persoană È›ine un smartphone în mână"</string> <!-- XHED: onboarding privacy preserving analytics (ppa) - headline --> <string name="onboarding_ppa_headline">"Partajare date"</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - body short text --> + <string name="onboarding_ppa_body_short">"SpuneÈ›i-ne cum utilizaÈ›i aplicaÈ›ia È™i ajutaÈ›i-ne să evaluăm eficacitatea acesteia."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - body text --> <string name="onboarding_ppa_body">"Ne puteÈ›i ajuta să îmbunătățim aplicaÈ›ia Corona-Warn. PartajaÈ›i datele dvs. despre utilizarea aplicaÈ›iei cu institutul RKI. Astfel, ajutaÈ›i RKI să evalueze eficacitatea aplicaÈ›iei. De asemenea, datele dvs. vor ajuta la îmbunătățirea caracteristicilor È™i utilizării aplicaÈ›iei."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - state title --> @@ -537,7 +539,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - age title --> <string name="onboarding_ppa_age_title">"Vârsta dvs. (opÈ›ional)"</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_more_info_title">"InformaÈ›ii detaliate despre prelucrarea acestor date È™i riscurile privind prelucrarea datelor în SUA"</string> + <string name="onboarding_ppa_more_info_title">"InformaÈ›ii detaliate despre prelucrarea acestor date È™i riscurile privind prelucrarea datelor din SUA È™i din alte țări terÈ›e"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - donate button --> <string name="onboarding_ppa_consent_donate_button">"Partajare date"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - dont donate button --> @@ -550,7 +552,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - more info - title --> <string name="onboarding_ppa_more_info_data_processing_title">"Prelucrarea datelor în procesul de partajare a datelor"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing body --> - <string name="onboarding_ppa_more_info_data_processing_body">"Dacă sunteÈ›i de acord să partajaÈ›i datele dvs., aplicaÈ›ia trimite diverse date către RKI în fiecare zi. Datele transmise vor fi analizate în scopuri variate:"</string> + <string name="onboarding_ppa_more_info_data_processing_body">"Dacă sunteÈ›i de acord să partajaÈ›i datele dvs., aplicaÈ›ia trimite diverse date către RKI în fiecare zi. Datele transmise ne vor ajuta să evaluăm eficacitatea aplicaÈ›iei È™i vor fi analizate pentru a permite următoarele îmbunătățiri:"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 1 --> <string name="onboarding_ppa_more_info_data_processing_point_1_text">"ÃŽmbunătățirea înregistrării în jurnal a expunerilor – Dorim să îmbunătățim precizia È™i încrederea calculului tehnic al riscurilor de infectare. Pentru aceasta, vom evalua informaÈ›iile despre expunerile la risc È™i avertizările pe care le vedeÈ›i. Drept rezultat, vom putea rafina metoda de calcul."</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 2 --> @@ -1794,21 +1796,21 @@ <string name="datadonation_details_survey_consent_details_title">"InformaÈ›ii detaliate despre prelucrarea datelor utilizate în timpul participării la sondaj"</string> <!-- XHED: Dialog error title for survey request error informations 1 --> - <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Ora de pe smartphone-ul dvs. nu corespunde cu ora curentă. Vă rugăm să corectaÈ›i ora în setările smartphone-ului dvs. (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Ora de pe smartphone-ul dvs. nu corespunde cu ora curentă. Vă rugăm să corectaÈ›i ora în setările smartphone-ului dvs. (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 2--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Nu se poate obÈ›ine sondajul (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Nu se poate obÈ›ine sondajul (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 3--> - <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Nu se poate obÈ›ine sondajul chiar acum. ActualizaÈ›i serviciile Google Play (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Nu se poate obÈ›ine sondajul chiar acum. ActualizaÈ›i serviciile Google Play (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 4--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Nu se poate obÈ›ine sondajul chiar acum. ÃŽncercaÈ›i din nou mai târziu (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Nu se poate obÈ›ine sondajul chiar acum. ÃŽncercaÈ›i din nou mai târziu (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 5--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Nu se poate obÈ›ine sondajul din cauza unor motive de securitate. PuteÈ›i participa din nou la sondaj în următoarea lună calendaristică (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Nu se poate obÈ›ine sondajul din cauza unor motive de securitate. PuteÈ›i participa din nou la sondaj în următoarea lună calendaristică (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 6--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Nu s-a putut obÈ›ine sondajul pe smartphone-ul dvs. (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Vă mulÈ›umim pentru că doriÈ›i să ajutaÈ›i. Din păcate, utilizaÈ›i un smartphone care nu acceptă sondajul, din motive tehnice (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 7--> - <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Din motive de securitate, nu puteÈ›i participa la sondaj mai devreme de 24 de ore de la instalarea aplicaÈ›iei sau de la actualizarea aplicaÈ›iei (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Din motive de securitate, nu puteÈ›i participa la sondaj mai devreme de 24 de ore de la instalarea aplicaÈ›iei sau de la actualizarea aplicaÈ›iei (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 8--> - <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"AÈ›i participat deja la sondaj. PuteÈ›i participa la sondaj doar o dată pe lună (cod de eroare {0})."</string> + <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"AÈ›i participat deja la sondaj. PuteÈ›i participa la sondaj doar o dată pe lună (cod de eroare %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations dialog--> <string name="datadonation_details_survey_consent_error_dialog_title">"Eroare"</string> diff --git a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml index 08d66e742492648bac01db91e6b521b29190fc34..fa8eaf35084395b3eb504c95483ecbf6b8265be4 100644 --- a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml @@ -53,7 +53,7 @@ <!-- XHED: Title for the consent displayed above the button in the survey consent screen --> <string name="datadonation_details_survey_consent_info_card_title">"Rıza beyanınız"</string> <!-- XTXT: Text for the consent body displayed above the button in the survey consent screen --> - <string name="datadonation_details_survey_consent_info_card_body">"“Kabul ediyorum†seçeneÄŸine tıklayarak, ÅŸunlara onay vermiÅŸ olursunuz:\n\nKatılım linki size gösterilmeden önce, Uygulamanızın orijinal olup olmadığı bir kez kontrol edilir. Bunun için, akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve Google’ın Uygulamanızın orijinal ürün olduÄŸunu RKI’ye doÄŸrulaması için ABD’deki Google’e aktarılır. Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Google, Uygulamadan daha fazla bilgi almaz. Bu sayede her kullanıcının ankete sadece bir kez katılabilmesi ve istatistiklerin yanlış sonuçlar vermemesi saÄŸlanır."</string> + <string name="datadonation_details_survey_consent_info_card_body">"“Kabul ediyorum†seçeneÄŸine tıklayarak, ÅŸunlara onay vermiÅŸ olursunuz:\n\nKatılım linki size gösterilmeden önce, Uygulamanızın orijinal olup olmadığı bir kez kontrol edilir. Bunun için, akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve Google’ın Uygulamanızın orijinal ürün olduÄŸunu RKI’ye doÄŸrulaması için ABD’deki veya diÄŸer bir üçüncü ülkedeki Google’e aktarılır. Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Google, böylece kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik kontrolünün yapıldığını anlayabilir. Google, Uygulamadan daha fazla bilgi almaz. Bu sayede her kullanıcının ankete sadece bir kez katılabilmesi ve istatistiklerin yanlış sonuçlar vermemesi saÄŸlanır."</string> <!-- XTXT: First bullet point in the consent body displayed above the button in the survey consent screen --> <string name="datadonation_details_survey_consent_bullet_point_one">"VerdiÄŸiniz rıza beyanı, isteÄŸe baÄŸlı gerçekleÅŸmektedir. Anketin amaçları doÄŸrultusunda rıza beyanında bulunmasanız bile Uygulamayı eksiksiz olarak kullanabilirsiniz."</string> <!-- XTXT: Second bullet point in the consent body displayed above the button in the survey consent screen --> @@ -65,24 +65,25 @@ <!-- XHED: Title for the information box in the survey consent detail screen --> <string name="datadonation_survey_consent_details_title">"Orijinallik doÄŸrulanması ve üçüncü ülkelere aktarım"</string> <!-- XTXT: Text for the information box in the survey consent detail screen --> - <string name="datadonation_survey_consent_details_text">"Uygulamanızın orijinal olduÄŸunu onaylamak adına akıllı telefonunuz, akıllı telefonunuzun sürümü ve Uygulamaya iliÅŸkin veriler içeren benzersiz bir kimlik kodu oluÅŸturur. Kullanıcıların ankete birden çok kez katılmasını ve böylece anket sonuçlarını tahrif etmesini önlemek için bu kodun oluÅŸturulması gerekmektedir. Bu kimlik kodları, sadece bir kez Google’e aktarılır. Bu süreçte verilerin ABD’ye aktarılması da söz konusu olabilir. Orada Avrupa hukukuna uygun kiÅŸisel verilerin koruma seviyesi bulunmamaktadır ve Avrupa’daki veri koruma haklarınız uygulanmayabilir. Bu baÄŸlamda özellikle ABD güvenlik makamlarının, somut bir şüphe olmasa bile, Google’e aktarılan bu verilere eriÅŸme ve örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları deÄŸerlendirmeye alma olasılığı bulunmaktadır. Bu durum sadece Google’e aktarılan kimlik kodları için söz konusudur. Google ankete katılımız ile ilgili daha fazla bilgi almaz."</string> + <string name="datadonation_survey_consent_details_text">"Uygulamanızın orijinal olduÄŸunu onaylamak adına akıllı telefonunuz, akıllı telefonunuzun sürümü ve Uygulamaya iliÅŸkin veriler içeren benzersiz bir kimlik kodu oluÅŸturur. Kullanıcıların ankete birden çok kez katılmasını ve böylece anket sonuçlarını tahrif etmesini önlemek için bu kodun oluÅŸturulması gerekmektedir. Bu kimlik kodları, sadece bir kez Google’e aktarılır. Bu süreçte verilerin ABD’ye veya üçüncü bir ülkeye aktarılması da söz konusu olabilir. Orada muhtemelen Avrupa hukuku standartlarına uygun kiÅŸisel verilerin koruma seviyesi bulunmayabilir ve Avrupa’daki veri koruma haklarınız uygulanmayabilir. Bu baÄŸlamda özellikle üçüncü ülke güvenlik makamlarının, somut bir şüphe olmasa bile, Google’e aktarılan bu verilere eriÅŸme ve örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları deÄŸerlendirmeye alma olasılığı bulunmaktadır. Bu durum sadece Google’e aktarılan kimlik kodları için söz konusudur. Google ankete katılımız ile ilgili daha fazla bilgi almaz. Ancak, Google kimlik kodunuz üzerinden kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik kontrolünün yapıldığını anlayabilir."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> <string name="ppa_onboarding_consent_title" translatable="false">"Rıza beyanınız"</string> <!-- XTXT: Body for Privacy-preserving Analytics onboarding --> - <string name="ppa_onboarding_privacy_information_body" translatable="false">"“Kabul ediyorum†seçeneÄŸine tıklayarak, ÅŸunlara onay vermiÅŸ olursunuz:\n\ Uygulama, topladığı bilgileri her gün RKI’ye aktarır. Bunlar, görüntülenen riskli karşılaÅŸmalar ve uyarılar, size gönderilen test sonuçları, diÄŸer kullanıcıları uyarıp uyarmadığınız ve akıllı telefonunuzun iÅŸletim sistemine iliÅŸkin verilerdir. Ayrıca baÅŸka bilgiler de verdiyseniz (bölge, yaÅŸ grubu gibi), bunlar da RKI’ye aktarılır.\n\ RKI, Uygulamanın etki gücünü ve iÅŸlevselliÄŸini deÄŸerlendirmek ve pandemi hakkında yeni çıkarımlar elde etmek için, bu verileri birleÅŸtirecek ve istatistikler olarak deÄŸerlendirecektir. Bu süreçte edinilen bulgular, Uygulamanın iÅŸlevlerini ve kullanım kolaylığını iyileÅŸtirmenin yanı sıra pandemiye karşı mücadele için diÄŸer önlemlerin yönlendirilmesine yardımcı olmaktadır.\n\ Verilerinizin deÄŸerlendirilmesinden önce, veri bağışına katılan her Uygulamanın yalnızca bir kez sayıma alındığı ve istatistiklerin tahrif edilmediÄŸi kontrol edilir. Bu baÄŸlamda Uygulamanızın orijinal olduÄŸunun incelenmesi gerekmektedir. Bunun için, akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve Google’ın Uygulamanızın orijinal ürün olduÄŸunu RKI’ye doÄŸrulaması için ABD’deki Google’a aktarılır. Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Google, Uygulamadan daha fazla bilgi almaz.\n\ Uygulamanın ayarlarından “Verileri bağışla†seçeneÄŸini devre dışı bırakarak istediÄŸiniz zaman vermiÅŸ olduÄŸunuz rızayı geri alabilirsiniz."</string> + <string name="ppa_onboarding_privacy_information_body" translatable="false">"“Kabul ediyorum†seçeneÄŸine tıklayarak, ÅŸunlara onay vermiÅŸ olursunuz:\n\nUygulama, topladığı bilgileri her gün RKI’ye aktarır. Bunlar, görüntülenen riskli karşılaÅŸmalar ve uyarılar, size gönderilen test sonuçları, diÄŸer kullanıcıları uyarıp uyarmadığınız ve akıllı telefonunuzun iÅŸletim sistemine iliÅŸkin verilerdir. Ayrıca baÅŸka bilgiler de verdiyseniz (bölge, yaÅŸ grubu gibi), bunlar da RKI’ye aktarılır.\n\nRKI, Uygulamanın etki gücünü ve iÅŸlevselliÄŸini deÄŸerlendirmek ve pandemi hakkında yeni çıkarımlar elde etmek için, bu verileri birleÅŸtirecek ve istatistikler olarak deÄŸerlendirecektir. Bu süreçte edinilen bulgular, Uygulamanın iÅŸlevlerini ve kullanım kolaylığını iyileÅŸtirmenin yanı sıra pandemiye karşı mücadele için diÄŸer önlemlerin yönlendirilmesine yardımcı olmaktadır.\n\nVerilerinizin deÄŸerlendirilmesinden önce, veri bağışına katılan her Uygulamanın yalnızca bir kez sayıma alındığı ve istatistiklerin tahrif edilmediÄŸi kontrol edilir. Bu baÄŸlamda Uygulamanızın orijinal olduÄŸunun incelenmesi gerekmektedir. Bunun için, akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve Google’ın Uygulamanızın orijinal ürün olduÄŸunu RKI’ye doÄŸrulaması için ABD’deki veya diÄŸer bir üçüncü ülkedeki Google’a aktarılır. Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Google böylece kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik kontrolünün yapıldığını anlayabilir. Google, Uygulamadan daha fazla bilgi almaz.\n\nUygulamanın ayarlarından “Verileri bağışla†seçeneÄŸini devre dışı bırakarak istediÄŸiniz zaman vermiÅŸ olduÄŸunuz rızayı geri alabilirsiniz."</string> <!-- XTXT: Body point consent for Privacy-preserving Analytics --> - <string name="ppa_onboarding_privacy_information_point_consent" translatable="false">"VerdiÄŸiniz rıza beyanı, isteÄŸe baÄŸlı gerçekleÅŸmektedir. Rızanızı vermezseniz de Uygulamayı kullanabilirsiniz."</string> + <string name="ppa_onboarding_privacy_information_point_consent" translatable="false">"VerdiÄŸiniz rıza beyanı, isteÄŸe baÄŸlı gerçekleÅŸmektedir. Rızanızı vermezseniz de Uygulamayı kullanabilirsiniz."</string> <!-- XTXT: Body point identity for Privacy-preserving Analytics --> - <string name="ppa_onboarding_privacy_information_point_identity" translatable="false">"Uygulama kullanımınızla ilgili veri bağışında bulunursanız, kimliÄŸinizin korunması sürdürülür. RKI, kim olduÄŸunuzu veya kiminle karşılaÅŸtığınızı öğrenmez. Profil oluÅŸturma da yapılmaz."</string> + <string name="ppa_onboarding_privacy_information_point_identity" translatable="false">"Uygulama kullanımınızla ilgili veri bağışında bulunursanız, RKI nezdinde kimliÄŸinizin korunması sürdürülür. RKI, kim olduÄŸunuzu veya kiminle karşılaÅŸtığınızı öğrenmez. Profil oluÅŸturma da yapılmaz."</string> <!-- XTXT: Body point sixteen for Privacy-preserving Analytics --> <string name="ppa_onboarding_privacy_information_point_sixteen" translatable="false">"En azından 16 yaşında iseniz, rıza beyanında bulunabilirsiniz."</string> <!-- XHED: Title for Privacy-preserving Analytics additional info --> <string name="ppa_onboarding_more_info_title" translatable="false">"Orijinallik doÄŸrulanması ve üçüncü ülkelere aktarım"</string> <!-- XTXT: Body for Privacy-preserving Analytics additional info --> - <string name="ppa_onboarding_more_info_body" translatable="false">"Uygulamanızın orijinal olduÄŸunu onaylamak adına akıllı telefonunuz, akıllı telefonunuzun sürümü ve Uygulamaya iliÅŸkin veriler içeren benzersiz bir kimlik kodu oluÅŸturur. Verilerin birden fazla veya suistimal amaçlı RKI’ye aktarılmasını ve dolayısıyla analiz sonuçlarının tahrif edilmesini önlemek için bu kodun oluÅŸturulması gerekmektedir. Bu kimlik kodları, Google’e aktarılır. Bu süreçte verilerin ABD’ye aktarılması da söz konusu olabilir. Orada Avrupa hukukuna uygun kiÅŸisel verilerin koruma seviyesi bulunmamaktadır ve Avrupa’daki veri koruma haklarınız uygulanmayabilir. Bu baÄŸlamda özellikle ABD güvenlik makamlarının, somut bir şüphe olmasa bile, Google’e aktarılan bu verilere eriÅŸme ve örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları deÄŸerlendirmeye alma olasılığı bulunmaktadır. Bu durum sadece Google’e aktarılan kimlik kodları için söz konusudur. Google Corona-Warn-App kullanımınız ile ilgili daha fazla bilgi almaz.\Üçüncü ülkelere aktarımı kabul etmiyorsanız, lütfen “Kabul ediyorum†seçeneÄŸine tıklamayın. Buna raÄŸmen Uygulamayı kullanmaya devam edebilirsiniz, ancak veri bağışında bulunamazsınız."</string> + <string name="ppa_onboarding_more_info_body" translatable="false">"Uygulamanızın orijinal olduÄŸunu onaylamak adına akıllı telefonunuz, akıllı telefonunuzun sürümü ve Uygulamaya iliÅŸkin veriler içeren benzersiz bir kimlik kodu oluÅŸturur. Verilerin birden fazla veya suistimal amaçlı RKI’ye aktarılmasını ve dolayısıyla analiz sonuçlarının tahrif edilmesini önlemek için bu kodun oluÅŸturulması gerekmektedir. Bu kimlik kodları, Google’e aktarılır. Bu süreçte verilerin ABD’ye aktarılması da söz konusu olabilir. Orada muhtemelen Avrupa hukuku standartlarına uygun kiÅŸisel verilerin koruma seviyesi bulunmayabilir ve Avrupa’daki veri koruma haklarınız uygulanmayabilir. Bu baÄŸlamda özellikle üçüncü ülkelerdeki güvenlik makamlarının, somut bir şüphe olmasa bile, Google’e aktarılan bu verilere eriÅŸme ve örneÄŸin verileri diÄŸer bilgilerle iliÅŸkilendirerek bunları deÄŸerlendirmeye alma olasılığı bulunmaktadır. Bu durum sadece Google’e aktarılan kimlik kodları için söz konusudur. Google Corona-Warn-App kullanımınız ile ilgili daha fazla bilgi almaz.xAncak, Google kimlik kodunuz üzerinden kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik kontrolünün yapıldığını anlayabilir. Üçüncü ülkelere aktarımı kabul etmiyorsanız, lütfen “Kabul ediyorum†seçeneÄŸine tıklamayın. Buna raÄŸmen Uygulamayı kullanmaya devam edebilirsiniz, ancak veri bağışında bulunamazsınız."</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> + <string name="ppa_onboarding_privacy_information_title" translatable="false">"Rıza beyanınız"</string> <!-- XTXT: Body for Privacy-preserving Analytics settings --> - <string name="ppa_settings_privacy_information_body" translatable="false">"Yukarıdaki “Veri bağışı" seçeneÄŸini etkinleÅŸtirerek, ÅŸunlara onay vermiÅŸ olursunuz:\n\Uygulama, topladığı bilgileri her gün RKI’ye aktarır. Bunlar, görüntülenen riskli karşılaÅŸmalar ve uyarılar, size gönderilen test sonuçları, diÄŸer kullanıcıları uyarıp uyarmadığınız ve akıllı telefonunuzun iÅŸletim sistemine iliÅŸkin verilerdir. Ayrıca baÅŸka bilgiler de verdiyseniz (bölge, yaÅŸ grubu gibi), bunlar da RKI’ye aktarılır.\n\nRKI, Uygulamanın etki gücünü ve iÅŸlevselliÄŸini deÄŸerlendirmek ve pandemi hakkında yeni çıkarımlar elde etmek için, bu verileri birleÅŸtirecek ve istatistikler olarak deÄŸerlendirecektir. Bu süreçte edinilen bulgular, Uygulamanın iÅŸlevlerini ve kullanım kolaylığını iyileÅŸtirmenin yanı sıra pandemiye karşı mücadele için diÄŸer önlemlerin yönlendirilmesine yardımcı olmaktadır.\n\nVerilerinizin deÄŸerlendirilmesinden önce, veri bağışına katılan her Uygulamanın yalnızca bir kez sayıma alındığı ve istatistiklerin tahrif edilmediÄŸi kontrol edilir. Bu baÄŸlamda Uygulamanızın orijinal olduÄŸunun incelenmesi gerekmektedir. Bunun için, akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve Google’ın Uygulamanızın orijinal ürün olduÄŸunu RKI’ye doÄŸrulaması için ABD’deki Google’e aktarılır. Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Google, Uygulamadan daha fazla bilgi almaz.\n\n Yukarıdaki “Veri bağışı†seçeneÄŸini devre dışı bırakarak, verdiÄŸiniz rıza beyanını geri alabilirsiniz Yukarıdaki “Veri bağışı†seçeneÄŸini devre dışı bırakarak, verdiÄŸiniz rıza beyanını geri alabilirsiniz."</string> - + <string name="ppa_settings_privacy_information_body" translatable="false">"Yukarıdaki “Veri bağışı" seçeneÄŸini etkinleÅŸtirerek, ÅŸunlara onay vermiÅŸ olursunuz:\n\nUygulama, topladığı bilgileri her gün RKI’ye aktarır. Bunlar, görüntülenen riskli karşılaÅŸmalar ve uyarılar, size gönderilen test sonuçları, diÄŸer kullanıcıları uyarıp uyarmadığınız ve akıllı telefonunuzun iÅŸletim sistemine iliÅŸkin verilerdir. Ayrıca baÅŸka bilgiler de verdiyseniz (bölge, yaÅŸ grubu gibi), bunlar da RKI’ye aktarılır.\n\nRKI, Uygulamanın etki gücünü ve iÅŸlevselliÄŸini deÄŸerlendirmek ve pandemi hakkında yeni çıkarımlar elde etmek için, bu verileri birleÅŸtirecek ve istatistikler olarak deÄŸerlendirecektir. Bu süreçte edinilen bulgular, Uygulamanın iÅŸlevlerini ve kullanım kolaylığını iyileÅŸtirmenin yanı sıra pandemiye karşı mücadele için diÄŸer önlemlerin yönlendirilmesine yardımcı olmaktadır.\n\nVerilerinizin deÄŸerlendirilmesinden önce, veri bağışına katılan her Uygulamanın yalnızca bir kez sayıma alındığı ve istatistiklerin tahrif edilmediÄŸi kontrol edilir. Bu baÄŸlamda Uygulamanızın orijinal olduÄŸunun incelenmesi gerekmektedir. Bunun için, akıllı telefonunuz tarafından benzersiz bir kimlik kodu oluÅŸturulur ve Google’ın Uygulamanızın orijinal ürün olduÄŸunu RKI’ye doÄŸrulaması için ABD’deki veya diÄŸer bir üçüncü ülkeye Google’e aktarılır. Bu kimlik kodu, akıllı telefonunuzun sürümü ve Uygulama hakkında veriler içerir. Google, böylece kim olduÄŸunuzu ortaya çıkarabilir ve akıllı telefonunuzun orijinallik kontrolünün yapıldığını anlayabilir. Google, Uygulamadan daha fazla bilgi almaz.\n\nYukarıdaki “Veri bağışı†seçeneÄŸini devre dışı bırakarak, verdiÄŸiniz rıza beyanını geri alabilirsiniz."</string> </resources> 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 dd8f24235a186e8d7aef069e57bc1d4e0e9e564d..dbb190211959b7022cf7bb1981658c5c2c0bca48 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -339,7 +339,7 @@ <!-- XHED: risk details - infection risk headline, below behaviors --> <string name="risk_details_headline_infection_risk">"Enfeksiyon Riski"</string> <!-- XHED: risk details - infection period logged headling, below behaviors --> - <string name="risk_details_headline_period_logged">"Dönem günlüğe kaydedildi"</string> + <string name="risk_details_headline_period_logged">"Dönem Günlüğe Kaydedildi"</string> <!-- 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 --> @@ -496,9 +496,9 @@ <!-- XACT: onboarding(tracing) - illustraction description, header image --> <string name="onboarding_tracing_illustration_description">"Üç kiÅŸi akıllı telefonlarında maruz kalma günlüğünü etkinleÅŸtirdi ve birbirleri ile karşılaÅŸmaları günlüğe kaydedilecektir."</string> <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> - <string name="onboarding_tracing_location_headline">"Konum eriÅŸimine izin ver"</string> + <string name="onboarding_tracing_location_headline">"Konum Ayarını EtkinleÅŸtir"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Konumunuza eriÅŸilemiyor. Bluetooth\'u kullanmak için Google ve/veya Android\'in akıllı telefonunuzun konumuna eriÅŸmesi gerekiyor."</string> + <string name="onboarding_tracing_location_body">"Uygulama, cihazınızın konumunu belirleyemiyor ancak bazı Android sürümlerinde Bluetooth Düşük Enerji iÅŸlevinin kullanılması için cihazın konum ayarının etkinleÅŸtirilmesi gereklidir."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Cihaz Ayarlarını Aç"</string> <!-- XACT: Onboarding (test) page title --> @@ -526,6 +526,8 @@ <string name="onboarding_ppa_illustration_description">"Bir kiÅŸi elinde akıllı telefon tutuyor"</string> <!-- XHED: onboarding privacy preserving analytics (ppa) - headline --> <string name="onboarding_ppa_headline">"Verileri PaylaÅŸ"</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - body short text --> + <string name="onboarding_ppa_body_short">"Uygulamayı nasıl kullandığınızı belirtin ve uygulamanın etkinliÄŸini deÄŸerlendirmemize yardımcı olun."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - body text --> <string name="onboarding_ppa_body">"Corona-Warn-App’i iyileÅŸtirmemize yardımcı olabilirsiniz. Uygulamayı kullanımınız hakkındaki verileri RKI ile paylaşın. Bu, RKI’nin uygulamanın etkinliÄŸini deÄŸerlendirmesine yardımcı olacaktır. Verileriniz aynı zamanda uygulamanın özelliklerinin ve kullanılabilirliÄŸinin iyileÅŸtirilmesine yardımcı olacaktır."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - state title --> @@ -537,7 +539,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - age title --> <string name="onboarding_ppa_age_title">"Yaşınız (isteÄŸe baÄŸlı)"</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_more_info_title">"ABD’de Veri Ä°ÅŸleme ve Veri Koruma Riskleri Hakkında Ayrıntılı Bilgiler"</string> + <string name="onboarding_ppa_more_info_title">"ABD’de ve DiÄŸer Üçüncü Ãœlkelerde Veri Ä°ÅŸleme ve Veri Koruma Riskleri Hakkında Ayrıntılı Bilgiler"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - donate button --> <string name="onboarding_ppa_consent_donate_button">"Verileri PaylaÅŸ"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - dont donate button --> @@ -550,7 +552,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - more info - title --> <string name="onboarding_ppa_more_info_data_processing_title">"Veri PaylaÅŸma Sürecinde Veri Ä°ÅŸleme"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing body --> - <string name="onboarding_ppa_more_info_data_processing_body">"Verilerinizi paylaÅŸma izni vermeniz halinde uygulama RKI’ye her gün çeÅŸitli veriler gönderir. Aktarılan veriler çeÅŸitli amaçlarla analiz edilir:"</string> + <string name="onboarding_ppa_more_info_data_processing_body">"Verilerinizi paylaÅŸma izni vermeniz halinde uygulama RKI’ye her gün çeÅŸitli veriler gönderir. Aktarılan veriler, uygulamanın etkinliÄŸini deÄŸerlendirmemize yardımcı olacaktır ve çeÅŸitli amaçlarla analiz edilecektir:"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 1 --> <string name="onboarding_ppa_more_info_data_processing_point_1_text">"Ä°yileÅŸtirilmiÅŸ maruz kalma günlüğü – Teknik olarak enfeksiyon risklerini hesaplama doÄŸruluÄŸunu ve güvenilirliÄŸini iyileÅŸtirmek istiyoruz. Bunun için, maruz kalma riskleri ve size gösterilen uyarlar hakkındaki bilgileri deÄŸerlendireceÄŸiniz. Bunun sonucunda, hesaplama yöntemini geliÅŸtirebileceÄŸiz."</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 2 --> @@ -1794,21 +1796,21 @@ <string name="datadonation_details_survey_consent_details_title">"Anketi Yanıtlamaya Ä°liÅŸkin Veri Ä°ÅŸleme Hakkında Ayrıntılı Bilgiler"</string> <!-- XHED: Dialog error title for survey request error informations 1 --> - <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Akıllı telefonunuzun saati, geçerli saat ile eÅŸleÅŸmiyor. Lütfen akıllı telefonunuzun saatini düzeltin (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"Akıllı telefonunuzun saati, geçerli saat ile eÅŸleÅŸmiyor. Lütfen akıllı telefonunuzun saatini düzeltin (hata kodu: %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 2--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Anket alınamıyor (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"Anket alınamıyor (hata kodu: %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 3--> - <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Anket ÅŸu anda alınamıyor. Lütfen Google Plat Hizmetlerini güncelleyin (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"Anket ÅŸu anda alınamıyor. Lütfen Google Plat Hizmetlerini güncelleyin (hata kodu: %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 4--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Anket ÅŸu anda alınamıyor. Lütfen daha sonra tekrar deneyin (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"Anket ÅŸu anda alınamıyor. Lütfen daha sonra tekrar deneyin (hata kodu: %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 5--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Güvenlik nedenleriyle anket alınamıyor. Sonraki takvim ayında anketi yeniden yanıtlayabilirsiniz (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"Güvenlik nedenleriyle anket alınamıyor. Sonraki takvim ayında anketi yeniden yanıtlayabilirsiniz (hata kodu: %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 6--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Anket akıllı telefonunuzda alınamıyor (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Yardımcı olmak istediÄŸiniz için teÅŸekkür ederiz. Ne yazık ki teknik nedenlerle anketi desteklemeyen bir akıllı telefon kullanıyorsunuz (hata kodu %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 7--> - <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Güvenlik nedenleriyle uygulamayı yüklemenizin veya güncellemenizin üzerinden en az 24 saat geçmeden anketi yanıtlayamazsınız (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"Güvenlik nedenleriyle uygulamayı yüklemenizin veya güncellemenizin üzerinden en az 24 saat geçmeden anketi yanıtlayamazsınız (hata kodu: %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 8--> - <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"Anketi zaten yanıtladınız. Anketi yalnızca ayda bir kez yanıtlayabilirsiniz (hata kodu: {0})."</string> + <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"Anketi zaten yanıtladınız. Anketi yalnızca ayda bir kez yanıtlayabilirsiniz (hata kodu: %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations dialog--> <string name="datadonation_details_survey_consent_error_dialog_title">"Hata"</string> diff --git a/Corona-Warn-App/src/main/res/values/legal_strings.xml b/Corona-Warn-App/src/main/res/values/legal_strings.xml index 526d9eb99d52547a9dff854dc94ed3b552baf719..1b7680961b5f22d45112d8a2fe248a307fb69fad 100644 --- a/Corona-Warn-App/src/main/res/values/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values/legal_strings.xml @@ -54,7 +54,7 @@ <!-- XHED: Title for the consent displayed above the button in the survey consent screen --> <string name="datadonation_details_survey_consent_info_card_title" translatable="false">"Your Consent"</string> <!-- XTXT: Text for the consent body displayed above the button in the survey consent screen --> - <string name="datadonation_details_survey_consent_info_card_body" translatable="false">"By tapping on “Acceptâ€, you consent to the following:\n\nBefore your participation link can be shown to you, the authenticity of your app is checked once. For this purpose, a unique identifier is generated by your smartphone and transmitted to Google in the U.S., so that Google can confirm the authenticity of your app to the RKI. The identifier contains information about the version of your smartphone and the app. Google does not receive any other information from the app during this process. This ensures that each user can only participate in the survey once, so as not to distort the statistics."</string> + <string name="datadonation_details_survey_consent_info_card_body" translatable="false">"By tapping on “Acceptâ€, you consent to the following:\n\nBefore your participation link can be shown to you, the authenticity of your app is checked once. For this purpose, a unique identifier is generated by your smartphone and transmitted to Google in the U.S. or other third countries, so that Google can confirm the authenticity of your app to the RKI. The identifier contains information about the version of your smartphone and the app. On this basis, Apple [Google] may be able to infer your identity and learn that your smartphone has been authenticated. Google does not receive any other information from the app during this process. This ensures that each user can only participate in the survey once, so as not to distort the statistics."</string> <!-- XTXT: First bullet point in the consent body displayed above the button in the survey consent screen --> <string name="datadonation_details_survey_consent_bullet_point_one" translatable="false">"Your consent is voluntary. If you do not give your consent for the purposes of the survey, you will still be able to use the whole app."</string> <!-- XTXT: Second bullet point in the consent body displayed above the button in the survey consent screen --> @@ -66,23 +66,25 @@ <!-- XHED: Title for the information box in the survey consent detail screen --> <string name="datadonation_survey_consent_details_title" translatable="false">"Verification of Authenticity and Transfer to a Third Country"</string> <!-- XTXT: Text for the information box in the survey consent detail screen --> - <string name="datadonation_survey_consent_details_text" translatable="false">"To confirm the authenticity of your app, your smartphone will generate a unique identifier that contains information about the version of your smartphone and the app. This is necessary to prevent users from participating in the survey more than once, as this could distort the results of the survey. Once generated, the identifier will be transmitted once to Google. This may result in data being transferred to the U.S. There, the level of data protection is not considered adequate under European law and it may not be possible to enforce your European data protection rights. In particular, there is a possibility that once the transmitted data reaches Google, it may be accessed and analyzed by U.S. security authorities, even if they have no specific grounds for suspicion, for example by linking the data with other information. This only concerns the identifier sent to Google. Google will not receive the other information about your participation in the survey."</string> + <string name="datadonation_survey_consent_details_text" translatable="false">"To confirm the authenticity of your app, your smartphone will generate a unique identifier that contains information about the version of your smartphone and the app. This is necessary to prevent users from participating in the survey more than once, as this could distort the results of the survey. Once generated, the identifier will be transmitted once to Google. This may result in data being transferred to the U.S. or other third countries. There, the level of data protection may not be considered equivalent under European law and it may not be possible to enforce your European data protection rights. In particular, there is a possibility that once the transmitted data reaches Google, it may be accessed and analyzed by security authorities in the third country, even if they have no specific grounds for suspicion, for example by linking the data with other information. This only concerns the identifier sent to Google. Google will not receive the other information about your participation in the survey. It may however be possible for Apple [Google] to infer your identity from the identifier and learn that your smartphone has been authenticated. "</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> <string name="ppa_onboarding_consent_title" translatable="false">"Your Consent"</string> <!-- XTXT: Body for Privacy-preserving Analytics onboarding --> - <string name="ppa_onboarding_privacy_information_body" translatable="false">"By tapping on “Acceptâ€, you consent to the following:\n\nThe app will transmit information it records to the RKI, on a daily basis. The data concerns possible exposures and warnings that have been displayed to you, test results you have retrieved, and whether you have warned other users, and information about your smartphone’s operating system. If you have provided further details above (region, age group), then the RKI will also receive this information.\n\nThe RKI will compile this data into statistics and analyze it to assess the effectiveness and functioning of the app, and draw conclusions regarding the pandemic. The resulting knowledge will help to improve the app’s features and make it more user-friendly, as well as to inform other pandemic response measures.\n\nBefore your data is analyzed, it is necessary to ensure that each app that shares data is only counted once, so as not to distort the statistics. This is why the authenticity of your app needs to be verified. For this purpose, a unique identifier is generated by your smartphone and transmitted to Google in the U.S., so that Google can confirm the authenticity of your app to the RKI. The identifier contains information about the version of your smartphone and the app. Google does not receive any other information from the app during this process.\n\You can withdraw your consent at any time by disabling the data sharing feature in the app's settings."</string> - <!-- XTXT: Body point consent for Privacy-preserving Analytics --> + <string name="ppa_onboarding_privacy_information_body" translatable="false">"By tapping on “Acceptâ€, you consent to the following:\n\nThe app will transmit information it records to the RKI, on a daily basis. The data concerns possible exposures and warnings that have been displayed to you, test results you have retrieved, and whether you have warned other users, and information about your smartphone’s operating system. If you have provided further details above (region, age group), then the RKI will also receive this information.\n\nThe RKI will compile this data into statistics and analyze it to assess the effectiveness and functioning of the app, and draw conclusions regarding the pandemic. The resulting knowledge will help to improve the app’s features and make it more user-friendly, as well as to inform other pandemic response measures.\n\nBefore your data is analyzed, it is necessary to ensure that each app that shares data is only counted once, so as not to distort the statistics. This is why the authenticity of your app needs to be verified. For this purpose, a unique identifier is generated by your smartphone and transmitted to Google in the U.S. or other third countries, so that Google can confirm the authenticity of your app to the RKI. The identifier contains information about the version of your smartphone and the app. On this basis, Google may be able to infer your identity and learn that your smartphone has been authenticated. Google does not receive any other information from the app during this process.\n\nYou can withdraw your consent at any time by disabling the data sharing feature in the app's settings."</string> + <!-- XTXT: Body point consent for Privacy-preserving Analytics --> <string name="ppa_onboarding_privacy_information_point_consent" translatable="false">"Your consent is voluntary. If you do not give your consent, you will still be able to use the app."</string> - <!-- XTXT: Body point identity for Privacy-preserving Analytics --> - <string name="ppa_onboarding_privacy_information_point_identity" translatable="false">"Your identity will still be protected if you share data about your use of the app. This means the RKI will not find out who you are or who you have met. No profiles will be created either."</string> - <!-- XTXT: Body point sixteen for Privacy-preserving Analytics --> + <!-- XTXT: Body point identity for Privacy-preserving Analytics --> + <string name="ppa_onboarding_privacy_information_point_identity" translatable="false">"If you share data about your use of the app, your identity will still be kept secret from the RKI. This means the RKI will not find out who you are or who you have met. No profiles will be created either."</string> + <!-- XTXT: Body point sixteen for Privacy-preserving Analytics --> <string name="ppa_onboarding_privacy_information_point_sixteen" translatable="false">"You need to be at least 16 years old to give your consent."</string> - <!-- XHED: Title for Privacy-preserving Analytics additional info --> + <!-- XHED: Title for Privacy-preserving Analytics additional info --> <string name="ppa_onboarding_more_info_title" translatable="false">"Verification of authenticity and transfer to a third country"</string> - <!-- XTXT: Body for Privacy-preserving Analytics additional info --> - <string name="ppa_onboarding_more_info_body" translatable="false">"To confirm the authenticity of your app, your smartphone will generate a unique identifier that contains information about the version of your smartphone and the app. This is necessary to prevent data from being transmitted to the RKI more than once or in an unauthorised manner, as this could distort the results of the analysis. The identifier will be transmitted to Google. This may result in data being transferred to the U.S. There, the level of data protection is not considered adequate under European law and it may not be possible to enforce your European data protection rights. In particular, there is a possibility that once the transmitted data reaches Google, it may be accessed and analyzed by U.S. security authorities, even if they have no specific grounds for suspicion, for example by linking the data with other information. This only concerns the identifier sent to Google. Google will not receive the other information about how you use the Corona-Warn-App.\If you do not consent to this transfer of your data to a third country, please do not tap on “Acceptâ€. You will still be able to use the app, but not the data sharing feature."</string> - <!-- XTXT: Body for Privacy-preserving Analytics settings --> - <string name="ppa_settings_privacy_information_body" translatable="false">"By enabling “Share Data†above, you consent to the following:\n\nThe app will transmit information it records to the RKI, on a daily basis. The data concerns possible exposures and warnings that have been displayed to you, test results you have retrieved, and whether you have warned other users, and information about your smartphone’s operating system. If you have provided further details above (region, age group), then the RKI will also receive this information.\n\nThe RKI will compile this data into statistics and analyze it to assess the effectiveness and functioning of the app, and draw conclusions regarding the pandemic. The resulting knowledge will help to improve the app’s features and make it more user-friendly, as well as to inform other pandemic response measures.\n\nBefore your data is analyzed, it is necessary to ensure that each app that shares data is only counted once, so as not to distort the statistics. This is why the authenticity of your app needs to be verified. For this purpose, a unique identifier is generated by your smartphone and transmitted to Google in the U.S., so that Google can confirm the authenticity of your app to the RKI. The identifier contains information about the version of your smartphone and the app. Google does not receive any other information from the app during this process.\n\n You can withdraw your consent at any time by disabling “Share Data†above."</string> + <!-- XTXT: Body for Privacy-preserving Analytics additional info --> + <string name="ppa_onboarding_more_info_body" translatable="false">"To confirm the authenticity of your app, your smartphone will generate a unique identifier that contains information about the version of your smartphone and the app. This is necessary to prevent data from being transmitted to the RKI more than once or in an unauthorised manner, as this could distort the results of the analysis. The identifier will be transmitted to Google. This may result in data being transferred to the U.S. or other third countries. There, the level of data protection may not be considered equivalent under European law and it may not be possible to enforce your European data protection rights. In particular, there is a possibility that once the transmitted data reaches Google, it may be accessed and analyzed by security authorities in the third country, even if they have no specific grounds for suspicion, for example by linking the data with other information. This only concerns the identifier sent to Google. Google will not receive the other information about how you use the Corona-Warn-App. It may however be possible for Google to infer your identity from the identifier and learn that your smartphone has been authenticated.\n\nIf you do not consent to this transfer of your data to a third country, please do not tap on “Acceptâ€. You will still be able to use the app, but not the data sharing feature."</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> + <string name="ppa_onboarding_privacy_information_title" translatable="false">"Your Consent"</string> + <!-- XTXT: Body for Privacy-preserving Analytics settings --> + <string name="ppa_settings_privacy_information_body" translatable="false">"By enabling “Share Data†above, you consent to the following:\n\nThe app will transmit information it records to the RKI, on a daily basis. The data concerns possible exposures and warnings that have been displayed to you, test results you have retrieved, and whether you have warned other users, and information about your smartphone’s operating system. If you have provided further details above (region, age group), then the RKI will also receive this information.\n\nThe RKI will compile this data into statistics and analyze it to assess the effectiveness and functioning of the app, and draw conclusions regarding the pandemic. The resulting knowledge will help to improve the app’s features and make it more user-friendly, as well as to inform other pandemic response measures.\n\nBefore your data is analyzed, it is necessary to ensure that each app that shares data is only counted once, so as not to distort the statistics. This is why the authenticity of your app needs to be verified. For this purpose, a unique identifier is generated by your smartphone and transmitted to Google in the U.S. or other third countries, so that Google can confirm the authenticity of your app to the RKI. The identifier contains information about the version of your smartphone and the app. On this basis, Google may be able to infer your identity and learn that your smartphone has been authenticated. Google does not receive any other information from the app during this process.\n\n You can withdraw your consent at any time by disabling “Share Data†above."</string> </resources> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index db9fe666995e47ba54004b5667fd15f1180741b7..de1bd20084af2a084408b0bc6b3b8c904a8b6e2a 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -334,8 +334,8 @@ <!-- XMSG: risk details - link to faq, something like a bullet point --> <string name="risk_details_increased_risk_faq_link_text">"If you get tested, you will find additional information about the testing procedure in the FAQ."</string> - <!-- XTXT: Explanation screen increased risk level link label --> - <string name="risk_details_increased_risk_faq_link_label">"FAQ: Testing Procedure"</string> + <!-- XTXT: Explanation screen increased risk level link label - HAS TO MATCH the link text above --> + <string name="risk_details_increased_risk_faq_link_label">"FAQ"</string> <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/en/faq/#red_card_how_to_test"</string> @@ -350,7 +350,7 @@ <!-- XHED: risk details - infection risk headline, below behaviors --> <string name="risk_details_headline_infection_risk">"Risk of Infection"</string> <!-- XHED: risk details - infection period logged headling, below behaviors --> - <string name="risk_details_headline_period_logged">"Period logged"</string> + <string name="risk_details_headline_period_logged">"Period Logged"</string> <!-- 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 --> @@ -508,9 +508,9 @@ <!-- XACT: onboarding(tracing) - illustraction description, header image --> <string name="onboarding_tracing_illustration_description">"Three persons have activated exposure logging on their smartphones, which will log their encounters with each other."</string> <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline --> - <string name="onboarding_tracing_location_headline">"Allow location access"</string> + <string name="onboarding_tracing_location_headline">"Activate Location Setting"</string> <!-- XTXT: onboarding(tracing) - location explanation for bluetooth body text --> - <string name="onboarding_tracing_location_body">"Your location cannot be accessed. Google and/or Android requires access to your smartphone’s location to use Bluetooth."</string> + <string name="onboarding_tracing_location_body">"Your device location cannot be determined by the app, but the device location setting must be activated to use Bluetooth Low Energy in some Android versions."</string> <!-- XBUT: onboarding(tracing) - button enable tracing --> <string name="onboarding_tracing_location_button">"Open Device Settings"</string> <!-- XACT: Onboarding (test) page title --> @@ -534,12 +534,12 @@ <!-- XACT: onboarding(notifications) - illustraction description, header image --> <string name="onboarding_notifications_illustration_description">"A woman receives a notification from her Corona-Warn-App."</string> - <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_consent_title">"Ihr Einverständnis"</string> <!-- XACT: onboarding privacy preserving analytics (ppa) - illustraction description, header image --> <string name="onboarding_ppa_illustration_description">"A person is holding a smartphone in their hand"</string> <!-- XHED: onboarding privacy preserving analytics (ppa) - headline --> <string name="onboarding_ppa_headline">"Share Data"</string> + <!-- XTXT: onboarding privacy preserving analytics (ppa) - body short text --> + <string name="onboarding_ppa_body_short">"Let us know how you use the app and help us to assess its effectiveness."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - body text --> <string name="onboarding_ppa_body">"You can help us to improve the Corona-Warn-App. Share the data about your app usage with the RKI. This will help the RKI assess the app’s effectiveness. Your data will also help to improve the features and usability of the app."</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - state title --> @@ -551,7 +551,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - age title --> <string name="onboarding_ppa_age_title">"Your age (optional)"</string> <!-- XTXT: onboarding privacy preserving analytics (ppa) - consent title --> - <string name="onboarding_ppa_more_info_title">"Detailed Information about This Data Processing and Data Protection Risks in the U.S."</string> + <string name="onboarding_ppa_more_info_title">"Detailed Information about This Data Processing and Data Protection Risks in the U.S. and Other Third Countries"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - donate button --> <string name="onboarding_ppa_consent_donate_button">"Share Data"</string> <!-- XBUT: onboarding privacy preserving analytics (ppa) - dont donate button --> @@ -564,7 +564,7 @@ <!-- XTXT: onboarding privacy preserving analytics (ppa) - more info - title --> <string name="onboarding_ppa_more_info_data_processing_title">"Data Processing in the Data Sharing Process"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing body --> - <string name="onboarding_ppa_more_info_data_processing_body">"If you consent to share your data, the app sends various data to the RKI each day. The transmitted data will be analyzed for a variety of purposes:"</string> + <string name="onboarding_ppa_more_info_data_processing_body">"If you consent to share your data, the app sends various data to the RKI each day. The transmitted data will help us assess the effectiveness of the app and will be analyzed to support the following improvements:"</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 1 --> <string name="onboarding_ppa_more_info_data_processing_point_1_text">"Improved exposure logging – We want to improve the accuracy and reliability of the technical calculation of infection risks. To do so, we will evaluate information about risk exposures and the warnings you see. As a result, we will be able to refine the calculation method."</string> <!-- YTXT: onboarding privacy preserving analytics (ppa) - more info - data processing point 2 --> @@ -1812,21 +1812,21 @@ <string name="datadonation_details_survey_consent_details_title">"Detailed Information on Data Processing Involved in Taking the Survey"</string> <!-- XHED: Dialog error title for survey request error informations 1 --> - <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"The time on your smartphone does not match the current time. Please correct the time in your smartphone settings (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_CHANGE_DEVICE_TIME">"The time on your smartphone does not match the current time. Please correct the time in your smartphone settings (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 2--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"The survey cannot be retrieved (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_SUPPORTED">"The survey cannot be retrieved (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 3--> - <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"The survey cannot be retrieved right now. Please update the Google Play Services (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_UPDATE_PLAY_SERVICES">"The survey cannot be retrieved right now. Please update the Google Play Services (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 4--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"The survey cannot be retrieved right now. Please try again later (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_LATER">"The survey cannot be retrieved right now. Please try again later (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 5--> - <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"The survey cannot be retrieved due to security reasons. You can take the survey again in the next calendar month (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_TRY_AGAIN_NEXT_MONTH">"The survey cannot be retrieved due to security reasons. You can take the survey again in the next calendar month (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 6--> - <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"The survey could not be retrieved on your smartphone (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_DEVICE_NOT_TRUSTED">"Thank you for wanting to help. Unfortunately, you use a smartphone that does not support the survey for technical reasons (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 7--> - <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"For security reasons, you cannot take the survey less than 24 hours after you install or update the app (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_TIME_SINCE_ONBOARDING_UNVERIFIED">"For security reasons, you cannot take the survey less than 24 hours after you install or update the app (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations 8--> - <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"You have already taken the survey. You can only take the survey once per month (error code {0})."</string> + <string name="datadonation_details_survey_consent_error_ALREADY_PARTICIPATED">"You have already taken the survey. You can only take the survey once per month (error code %1$s)."</string> <!-- XHED: Dialog error title for survey request error informations dialog--> <string name="datadonation_details_survey_consent_error_dialog_title">"Error"</string> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt index 65fbea7326413eafe856079034c1ce49a0f81438..2a21d798e23c7a76a303650af05b6607037dbdeb 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt @@ -44,7 +44,7 @@ class DefaultAppConfigSanityCheck : BaseTest() { fun `current default matches checksum`() { val config = context.assets.open(configName).readBytes() val sha256 = context.assets.open(checkSumName).readBytes().toString(Charsets.UTF_8) - sha256 shouldBe "827fb746a1128e465d65ec77030fdf38c823dec593ae18aed55195069cf8b701" + sha256 shouldBe "12d0b93c0c02c6870ef75c173a53a8ffb9cab6828fbf22e751053329c425eef2" config.toSHA256() shouldBe sha256 } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/BugReporterTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/BugReporterTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..bab37d8a115cfd9bb161127c31c1b7b294a4315a --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/BugReporterTest.kt @@ -0,0 +1,19 @@ +package de.rki.coronawarnapp.bugreporting + +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class BugReporterTest : BaseTest() { + + @Test + fun `test emtpy tag`() { + // This just tests the timber statement + Exception().reportProblem(info = "info") + } + + @Test + fun `test empty info and tag`() { + // This just tests the timber statement + Exception().reportProblem() + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/OneTimePasswordTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/OneTimePasswordTest.kt index b8b9fda93b84e002cf8e02b10a5d943dc55a072e..6614c0122d97aee7ace11e468434195c22e4a0a0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/OneTimePasswordTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/OneTimePasswordTest.kt @@ -1,18 +1,24 @@ package de.rki.coronawarnapp.datadonation +import de.rki.coronawarnapp.server.protocols.internal.ppdd.EdusOtp import io.kotest.matchers.shouldBe import okio.ByteString.Companion.decodeBase64 -import org.junit.Test +import org.junit.jupiter.api.Test import java.util.UUID class OneTimePasswordTest { @Test fun `payload generation`() { - val otpPayload = - OneTimePassword(UUID.fromString("15cff19f-af26-41bc-94f2-c1a65075e894")) - .payloadForRequest - val expected = "MTVjZmYxOWYtYWYyNi00MWJjLTk0ZjItYzFhNjUwNzVlODk0".decodeBase64()!!.toByteArray() - otpPayload shouldBe expected + val uuid = UUID.fromString("15cff19f-af26-41bc-94f2-c1a65075e894") + val otp = OneTimePassword(uuid) + + val protoBuf = EdusOtp.EDUSOneTimePassword.newBuilder().setOtp(uuid.toString()).build() + val protoBufRaw = "CiQxNWNmZjE5Zi1hZjI2LTQxYmMtOTRmMi1jMWE2NTA3NWU4OTQ=".decodeBase64()!!.toByteArray() + otp.apply { + edusOneTimePassword shouldBe protoBuf + payloadForRequest shouldBe protoBuf.toByteArray() + payloadForRequest shouldBe protoBufRaw + } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt index 74de382bd2ff6241da82fc0cf6ee7930c942500d..306ff85627dfbb7a37afd3a54ae02c870bd3f07c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt @@ -7,15 +7,18 @@ import de.rki.coronawarnapp.appconfig.SafetyNetRequirements import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.datadonation.analytics.modules.exposureriskmetadata.ExposureRiskMetadataDonor +import de.rki.coronawarnapp.datadonation.analytics.modules.usermetadata.UserMetadataDonor import de.rki.coronawarnapp.datadonation.analytics.server.DataDonationAnalyticsServer import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.datadonation.analytics.storage.LastAnalyticsSubmissionLogger import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation +import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaDataRequestAndroid import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.TimeStamper +import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.clearAllMocks @@ -24,8 +27,11 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just +import io.mockk.mockk import io.mockk.mockkObject import io.mockk.spyk +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runBlockingTest import org.joda.time.Days import org.joda.time.Instant import org.junit.jupiter.api.AfterEach @@ -71,6 +77,8 @@ class AnalyticsTest : BaseTest() { every { LocalData.onboardingCompletedTimestamp() } returns twoDaysAgo.millis every { analyticsConfig.safetyNetRequirements } returns SafetyNetRequirementsContainer() + + coEvery { dataDonationAnalyticsServer.uploadAnalyticsData(any()) } just Runs } @AfterEach @@ -78,14 +86,12 @@ class AnalyticsTest : BaseTest() { clearAllMocks() } - private fun createDonorModules(): Set<DonorModule> = setOf(exposureRiskMetadataDonor) - - private fun createInstance() = spyk( + private fun createInstance(modules: Set<DonorModule> = setOf(exposureRiskMetadataDonor)) = spyk( Analytics( dataDonationAnalyticsServer = dataDonationAnalyticsServer, appConfigProvider = appConfigProvider, deviceAttestation = deviceAttestation, - donorModules = createDonorModules(), + donorModules = modules, settings = settings, logger = lastAnalyticsSubmissionLogger, timeStamper = timeStamper @@ -99,7 +105,11 @@ class AnalyticsTest : BaseTest() { val analytics = createInstance() runBlockingTest2 { - analytics.submitIfWanted() + val result = analytics.submitIfWanted() + result.apply { + successful shouldBe false + shouldRetry shouldBe false + } } coVerify(exactly = 1) { @@ -119,7 +129,11 @@ class AnalyticsTest : BaseTest() { val analytics = createInstance() runBlockingTest2 { - analytics.submitIfWanted() + val result = analytics.submitIfWanted() + result.apply { + successful shouldBe false + shouldRetry shouldBe false + } } coVerify(exactly = 1) { @@ -140,7 +154,11 @@ class AnalyticsTest : BaseTest() { val analytics = createInstance() runBlockingTest2 { - analytics.submitIfWanted() + val result = analytics.submitIfWanted() + result.apply { + successful shouldBe false + shouldRetry shouldBe false + } } coVerify(exactly = 1) { @@ -162,7 +180,11 @@ class AnalyticsTest : BaseTest() { val analytics = createInstance() runBlockingTest2 { - analytics.submitIfWanted() + val result = analytics.submitIfWanted() + result.apply { + successful shouldBe false + shouldRetry shouldBe false + } } coVerify(exactly = 1) { @@ -185,7 +207,11 @@ class AnalyticsTest : BaseTest() { val analytics = createInstance() runBlockingTest2 { - analytics.submitIfWanted() + val result = analytics.submitIfWanted() + result.apply { + successful shouldBe false + shouldRetry shouldBe false + } } coVerify(exactly = 1) { @@ -233,12 +259,14 @@ class AnalyticsTest : BaseTest() { override fun requirePass(requirements: SafetyNetRequirements) {} } - coEvery { dataDonationAnalyticsServer.uploadAnalyticsData(any()) } just Runs - val analytics = createInstance() runBlockingTest2 { - analytics.submitIfWanted() + val result = analytics.submitIfWanted() + result.apply { + successful shouldBe true + shouldRetry shouldBe false + } } coVerify(exactly = 1) { @@ -246,4 +274,185 @@ class AnalyticsTest : BaseTest() { dataDonationAnalyticsServer.uploadAnalyticsData(analyticsRequest) } } + + @Test + fun `despite error on beginDonation modules can still cleanup`() { + val userMetadataDonor = mockk<UserMetadataDonor>().apply { + coEvery { beginDonation(any()) } throws Exception("KABOOM!") + } + val modules = setOf(exposureRiskMetadataDonor, userMetadataDonor) + + val mockExposureRisk = mockk<DonorModule.Contribution>().apply { + coEvery { injectData(any()) } just Runs + coEvery { finishDonation(any()) } just Runs + } + coEvery { exposureRiskMetadataDonor.beginDonation(any()) } returns mockExposureRisk + + coEvery { deviceAttestation.attest(any()) } returns object : DeviceAttestation.Result { + override val accessControlProtoBuf: PpacAndroid.PPACAndroid + get() = PpacAndroid.PPACAndroid.getDefaultInstance() + + override fun requirePass(requirements: SafetyNetRequirements) {} + } + + val analytics = createInstance(modules = modules) + + runBlockingTest { + analytics.submitIfWanted() + } + + coVerify(exactly = 1) { + userMetadataDonor.beginDonation(any()) + + exposureRiskMetadataDonor.beginDonation(any()) + mockExposureRisk.injectData(any()) + mockExposureRisk.finishDonation(true) + + analytics.submitAnalyticsData(any()) + } + } + + @Test + fun `despite errors during donation modules can still cleanup`() { + val userMetaDataDonation = mockk<DonorModule.Contribution>().apply { + coEvery { injectData(any()) } throws Exception("KAPOW!") + coEvery { finishDonation(any()) } throws Exception("CRUNCH!") + } + val userMetadataDonor = mockk<UserMetadataDonor>().apply { + coEvery { beginDonation(any()) } returns userMetaDataDonation + } + val modules = setOf(exposureRiskMetadataDonor, userMetadataDonor) + + val exposureRiskDonation = mockk<ExposureRiskMetadataDonor.ExposureRiskMetadataContribution>().apply { + coEvery { injectData(any()) } just Runs + coEvery { finishDonation(any()) } just Runs + } + coEvery { exposureRiskMetadataDonor.beginDonation(any()) } returns exposureRiskDonation + + coEvery { deviceAttestation.attest(any()) } returns object : DeviceAttestation.Result { + override val accessControlProtoBuf: PpacAndroid.PPACAndroid + get() = PpacAndroid.PPACAndroid.getDefaultInstance() + + override fun requirePass(requirements: SafetyNetRequirements) {} + } + + val analytics = createInstance(modules = modules) + + runBlockingTest { + analytics.submitIfWanted() + } + + coVerify(exactly = 1) { + exposureRiskMetadataDonor.beginDonation(any()) + exposureRiskDonation.injectData(any()) + exposureRiskDonation.finishDonation(true) + + userMetadataDonor.beginDonation(any()) + userMetaDataDonation.injectData(any()) + userMetaDataDonation.finishDonation(true) + + analytics.submitAnalyticsData(any()) + } + } + + @Test + fun `we catch safetynet timeout and enable retry`() { + val exposureRiskDonation = mockk<ExposureRiskMetadataDonor.ExposureRiskMetadataContribution>().apply { + coEvery { injectData(any()) } just Runs + coEvery { finishDonation(any()) } just Runs + } + coEvery { exposureRiskMetadataDonor.beginDonation(any()) } returns exposureRiskDonation + + coEvery { deviceAttestation.attest(any()) } throws SafetyNetException( + type = SafetyNetException.Type.ATTESTATION_REQUEST_FAILED, + "Timeout???", + cause = Exception() + ) + + val analytics = createInstance() + + runBlockingTest { + val result = analytics.submitIfWanted() + result.successful shouldBe false + result.shouldRetry shouldBe true + } + + coVerify(exactly = 1) { + exposureRiskMetadataDonor.beginDonation(any()) + exposureRiskDonation.injectData(any()) + exposureRiskDonation.finishDonation(false) + } + + coVerify(exactly = 0) { dataDonationAnalyticsServer.uploadAnalyticsData(any()) } + } + + @Test + fun `overall submission can timeout on safetynet and still allow modules to cleanup`() { + val exposureRiskDonation = mockk<ExposureRiskMetadataDonor.ExposureRiskMetadataContribution>().apply { + coEvery { injectData(any()) } just Runs + coEvery { finishDonation(any()) } just Runs + } + coEvery { exposureRiskMetadataDonor.beginDonation(any()) } returns exposureRiskDonation + + coEvery { deviceAttestation.attest(any()) } coAnswers { + // Timeout should be 360s + delay(370_000) + mockk() + } + + val analytics = createInstance() + + runBlockingTest { + val result = analytics.submitIfWanted() + result.successful shouldBe false + result.shouldRetry shouldBe true + } + + coVerify(exactly = 1) { + exposureRiskMetadataDonor.beginDonation(any()) + exposureRiskDonation.injectData(any()) + exposureRiskDonation.finishDonation(false) + } + + coVerify(exactly = 0) { + dataDonationAnalyticsServer.uploadAnalyticsData(any()) + } + } + + @Test + fun `overall submission can timeout on upload and still allow modules to cleanup`() { + val exposureRiskDonation = mockk<ExposureRiskMetadataDonor.ExposureRiskMetadataContribution>().apply { + coEvery { injectData(any()) } just Runs + coEvery { finishDonation(any()) } just Runs + } + coEvery { exposureRiskMetadataDonor.beginDonation(any()) } returns exposureRiskDonation + + coEvery { deviceAttestation.attest(any()) } returns object : DeviceAttestation.Result { + override val accessControlProtoBuf: PpacAndroid.PPACAndroid + get() = PpacAndroid.PPACAndroid.getDefaultInstance() + + override fun requirePass(requirements: SafetyNetRequirements) {} + } + + coEvery { dataDonationAnalyticsServer.uploadAnalyticsData(any()) } coAnswers { + // Timeout should be 360s + delay(370_000) + mockk() + } + + val analytics = createInstance() + + runBlockingTest { + val result = analytics.submitIfWanted() + result.successful shouldBe false + result.shouldRetry shouldBe true + } + + coVerify(exactly = 1) { + exposureRiskMetadataDonor.beginDonation(any()) + exposureRiskDonation.injectData(any()) + exposureRiskDonation.finishDonation(false) + dataDonationAnalyticsServer.uploadAnalyticsData(any()) + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt index 33a064d6713352439410677d29352dc7482338c8..2ae64c468af8852f5eda27236680901da2e19b61 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.risk.RiskLevelResult import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData +import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.clearAllMocks @@ -27,12 +28,16 @@ class ExposureRiskMetadataDonorTest : BaseTest() { @MockK lateinit var highAggregatedRiskResult: AggregatedRiskResult @MockK lateinit var lowAggregatedRiskResult: AggregatedRiskResult + private val baseDate: Instant = Instant.ofEpochMilli(101010) + @BeforeEach fun setup() { MockKAnnotations.init(this) every { highAggregatedRiskResult.isIncreasedRisk() } returns true + every { highAggregatedRiskResult.mostRecentDateWithHighRisk } returns baseDate every { lowAggregatedRiskResult.isIncreasedRisk() } returns false + every { lowAggregatedRiskResult.mostRecentDateWithHighRisk } returns baseDate } @AfterEach @@ -60,11 +65,9 @@ class ExposureRiskMetadataDonorTest : BaseTest() { @Test fun `risk metadata is properly collected`() { - val recentDate = Instant.now() - val expectedMetadata = PpaData.ExposureRiskMetadata.newBuilder() .setRiskLevel(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) - .setMostRecentDateAtRiskLevel(recentDate.millis) + .setMostRecentDateAtRiskLevel(baseDate.seconds) .setRiskLevelChangedComparedToPreviousSubmission(true) .setDateChangedComparedToPreviousSubmission(true) .build() @@ -75,12 +78,12 @@ class ExposureRiskMetadataDonorTest : BaseTest() { createRiskLevelResult( aggregatedRiskResult = highAggregatedRiskResult, failureReason = null, - calculatedAt = recentDate + calculatedAt = baseDate ), createRiskLevelResult( aggregatedRiskResult = lowAggregatedRiskResult, failureReason = RiskLevelResult.FailureReason.UNKNOWN, - calculatedAt = recentDate + calculatedAt = baseDate ) ) ) @@ -100,18 +103,16 @@ class ExposureRiskMetadataDonorTest : BaseTest() { @Test fun `risk metadata change is properly collected`() { - val recentDate = Instant.now() - val initialMetadata = PpaData.ExposureRiskMetadata.newBuilder() .setRiskLevel(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) - .setMostRecentDateAtRiskLevel(recentDate.millis) + .setMostRecentDateAtRiskLevel(baseDate.seconds) .setRiskLevelChangedComparedToPreviousSubmission(true) .setDateChangedComparedToPreviousSubmission(true) .build() val expectedMetadata = PpaData.ExposureRiskMetadata.newBuilder() .setRiskLevel(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) - .setMostRecentDateAtRiskLevel(recentDate.millis) + .setMostRecentDateAtRiskLevel(baseDate.seconds) .setRiskLevelChangedComparedToPreviousSubmission(false) .setDateChangedComparedToPreviousSubmission(false) .build() @@ -123,12 +124,12 @@ class ExposureRiskMetadataDonorTest : BaseTest() { createRiskLevelResult( aggregatedRiskResult = highAggregatedRiskResult, failureReason = null, - calculatedAt = recentDate + calculatedAt = baseDate ), createRiskLevelResult( aggregatedRiskResult = lowAggregatedRiskResult, failureReason = RiskLevelResult.FailureReason.UNKNOWN, - calculatedAt = recentDate + calculatedAt = baseDate ) ) ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsPeriodicWorkerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsPeriodicWorkerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2ea4a82db16288a86dc944bad41944940386f3d6 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsPeriodicWorkerTest.kt @@ -0,0 +1,70 @@ +package de.rki.coronawarnapp.datadonation.analytics.worker + +import android.content.Context +import androidx.work.ListenableWorker +import androidx.work.WorkerParameters +import de.rki.coronawarnapp.datadonation.analytics.Analytics +import de.rki.coronawarnapp.worker.BackgroundConstants +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.RelaxedMockK +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class DataDonationAnalyticsPeriodicWorkerTest : BaseTest() { + + @MockK lateinit var context: Context + @MockK lateinit var analytics: Analytics + @RelaxedMockK lateinit var workerParams: WorkerParameters + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createWorker() = DataDonationAnalyticsPeriodicWorker( + context = context, + workerParams = workerParams, + analytics = analytics + ) + + @Test + fun `if result says retry, do retry`() = runBlockingTest { + coEvery { analytics.submitIfWanted() } returns Analytics.Result(successful = false, shouldRetry = true) + createWorker().doWork() shouldBe ListenableWorker.Result.Retry() + + coEvery { analytics.submitIfWanted() } returns Analytics.Result(successful = false, shouldRetry = false) + createWorker().doWork() shouldBe ListenableWorker.Result.Failure() + + coEvery { analytics.submitIfWanted() } returns Analytics.Result(successful = true, shouldRetry = false) + createWorker().doWork() shouldBe ListenableWorker.Result.Success() + } + + @Test + fun `maximum of 2 retry attemtps`() = runBlockingTest { + val worker = createWorker() + worker.runAttemptCount shouldBe 0 + + every { worker.runAttemptCount } returns BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD + 1 + + worker.doWork() shouldBe ListenableWorker.Result.Failure() + } + + @Test + fun `unexpected errors do not cause a retry`() = runBlockingTest { + coEvery { analytics.submitIfWanted() } throws Exception("SURPRISE!!!") + createWorker().doWork() shouldBe ListenableWorker.Result.Failure() + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt index 2cab39c71d9bd36af645990198e301cc84b038e2..9b04b9a50707d5d219c25e3f941be2bd0b5bcea7 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt @@ -6,11 +6,15 @@ import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.server.protocols.internal.ppdd.EdusOtp +import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid +import de.rki.coronawarnapp.storage.TestSettings +import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.HashExtensions.Format.BASE64 import de.rki.coronawarnapp.util.HashExtensions.toSHA256 import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.gplay.GoogleApiVersion +import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations @@ -20,6 +24,7 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk +import io.mockk.mockkObject import kotlinx.coroutines.test.runBlockingTest import okio.ByteString.Companion.decodeBase64 import org.joda.time.Duration @@ -28,6 +33,7 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference import java.security.SecureRandom import kotlin.random.Random @@ -44,14 +50,18 @@ class CWASafetyNetTest : BaseTest() { @MockK lateinit var appConfigProvider: AppConfigProvider @MockK lateinit var appConfigData: ConfigData + @MockK lateinit var testSettings: TestSettings private val defaultPayload = "Computer says no.".toByteArray() private val firstSalt = "LMK0jFCu/lOzl07ZHmtOqQ==".decodeBase64()!! - private val defaultNonce = (firstSalt.toByteArray() + defaultPayload).toSHA256(format = BASE64) + private val defaultNonce = (firstSalt.toByteArray() + defaultPayload).toSHA256(format = BASE64).decodeBase64()!! @BeforeEach fun setup() { MockKAnnotations.init(this) + mockkObject(CWADebug) + every { CWADebug.isDeviceForTestersBuild } returns false + every { environmentSetup.safetyNetApiKey } returns "very safe" coEvery { safetyNetClientWrapper.attest(any()) } returns clientReport every { secureRandom.nextBytes(any()) } answers { @@ -75,6 +85,8 @@ class CWASafetyNetTest : BaseTest() { every { cwaSettings.firstReliableDeviceTime } returns Instant.EPOCH.plus(Duration.standardDays(7)) every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardDays(8)) + + every { testSettings.skipSafetyNetTimeCheck } returns mockFlowPreference(false) } @AfterEach @@ -89,7 +101,8 @@ class CWASafetyNetTest : BaseTest() { appConfigProvider = appConfigProvider, googleApiVersion = googleApiVersion, timeStamper = timeStamper, - cwaSettings = cwaSettings + cwaSettings = cwaSettings, + testSettings = testSettings ) @Test @@ -109,7 +122,7 @@ class CWASafetyNetTest : BaseTest() { salt, payload ) - nonce shouldBe "M2EqczgxveKiptESiBNRmKqxYv5raTdzyeSZyzsCvjg=" + nonce shouldBe "M2EqczgxveKiptESiBNRmKqxYv5raTdzyeSZyzsCvjg=".decodeBase64() } @Test @@ -122,7 +135,7 @@ class CWASafetyNetTest : BaseTest() { otp.otp shouldBe "hello-world" val nonce = createInstance().calculateNonce(salt, payload) - nonce shouldBe "ANjVoDcS8v8iQdlNrcxehSggE9WZwIp7VNpjoU7cPsg=" + nonce shouldBe "ANjVoDcS8v8iQdlNrcxehSggE9WZwIp7VNpjoU7cPsg=".decodeBase64() } @Test @@ -134,7 +147,20 @@ class CWASafetyNetTest : BaseTest() { salt, payload ) - nonce shouldBe "Alzb6UASmHCdnnT0M8pQv5bQ/r/+lfS/jb760+ikhxc=" + nonce shouldBe "Alzb6UASmHCdnnT0M8pQv5bQ/r/+lfS/jb760+ikhxc=".decodeBase64() + } + + @Test + fun `nonce matches server calculation - serverstyle - PPA Payload`() { + // Server get's base64 encoded data and has to decode it first. + val salt = "Ri0AXC9U+b9hE58VqupI8Q==".decodeBase64()!!.toByteArray() + val payload = "Eg0IAxABGMGFyOT6LiABOgkIBBDdj6AFGAI=".decodeBase64()!!.toByteArray() + + val ppa = PpaData.PPADataAndroid.parseFrom(payload) + ppa.exposureRiskMetadataSetList.first().riskLevel shouldBe PpaData.PPARiskLevel.RISK_LEVEL_HIGH + + val nonce = createInstance().calculateNonce(salt, payload) + nonce shouldBe "bd6kMfLKby3pzEqW8go1ZgmHN/bU1p/4KG6+1GeB288=".decodeBase64() } @Test @@ -142,7 +168,7 @@ class CWASafetyNetTest : BaseTest() { val payload = "Computer says no.".toByteArray() val salt = "Don't be so salty".toByteArray() val nonce = createInstance().calculateNonce(salt, payload) - nonce shouldBe (salt + payload).toSHA256(format = BASE64) + nonce shouldBe (salt + payload).toSHA256(format = BASE64).decodeBase64() } @Test @@ -172,7 +198,7 @@ class CWASafetyNetTest : BaseTest() { @Test fun `request nonce must match response nonce`() = runBlockingTest { - every { clientReport.nonce } returns "missmatch" + every { clientReport.nonce } returns "missmatch".decodeBase64() val exception = shouldThrow<SafetyNetException> { createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) } @@ -208,6 +234,27 @@ class CWASafetyNetTest : BaseTest() { exception.type shouldBe SafetyNetException.Type.TIME_SINCE_ONBOARDING_UNVERIFIED } + @Test + fun `24h since onboarding can be skipped on deviceForTester builds`() = runBlockingTest { + every { timeStamper.nowUTC } returns Instant.EPOCH + + shouldThrow<SafetyNetException> { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + }.type shouldBe SafetyNetException.Type.TIME_SINCE_ONBOARDING_UNVERIFIED + + every { testSettings.skipSafetyNetTimeCheck } returns mockFlowPreference(true) + + shouldThrow<SafetyNetException> { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + }.type shouldBe SafetyNetException.Type.TIME_SINCE_ONBOARDING_UNVERIFIED + + every { CWADebug.isDeviceForTestersBuild } returns true + + shouldNotThrowAny { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + } + } + @Test fun `first reliable devicetime timestamp needs to be set`() = runBlockingTest { every { cwaSettings.firstReliableDeviceTime } returns Instant.EPOCH diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapperTest.kt index bfb6411eb77d9527a06eb820d603d07872972211..707950e24843cceaa4a085088aa593da6e8c4961 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapperTest.kt @@ -24,7 +24,6 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest -import testhelpers.coroutines.runBlockingTest2 import testhelpers.gms.MockGMSTask import java.io.IOException @@ -74,7 +73,7 @@ class SafetyNetClientWrapperTest : BaseTest() { } @Test - fun `attestation can time out`() = runBlockingTest2(ignoreActive = true) { + fun `attestation can time out`() = runBlockingTest { every { safetyNetClient.attest(any(), any()) } returns MockGMSTask.timeout() val resultAsync = async { @@ -83,10 +82,8 @@ class SafetyNetClientWrapperTest : BaseTest() { } } - advanceTimeBy(31 * 1000L) - val error = resultAsync.await() - error.type shouldBe SafetyNetException.Type.ATTESTATION_FAILED + error.type shouldBe SafetyNetException.Type.ATTESTATION_REQUEST_FAILED error.cause shouldBe instanceOf(TimeoutCancellationException::class) } @@ -148,7 +145,7 @@ class SafetyNetClientWrapperTest : BaseTest() { body shouldBe JsonParser.parseString(JWS_BODY) signature shouldBe JWS_SIGNATURE_BASE64.decodeBase64()!!.toByteArray() - nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==".decodeBase64()?.utf8() + nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==".decodeBase64() apkPackageName shouldBe "de.rki.coronawarnapp.test" basicIntegrity shouldBe false ctsProfileMatch shouldBe false @@ -164,7 +161,7 @@ class SafetyNetClientWrapperTest : BaseTest() { createInstance().attest("hodl".toByteArray()).apply { body shouldBe JsonParser.parseString(JWS_BODY_MINIMAL) - nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==".decodeBase64()?.utf8() + nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==".decodeBase64() apkPackageName shouldBe "de.rki.coronawarnapp.test" basicIntegrity shouldBe false ctsProfileMatch shouldBe false diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt index ba9894c5f47557708907931935a218e32e8f2a65..6fd1b7161a4109a3cc916257aa5cac7da5627207 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt @@ -101,7 +101,8 @@ class SurveySettingsTest : BaseTest() { { "uuid":"e103c755-0975-4588-a639-d0cd1ba421a1", "authorized": true, - "redeemedAt": 1612381217443 + "redeemedAt": 1612381217443, + "invalidated": true } """.trimIndent() ).apply() @@ -111,6 +112,7 @@ class SurveySettingsTest : BaseTest() { value!!.uuid.toString() shouldBe "e103c755-0975-4588-a639-d0cd1ba421a1" value.authorized shouldBe true value.redeemedAt.millis shouldBe 1612381217443 + value.invalidated shouldBe true } @Test @@ -133,14 +135,15 @@ class SurveySettingsTest : BaseTest() { val redeemedAt = Instant.ofEpochMilli(1612381217445) val instance = SurveySettings(context, baseGson) - instance.otpAuthorizationResult = OTPAuthorizationResult(uuid, authorized, redeemedAt) + instance.otpAuthorizationResult = OTPAuthorizationResult(uuid, authorized, redeemedAt, false) val value = preferences.getString("otp_result", null) value shouldBe """ { "uuid": "e103c755-0975-4588-a639-d0cd1ba421a0", "authorized": false, - "redeemedAt": 1612381217445 + "redeemedAt": 1612381217445, + "invalidated": false } """.trimIndent() } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt index c22593689bdb3fb649c31a47c0c244abc18a0b52..df2e2ce3dae0471911328808e994e452581ab29a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTaskTest.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.TimeStamper import io.mockk.MockKAnnotations import io.mockk.Runs @@ -54,6 +55,8 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() { mockkObject(BuildConfigWrap) every { BuildConfigWrap.VERSION_CODE } returns 1080005 + mockkObject(LocalData) + every { LocalData.isAllowedToSubmitDiagnosisKeys() } returns false availableKey1.apply { every { path } returns File("availableKey1") @@ -230,4 +233,20 @@ class DownloadDiagnosisKeysTaskTest : BaseTest() { enfClient.provideDiagnosisKeys(any(), any()) } } + + @Test + fun `we do not submit keys if user got positive test results`() = runBlockingTest { + every { LocalData.isAllowedToSubmitDiagnosisKeys() } returns true + + createInstance().run(DownloadDiagnosisKeysTask.Arguments()) + + coVerifySequence { + enfClient.isTracingEnabled + enfClient.latestTrackedExposureDetection() + } + + coVerify(exactly = 0) { + enfClient.provideDiagnosisKeys(any(), any()) + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/HttpErrorParserTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/HttpErrorParserTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6e5c4b649ed8ecb5423233f2fb4eaa710c884641 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/HttpErrorParserTest.kt @@ -0,0 +1,126 @@ +package de.rki.coronawarnapp.http + +import de.rki.coronawarnapp.exception.http.CwaWebException +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class HttpErrorParserTest : BaseTest() { + + @MockK lateinit var chain: Interceptor.Chain + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { chain.request() } returns mockk() + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private val baseResponse: Response.Builder + get() = Response.Builder().apply { + protocol(Protocol.HTTP_1_1) + Request.Builder().apply { + url("http://url.url") + }.build().let { request(it) } + } + + @Test + fun `normal response`() { + val response = baseResponse.apply { + code(200) + message("") + }.build() + every { chain.proceed(any()) } returns response + HttpErrorParser().intercept(chain) shouldBe response + } + + @Test + fun `error without message`() { + val response = baseResponse.apply { + code(404) + message("") + }.build() + every { chain.proceed(any()) } returns response + val exception = shouldThrow<CwaWebException> { HttpErrorParser().intercept(chain) } + exception.statusCode shouldBe 404 + exception.message shouldContain "code=404 message= body=null" + } + + @Test + fun `error with message`() { + val response = baseResponse.apply { + code(403) + message("Forbidden") + }.build() + every { chain.proceed(any()) } returns response + val exception = shouldThrow<CwaWebException> { HttpErrorParser().intercept(chain) } + exception.statusCode shouldBe 403 + exception.message shouldContain "message=Forbidden" + exception.message shouldContain "body=null" + } + + @Test + fun `error in body`() { + val response = baseResponse.apply { + code(500) + message("") + body("{\"errorCode\":\"APK_CERTIFICATE_MISMATCH\"}".toResponseBody("application/json".toMediaTypeOrNull())) + }.build() + every { chain.proceed(any()) } returns response + val exception = shouldThrow<CwaWebException> { HttpErrorParser().intercept(chain) } + exception.statusCode shouldBe 500 + exception.message shouldContain "message= " + exception.message shouldContain "body={\"errorCode\":\"APK_CERTIFICATE_MISMATCH\"}" + } + + @Test + fun `error in message and body`() { + val response = baseResponse.apply { + code(501) + message("Error") + body("{\"errorCode\":\"APK_CERTIFICATE_MISMATCH\"}".toResponseBody("application/json".toMediaTypeOrNull())) + }.build() + every { chain.proceed(any()) } returns response + val exception = shouldThrow<CwaWebException> { HttpErrorParser().intercept(chain) } + exception.statusCode shouldBe 501 + exception.message shouldContain "message=Error" + exception.message shouldContain "body={\"errorCode\":\"APK_CERTIFICATE_MISMATCH\"}" + } + + @Test + fun `oversized errors are handled`() { + val response = baseResponse.apply { + code(501) + message("") + body( + (1..5000).joinToString { "1" }.toResponseBody() + ) + }.build() + every { chain.proceed(any()) } returns response + val exception = shouldThrow<CwaWebException> { HttpErrorParser().intercept(chain) } + exception.statusCode shouldBe 501 + val start = exception.message!!.indexOf("body=") + val body = exception.message!!.substring(start + "body=".length) + body.length shouldBe 2049 + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProviderTest.kt index abdd453e5edc54d71868b7c23fd3dc9412215ea6..9d4aa696bf426187d313f2d46a45c2ae9a90c1f6 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tekhistory/DefaultTEKHistoryProviderTest.kt @@ -212,14 +212,14 @@ class DefaultTEKHistoryProviderTest : BaseTest() { } @Test - fun `ENFV1_8 pre authorized key release timeout after 20 seconds`() { + fun `ENFV1_8 pre authorized key release timeout after 5 seconds`() { coEvery { enfVersion.isAtLeast(ENFVersion.V1_8) } returns true every { client.requestPreAuthorizedTemporaryExposureKeyRelease() } returns MockGMSTask.timeout() verify(exactly = 0) { context.unregisterReceiver(any()) } runBlockingTest { val deferred = async { createInstance().getPreAuthorizedExposureKeys() } - advanceTimeBy(21_000) + advanceTimeBy(6_000) deferred.getCompletionExceptionOrNull() shouldBe instanceOf(TimeoutCancellationException::class) } @@ -228,7 +228,7 @@ class DefaultTEKHistoryProviderTest : BaseTest() { } @Test - fun `ENFV1_8 pre authorized key release broadcast receiver timeout after 20 seconds`() { + fun `ENFV1_8 pre authorized key release broadcast receiver timeout after 5 seconds`() { coEvery { enfVersion.isAtLeast(ENFVersion.V1_8) } returns true // We don't call onReceive @@ -241,7 +241,7 @@ class DefaultTEKHistoryProviderTest : BaseTest() { runBlockingTest { val deferred = async { createInstance().getPreAuthorizedExposureKeys() } - advanceTimeBy(21_000) + advanceTimeBy(6_000) deferred.getCompletionExceptionOrNull() shouldBe instanceOf(TimeoutCancellationException::class) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt index 55039c606fede0fde6b9eadd6f5a2bdc4de1f289..bffa97b6513e12370c53a3d0900235720cdcbd93 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TimeAndDateExtensionsTest.kt @@ -4,6 +4,8 @@ import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.util.TimeAndDateExtensions.ageInDays import de.rki.coronawarnapp.util.TimeAndDateExtensions.calculateDays import de.rki.coronawarnapp.util.TimeAndDateExtensions.getCurrentHourUTC +import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds +import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.mockkObject import io.mockk.unmockkAll @@ -13,24 +15,29 @@ import org.joda.time.DateTime import org.joda.time.DateTimeZone import org.joda.time.Instant import org.joda.time.LocalDate -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest import java.util.concurrent.TimeUnit /** * TimeAndDateExtensions test. */ -class TimeAndDateExtensionsTest { +class TimeAndDateExtensionsTest : BaseTest() { - @Before + @BeforeEach fun setUp() { MockKAnnotations.init(this) mockkObject(CoronaWarnApplication) } + @AfterEach + fun cleanUp() { + unmockkAll() + } + @Test fun getCurrentHourUTCTest() { val result = getCurrentHourUTC() @@ -48,28 +55,26 @@ class TimeAndDateExtensionsTest { @Test fun test_daysAgo() { - Assert.assertEquals( - 0, - LocalDate(2012, 3, 4).ageInDays( - LocalDate(2012, 3, 4) - ) - ) - Assert.assertEquals( - 2, - LocalDate(2013, 12, 31).ageInDays( - LocalDate(2014, 1, 2) - ) - ) - Assert.assertEquals( - 3, - LocalDate(2014, 5, 2).ageInDays( - LocalDate(2014, 5, 5) - ) - ) + LocalDate(2012, 3, 4).ageInDays( + LocalDate(2012, 3, 4) + ) shouldBe 0 + + LocalDate(2013, 12, 31).ageInDays( + LocalDate(2014, 1, 2) + ) shouldBe 2 + + LocalDate(2014, 5, 2).ageInDays( + LocalDate(2014, 5, 5) + ) shouldBe 3 } - @After - fun cleanUp() { - unmockkAll() + @Test + fun `instant seconds extension`() { + Instant.ofEpochMilli(-1).seconds shouldBe 0 + Instant.ofEpochMilli(0).seconds shouldBe 0 + Instant.ofEpochMilli(999).seconds shouldBe 0 + Instant.ofEpochMilli(1000).seconds shouldBe 1 + Instant.ofEpochMilli(1999).seconds shouldBe 1 + Instant.ofEpochMilli(2000).seconds shouldBe 2 } } diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt index 2e38ca455a29ba71aeae62febc1759557badd09c..e511f7bdb08327311abd5b460c9aa22a63deb008 100644 --- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt @@ -39,6 +39,7 @@ class DebugOptionsFragmentViewModelTest : BaseTest() { every { environmentSetup.submissionCdnUrl } returns "submissionUrl" every { environmentSetup.downloadCdnUrl } returns "downloadUrl" every { environmentSetup.verificationCdnUrl } returns "verificationUrl" + every { environmentSetup.dataDonationCdnUrl } returns "dataDonationUrl" every { environmentSetup.currentEnvironment = any() } answers { currentEnvironment = arg(0) diff --git a/fastlane/Screengrabfile b/fastlane/Screengrabfile index d048aff245234ffdc4efc8fb38a6dd7e9dcccf1f..d914a83a9879e904443a5b827e01c809330c07cf 100644 --- a/fastlane/Screengrabfile +++ b/fastlane/Screengrabfile @@ -1,4 +1,4 @@ -locales ['de-DE', 'en-US', 'tr-TR', 'bg-BG', 'pl-PL', 'ro-RO'] +locales ['de-DE', 'en-US'] use_adb_root true clear_previous_screenshots true app_package_name 'de.rki.coronawarnapp.test'