From 70bf5cf192727d6ffe15e2311cac0c828ee3fbbb Mon Sep 17 00:00:00 2001 From: chris-cwa <69595386+chris-cwa@users.noreply.github.com> Date: Fri, 23 Apr 2021 17:49:08 +0200 Subject: [PATCH] Rapid Antigene Test Result Polling (EXPOSUREAPP-6517) (#2912) * + rat worker * minded mode switching * fixed tests * renaming * renaming * no skipping for rat workers * - unused method * reverted change to vals in order to make it suspend functions in the future * start worker on rat creation * renamed confusing method names * fixed typpos * moved constants to where they are used * fixed log statement * fixed test * separate scheduler for every test * use both states * renamed classes * fixed logs Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> --- .../coronatest/type/pcr/PCRProcessor.kt | 6 +- .../rapidantigen/RapidAntigenProcessor.kt | 6 ++ .../worker/PCRResultRetrievalWorker.kt} | 29 +++-- .../worker/RAResultRetrievalWorker.kt | 101 ++++++++++++++++++ .../execution/PCRResultScheduler.kt} | 53 +++++---- .../worker/execution/RAResultScheduler.kt | 89 +++++++++++++++ .../coronawarnapp/util/worker/WorkerBinder.kt | 18 +++- .../worker/BackgroundWorkHelper.kt | 13 --- .../worker/BackgroundWorkScheduler.kt | 8 +- .../util/worker/WorkerBinderTest.kt | 8 +- .../worker/BackgroundWorkHelperTest.kt | 3 +- ...isTestResultRetrievalPeriodicWorkerTest.kt | 19 ++-- 12 files changed, 279 insertions(+), 74 deletions(-) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/{worker/DiagnosisTestResultRetrievalPeriodicWorker.kt => coronatest/worker/PCRResultRetrievalWorker.kt} (84%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/RAResultRetrievalWorker.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/{execution/TestResultScheduler.kt => worker/execution/PCRResultScheduler.kt} (59%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/execution/RAResultScheduler.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt index f7e353433..59848afdd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.coronatest.type.pcr import dagger.Reusable import de.rki.coronawarnapp.coronatest.TestRegistrationRequest -import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID @@ -19,6 +18,7 @@ import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor import de.rki.coronawarnapp.coronatest.type.CoronaTestService +import de.rki.coronawarnapp.coronatest.worker.execution.PCRResultScheduler import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.TestResultDataCollector import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler @@ -37,7 +37,7 @@ class PCRProcessor @Inject constructor( private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, private val testResultDataCollector: TestResultDataCollector, private val deadmanNotificationScheduler: DeadmanNotificationScheduler, - private val testResultScheduler: TestResultScheduler, + private val pcrTestResultScheduler: PCRResultScheduler, ) : CoronaTestProcessor { override val type: CoronaTest.Type = CoronaTest.Type.PCR @@ -82,7 +82,7 @@ class PCRProcessor @Inject constructor( analyticsKeySubmissionCollector.reportTestRegistered() if (testResult == PCR_OR_RAT_PENDING) { - testResultScheduler.setPeriodicTestPolling(enabled = true) + pcrTestResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = true) } return PCRCoronaTest( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt index 482f6af98..fea68d822 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt @@ -17,6 +17,7 @@ import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor import de.rki.coronawarnapp.coronatest.type.CoronaTestService +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report @@ -29,6 +30,7 @@ import javax.inject.Inject class RapidAntigenProcessor @Inject constructor( private val timeStamper: TimeStamper, private val submissionService: CoronaTestService, + private val resultScheduler: RAResultScheduler, ) : CoronaTestProcessor { override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN @@ -41,6 +43,10 @@ class RapidAntigenProcessor @Inject constructor( val testResult = registrationData.testResult.validOrThrow() + if (testResult == PCR_OR_RAT_PENDING || testResult == RAT_PENDING) { + resultScheduler.setRatResultPeriodicPollingMode(mode = RAResultScheduler.RatPollingMode.PHASE1) + } + return RACoronaTest( identifier = request.identifier, registeredAt = timeStamper.nowUTC, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/PCRResultRetrievalWorker.kt similarity index 84% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/PCRResultRetrievalWorker.kt index dd9ce5a95..4fe1c6e55 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/PCRResultRetrievalWorker.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.worker +package de.rki.coronawarnapp.coronatest.worker import android.content.Context import androidx.work.CoroutineWorker @@ -7,16 +7,17 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.CoronaTestRepository -import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler import de.rki.coronawarnapp.coronatest.latestPCRT import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest +import de.rki.coronawarnapp.coronatest.worker.execution.PCRResultScheduler import de.rki.coronawarnapp.notification.GeneralNotifications import de.rki.coronawarnapp.notification.NotificationConstants import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory +import de.rki.coronawarnapp.worker.BackgroundConstants import kotlinx.coroutines.flow.first import org.joda.time.Duration import org.joda.time.Instant @@ -24,17 +25,15 @@ import timber.log.Timber /** * Diagnosis test result retrieval by periodic polling - * - * @see BackgroundWorkScheduler */ -class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( +class PCRResultRetrievalWorker @AssistedInject constructor( @Assisted val context: Context, @Assisted workerParams: WorkerParameters, private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, private val notificationHelper: GeneralNotifications, private val coronaTestRepository: CoronaTestRepository, private val timeStamper: TimeStamper, - private val testResultScheduler: TestResultScheduler, + private val testResultScheduler: PCRResultScheduler, ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { @@ -43,7 +42,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) { Timber.tag(TAG).d("$id doWork() failed after $runAttemptCount attempts. Rescheduling") - testResultScheduler.setPeriodicTestPolling(enabled = true) + testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = true) Timber.tag(TAG).d("$id Rescheduled background worker") return Result.failure() @@ -52,7 +51,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( try { if (abortConditionsMet(timeStamper.nowUTC)) { Timber.tag(TAG).d(" $id Stopping worker.") - stopWorker() + disablePolling() } else { Timber.tag(TAG).d(" $id Running worker.") @@ -71,7 +70,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( cancelRiskLevelScoreNotification() Timber.tag(TAG) .d("$id: Test Result available - notification sent & risk level notification canceled") - stopWorker() + disablePolling() } } } catch (e: Exception) { @@ -87,7 +86,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( private suspend fun abortConditionsMet(nowUTC: Instant): Boolean { val pcrTest = coronaTestRepository.latestPCRT.first() if (pcrTest == null) { - Timber.tag(TAG).w("There is no PCR test available!?") + Timber.tag(TAG).w("There is no PCR available!?") return true } @@ -123,15 +122,15 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( ) } - private fun stopWorker() { - testResultScheduler.setPeriodicTestPolling(enabled = false) - Timber.tag(TAG).d("$id: Background worker stopped") + private fun disablePolling() { + testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false) + Timber.tag(TAG).d("$id: polling disabled") } @AssistedFactory - interface Factory : InjectedWorkerFactory<DiagnosisTestResultRetrievalPeriodicWorker> + interface Factory : InjectedWorkerFactory<PCRResultRetrievalWorker> companion object { - private val TAG = DiagnosisTestResultRetrievalPeriodicWorker::class.java.simpleName + private val TAG = PCRResultRetrievalWorker::class.java.simpleName } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/RAResultRetrievalWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/RAResultRetrievalWorker.kt new file mode 100644 index 000000000..4ee68a0d4 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/RAResultRetrievalWorker.kt @@ -0,0 +1,101 @@ +package de.rki.coronawarnapp.coronatest.worker + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.latestRAT +import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler.RatPollingMode.DISABLED +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler.RatPollingMode.PHASE1 +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler.RatPollingMode.PHASE2 +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory +import de.rki.coronawarnapp.worker.BackgroundConstants +import kotlinx.coroutines.flow.first +import org.joda.time.Duration +import timber.log.Timber + +/** + * RAT result retrieval by periodic polling + */ +class RAResultRetrievalWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters, + private val coronaTestRepository: CoronaTestRepository, + private val timeStamper: TimeStamper, + private val ratResultScheduler: RAResultScheduler, +) : CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + Timber.tag(TAG).d("$id: doWork() started. Run attempt: $runAttemptCount") + + if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) { + Timber.tag(TAG).d("$id doWork() failed after $runAttemptCount attempts. Rescheduling") + + ratResultScheduler.setRatResultPeriodicPollingMode(mode = ratResultScheduler.ratResultPeriodicPollingMode) + Timber.tag(TAG).d("$id Rescheduled background worker") + + return Result.failure() + } + + // checking abort conditions + val rat = coronaTestRepository.latestRAT.first() + if (rat == null) { + Timber.tag(TAG).w("There is no RAT test available!?") + disablePolling() + return Result.success() + } else { + val nowUTC = timeStamper.nowUTC + val days = Duration(rat.registeredAt, nowUTC).standardDays + val minutes = Duration(rat.registeredAt, nowUTC).standardMinutes + val isPhase1 = ratResultScheduler.ratResultPeriodicPollingMode == PHASE1 + Timber.tag(TAG).d("Calculated days: %d", days) + when { + rat.isResultAvailableNotificationSent -> { + Timber.tag(TAG).d("$id: Notification already sent.") + disablePolling() + } + rat.isViewed -> { + Timber.tag(TAG).d("$id: Test result has already been viewed.") + disablePolling() + } + days >= BackgroundConstants.POLLING_VALIDITY_MAX_DAYS -> { + Timber.tag(TAG).d("$id $days is exceeding the maximum polling duration") + disablePolling() + } + isPhase1 && minutes >= RAT_POLLING_END_OF_PHASE1_MINUTES -> { + Timber.tag(TAG).d("$id $minutes minutes - time for a phase 2!") + ratResultScheduler.setRatResultPeriodicPollingMode(mode = PHASE2) + } + else -> { + coronaTestRepository.refresh(CoronaTest.Type.RAPID_ANTIGEN) + } + } + return Result.success() + } + } + + private fun disablePolling() { + ratResultScheduler.setRatResultPeriodicPollingMode(mode = DISABLED) + Timber.tag(TAG).d("$id: polling disabled") + } + + @AssistedFactory + interface Factory : InjectedWorkerFactory<RAResultRetrievalWorker> + + companion object { + private val TAG = RAResultRetrievalWorker::class.java.simpleName + + /** + * The time when rat polling is switched to a larger interval + * + * @see TimeUnit.MINUTES + */ + private const val RAT_POLLING_END_OF_PHASE1_MINUTES = 90 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/execution/TestResultScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/execution/PCRResultScheduler.kt similarity index 59% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/execution/TestResultScheduler.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/execution/PCRResultScheduler.kt index a1c7ba36b..c9e83f1f8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/execution/TestResultScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/execution/PCRResultScheduler.kt @@ -1,48 +1,49 @@ -package de.rki.coronawarnapp.coronatest.execution +package de.rki.coronawarnapp.coronatest.worker.execution +import androidx.annotation.VisibleForTesting import androidx.work.BackoffPolicy import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkInfo import androidx.work.WorkManager import dagger.Reusable -import de.rki.coronawarnapp.storage.TracingSettings -import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.coronatest.worker.PCRResultRetrievalWorker import de.rki.coronawarnapp.util.coroutine.await import de.rki.coronawarnapp.worker.BackgroundConstants +import de.rki.coronawarnapp.worker.BackgroundConstants.DIAGNOSIS_TEST_RESULT_RETRIEVAL_TRIES_PER_DAY +import de.rki.coronawarnapp.worker.BackgroundConstants.MINUTES_IN_DAY import de.rki.coronawarnapp.worker.BackgroundWorkHelper -import de.rki.coronawarnapp.worker.DiagnosisTestResultRetrievalPeriodicWorker import kotlinx.coroutines.runBlocking import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject @Reusable -class TestResultScheduler @Inject constructor( - private val tracingSettings: TracingSettings, +class PCRResultScheduler @Inject constructor( private val workManager: WorkManager, - private val timeStamper: TimeStamper, ) { - private suspend fun isScheduled(): Boolean { - val workerInfos = workManager.getWorkInfosForUniqueWork(PCR_TESTRESULT_WORKER_UNIQUEUNAME).await() + private suspend fun isPcrScheduled() = + workManager.getWorkInfosForUniqueWork(PCR_TESTRESULT_WORKER_UNIQUEUNAME) + .await() + .any { it.isScheduled } - return workerInfos.any { it.isScheduled } - } + private val WorkInfo.isScheduled: Boolean + get() = state == WorkInfo.State.RUNNING || state == WorkInfo.State.ENQUEUED - fun setPeriodicTestPolling(enabled: Boolean) { + fun setPcrPeriodicTestPollingEnabled(enabled: Boolean) { if (enabled) { // TODO Refactor runBlocking away - val isScheduled = runBlocking { isScheduled() } + val isScheduled = runBlocking { isPcrScheduled() } if (isScheduled) { Timber.tag(TAG).w("Already scheduled, skipping") return } - Timber.tag(TAG).i("Queueing test result worker (DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER)") + Timber.tag(TAG).i("Queueing pcr test result worker (DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER)") workManager.enqueueUniquePeriodicWork( PCR_TESTRESULT_WORKER_UNIQUEUNAME, ExistingPeriodicWorkPolicy.REPLACE, - buildDiagnosisTestResultRetrievalPeriodicWork() + buildPcrTestResultRetrievalPeriodicWork() ) } else { Timber.tag(TAG).d("cancelWorker()") @@ -50,9 +51,9 @@ class TestResultScheduler @Inject constructor( } } - private fun buildDiagnosisTestResultRetrievalPeriodicWork() = - PeriodicWorkRequestBuilder<DiagnosisTestResultRetrievalPeriodicWorker>( - BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(), + private fun buildPcrTestResultRetrievalPeriodicWork() = + PeriodicWorkRequestBuilder<PCRResultRetrievalWorker>( + getPcrTestResultRetrievalPeriodicWorkTimeInterval(), TimeUnit.MINUTES ) .addTag(PCR_TESTRESULT_WORKER_TAG) @@ -67,9 +68,6 @@ class TestResultScheduler @Inject constructor( ) .build() - private val WorkInfo.isScheduled: Boolean - get() = state == WorkInfo.State.RUNNING || state == WorkInfo.State.ENQUEUED - companion object { /** * Kind initial delay in minutes for periodic work for accessibility reason @@ -80,6 +78,17 @@ class TestResultScheduler @Inject constructor( private const val PCR_TESTRESULT_WORKER_TAG = "DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER" private const val PCR_TESTRESULT_WORKER_UNIQUEUNAME = "DiagnosisTestResultBackgroundPeriodicWork" - private const val TAG = "TestResultScheduler" + private const val TAG = "PCRTestResultScheduler" + + /** + * Calculate the time for pcr diagnosis key retrieval periodic work + * + * @return Long + * + * @see BackgroundConstants.MINUTES_IN_DAY + */ + @VisibleForTesting + internal fun getPcrTestResultRetrievalPeriodicWorkTimeInterval() = + (MINUTES_IN_DAY / DIAGNOSIS_TEST_RESULT_RETRIEVAL_TRIES_PER_DAY).toLong() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/execution/RAResultScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/execution/RAResultScheduler.kt new file mode 100644 index 000000000..f1253c501 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/worker/execution/RAResultScheduler.kt @@ -0,0 +1,89 @@ +package de.rki.coronawarnapp.coronatest.worker.execution + +import androidx.work.BackoffPolicy +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequest +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import dagger.Reusable +import de.rki.coronawarnapp.coronatest.worker.RAResultRetrievalWorker +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler.RatPollingMode.DISABLED +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler.RatPollingMode.PHASE1 +import de.rki.coronawarnapp.worker.BackgroundConstants +import de.rki.coronawarnapp.worker.BackgroundWorkHelper +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +@Reusable +class RAResultScheduler @Inject constructor( + private val workManager: WorkManager, +) { + + private var ratWorkerMode = DISABLED + val ratResultPeriodicPollingMode + get() = ratWorkerMode + + enum class RatPollingMode { + DISABLED, + PHASE1, + PHASE2 + } + + fun setRatResultPeriodicPollingMode(mode: RatPollingMode) { + ratWorkerMode = mode + if (mode == DISABLED) { + Timber.tag(TAG).d("cancelWorker()") + workManager.cancelUniqueWork(RAT_RESULT_WORKER_UNIQUEUNAME) + } else { + // no check for already running workers! + // worker must be replaced by next phase instance + Timber.tag(TAG).i("Queueing rat result worker (RAT_RESULT_PERIODIC_WORKER)") + workManager.enqueueUniquePeriodicWork( + RAT_RESULT_WORKER_UNIQUEUNAME, + ExistingPeriodicWorkPolicy.REPLACE, + buildRatResultRetrievalPeriodicWork(mode) + ) + } + } + + private fun buildRatResultRetrievalPeriodicWork(pollingMode: RatPollingMode): PeriodicWorkRequest { + val repeatInterval = if (pollingMode == PHASE1) { + ratResultRetrievalPeriodicWorkPhase1IntervalInMinutes + } else { + ratResultRetrievalPeriodicWorkPhase2IntervalInMinutes + } + return PeriodicWorkRequestBuilder<RAResultRetrievalWorker>( + repeatInterval, + TimeUnit.MINUTES + ) + .addTag(RAT_RESULT_WORKER_TAG) + .setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork()) + .setInitialDelay( + DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY, + TimeUnit.SECONDS + ).setBackoffCriteria( + BackoffPolicy.LINEAR, + BackgroundConstants.KIND_DELAY, + TimeUnit.MINUTES + ) + .build() + } + + companion object { + /** + * Kind initial delay in minutes for periodic work for accessibility reason + * + * @see TimeUnit.SECONDS + */ + private const val DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY = 10L + private const val RAT_RESULT_WORKER_TAG = "RAT_RESULT_PERIODIC_WORKER" + private const val RAT_RESULT_WORKER_UNIQUEUNAME = "RatResultRetrievalWorker" + + private const val TAG = "RatResultScheduler" + + private const val ratResultRetrievalPeriodicWorkPhase1IntervalInMinutes = 15L + + private const val ratResultRetrievalPeriodicWorkPhase2IntervalInMinutes = 90L + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt index 90dc9a5a5..7f1d39bc4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt @@ -5,18 +5,19 @@ import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryRetentionWorker +import de.rki.coronawarnapp.coronatest.worker.PCRResultRetrievalWorker +import de.rki.coronawarnapp.coronatest.worker.RAResultRetrievalWorker import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsPeriodicWorker import de.rki.coronawarnapp.deadman.DeadmanNotificationOneTimeWorker import de.rki.coronawarnapp.deadman.DeadmanNotificationPeriodicWorker import de.rki.coronawarnapp.deniability.BackgroundNoiseOneTimeWorker import de.rki.coronawarnapp.deniability.BackgroundNoisePeriodicWorker import de.rki.coronawarnapp.diagnosiskeys.execution.DiagnosisKeyRetrievalWorker -import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpPeriodicWorker import de.rki.coronawarnapp.nearby.ExposureStateUpdateWorker import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOutWorker import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningWorker +import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpPeriodicWorker import de.rki.coronawarnapp.submission.auto.SubmissionWorker -import de.rki.coronawarnapp.worker.DiagnosisTestResultRetrievalPeriodicWorker @Module abstract class WorkerBinder { @@ -51,9 +52,16 @@ abstract class WorkerBinder { @Binds @IntoMap - @WorkerKey(DiagnosisTestResultRetrievalPeriodicWorker::class) - abstract fun testResultRetrievalPeriodic( - factory: DiagnosisTestResultRetrievalPeriodicWorker.Factory + @WorkerKey(PCRResultRetrievalWorker::class) + abstract fun pcrTestResultRetrievalPeriodic( + factory: PCRResultRetrievalWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> + + @Binds + @IntoMap + @WorkerKey(RAResultRetrievalWorker::class) + abstract fun ratResultRetrievalPeriodic( + factory: RAResultRetrievalWorker.Factory ): InjectedWorkerFactory<out ListenableWorker> @Binds diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt index a9e401d30..67a621d61 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkHelper.kt @@ -13,19 +13,6 @@ import kotlin.random.Random */ object BackgroundWorkHelper { - /** - * Calculate the time for diagnosis key retrieval periodic work - * - * @return Long - * - * @see BackgroundConstants.MINUTES_IN_DAY - */ - fun getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(): Long = - ( - BackgroundConstants.MINUTES_IN_DAY / - BackgroundConstants.DIAGNOSIS_TEST_RESULT_RETRIEVAL_TRIES_PER_DAY - ).toLong() - /** * Get background noise one time work delay * The periodic job is already delayed by MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt index 706db5566..cad840478 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundWorkScheduler.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.worker import de.rki.coronawarnapp.coronatest.CoronaTestRepository -import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler +import de.rki.coronawarnapp.coronatest.worker.execution.PCRResultScheduler import de.rki.coronawarnapp.deniability.NoiseScheduler import de.rki.coronawarnapp.risk.execution.RiskWorkScheduler import kotlinx.coroutines.flow.first @@ -21,7 +21,7 @@ import javax.inject.Singleton class BackgroundWorkScheduler @Inject constructor( private val riskWorkScheduler: RiskWorkScheduler, private val coronaTestRepository: CoronaTestRepository, - private val testResultScheduler: TestResultScheduler, + private val testResultScheduler: PCRResultScheduler, private val noiseScheduler: NoiseScheduler, ) { @@ -36,14 +36,14 @@ class BackgroundWorkScheduler @Inject constructor( val hasPendingTests = coronatests.any { !it.isResultAvailableNotificationSent } if (!isSubmissionSuccessful && hasPendingTests) { - testResultScheduler.setPeriodicTestPolling(enabled = true) + testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = true) } } fun stopWorkScheduler() { noiseScheduler.setPeriodicNoise(enabled = false) riskWorkScheduler.setPeriodicRiskCalculation(enabled = false) - testResultScheduler.setPeriodicTestPolling(enabled = false) + testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false) Timber.d("All Background Jobs Stopped") } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt index ab535e027..34e53717f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt @@ -7,7 +7,8 @@ import dagger.Component import dagger.Module import dagger.Provides import de.rki.coronawarnapp.coronatest.CoronaTestRepository -import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler +import de.rki.coronawarnapp.coronatest.worker.execution.PCRResultScheduler +import de.rki.coronawarnapp.coronatest.worker.execution.RAResultScheduler import de.rki.coronawarnapp.datadonation.analytics.Analytics import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler @@ -162,5 +163,8 @@ class MockProvider { fun noiseScheduler(): NoiseScheduler = mockk() @Provides - fun testResultScheduler(): TestResultScheduler = mockk() + fun pcrTestResultScheduler(): PCRResultScheduler = mockk() + + @Provides + fun ratResultScheduler(): RAResultScheduler = mockk() } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundWorkHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundWorkHelperTest.kt index 0ab0f5e23..d2251ee48 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundWorkHelperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/BackgroundWorkHelperTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.worker import androidx.work.NetworkType +import de.rki.coronawarnapp.coronatest.worker.execution.PCRResultScheduler import org.junit.Assert import org.junit.Test @@ -9,7 +10,7 @@ class BackgroundWorkHelperTest { @Test fun getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval() { Assert.assertEquals( - BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(), + PCRResultScheduler.getPcrTestResultRetrievalPeriodicWorkTimeInterval(), 120 ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt index ee61927fa..33ec41f79 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt @@ -5,10 +5,11 @@ import androidx.work.ListenableWorker import androidx.work.WorkRequest import androidx.work.WorkerParameters import de.rki.coronawarnapp.coronatest.CoronaTestRepository -import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest +import de.rki.coronawarnapp.coronatest.worker.PCRResultRetrievalWorker +import de.rki.coronawarnapp.coronatest.worker.execution.PCRResultScheduler import de.rki.coronawarnapp.notification.GeneralNotifications import de.rki.coronawarnapp.notification.NotificationConstants import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService @@ -48,7 +49,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { @MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool @MockK lateinit var timeStamper: TimeStamper @MockK lateinit var coronaTestRepository: CoronaTestRepository - @MockK lateinit var testResultScheduler: TestResultScheduler + @MockK lateinit var testResultScheduler: PCRResultScheduler @RelaxedMockK lateinit var workerParams: WorkerParameters private val currentInstant = Instant.ofEpochSecond(1611764225) @@ -66,7 +67,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory every { appComponent.errorResetTool } returns encryptionErrorResetTool - every { testResultScheduler.setPeriodicTestPolling(enabled = any()) } just Runs + every { testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = any()) } just Runs every { notificationHelper.cancelCurrentNotification(any()) } just Runs @@ -94,7 +95,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { } } - private fun createWorker() = DiagnosisTestResultRetrievalPeriodicWorker( + private fun createWorker() = PCRResultRetrievalWorker( context = context, workerParams = workerParams, testResultAvailableNotificationService = testResultAvailableNotificationService, @@ -111,7 +112,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val result = createWorker().doWork() coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } - verify(exactly = 1) { testResultScheduler.setPeriodicTestPolling(enabled = false) } + verify(exactly = 1) { testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false) } result shouldBe ListenableWorker.Result.success() } @@ -122,7 +123,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val result = createWorker().doWork() coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } - verify(exactly = 1) { testResultScheduler.setPeriodicTestPolling(enabled = false) } + verify(exactly = 1) { testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false) } result shouldBe ListenableWorker.Result.success() } @@ -135,7 +136,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val result = createWorker().doWork() coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } - verify(exactly = 1) { testResultScheduler.setPeriodicTestPolling(enabled = false) } + verify(exactly = 1) { testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false) } result shouldBe ListenableWorker.Result.success() } @@ -236,7 +237,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { notificationHelper.cancelCurrentNotification( NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID ) - testResultScheduler.setPeriodicTestPolling(enabled = false) + testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false) } result shouldBe ListenableWorker.Result.success() @@ -250,7 +251,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { val result = createWorker().doWork() coVerify(exactly = 1) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } - coVerify(exactly = 0) { testResultScheduler.setPeriodicTestPolling(any()) } + coVerify(exactly = 0) { testResultScheduler.setPcrPeriodicTestPollingEnabled(any()) } result shouldBe ListenableWorker.Result.retry() } } -- GitLab