From a5630bed85c2f20cb81bf43a3a3ce3568876c05f Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Thu, 12 Nov 2020 16:35:34 +0100 Subject: [PATCH] Enhance enf client (EXPOSUREAPP-3540) (#1584) * Implemented exposure window provider, adjusted ENF Client * Implemented new provideDiagnosisKeys to activate ExposureWindow mode, deprecated old diagnosis related content * Adjusted ENFClientTest * conflict fix - wip * test fix --- .../de/rki/coronawarnapp/nearby/ENFClient.kt | 21 +++++++++++++++- .../de/rki/coronawarnapp/nearby/ENFModule.kt | 7 ++++++ .../DefaultDiagnosisKeyProvider.kt | 25 +++++++++++++++++++ .../DiagnosisKeyProvider.kt | 3 +++ .../diagnosiskeyprovider/SubmissionQuota.kt | 4 +++ .../DefaultExposureWindowProvider.kt | 20 +++++++++++++++ .../exposurewindow/ExposureWindowProvider.kt | 7 ++++++ .../coronawarnapp/util/GoogleAPIVersion.kt | 1 + .../rki/coronawarnapp/nearby/ENFClientTest.kt | 18 +++++++++++++ 9 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt 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 c3de7abd0..38d4f40b1 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 @@ -4,9 +4,11 @@ package de.rki.coronawarnapp.nearby import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +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 kotlinx.coroutines.flow.Flow @@ -23,8 +25,10 @@ class ENFClient @Inject constructor( private val diagnosisKeyProvider: DiagnosisKeyProvider, private val tracingStatus: TracingStatus, private val scanningSupport: ScanningSupport, + private val exposureWindowProvider: ExposureWindowProvider, + private val exposureDetectionTracker: ExposureDetectionTracker -) : DiagnosisKeyProvider, TracingStatus, ScanningSupport { +) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider { // TODO Remove this once we no longer need direct access to the ENF Client, // i.e. in **[InternalExposureNotificationClient]** @@ -51,6 +55,19 @@ class ENFClient @Inject constructor( } } + override suspend fun provideDiagnosisKeys(keyFiles: Collection<File>): Boolean { + Timber.d("asyncProvideDiagnosisKeys(keyFiles=$keyFiles)") + + return if (keyFiles.isEmpty()) { + Timber.d("No key files submitted, returning early.") + true + } else { + Timber.d("Forwarding %d key files to our DiagnosisKeyProvider.", keyFiles.size) + TODO("Call calculationTracker.trackNewCalaculation with an UUID as replacement for token?") + diagnosisKeyProvider.provideDiagnosisKeys(keyFiles) + } + } + override val isLocationLessScanningSupported: Flow<Boolean> get() = scanningSupport.isLocationLessScanningSupported @@ -72,4 +89,6 @@ class ENFClient @Inject constructor( .filter { !it.isCalculating && it.isSuccessful } .maxByOrNull { it.finishedAt ?: Instant.EPOCH } } + + override suspend fun exposureWindows(): List<ExposureWindow> = exposureWindowProvider.exposureWindows() } 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 9d98d5b33..ec99ff4e7 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 @@ -9,6 +9,8 @@ import de.rki.coronawarnapp.nearby.modules.detectiontracker.DefaultExposureDetec import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DefaultDiagnosisKeyProvider import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import de.rki.coronawarnapp.nearby.modules.exposurewindow.DefaultExposureWindowProvider +import de.rki.coronawarnapp.nearby.modules.exposurewindow.ExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.locationless.DefaultScanningSupport import de.rki.coronawarnapp.nearby.modules.locationless.ScanningSupport import de.rki.coronawarnapp.nearby.modules.tracing.DefaultTracingStatus @@ -39,6 +41,11 @@ class ENFModule { fun scanningSupport(scanningSupport: DefaultScanningSupport): ScanningSupport = scanningSupport + @Singleton + @Provides + fun exposureWindowProvider(exposureWindowProvider: DefaultExposureWindowProvider): ExposureWindowProvider = + exposureWindowProvider + @Singleton @Provides fun calculationTracker(exposureDetectionTracker: DefaultExposureDetectionTracker): ExposureDetectionTracker = 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 59114d58d..a0cbf8b53 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 @@ -54,6 +54,31 @@ class DefaultDiagnosisKeyProvider @Inject constructor( } } + override suspend fun provideDiagnosisKeys(keyFiles: Collection<File>): Boolean { + if (keyFiles.isEmpty()) { + Timber.d("No key files submitted, returning early.") + return true + } + + if (!googleAPIVersion.isAtLeast(GoogleAPIVersion.V15)) { + // Actually this shouldn't happen + Timber.d("No key files submitted because client uses an old unsupported version") + return false + } + + if (!submissionQuota.consumeQuota(1)) { + Timber.w("No key files submitted because not enough quota available.") + } + + return suspendCoroutine { cont -> + Timber.d("Performing key submission.") + enfClient + .provideDiagnosisKeys(keyFiles.toList()) + .addOnSuccessListener { cont.resume(true) } + .addOnFailureListener { cont.resumeWithException(it) } + } + } + private suspend fun provideKeys( files: Collection<File>, configuration: ExposureConfiguration, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt index accedeed0..d4a2f00fa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt @@ -17,9 +17,12 @@ interface DiagnosisKeyProvider { * @param token * @return */ + @Deprecated("Use provideDiagnosisKeys with only keyFiles as param to activate WindowExposure mode") suspend fun provideDiagnosisKeys( keyFiles: Collection<File>, configuration: ExposureConfiguration?, token: String ): Boolean + + suspend fun provideDiagnosisKeys(keyFiles: Collection<File>): Boolean } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt index d9bd53506..bf5ff885a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt @@ -86,6 +86,10 @@ class SubmissionQuota @Inject constructor( companion object { @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + /** + * This quota changes when using ExposureWindow mode from 20 to 6 per day + * See: https://developers.google.com/android/exposure-notifications/release-notes + */ internal const val DEFAULT_QUOTA = 20 } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt new file mode 100644 index 000000000..5f83c9bba --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt @@ -0,0 +1,20 @@ +package de.rki.coronawarnapp.nearby.modules.exposurewindow + +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Singleton +class DefaultExposureWindowProvider @Inject constructor( + private val client: ExposureNotificationClient +) : ExposureWindowProvider { + override suspend fun exposureWindows(): List<ExposureWindow> = suspendCoroutine { cont -> + client.exposureWindows + .addOnSuccessListener { cont.resume(it) } + .addOnFailureListener { cont.resumeWithException(it) } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt new file mode 100644 index 000000000..713715c87 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.nearby.modules.exposurewindow + +import com.google.android.gms.nearby.exposurenotification.ExposureWindow + +interface ExposureWindowProvider { + suspend fun exposureWindows(): List<ExposureWindow> +} 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 index 6af00068b..8119586bc 100644 --- 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 @@ -36,5 +36,6 @@ class GoogleAPIVersion @Inject constructor() { 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/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt index 4880b66aa..1f242c4c2 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 @@ -2,9 +2,11 @@ package de.rki.coronawarnapp.nearby import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +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 io.kotest.matchers.shouldBe @@ -37,6 +39,7 @@ class ENFClientTest : BaseTest() { @MockK lateinit var diagnosisKeyProvider: DiagnosisKeyProvider @MockK lateinit var tracingStatus: TracingStatus @MockK lateinit var scanningSupport: ScanningSupport + @MockK lateinit var exposureWindowProvider: ExposureWindowProvider @MockK lateinit var exposureDetectionTracker: ExposureDetectionTracker @BeforeEach @@ -56,6 +59,8 @@ class ENFClientTest : BaseTest() { diagnosisKeyProvider = diagnosisKeyProvider, tracingStatus = tracingStatus, scanningSupport = scanningSupport, + + exposureWindowProvider = exposureWindowProvider, exposureDetectionTracker = exposureDetectionTracker ) @@ -265,4 +270,17 @@ class ENFClientTest : BaseTest() { createClient().lastSuccessfulTrackedExposureDetection().first()!!.identifier shouldBe "1" } } + + @Test + fun `exposure windows check is forwarded to the right module`() = runBlocking { + val exposureWindowList = emptyList<ExposureWindow>() + coEvery { exposureWindowProvider.exposureWindows() } returns exposureWindowList + + val client = createClient() + client.exposureWindows() shouldBe exposureWindowList + + coVerify(exactly = 1) { + exposureWindowProvider.exposureWindows() + } + } } -- GitLab