diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/RiskLevelAndKeyRetrievalBenchmark.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/RiskLevelAndKeyRetrievalBenchmark.kt deleted file mode 100644 index 6771827fa9cab7f2cc67ea55cdb6a0c537a6f3ec..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/RiskLevelAndKeyRetrievalBenchmark.kt +++ /dev/null @@ -1,163 +0,0 @@ -package de.rki.coronawarnapp.test - -import android.content.Context -import android.text.format.Formatter -import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask -import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.ApiSubmissionFinished -import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.ApiSubmissionStarted -import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.KeyFilesDownloadFinished -import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.KeyFilesDownloadStarted -import de.rki.coronawarnapp.risk.RiskLevelTask -import de.rki.coronawarnapp.task.Task -import de.rki.coronawarnapp.task.common.DefaultTaskRequest -import de.rki.coronawarnapp.task.submitAndListen -import de.rki.coronawarnapp.util.di.AppInjector -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import timber.log.Timber -import java.util.UUID - -class RiskLevelAndKeyRetrievalBenchmark( - private val context: Context, - private val countries: List<String> -) { - - /** - * the key cache instance used to store queried dates and hours - */ - private val keyCache = AppInjector.component.keyCacheRepository - - /** - * Calls the RetrieveDiagnosisKeysTransaction and RiskLevelTransaction and measures them. - * Results are displayed using a label - * @param callCount defines how often the transactions should be called (each call will be - * measured separately) - */ - suspend fun start( - callCount: Int, - onBenchmarkCompletedListener: OnBenchmarkCompletedListener - ) { - - var resultInfo = StringBuilder() - .append( - "MEASUREMENT Running for Countries:\n " + - "${countries.joinToString(", ")}\n\n" - ) - .append("Result: \n\n") - .append("#\t Combined \t Download \t Sub \t Risk \t File # \t F. size\n") - - onBenchmarkCompletedListener(resultInfo.toString()) - - repeat(callCount) { index -> - - keyCache.clear() - - var keyRetrievalError = "" - var keyFileCount: Int = -1 - var keyFileDownloadDuration: Long = -1 - var keyFilesSize: Long = -1 - var apiSubmissionDuration: Long = -1 - - measureDiagnosticKeyRetrieval( - label = "#$index", - countries = countries, - downloadFinished = { duration, keyCount, totalFileSize -> - keyFileCount = keyCount - keyFileDownloadDuration = duration - keyFilesSize = totalFileSize - }, apiSubmissionFinished = { duration -> - apiSubmissionDuration = duration - }) - - var calculationDuration: Long = -1 - var calculationError = "" - - measureKeyCalculation("#$index") { - if (it != null) calculationDuration = it - - // build result entry for current iteration with all gathered data - resultInfo.append( - "${index + 1}. \t ${calculationDuration + keyFileDownloadDuration + apiSubmissionDuration} ms \t " + - "$keyFileDownloadDuration ms " + "\t $apiSubmissionDuration ms" + - "\t $calculationDuration ms \t $keyFileCount \t " + - "${Formatter.formatFileSize(context, keyFilesSize)}\n" - ) - - if (keyRetrievalError.isNotEmpty()) { - resultInfo.append("Key Retrieval Error: $keyRetrievalError\n") - } - - if (calculationError.isNotEmpty()) { - resultInfo.append("Calculation Error: $calculationError\n") - } - - onBenchmarkCompletedListener(resultInfo.toString()) - } - } - } - - private suspend fun measureKeyCalculation(label: String, callback: (Long?) -> Unit) { - val uuid = UUID.randomUUID() - val t0 = System.currentTimeMillis() - AppInjector.component.taskController.tasks - .map { - it - .map { taskInfo -> taskInfo.taskState } - .filter { taskState -> taskState.request.id == uuid && taskState.isFinished } - } - .collect { - it.firstOrNull()?.also { state -> - Timber.v("MEASURE [Risk Level Calculation] $label finished") - callback.invoke( - if (state.error != null) - null - else - System.currentTimeMillis() - t0 - ) - } - } - Timber.v("MEASURE [Risk Level Calculation] $label started") - AppInjector.component.taskController.submit( - DefaultTaskRequest( - RiskLevelTask::class, - object : Task.Arguments {}, - uuid - ) - ) - } - - private suspend fun measureDiagnosticKeyRetrieval( - label: String, - countries: List<String>, - downloadFinished: (duration: Long, keyCount: Int, fileSize: Long) -> Unit, - apiSubmissionFinished: (duration: Long) -> Unit - ) { - var keyFileDownloadStart: Long = -1 - var apiSubmissionStarted: Long = -1 - - AppInjector.component.taskController.submitAndListen( - DefaultTaskRequest(DownloadDiagnosisKeysTask::class, DownloadDiagnosisKeysTask.Arguments(countries)) - ).collect { progress: Task.Progress -> - when (progress) { - is KeyFilesDownloadStarted -> { - Timber.v("MEASURE [Diagnostic Key Files] $label started") - keyFileDownloadStart = System.currentTimeMillis() - } - is KeyFilesDownloadFinished -> { - Timber.v("MEASURE [Diagnostic Key Files] $label finished") - val duration = System.currentTimeMillis() - keyFileDownloadStart - downloadFinished(duration, progress.keyCount, progress.fileSize) - } - is ApiSubmissionStarted -> { - apiSubmissionStarted = System.currentTimeMillis() - } - is ApiSubmissionFinished -> { - val duration = System.currentTimeMillis() - apiSubmissionStarted - apiSubmissionFinished(duration) - } - } - } - } -} - -typealias OnBenchmarkCompletedListener = (resultInfo: String) -> Unit diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt index f2ebfd26e4c413d2b3c80cb8cce605669310ee03..d277c9b961d19aa3ab2eba3c8eb262c0fe71fe51 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt @@ -18,7 +18,7 @@ class TestForApiFragmentViewModel @AssistedInject constructor( ) : CWAViewModel() { fun calculateRiskLevelClicked() { - taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) + taskController.submit(DefaultTaskRequest(RiskLevelTask::class, originTag = "TestForApiFragmentViewModel")) } val gmsState by smartLiveData { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index f521a36ac7ad1e978682fbd77bd24f0f63b8606a..8d3d50bef73f1b4ed81fd3287f76d39bdd889ee5 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -80,14 +80,23 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( fun retrieveDiagnosisKeys() { launch { taskController.submitBlocking( - DefaultTaskRequest(DownloadDiagnosisKeysTask::class, DownloadDiagnosisKeysTask.Arguments()) + DefaultTaskRequest( + DownloadDiagnosisKeysTask::class, + DownloadDiagnosisKeysTask.Arguments(), + originTag = "TestRiskLevelCalculationFragmentCWAViewModel.retrieveDiagnosisKeys()" + ) ) calculateRiskLevel() } } fun calculateRiskLevel() { - taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) + taskController.submit( + DefaultTaskRequest( + RiskLevelTask::class, + originTag = "TestRiskLevelCalculationFragmentCWAViewModel.calculateRiskLevel()" + ) + ) } fun resetRiskLevel() { diff --git a/Corona-Warn-App/src/main/assets/privacy_de.html b/Corona-Warn-App/src/main/assets/privacy_de.html index 8bea0c0161487c05071636b59b12f45a01908d3c..eb3a024ed1a780760397c4a5df80ed208e0e8b42 100644 --- a/Corona-Warn-App/src/main/assets/privacy_de.html +++ b/Corona-Warn-App/src/main/assets/privacy_de.html @@ -1,10 +1,6 @@ <p> Datenschutzerklärung </p> -<p> - Stand: 15.11.2020, Version 1.8 (frühere Versionen abrufbar unter: - https://www.coronawarn.app/de/privacy) -</p> <p> In dieser Datenschutzerklärung erfahren Sie, wie Ihre Daten verarbeitet werden und welche Datenschutzrechte Sie haben, wenn Sie die offizielle @@ -463,8 +459,8 @@ d. Informatorische Nutzung der App </h2> <p> - <u>Die täglichen Statistiken, die in der App erscheinen, erhält die App - automatisch über das Serversystem. Dabei fallen Zugriffsdaten an.</u> + Die täglichen Statistiken, die in der App erscheinen, erhält die App + automatisch über das Serversystem. Dabei fallen Zugriffsdaten an. In der App verlinkte Webseiten, z. B.: www.bundesregierung.de, werden im Standard-Browser (Android-Smartphones) oder in der App (iPhones) geöffnet und angezeigt. Welche Daten dabei verarbeitet werden, wird von den jeweiligen Anbietern der aufgerufenen Webseite festgelegt. @@ -795,3 +791,6 @@ Datenschutzbeauftragten, Nordufer 20, 13353 Berlin oder per E-Mail an: datenschutz@rki.de. </p> +<p> + Stand: 15.11.2020 +</p> diff --git a/Corona-Warn-App/src/main/assets/privacy_en.html b/Corona-Warn-App/src/main/assets/privacy_en.html index e6914d659fecbe82976e73750595d7c945c5e6ac..411f8c5accb2a0ed0ad356308fcdea5d8ac0d5ec 100644 --- a/Corona-Warn-App/src/main/assets/privacy_en.html +++ b/Corona-Warn-App/src/main/assets/privacy_en.html @@ -1,9 +1,7 @@ <p> Privacy notice </p> -<p> - Last amended: 15 November 2020. Valid from app version 1.8 (earlier versions available at: https://www.coronawarn.app/en/privacy) -</p> + <p> This privacy notice explains how your data is processed and what data protection rights you have when using the German Federal Government’s @@ -767,5 +765,5 @@ 13353 Berlin, or by emailing datenschutz@rki.de. </p> <p> - *** + Last amended: 15 November 2020 </p> diff --git a/Corona-Warn-App/src/main/assets/privacy_tr.html b/Corona-Warn-App/src/main/assets/privacy_tr.html index 55243e394d5031feef9b974a735dce3ed2b231d2..cebbc84a12a3faf296ba8a5961597b2705c86aa7 100644 --- a/Corona-Warn-App/src/main/assets/privacy_tr.html +++ b/Corona-Warn-App/src/main/assets/privacy_tr.html @@ -79,22 +79,25 @@ <p> Uygulamanın kullanımı isteğe bağlıdır; Uygulamayı yüklemeniz, Uygulamanın hangi işlevlerini kullanmanız ve verileri diğer kişilerle paylaşmanız - noktasında yalnızca siz karar verirsiniz. Uygulamanın veri aktarımını - gerektiren tüm işlevleri, öncesinde sizin açık bir şekilde onay vermenizi - ister. Onay vermezseniz veya sonradan bu onayı geri alırsanız, bu durum - sizin için bir sakınca doğurmaz. + noktasında yalnızca siz karar verirsiniz. Maruz kalma veya sağlık verilerinizin + aktarılmasını gerektiren Uygulamanın tüm işlevleri, sizden önceden açıkça rızanızı + vermenizi gerektirir. Rızanızı vermezseniz veya sonradan bu rızayı geri alırsanız, + bu durum sizin için bir sakınca doğurmaz. </p> <h1> 3. Verileriniz işlenmesinde hangi yasal dayanaklar söz konusudur? </h1> <p> - Verileriniz esas itibariyle yalnızca açık bir şekilde verdiğiniz onay + Verileriniz esas itibariyle açık bir şekilde verdiğiniz onay temelinde işlenir. Bu bağlamdaki yasal dayanak, GVKT (Genel Veri Koruma Tüzüğü) madde 6, fıkra 1, cümle 1, bent a ve sağlık verileri durumundaki yasal dayanak ise GVKT madde 9, fıkra 2, bent a’dır. Verdiğiniz onayı, istediğiniz zaman geri alabilirsiniz. Onayınız geri alma hakkı ile ilgili - ayrıntılı bilgileri madde 12’de bulabilirsiniz. + ayrıntılı bilgileri madde 12’de bulabilirsiniz. Günlük istatistiklerin alınması + için erişim verilerinin işlenmesi (bkz. Madde 6 d.), GVKT madde 6, fıkra 1, cümle 1, bent + e ile bağlantılı olarak BGA-NachfG (Federal Sağlık Kurumu Halef Kuruluşları + hakkında Kanun) madde 4, fıkra 4 uyarınca RKI tarafından toplumun bilgilendirilmesi kapsamında gerçekleşir. </p> <h1> 4. Uygulama kimleri hedefler? @@ -271,14 +274,13 @@ bilgileri temin etmektir. </p> <p> - Bu amaç için Uygulama, arka planda çalışarak sunucu sisteminden, Korona - testi pozitif çıkan ve sınır ötesi uyarı sistemine katılan ülkelerin resmi - Korona uygulamaları aracılığıyla gönüllü olarak bir uyarı tetikleyen - kullanıcılardan rastgele kimlik numaraları ve varsa semptomların - başlangıcına ilişkin bilgiyi içeren günlük bir liste çağırır (bundan böyle: <strong>pozitif - liste</strong>). Pozitif listedeki rastgele kimlik - numaraları, ek olarak ayrıca bir taşıma riski değeri ve tanı tipi hakkında - bilgi de içerir (bkz. Madde 6 c.). + Bu amaç doğrultusunda Uygulama, arka planda çalışarak sunucu sisteminden, + Korona testi pozitif çıkan ve sınır ötesi uyarı sistemine katılan ülkelerin + resmi Korona uygulamaları aracılığıyla bir uyarı tetikleyen kullanıcılardan + rastgele kimlik numaraları ve varsa semptomların başlangıcına ilişkin bilgiyi + içeren listeleri günde birçok kez çağırır (bundan böyle: <strong>pozitif liste</strong>). + Pozitif listedeki rastgele kimlik numaraları, ek olarak ayrıca bir taşıma riski + değeri ve tanı tipi hakkında bilgi de içerir (bkz. Madde 6 c.). </p> <p> Uygulama bu rastgele kimlik numaralarını, COVID-19 bildirim sistemine @@ -446,13 +448,11 @@ d. Uygulamanın bilgilenme amaçlı kullanımı </h2> <p> - Uygulamayı yalnızca bilgi edinme amaçlı kullanıyorsanız, yani yukarıda - belirtilen işlevlerden hiçbirini kullanmıyorsanız, veri işleme yalnızca - kendi akıllı telefonunuzda gerçekleşir ve RKI tarafından hiçbir kişisel - veri işlenmez. Uygulamada, örneğin www.bundesregierung.de gibi - bağlantılı web siteleri açılır ve standart tarayıcıda (Android akıllı - telefonlar) veya Uygulamada (iPhone’lar) görüntülenir. Hangi verilerin - işleneceği, erişilen web sitesinin ilgili sağlayıcısı tarafından + Uygulama otomatik olarak sunucu sistemi üzerinden günlük istatistikleri alır + ve bunlar Uygulamada görüntülenir. Bu sırada erişim verileri oluşur. Uygulamada, + örneğin www.bundesregierung.de gibi bağlantılı web siteleri açılır ve standart + tarayıcıda (Android akıllı telefonlar) veya Uygulamada (iPhone’lar) görüntülenir. + Hangi verilerin işleneceği, erişilen web sitesinin ilgili sağlayıcısı tarafından belirlenmektedir. </p> <h1> @@ -772,5 +772,4 @@ veya e-posta yoluyla: datenschutz@rki.de. </p> <p> - Yayım tarihi: 17.10.2020 -</p> + Baskı: 15.11.2020 diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt index cf17b9f3e954cf2840ff98d16bb2bf45c21bc943..588451c9dec8b5393a6c55e1aaf9fbb00bc2317b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigChangeDetector.kt @@ -47,7 +47,7 @@ class ConfigChangeDetector @Inject constructor( if (newIdentifier != oldConfigId) { Timber.i("New config id ($newIdentifier) differs from last one ($oldConfigId), resetting.") RiskLevelRepositoryDeferrer.resetRiskLevel() - taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) + taskController.submit(DefaultTaskRequest(RiskLevelTask::class, originTag = "ConfigChangeDetector")) } else { Timber.v("Config identifier ($oldConfigId) didn't change, NOOP.") } 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 90b013bf159ff3d4d533b2262f71ea493805db7a..8eb886fcd046455684e89b4394190e15640cc26f 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 @@ -63,9 +63,6 @@ class DownloadDiagnosisKeysTask @Inject constructor( val currentDate = Date(timeStamper.nowUTC.millis) Timber.tag(TAG).d("Using $currentDate as current date in task.") - /**************************************************** - * DOWNLOAD KEYS - ****************************************************/ throwIfCancelled() // RETRIEVE RISK SCORE PARAMETERS @@ -229,8 +226,7 @@ class DownloadDiagnosisKeysTask @Inject constructor( } class Arguments( - val requestedCountries: List<String>? = null, - val withConstraints: Boolean = false + val requestedCountries: List<String>? = null ) : Task.Arguments data class Config( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt index 62b1aa91462f3b54232b55f04585538497c77306..5f177f2920d7e8bc8bf9738df7e19189f9aa6f0b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/reporting/ExceptionReporter.kt @@ -17,8 +17,9 @@ import java.io.PrintWriter import java.io.StringWriter import java.util.concurrent.CancellationException -fun Throwable.report(exceptionCategory: ExceptionCategory) = +fun Throwable.report(exceptionCategory: ExceptionCategory) { this.report(exceptionCategory, null, null) +} fun Throwable.report( exceptionCategory: ExceptionCategory, @@ -32,6 +33,9 @@ fun Throwable.report( if (this is CancellationException) return reportProblem(tag = prefix, info = suffix) + + if (CWADebug.isAUnitTest) return + val context = CoronaWarnApplication.getAppContext() val intent = Intent(ReportingConstants.ERROR_REPORT_LOCAL_BROADCAST_CHANNEL) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt index b2a7d5f6a3962eadcd7d8893f86a29fcba05edfe..c965b791d0c50cb0fff69412843c6709f4277c55 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt @@ -10,6 +10,7 @@ import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProv import de.rki.coronawarnapp.nearby.modules.exposurewindow.ExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.locationless.ScanningSupport import de.rki.coronawarnapp.nearby.modules.tracing.TracingStatus +import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import org.joda.time.Instant @@ -26,8 +27,9 @@ class ENFClient @Inject constructor( private val tracingStatus: TracingStatus, private val scanningSupport: ScanningSupport, private val exposureWindowProvider: ExposureWindowProvider, - private val exposureDetectionTracker: ExposureDetectionTracker -) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider { + private val exposureDetectionTracker: ExposureDetectionTracker, + private val enfVersion: ENFVersion +) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider, ENFVersion by enfVersion { // TODO Remove this once we no longer need direct access to the ENF Client, // i.e. in **[InternalExposureNotificationClient]** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt index ec99ff4e77f53c5a7f777519cd01ad799f56d61c..1d4220ee0df1f0640c4d5587ddd56bfdbd669e77 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt @@ -15,6 +15,8 @@ import de.rki.coronawarnapp.nearby.modules.locationless.DefaultScanningSupport import de.rki.coronawarnapp.nearby.modules.locationless.ScanningSupport import de.rki.coronawarnapp.nearby.modules.tracing.DefaultTracingStatus import de.rki.coronawarnapp.nearby.modules.tracing.TracingStatus +import de.rki.coronawarnapp.nearby.modules.version.DefaultENFVersion +import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import de.rki.coronawarnapp.util.di.AppContext import javax.inject.Singleton @@ -50,4 +52,8 @@ class ENFModule { @Provides fun calculationTracker(exposureDetectionTracker: DefaultExposureDetectionTracker): ExposureDetectionTracker = exposureDetectionTracker + + @Singleton + @Provides + fun enfClientVersion(enfVersion: DefaultENFVersion): ENFVersion = enfVersion } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt index d5fee52d0c01b1565510170d1ba9fed1bc2ca95a..e1a84422b7ca39f9c9149ffbb77828ee77f6df33 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt @@ -31,7 +31,9 @@ class ExposureStateUpdateWorker @AssistedInject constructor( Timber.v("exposure summary state updated: $it") } - taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) + taskController.submit( + DefaultTaskRequest(RiskLevelTask::class, originTag = "ExposureStateUpdateWorker") + ) Timber.v("risk level calculation triggered") } catch (e: ApiException) { e.report(ExceptionCategory.EXPOSURENOTIFICATION) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt index 2284f36d64cc0dd7ba523a72a403bafd99340938..3ded662b655f7aa9dc7b87cdada9cbd6425031a6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt @@ -88,15 +88,6 @@ object InternalExposureNotificationClient { } } - suspend fun getVersion(): Long = suspendCoroutine { cont -> - exposureNotificationClient.version - .addOnSuccessListener { - cont.resume(it) - }.addOnFailureListener { - cont.resumeWithException(it) - } - } - /** * Retrieves key history from the data store on the device for uploading to your * internet-accessible server. Calling this method prompts Google Play services to display diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt index ebf8523d7de03944a06598d2005ba21d7ed43e6a..c612cf3f0b2ca93a726a36b919d23aa89a485ebd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider import com.google.android.gms.common.api.ApiException import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import de.rki.coronawarnapp.exception.reporting.ReportingConstants -import de.rki.coronawarnapp.util.GoogleAPIVersion +import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import timber.log.Timber import java.io.File import javax.inject.Inject @@ -14,7 +14,7 @@ import kotlin.coroutines.suspendCoroutine @Singleton class DefaultDiagnosisKeyProvider @Inject constructor( - private val googleAPIVersion: GoogleAPIVersion, + private val enfVersion: ENFVersion, private val submissionQuota: SubmissionQuota, private val enfClient: ExposureNotificationClient ) : DiagnosisKeyProvider { @@ -25,7 +25,7 @@ class DefaultDiagnosisKeyProvider @Inject constructor( return true } - if (!googleAPIVersion.isAtLeast(GoogleAPIVersion.V15)) { + if (!enfVersion.isAtLeast(ENFVersion.V15)) { // Actually this shouldn't happen Timber.d("No key files submitted because client uses an old unsupported version") return false diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/DefaultENFVersion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/DefaultENFVersion.kt new file mode 100644 index 0000000000000000000000000000000000000000..f1983ba68c6f080becbb1d746b66ea137e1d60f7 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/DefaultENFVersion.kt @@ -0,0 +1,53 @@ +package de.rki.coronawarnapp.nearby.modules.version + +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine +import kotlin.math.abs + +@Singleton +class DefaultENFVersion @Inject constructor( + private val client: ExposureNotificationClient +) : ENFVersion { + + override suspend fun getENFClientVersion(): Long? = try { + internalGetENFClientVersion() + } catch (e: Exception) { + Timber.w(e, "Failed to get ENFClient version.") + null + } + + override suspend fun isAtLeast(compareVersion: Long): Boolean { + if (!compareVersion.isCorrectVersionLength) throw IllegalArgumentException("given version has incorrect length") + + return try { + internalGetENFClientVersion() >= compareVersion + } catch (apiException: ApiException) { + if (apiException.statusCode != CommonStatusCodes.API_NOT_CONNECTED) { + throw apiException + } else { + return false + } + } + } + + private suspend fun internalGetENFClientVersion(): Long = suspendCoroutine { cont -> + client.version + .addOnSuccessListener { cont.resume(it) } + .addOnFailureListener { cont.resumeWithException(it) } + } + + // check if a raw long has the correct length to be considered an API version + private val Long.isCorrectVersionLength + get(): Boolean = abs(this).toString().length == GOOGLE_API_VERSION_FIELD_LENGTH + + companion object { + private const val GOOGLE_API_VERSION_FIELD_LENGTH = 8 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt new file mode 100644 index 0000000000000000000000000000000000000000..e0f3fec558f0a5771f0e13d2852d17f0935be3c1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt @@ -0,0 +1,16 @@ +package de.rki.coronawarnapp.nearby.modules.version + +interface ENFVersion { + suspend fun getENFClientVersion(): Long? + + /** + * Indicates if the client runs above a certain version + * + * @return isAboveVersion, if connected to an old unsupported version, return false + */ + suspend fun isAtLeast(compareVersion: Long): Boolean + + companion object { + const val V16 = 16000000L + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt index d58137f5eeceee48d15a7151ee2db98bc5f524cc..5fb17c90d2172fad81d2f77f21acd51be979d670 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt @@ -15,7 +15,7 @@ import de.rki.coronawarnapp.exception.UnknownBroadcastException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.ExposureStateUpdateWorker import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker -import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection +import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection.Result import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import kotlinx.coroutines.CoroutineScope @@ -41,26 +41,35 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { @Inject @AppScope lateinit var scope: CoroutineScope @Inject lateinit var dispatcherProvider: DispatcherProvider @Inject lateinit var exposureDetectionTracker: ExposureDetectionTracker - lateinit var context: Context + @Inject lateinit var workManager: WorkManager override fun onReceive(context: Context, intent: Intent) { Timber.tag(TAG).d("onReceive(context=%s, intent=%s)", context, intent) AndroidInjection.inject(this, context) - this.context = context val action = intent.action Timber.tag(TAG).v("Looking up action: %s", action) val async = goAsync() - scope.launch(context = dispatcherProvider.Default) { + + scope.launch(context = scope.coroutineContext) { try { - val token = intent.getStringExtra(EXTRA_TOKEN) - when (action) { - ACTION_EXPOSURE_STATE_UPDATED -> processStateUpdates(token) - ACTION_EXPOSURE_NOT_FOUND -> processNotFound(token) - else -> throw UnknownBroadcastException(action) - } + val token = intent.requireToken() + + trackDetection(token, action) + + val data = Data + .Builder() + .putString(EXTRA_TOKEN, token) + .build() + + OneTimeWorkRequest + .Builder(ExposureStateUpdateWorker::class.java) + .setInputData(data) + .build() + .let { workManager.enqueue(it) } } catch (e: Exception) { + Timber.e(e, "Failed to process intent.") e.report(INTERNAL) } finally { Timber.tag(TAG).i("Finished processing broadcast.") @@ -69,36 +78,16 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { } } - private fun processStateUpdates(token: String?) { - Timber.tag(TAG).i("Processing ACTION_EXPOSURE_STATE_UPDATED") - - val workManager = WorkManager.getInstance(context) - - // TODO("Remove token from ExposureStateUpdateWorker") - val data = Data - .Builder() - .putString(EXTRA_TOKEN, token) - .build() - - OneTimeWorkRequest - .Builder(ExposureStateUpdateWorker::class.java) - .setInputData(data) - .build() - .let { workManager.enqueue(it) } - - exposureDetectionTracker.finishExposureDetection( - token, - TrackedExposureDetection.Result.UPDATED_STATE - ) - } - - private fun processNotFound(token: String?) { - Timber.tag(TAG).i("Processing ACTION_EXPOSURE_NOT_FOUND") - - exposureDetectionTracker.finishExposureDetection( - token, - TrackedExposureDetection.Result.NO_MATCHES - ) + private fun trackDetection(token: String, action: String?) { + when (action) { + ACTION_EXPOSURE_STATE_UPDATED -> { + exposureDetectionTracker.finishExposureDetection(token, Result.UPDATED_STATE) + } + ACTION_EXPOSURE_NOT_FOUND -> { + exposureDetectionTracker.finishExposureDetection(token, Result.NO_MATCHES) + } + else -> throw UnknownBroadcastException(action) + } } companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt index 9f912cf2579fd0d1ae95fb0491bbf1e440370498..75be4b8cf6503bb73bdf1a9dbda5c92e1fad2206 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt @@ -69,6 +69,7 @@ class TracingRepository @Inject constructor( private fun List<TaskInfo>.isRiskLevelTaskRunning() = any { it.taskState.isActive && it.taskState.request.type == RiskLevelTask::class } + private fun List<TaskInfo>.isDownloadDiagnosisKeysTaskRunning() = any { it.taskState.isActive && it.taskState.request.type == DownloadDiagnosisKeysTask::class } @@ -85,10 +86,15 @@ class TracingRepository @Inject constructor( taskController.submitBlocking( DefaultTaskRequest( DownloadDiagnosisKeysTask::class, - DownloadDiagnosisKeysTask.Arguments() + DownloadDiagnosisKeysTask.Arguments(), + originTag = "TracingRepository.refreshDiagnosisKeys()" + ) + ) + taskController.submit( + DefaultTaskRequest( + RiskLevelTask::class, originTag = "TracingRepository.refreshDiagnosisKeys()" ) ) - taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) TimerHelper.startManualKeyRetrievalTimer() } } @@ -133,12 +139,15 @@ class TracingRepository @Inject constructor( taskController.submitBlocking( DefaultTaskRequest( DownloadDiagnosisKeysTask::class, - DownloadDiagnosisKeysTask.Arguments() + DownloadDiagnosisKeysTask.Arguments(), + originTag = "TracingRepository.refreshRisklevel()" ) ) TimerHelper.checkManualKeyRetrievalTimer() - taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) + taskController.submit( + DefaultTaskRequest(RiskLevelTask::class, originTag = "TracingRepository.refreshRiskLevel()") + ) } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt index df111a364ba660bc00759312bd44578ea3c371b0..e34681d7755b32565a6865a90c69a688e2184391 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/task/common/DefaultTaskRequest.kt @@ -8,7 +8,8 @@ import kotlin.reflect.KClass data class DefaultTaskRequest( override val type: KClass<out Task<Task.Progress, Task.Result>>, override val arguments: Task.Arguments = object : Task.Arguments {}, - override val id: UUID = UUID.randomUUID() + override val id: UUID = UUID.randomUUID(), + val originTag: String? = null ) : TaskRequest { fun toNewTask(): DefaultTaskRequest = copy(id = UUID.randomUUID()) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt index e6b1683ce430f4073e569959a96785f79ae6eec2..caac006b32562d2d7ccffa0881ad81d6eb3eec9a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt @@ -1,27 +1,58 @@ package de.rki.coronawarnapp.ui.information +import android.content.Intent import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentInformationBinding import de.rki.coronawarnapp.ui.doNavigate import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.util.ExternalActionHelper +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.setGone import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import timber.log.Timber +import javax.inject.Inject /** * Basic Fragment which links to static and web content. */ -class InformationFragment : Fragment(R.layout.fragment_information) { +class InformationFragment : Fragment(R.layout.fragment_information), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: InformationFragmentViewModel by cwaViewModels { viewModelFactory } private val binding: FragmentInformationBinding by viewBindingLazy() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + vm.currentENFVersion.observe2(this) { + binding.informationEnfVersion.apply { + setGone(it == null) + text = it + } + } + vm.appVersion.observe2(this) { + binding.informationVersion.text = it + } + + binding.informationEnfVersion.setOnClickListener { + try { + startActivity(Intent(ExposureNotificationClient.ACTION_EXPOSURE_NOTIFICATION_SETTINGS)) + } catch (e: Exception) { + Timber.e(e, "Can't open ENF settings.") + } + } + setButtonOnClickListener() setAccessibilityDelegate() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..7b7473491c6c067a89555f75535491ffbdd81b27 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentModule.kt @@ -0,0 +1,22 @@ +package de.rki.coronawarnapp.ui.information + +import dagger.Binds +import dagger.Module +import dagger.android.ContributesAndroidInjector +import dagger.multibindings.IntoMap +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey + +@Module +abstract class InformationFragmentModule { + @Binds + @IntoMap + @CWAViewModelKey(InformationFragmentViewModel::class) + abstract fun informationFragmentViewModel( + factory: InformationFragmentViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> + + @ContributesAndroidInjector + abstract fun informationFragment(): InformationFragment +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..27f53ae57b79ddf2ea0c09a62fed833f2125a3b4 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragmentViewModel.kt @@ -0,0 +1,34 @@ +package de.rki.coronawarnapp.ui.information + +import android.content.Context +import androidx.lifecycle.asLiveData +import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.BuildConfig +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.nearby.ENFClient +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf + +class InformationFragmentViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider, + enfClient: ENFClient, + @AppContext private val context: Context +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + val currentENFVersion = flow { + val enfVersion = enfClient.getENFClientVersion() + ?.let { "ENF ${context.getString(R.string.information_version).format(it)}" } + emit(enfVersion) + }.asLiveData(context = dispatcherProvider.Default) + + val appVersion = flowOf( + context.getString(R.string.information_version).format(BuildConfig.VERSION_NAME) + ).asLiveData(context = dispatcherProvider.Default) + + @AssistedInject.Factory + interface Factory : SimpleCWAViewModelFactory<InformationFragmentViewModel> +} 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 efb78a5cbf81f040fe8b6cac1c78e64785a86303..fd0e01406f0c89425fac3b81ffb0a5bc0bbcbc8e 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 @@ -10,13 +10,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.ViewModelProviders -import androidx.lifecycle.lifecycleScope import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import de.rki.coronawarnapp.R import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler -import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.ui.base.startActivitySafely import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel @@ -30,7 +28,6 @@ import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import de.rki.coronawarnapp.worker.BackgroundWorkScheduler -import kotlinx.coroutines.launch import javax.inject.Inject /** @@ -105,16 +102,10 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { settingsViewModel.updateBackgroundJobEnabled(ConnectivityHelper.autoModeEnabled(this)) scheduleWork() checkShouldDisplayBackgroundWarning() - doBackgroundNoiseCheck() + vm.doBackgroundNoiseCheck() deadmanScheduler.schedulePeriodic() } - private fun doBackgroundNoiseCheck() { - lifecycleScope.launch { - BackgroundNoise.getInstance().foregroundScheduleCheck() - } - } - private fun showEnergyOptimizedEnabledForBackground() { val dialog = DialogHelper.DialogInstance( this, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt index 7a596bfc0666c156fe94d05749be586282b531e7..998c4e89dd36104eba97220c65164608a4961f36 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt @@ -4,6 +4,7 @@ import dagger.Binds import dagger.Module import dagger.android.ContributesAndroidInjector import dagger.multibindings.IntoMap +import de.rki.coronawarnapp.ui.information.InformationFragmentModule import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragment import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragmentModule import de.rki.coronawarnapp.ui.main.home.HomeFragmentModule @@ -23,7 +24,8 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey HomeFragmentModule::class, RiskDetailsFragmentModule::class, SettingFragmentsModule::class, - SubmissionFragmentModule::class + SubmissionFragmentModule::class, + InformationFragmentModule::class ] ) abstract class MainActivityModule { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt index 8345f33c5aafac4b55a784fd5090d4ebb8a6779d..02dbea80d6c3e99fe272b5c8980600a2a8f35a70 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.main import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.environment.EnvironmentSetup +import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -28,6 +29,12 @@ class MainActivityViewModel @AssistedInject constructor( } } + fun doBackgroundNoiseCheck() { + launch { + BackgroundNoise.getInstance().foregroundScheduleCheck() + } + } + @AssistedInject.Factory interface Factory : SimpleCWAViewModelFactory<MainActivityViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt index aba0e9850f7be7eb0f6945755d7259da9ff4d1b9..af99a6380e0446e1338420c9a1e973b67b68eb96 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt @@ -5,7 +5,6 @@ import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentHomeBinding import de.rki.coronawarnapp.util.DialogHelper @@ -18,7 +17,6 @@ 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 kotlinx.coroutines.launch import javax.inject.Inject /** @@ -100,7 +98,7 @@ class HomeFragment : Fragment(R.layout.fragment_home), AutoInject { } } - lifecycleScope.launch { vm.observeTestResultToSchedulePositiveTestResultReminder() } + vm.observeTestResultToSchedulePositiveTestResultReminder() } override fun onResume() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index 36fb7f775e1137820d510815ac191c27c78cd685..99d389d7d608dc1a9491aaa1e5c444c344da490a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -76,10 +76,11 @@ class HomeFragmentViewModel @AssistedInject constructor( private var isLoweredRiskLevelDialogBeingShown = false - suspend fun observeTestResultToSchedulePositiveTestResultReminder() = + fun observeTestResultToSchedulePositiveTestResultReminder() = launch { submissionCardsStateProvider.state .first { it.isPositiveSubmissionCardVisible() } .also { testResultNotificationService.schedulePositiveTestResultReminder() } + } // TODO only lazy to keep tests going which would break because of LocalData access val showLoweredRiskLevelDialog: LiveData<Boolean> by lazy { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt index 0de5f0efad201d2a0f065cd443291523ca65275c..86d6b9bc9c8f73aa14fee8ef1380bdde24e1dad3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt @@ -6,7 +6,6 @@ import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultBinding import de.rki.coronawarnapp.exception.http.CwaClientError @@ -22,7 +21,6 @@ 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 kotlinx.coroutines.launch import javax.inject.Inject class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_result), @@ -136,7 +134,7 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_ } } - lifecycleScope.launch { viewModel.observeTestResultToSchedulePositiveTestResultReminder() } + viewModel.observeTestResultToSchedulePositiveTestResultReminder() } override fun onResume() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt index 370ecbf292818116417636008439820aeb2a02e7..ff12359c1b6403092b4b8ac318ca6f2e2412e0cf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt @@ -57,7 +57,7 @@ class SubmissionTestResultViewModel @AssistedInject constructor( ).let { emit(it) } }.asLiveData(context = dispatcherProvider.Default) - suspend fun observeTestResultToSchedulePositiveTestResultReminder() = + fun observeTestResultToSchedulePositiveTestResultReminder() = launch { SubmissionRepository.deviceUIStateFlow .first { request -> request.withSuccess(false) { @@ -65,6 +65,7 @@ class SubmissionTestResultViewModel @AssistedInject constructor( } } .also { testResultNotificationService.schedulePositiveTestResultReminder() } + } fun onBackPressed() { routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt index 56ef3c2d2747d7f329aa3a1eb04e769ed12f9ece..489f83c59a0bbe31475fd18214d8637bd4cb2752 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt @@ -5,17 +5,14 @@ import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSettingsTracingBinding -import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.ui.doNavigate import de.rki.coronawarnapp.ui.main.MainActivity +import de.rki.coronawarnapp.ui.tracing.settings.SettingsTracingFragmentViewModel.Event import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.ExternalActionHelper @@ -25,7 +22,6 @@ import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import de.rki.coronawarnapp.worker.BackgroundWorkScheduler -import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -47,7 +43,7 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), private val binding: FragmentSettingsTracingBinding by viewBindingLazy() - private lateinit var internalExposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper + private lateinit var exposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -63,11 +59,21 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), TracingSettingsState.BluetoothDisabled, TracingSettingsState.LocationDisabled -> setOnClickListener(null) TracingSettingsState.TracingInActive, - TracingSettingsState.TracingActive -> setOnClickListener { startStopTracing() } + TracingSettingsState.TracingActive -> setOnClickListener { vm.startStopTracing() } } } } + exposureNotificationPermissionHelper = InternalExposureNotificationPermissionHelper(this, this) + + vm.events.observe2(this) { + when (it) { + Event.RequestPermissions -> exposureNotificationPermissionHelper.requestPermissionToStartTracing() + Event.ShowConsentDialog -> showConsentDialog() + Event.ManualCheckingDialog -> showManualCheckingRequiredDialog() + } + } + setButtonOnClickListener() } @@ -77,7 +83,7 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - internalExposureNotificationPermissionHelper.onResolutionComplete( + exposureNotificationPermissionHelper.onResolutionComplete( requestCode, resultCode ) @@ -98,13 +104,10 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), val location = binding.settingsTracingStatusLocation.tracingStatusCardButton val interoperability = binding.settingsInteroperabilityRow.settingsPlainRow - internalExposureNotificationPermissionHelper = - InternalExposureNotificationPermissionHelper(this, this) switch.setOnCheckedChangeListener { view, _ -> - // Make sure that listener is called by user interaction if (view.isPressed) { - startStopTracing() + vm.startStopTracing() // Focus on the body text after to announce the tracing status for accessibility reasons binding.settingsTracingSwitchRow.settingsSwitchRowHeaderBody .sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) @@ -131,39 +134,6 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), ) } - private fun startStopTracing() { - // if tracing is enabled when listener is activated it should be disabled - lifecycleScope.launch { - try { - if (InternalExposureNotificationClient.asyncIsEnabled()) { - InternalExposureNotificationClient.asyncStop() - BackgroundWorkScheduler.stopWorkScheduler() - } else { - // tracing was already activated - if (LocalData.initialTracingActivationTimestamp() != null) { - internalExposureNotificationPermissionHelper.requestPermissionToStartTracing() - } else { - // tracing was never activated - // ask for consent via dialog for initial tracing activation when tracing was not - // activated during onboarding - showConsentDialog() - // check if background processing is switched off, if it is, show the manual calculation dialog explanation before turning on. - val activity = requireActivity() as MainActivity - if (!activity.backgroundPrioritization.isBackgroundActivityPrioritized) { - showManualCheckingRequiredDialog() - } - } - } - } catch (exception: Exception) { - exception.report( - ExceptionCategory.EXPOSURENOTIFICATION, - TAG, - null - ) - } - } - } - private fun showManualCheckingRequiredDialog() { val dialog = DialogHelper.DialogInstance( requireActivity(), @@ -186,7 +156,7 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), R.string.onboarding_button_enable, R.string.onboarding_button_cancel, true, { - internalExposureNotificationPermissionHelper.requestPermissionToStartTracing() + exposureNotificationPermissionHelper.requestPermissionToStartTracing() }, { // Declined }) @@ -194,6 +164,6 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), } companion object { - private val TAG: String? = SettingsTracingFragment::class.simpleName + internal val TAG: String? = SettingsTracingFragment::class.simpleName } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt index c9a5462932dcd507732537d002b785f6a7b659fe..6b2e57dfa22ec73593740d62705248d4ce98e6fe 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt @@ -4,13 +4,20 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.exception.ExceptionCategory +import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.ui.tracing.details.TracingDetailsState import de.rki.coronawarnapp.ui.tracing.details.TracingDetailsStateProvider +import de.rki.coronawarnapp.util.BackgroundPrioritization import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.flow.shareLatest +import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -20,7 +27,8 @@ import timber.log.Timber class SettingsTracingFragmentViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, tracingDetailsStateProvider: TracingDetailsStateProvider, - tracingStatus: GeneralTracingStatus + tracingStatus: GeneralTracingStatus, + private val backgroundPrioritization: BackgroundPrioritization ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val tracingDetailsState: LiveData<TracingDetailsState> = tracingDetailsStateProvider.state @@ -36,6 +44,47 @@ class SettingsTracingFragmentViewModel @AssistedInject constructor( ) .asLiveData(dispatcherProvider.Main) + val events = SingleLiveEvent<Event>() + + fun startStopTracing() { + // if tracing is enabled when listener is activated it should be disabled + launch { + try { + if (InternalExposureNotificationClient.asyncIsEnabled()) { + InternalExposureNotificationClient.asyncStop() + BackgroundWorkScheduler.stopWorkScheduler() + } else { + // tracing was already activated + if (LocalData.initialTracingActivationTimestamp() != null) { + events.postValue(Event.RequestPermissions) + } else { + // tracing was never activated + // ask for consent via dialog for initial tracing activation when tracing was not + // activated during onboarding + events.postValue(Event.ShowConsentDialog) + // check if background processing is switched off, + // if it is, show the manual calculation dialog explanation before turning on. + if (!backgroundPrioritization.isBackgroundActivityPrioritized) { + events.postValue(Event.ManualCheckingDialog) + } + } + } + } catch (exception: Exception) { + exception.report( + ExceptionCategory.EXPOSURENOTIFICATION, + SettingsTracingFragment.TAG, + null + ) + } + } + } + + sealed class Event { + object RequestPermissions : Event() + object ShowConsentDialog : Event() + object ManualCheckingDialog : Event() + } + @AssistedInject.Factory interface Factory : SimpleCWAViewModelFactory<SettingsTracingFragmentViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DefaultBackgroundPrioritization.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DefaultBackgroundPrioritization.kt index 7cfd0d07b2c5fbbc01e76b1002709c5bc06b92ca..eab3573bc450f2792c8ed8fa085f16cdd13f0ce6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DefaultBackgroundPrioritization.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DefaultBackgroundPrioritization.kt @@ -1,8 +1,10 @@ package de.rki.coronawarnapp.util +import dagger.Reusable import de.rki.coronawarnapp.util.device.PowerManagement import javax.inject.Inject +@Reusable class DefaultBackgroundPrioritization @Inject constructor( private val powerManagement: PowerManagement ) : BackgroundPrioritization { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt deleted file mode 100644 index 8119586bcbab639d1634a4a2b92e9544d8aaa02f..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt +++ /dev/null @@ -1,41 +0,0 @@ -package de.rki.coronawarnapp.util - -import com.google.android.gms.common.api.ApiException -import com.google.android.gms.common.api.CommonStatusCodes -import dagger.Reusable -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient -import javax.inject.Inject -import kotlin.math.abs - -@Reusable -class GoogleAPIVersion @Inject constructor() { - /** - * Indicates if the client runs above a certain version - * - * @return isAboveVersion, if connected to an old unsupported version, return false - */ - suspend fun isAtLeast(compareVersion: Long): Boolean { - if (!compareVersion.isCorrectVersionLength) { - throw IllegalArgumentException("given version has incorrect length") - } - return try { - val currentVersion = InternalExposureNotificationClient.getVersion() - currentVersion >= compareVersion - } catch (apiException: ApiException) { - if (apiException.statusCode != CommonStatusCodes.API_NOT_CONNECTED) { - throw apiException - } - return false - } - } - - // check if a raw long has the correct length to be considered an API version - private val Long.isCorrectVersionLength - get(): Boolean = abs(this).toString().length == GOOGLE_API_VERSION_FIELD_LENGTH - - companion object { - private const val GOOGLE_API_VERSION_FIELD_LENGTH = 8 - const val V16 = 16000000L - const val V15 = 15000000L - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt index 00d62d17067f43ef080520518ba36bf2d4ee5d8f..cd3eccf6fce55cb8dc4bcdd7f1df52c544f3a8f3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt @@ -51,7 +51,8 @@ class WatchdogService @Inject constructor( val state = taskController.submitBlocking( DefaultTaskRequest( DownloadDiagnosisKeysTask::class, - DownloadDiagnosisKeysTask.Arguments(null, true) + DownloadDiagnosisKeysTask.Arguments(), + originTag = "WatchdogService" ) ) if (state.isFailed) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationHelper.kt deleted file mode 100644 index 913424b0671ddfef1a1a9a00e79304ab5b6486f7..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationHelper.kt +++ /dev/null @@ -1,13 +0,0 @@ -@file:JvmName("FormatterInformationHelper") - -package de.rki.coronawarnapp.util.formatter - -import de.rki.coronawarnapp.BuildConfig -import de.rki.coronawarnapp.CoronaWarnApplication -import de.rki.coronawarnapp.R - -fun formatVersion(): String { - val appContext = CoronaWarnApplication.getAppContext() - val versionName: String = BuildConfig.VERSION_NAME - return appContext.getString(R.string.information_version).format(versionName) -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt index ef5f51077634e914630f793378753b17336e4432..421d8d872b385bdb76c2b786fc30456053bab6de 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalOneTimeWorker.kt @@ -40,12 +40,11 @@ class DiagnosisKeyRetrievalOneTimeWorker @AssistedInject constructor( taskController.submitBlocking( DefaultTaskRequest( DownloadDiagnosisKeysTask::class, - DownloadDiagnosisKeysTask.Arguments(null, true) + DownloadDiagnosisKeysTask.Arguments(), + originTag = "DiagnosisKeyRetrievalOneTimeWorker" ) ).error?.also { error: Throwable -> - Timber.w( - error, "$id: Error during startWithConstraints()." - ) + Timber.w(error, "$id: Error when submitting DownloadDiagnosisKeysTask.") if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) { Timber.w(error, "$id: Retry attempts exceeded.") diff --git a/Corona-Warn-App/src/main/res/layout/fragment_information.xml b/Corona-Warn-App/src/main/res/layout/fragment_information.xml index d51c313a9172fd2dba61cbbcdfa0a807b3e55a57..b18582525731b852608527024c79dc44b8c39503 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_information.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_information.xml @@ -1,12 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - - <data> - - <import type="de.rki.coronawarnapp.util.formatter.FormatterInformationHelper" /> - - </data> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/information_container" @@ -117,11 +112,27 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_small" android:focusable="true" - android:text="@{FormatterInformationHelper.formatVersion()}" + tools:text="v1.8.0-RC1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline_body" app:layout_constraintTop_toBottomOf="@+id/information_legal" /> + <TextView + android:id="@+id/information_enf_version" + style="@style/body2Medium" + android:visibility="gone" + tools:visibility="visible" + android:layout_width="@dimen/match_constraint" + android:paddingTop="@dimen/spacing_tiny" + android:paddingBottom="@dimen/spacing_tiny" + android:layout_height="wrap_content" + android:focusable="true" + android:background="?selectableItemBackground" + tools:text="16000000" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/guideline_body" + app:layout_constraintTop_toBottomOf="@+id/information_version" /> + <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline_body" android:layout_width="wrap_content" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt index 624df3ea1a0afe8819a4637d929c4f02c8de93ad..b24f8fc8964adf921e00389250ed379eb54ea4d1 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt @@ -8,12 +8,14 @@ import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProv import de.rki.coronawarnapp.nearby.modules.exposurewindow.ExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.locationless.ScanningSupport import de.rki.coronawarnapp.nearby.modules.tracing.TracingStatus +import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.coVerifySequence import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just @@ -38,6 +40,7 @@ class ENFClientTest : BaseTest() { @MockK lateinit var scanningSupport: ScanningSupport @MockK lateinit var exposureWindowProvider: ExposureWindowProvider @MockK lateinit var exposureDetectionTracker: ExposureDetectionTracker + @MockK lateinit var enfVersion: ENFVersion @BeforeEach fun setup() { @@ -56,7 +59,7 @@ class ENFClientTest : BaseTest() { diagnosisKeyProvider = diagnosisKeyProvider, tracingStatus = tracingStatus, scanningSupport = scanningSupport, - + enfVersion = enfVersion, exposureWindowProvider = exposureWindowProvider, exposureDetectionTracker = exposureDetectionTracker ) @@ -274,4 +277,13 @@ class ENFClientTest : BaseTest() { exposureWindowProvider.exposureWindows() } } + + @Test + fun `enf version check is forwaded to the right module`() = runBlocking { + coEvery { enfVersion.getENFClientVersion() } returns Long.MAX_VALUE + + createClient().getENFClientVersion() shouldBe Long.MAX_VALUE + + coVerifySequence { enfVersion.getENFClientVersion() } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt index 6ef70a9cddd0ffca0e67ede59a0d8dffb56805b9..d4e8aa99150773a347cd9152363176a69e97513f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient -import de.rki.coronawarnapp.util.GoogleAPIVersion +import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.clearAllMocks @@ -17,11 +17,9 @@ import testhelpers.gms.MockGMSTask import java.io.File class DefaultDiagnosisKeyProviderTest : BaseTest() { - @MockK - lateinit var googleENFClient: ExposureNotificationClient - - @MockK - lateinit var googleAPIVersion: GoogleAPIVersion + @MockK lateinit var googleENFClient: ExposureNotificationClient + @MockK lateinit var enfVersion: ENFVersion + @MockK lateinit var submissionQuota: SubmissionQuota @MockK lateinit var submissionQuota: SubmissionQuota @@ -36,7 +34,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { coEvery { googleENFClient.provideDiagnosisKeys(any<List<File>>()) } returns MockGMSTask.forValue(null) - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true + coEvery { enfVersion.isAtLeast(ENFVersion.V16) } returns true } @AfterEach @@ -45,14 +43,14 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { } private fun createProvider() = DefaultDiagnosisKeyProvider( - googleAPIVersion = googleAPIVersion, + enfVersion = enfVersion, submissionQuota = submissionQuota, enfClient = googleENFClient ) @Test fun `key provision is used on older ENF versions`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns false + coEvery { enfVersion.isAtLeast(ENFVersion.V15) } returns false val provider = createProvider() @@ -68,7 +66,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { @Test fun `key provision is used on newer ENF versions`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true + coEvery { enfVersion.isAtLeast(ENFVersion.V15) } returns true val provider = createProvider() @@ -83,7 +81,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { @Test fun `quota is consumed silently`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true + coEvery { enfVersion.isAtLeast(ENFVersion.V15) } returns true coEvery { submissionQuota.consumeQuota(any()) } returns false val provider = createProvider() @@ -100,7 +98,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { @Test fun `provide empty key list`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true + coEvery { enfVersion.isAtLeast(ENFVersion.V15) } returns true val provider = createProvider() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/version/DefaultENFVersionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/version/DefaultENFVersionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a8ebf8773183208c180051eb08dff547f9994d3 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/version/DefaultENFVersionTest.kt @@ -0,0 +1,77 @@ +package de.rki.coronawarnapp.nearby.modules.version + +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.CommonStatusCodes.API_NOT_CONNECTED +import com.google.android.gms.common.api.Status +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import io.kotest.matchers.shouldBe +import io.mockk.Called +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import testhelpers.gms.MockGMSTask + +@ExperimentalCoroutinesApi +internal class DefaultENFVersionTest { + + @MockK lateinit var client: ExposureNotificationClient + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + } + + @AfterEach + fun tearDown() { + clearAllMocks() + } + + fun createInstance() = DefaultENFVersion( + client = client + ) + + @Test + fun `isAbove API v16 is true for v17`() { + every { client.version } returns MockGMSTask.forValue(17000000L) + + runBlockingTest { + createInstance().isAtLeast(ENFVersion.V16) shouldBe true + } + } + + @Test + fun `isAbove API v16 is false for v15`() { + every { client.version } returns MockGMSTask.forValue(15000000L) + + runBlockingTest { + createInstance().isAtLeast(ENFVersion.V16) shouldBe false + } + } + + @Test + fun `isAbove API v16 throws IllegalArgument for invalid version`() { + assertThrows<IllegalArgumentException> { + runBlockingTest { + createInstance().isAtLeast(1L) + } + verify { client.version wasNot Called } + } + } + + @Test + fun `isAbove API v16 false when APIException for too low version`() { + every { client.version } returns MockGMSTask.forError(ApiException(Status(API_NOT_CONNECTED))) + + runBlockingTest { + createInstance().isAtLeast(ENFVersion.V16) shouldBe false + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt index fbc170105e025901200d40c8dc3e40d92a95e3c8..9a703be68780ffe862e47172cb454a6c5a4d9c26 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt @@ -12,12 +12,13 @@ import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTra import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.util.di.AppInjector import io.mockk.MockKAnnotations +import io.mockk.Runs import io.mockk.clearAllMocks 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.mockkStatic import io.mockk.verifySequence import kotlinx.coroutines.test.TestCoroutineScope import org.junit.jupiter.api.AfterEach @@ -36,6 +37,7 @@ class ExposureStateUpdateReceiverTest : BaseTest() { @MockK private lateinit var intent: Intent @MockK private lateinit var workManager: WorkManager @MockK private lateinit var exposureDetectionTracker: ExposureDetectionTracker + private val scope = TestCoroutineScope() class TestApp : Application(), HasAndroidInjector { @@ -48,23 +50,25 @@ class ExposureStateUpdateReceiverTest : BaseTest() { @BeforeEach fun setUp() { MockKAnnotations.init(this) - mockkStatic(WorkManager::class) every { intent.getStringExtra(ExposureNotificationClient.EXTRA_TOKEN) } returns "token" mockkObject(AppInjector) + every { workManager.enqueue(any<WorkRequest>()) } answers { mockk() } + val application = mockk<TestApp>() every { context.applicationContext } returns application + val broadcastReceiverInjector = AndroidInjector<Any> { it as ExposureStateUpdateReceiver it.exposureDetectionTracker = exposureDetectionTracker it.dispatcherProvider = TestDispatcherProvider it.scope = scope + it.workManager = workManager } every { application.androidInjector() } returns broadcastReceiverInjector - every { WorkManager.getInstance(context) } returns workManager - every { workManager.enqueue(any<WorkRequest>()) } answers { mockk() } + every { exposureDetectionTracker.finishExposureDetection(any(), any()) } just Runs } @AfterEach @@ -77,9 +81,11 @@ class ExposureStateUpdateReceiverTest : BaseTest() { every { intent.action } returns ExposureNotificationClient.ACTION_EXPOSURE_STATE_UPDATED ExposureStateUpdateReceiver().onReceive(context, intent) + scope.advanceUntilIdle() + verifySequence { - workManager.enqueue(any<WorkRequest>()) exposureDetectionTracker.finishExposureDetection("token", TrackedExposureDetection.Result.UPDATED_STATE) + workManager.enqueue(any<WorkRequest>()) } } @@ -88,8 +94,11 @@ class ExposureStateUpdateReceiverTest : BaseTest() { every { intent.action } returns ExposureNotificationClient.ACTION_EXPOSURE_NOT_FOUND ExposureStateUpdateReceiver().onReceive(context, intent) + scope.advanceUntilIdle() + verifySequence { exposureDetectionTracker.finishExposureDetection("token", TrackedExposureDetection.Result.NO_MATCHES) + workManager.enqueue(any<WorkRequest>()) } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt deleted file mode 100644 index 110d8cbeed2aef3b469c0355c3a848025f1faadf..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package de.rki.coronawarnapp.util - -import com.google.android.gms.common.api.ApiException -import com.google.android.gms.common.api.CommonStatusCodes.API_NOT_CONNECTED -import com.google.android.gms.common.api.Status -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient -import io.kotest.matchers.shouldBe -import io.mockk.Called -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockkObject -import io.mockk.unmockkObject -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -@ExperimentalCoroutinesApi -internal class GoogleAPIVersionTest { - - private lateinit var classUnderTest: GoogleAPIVersion - - @BeforeEach - fun setUp() { - mockkObject(InternalExposureNotificationClient) - classUnderTest = GoogleAPIVersion() - } - - @AfterEach - fun tearDown() { - unmockkObject(InternalExposureNotificationClient) - } - - @Test - fun `isAbove API v16 is true for v17`() { - coEvery { InternalExposureNotificationClient.getVersion() } returns 17000000L - - runBlockingTest { - classUnderTest.isAtLeast(GoogleAPIVersion.V16) shouldBe true - } - } - - @Test - fun `isAbove API v16 is false for v15`() { - coEvery { InternalExposureNotificationClient.getVersion() } returns 15000000L - - runBlockingTest { - classUnderTest.isAtLeast(GoogleAPIVersion.V16) shouldBe false - } - } - - @Test - fun `isAbove API v16 throws IllegalArgument for invalid version`() { - assertThrows<IllegalArgumentException> { - runBlockingTest { - classUnderTest.isAtLeast(1L) - } - coVerify { - InternalExposureNotificationClient.getVersion() wasNot Called - } - } - } - - @Test - fun `isAbove API v16 false when APIException for too low version`() { - coEvery { InternalExposureNotificationClient.getVersion() } throws - ApiException(Status(API_NOT_CONNECTED)) - - runBlockingTest { - classUnderTest.isAtLeast(GoogleAPIVersion.V16) shouldBe false - } - } -} diff --git a/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt b/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt index d0ed11f4ec929f0ba4ec6ff7e89bfc832fef9f01..321a733ae210c6fa0dd2600912032fdbb4d08159 100644 --- a/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt +++ b/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt @@ -8,12 +8,12 @@ import io.mockk.mockk object MockGMSTask { fun <T> forError(error: Exception): Task<T> = mockk<Task<T>>().apply { - every { addOnSuccessListener(any()) } answers { + every { addOnFailureListener(any()) } answers { val listener = arg<OnFailureListener>(0) listener.onFailure(error) this@apply } - every { addOnFailureListener(any()) } returns this + every { addOnSuccessListener(any()) } returns this } fun <T> forValue(value: T): Task<T> = mockk<Task<T>>().apply { diff --git a/gradle.properties b/gradle.properties index 94936bbe11127030181ed8fc471cffd28322536d..3802281e4f1879b6ddca5ff34f9f5b3376baaee3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ org.gradle.dependency.verification.console=verbose VERSION_MAJOR=1 VERSION_MINOR=8 VERSION_PATCH=0 -VERSION_BUILD=5 +VERSION_BUILD=7