Skip to content
Snippets Groups Projects
Unverified Commit 70bf5cf1 authored by chris-cwa's avatar chris-cwa Committed by GitHub
Browse files

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: default avatarharambasicluka <64483219+harambasicluka@users.noreply.github.com>
parent d40ba276
No related branches found
Tags v2.1.0-RC4
No related merge requests found
Showing
with 279 additions and 74 deletions
...@@ -2,7 +2,6 @@ package de.rki.coronawarnapp.coronatest.type.pcr ...@@ -2,7 +2,6 @@ package de.rki.coronawarnapp.coronatest.type.pcr
import dagger.Reusable import dagger.Reusable
import de.rki.coronawarnapp.coronatest.TestRegistrationRequest 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.qrcode.CoronaTestQRCode
import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID
...@@ -19,6 +18,7 @@ import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN ...@@ -19,6 +18,7 @@ import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTest
import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
import de.rki.coronawarnapp.coronatest.type.CoronaTestService 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.keysubmission.AnalyticsKeySubmissionCollector
import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.TestResultDataCollector import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.TestResultDataCollector
import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
...@@ -37,7 +37,7 @@ class PCRProcessor @Inject constructor( ...@@ -37,7 +37,7 @@ class PCRProcessor @Inject constructor(
private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
private val testResultDataCollector: TestResultDataCollector, private val testResultDataCollector: TestResultDataCollector,
private val deadmanNotificationScheduler: DeadmanNotificationScheduler, private val deadmanNotificationScheduler: DeadmanNotificationScheduler,
private val testResultScheduler: TestResultScheduler, private val pcrTestResultScheduler: PCRResultScheduler,
) : CoronaTestProcessor { ) : CoronaTestProcessor {
override val type: CoronaTest.Type = CoronaTest.Type.PCR override val type: CoronaTest.Type = CoronaTest.Type.PCR
...@@ -82,7 +82,7 @@ class PCRProcessor @Inject constructor( ...@@ -82,7 +82,7 @@ class PCRProcessor @Inject constructor(
analyticsKeySubmissionCollector.reportTestRegistered() analyticsKeySubmissionCollector.reportTestRegistered()
if (testResult == PCR_OR_RAT_PENDING) { if (testResult == PCR_OR_RAT_PENDING) {
testResultScheduler.setPeriodicTestPolling(enabled = true) pcrTestResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = true)
} }
return PCRCoronaTest( return PCRCoronaTest(
......
...@@ -17,6 +17,7 @@ import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN ...@@ -17,6 +17,7 @@ import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTest
import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
import de.rki.coronawarnapp.coronatest.type.CoronaTestService 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.ExceptionCategory
import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.http.CwaWebException
import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.exception.reporting.report
...@@ -29,6 +30,7 @@ import javax.inject.Inject ...@@ -29,6 +30,7 @@ import javax.inject.Inject
class RapidAntigenProcessor @Inject constructor( class RapidAntigenProcessor @Inject constructor(
private val timeStamper: TimeStamper, private val timeStamper: TimeStamper,
private val submissionService: CoronaTestService, private val submissionService: CoronaTestService,
private val resultScheduler: RAResultScheduler,
) : CoronaTestProcessor { ) : CoronaTestProcessor {
override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
...@@ -41,6 +43,10 @@ class RapidAntigenProcessor @Inject constructor( ...@@ -41,6 +43,10 @@ class RapidAntigenProcessor @Inject constructor(
val testResult = registrationData.testResult.validOrThrow() val testResult = registrationData.testResult.validOrThrow()
if (testResult == PCR_OR_RAT_PENDING || testResult == RAT_PENDING) {
resultScheduler.setRatResultPeriodicPollingMode(mode = RAResultScheduler.RatPollingMode.PHASE1)
}
return RACoronaTest( return RACoronaTest(
identifier = request.identifier, identifier = request.identifier,
registeredAt = timeStamper.nowUTC, registeredAt = timeStamper.nowUTC,
......
package de.rki.coronawarnapp.worker package de.rki.coronawarnapp.coronatest.worker
import android.content.Context import android.content.Context
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
...@@ -7,16 +7,17 @@ import dagger.assisted.Assisted ...@@ -7,16 +7,17 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.coronatest.CoronaTestRepository import de.rki.coronawarnapp.coronatest.CoronaTestRepository
import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler
import de.rki.coronawarnapp.coronatest.latestPCRT import de.rki.coronawarnapp.coronatest.latestPCRT
import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTest
import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest 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.GeneralNotifications
import de.rki.coronawarnapp.notification.NotificationConstants import de.rki.coronawarnapp.notification.NotificationConstants
import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService
import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.TimeStamper
import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory
import de.rki.coronawarnapp.worker.BackgroundConstants
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import org.joda.time.Duration import org.joda.time.Duration
import org.joda.time.Instant import org.joda.time.Instant
...@@ -24,17 +25,15 @@ import timber.log.Timber ...@@ -24,17 +25,15 @@ import timber.log.Timber
/** /**
* Diagnosis test result retrieval by periodic polling * Diagnosis test result retrieval by periodic polling
*
* @see BackgroundWorkScheduler
*/ */
class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( class PCRResultRetrievalWorker @AssistedInject constructor(
@Assisted val context: Context, @Assisted val context: Context,
@Assisted workerParams: WorkerParameters, @Assisted workerParams: WorkerParameters,
private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService,
private val notificationHelper: GeneralNotifications, private val notificationHelper: GeneralNotifications,
private val coronaTestRepository: CoronaTestRepository, private val coronaTestRepository: CoronaTestRepository,
private val timeStamper: TimeStamper, private val timeStamper: TimeStamper,
private val testResultScheduler: TestResultScheduler, private val testResultScheduler: PCRResultScheduler,
) : CoroutineWorker(context, workerParams) { ) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
...@@ -43,7 +42,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( ...@@ -43,7 +42,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) { if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) {
Timber.tag(TAG).d("$id doWork() failed after $runAttemptCount attempts. Rescheduling") 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") Timber.tag(TAG).d("$id Rescheduled background worker")
return Result.failure() return Result.failure()
...@@ -52,7 +51,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( ...@@ -52,7 +51,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
try { try {
if (abortConditionsMet(timeStamper.nowUTC)) { if (abortConditionsMet(timeStamper.nowUTC)) {
Timber.tag(TAG).d(" $id Stopping worker.") Timber.tag(TAG).d(" $id Stopping worker.")
stopWorker() disablePolling()
} else { } else {
Timber.tag(TAG).d(" $id Running worker.") Timber.tag(TAG).d(" $id Running worker.")
...@@ -71,7 +70,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( ...@@ -71,7 +70,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
cancelRiskLevelScoreNotification() cancelRiskLevelScoreNotification()
Timber.tag(TAG) Timber.tag(TAG)
.d("$id: Test Result available - notification sent & risk level notification canceled") .d("$id: Test Result available - notification sent & risk level notification canceled")
stopWorker() disablePolling()
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
...@@ -87,7 +86,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( ...@@ -87,7 +86,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
private suspend fun abortConditionsMet(nowUTC: Instant): Boolean { private suspend fun abortConditionsMet(nowUTC: Instant): Boolean {
val pcrTest = coronaTestRepository.latestPCRT.first() val pcrTest = coronaTestRepository.latestPCRT.first()
if (pcrTest == null) { if (pcrTest == null) {
Timber.tag(TAG).w("There is no PCR test available!?") Timber.tag(TAG).w("There is no PCR available!?")
return true return true
} }
...@@ -123,15 +122,15 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( ...@@ -123,15 +122,15 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
) )
} }
private fun stopWorker() { private fun disablePolling() {
testResultScheduler.setPeriodicTestPolling(enabled = false) testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false)
Timber.tag(TAG).d("$id: Background worker stopped") Timber.tag(TAG).d("$id: polling disabled")
} }
@AssistedFactory @AssistedFactory
interface Factory : InjectedWorkerFactory<DiagnosisTestResultRetrievalPeriodicWorker> interface Factory : InjectedWorkerFactory<PCRResultRetrievalWorker>
companion object { companion object {
private val TAG = DiagnosisTestResultRetrievalPeriodicWorker::class.java.simpleName private val TAG = PCRResultRetrievalWorker::class.java.simpleName
} }
} }
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
}
}
package de.rki.coronawarnapp.coronatest.execution package de.rki.coronawarnapp.coronatest.worker.execution
import androidx.annotation.VisibleForTesting
import androidx.work.BackoffPolicy import androidx.work.BackoffPolicy
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import dagger.Reusable import dagger.Reusable
import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.coronatest.worker.PCRResultRetrievalWorker
import de.rki.coronawarnapp.util.TimeStamper
import de.rki.coronawarnapp.util.coroutine.await import de.rki.coronawarnapp.util.coroutine.await
import de.rki.coronawarnapp.worker.BackgroundConstants 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.BackgroundWorkHelper
import de.rki.coronawarnapp.worker.DiagnosisTestResultRetrievalPeriodicWorker
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@Reusable @Reusable
class TestResultScheduler @Inject constructor( class PCRResultScheduler @Inject constructor(
private val tracingSettings: TracingSettings,
private val workManager: WorkManager, private val workManager: WorkManager,
private val timeStamper: TimeStamper,
) { ) {
private suspend fun isScheduled(): Boolean { private suspend fun isPcrScheduled() =
val workerInfos = workManager.getWorkInfosForUniqueWork(PCR_TESTRESULT_WORKER_UNIQUEUNAME).await() 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) { if (enabled) {
// TODO Refactor runBlocking away // TODO Refactor runBlocking away
val isScheduled = runBlocking { isScheduled() } val isScheduled = runBlocking { isPcrScheduled() }
if (isScheduled) { if (isScheduled) {
Timber.tag(TAG).w("Already scheduled, skipping") Timber.tag(TAG).w("Already scheduled, skipping")
return 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( workManager.enqueueUniquePeriodicWork(
PCR_TESTRESULT_WORKER_UNIQUEUNAME, PCR_TESTRESULT_WORKER_UNIQUEUNAME,
ExistingPeriodicWorkPolicy.REPLACE, ExistingPeriodicWorkPolicy.REPLACE,
buildDiagnosisTestResultRetrievalPeriodicWork() buildPcrTestResultRetrievalPeriodicWork()
) )
} else { } else {
Timber.tag(TAG).d("cancelWorker()") Timber.tag(TAG).d("cancelWorker()")
...@@ -50,9 +51,9 @@ class TestResultScheduler @Inject constructor( ...@@ -50,9 +51,9 @@ class TestResultScheduler @Inject constructor(
} }
} }
private fun buildDiagnosisTestResultRetrievalPeriodicWork() = private fun buildPcrTestResultRetrievalPeriodicWork() =
PeriodicWorkRequestBuilder<DiagnosisTestResultRetrievalPeriodicWorker>( PeriodicWorkRequestBuilder<PCRResultRetrievalWorker>(
BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(), getPcrTestResultRetrievalPeriodicWorkTimeInterval(),
TimeUnit.MINUTES TimeUnit.MINUTES
) )
.addTag(PCR_TESTRESULT_WORKER_TAG) .addTag(PCR_TESTRESULT_WORKER_TAG)
...@@ -67,9 +68,6 @@ class TestResultScheduler @Inject constructor( ...@@ -67,9 +68,6 @@ class TestResultScheduler @Inject constructor(
) )
.build() .build()
private val WorkInfo.isScheduled: Boolean
get() = state == WorkInfo.State.RUNNING || state == WorkInfo.State.ENQUEUED
companion object { companion object {
/** /**
* Kind initial delay in minutes for periodic work for accessibility reason * Kind initial delay in minutes for periodic work for accessibility reason
...@@ -80,6 +78,17 @@ class TestResultScheduler @Inject constructor( ...@@ -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_TAG = "DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER"
private const val PCR_TESTRESULT_WORKER_UNIQUEUNAME = "DiagnosisTestResultBackgroundPeriodicWork" 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()
} }
} }
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
}
}
...@@ -5,18 +5,19 @@ import dagger.Binds ...@@ -5,18 +5,19 @@ import dagger.Binds
import dagger.Module import dagger.Module
import dagger.multibindings.IntoMap import dagger.multibindings.IntoMap
import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryRetentionWorker 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.datadonation.analytics.worker.DataDonationAnalyticsPeriodicWorker
import de.rki.coronawarnapp.deadman.DeadmanNotificationOneTimeWorker import de.rki.coronawarnapp.deadman.DeadmanNotificationOneTimeWorker
import de.rki.coronawarnapp.deadman.DeadmanNotificationPeriodicWorker import de.rki.coronawarnapp.deadman.DeadmanNotificationPeriodicWorker
import de.rki.coronawarnapp.deniability.BackgroundNoiseOneTimeWorker import de.rki.coronawarnapp.deniability.BackgroundNoiseOneTimeWorker
import de.rki.coronawarnapp.deniability.BackgroundNoisePeriodicWorker import de.rki.coronawarnapp.deniability.BackgroundNoisePeriodicWorker
import de.rki.coronawarnapp.diagnosiskeys.execution.DiagnosisKeyRetrievalWorker 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.nearby.ExposureStateUpdateWorker
import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOutWorker import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOutWorker
import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningWorker 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.submission.auto.SubmissionWorker
import de.rki.coronawarnapp.worker.DiagnosisTestResultRetrievalPeriodicWorker
@Module @Module
abstract class WorkerBinder { abstract class WorkerBinder {
...@@ -51,9 +52,16 @@ abstract class WorkerBinder { ...@@ -51,9 +52,16 @@ abstract class WorkerBinder {
@Binds @Binds
@IntoMap @IntoMap
@WorkerKey(DiagnosisTestResultRetrievalPeriodicWorker::class) @WorkerKey(PCRResultRetrievalWorker::class)
abstract fun testResultRetrievalPeriodic( abstract fun pcrTestResultRetrievalPeriodic(
factory: DiagnosisTestResultRetrievalPeriodicWorker.Factory factory: PCRResultRetrievalWorker.Factory
): InjectedWorkerFactory<out ListenableWorker>
@Binds
@IntoMap
@WorkerKey(RAResultRetrievalWorker::class)
abstract fun ratResultRetrievalPeriodic(
factory: RAResultRetrievalWorker.Factory
): InjectedWorkerFactory<out ListenableWorker> ): InjectedWorkerFactory<out ListenableWorker>
@Binds @Binds
......
...@@ -13,19 +13,6 @@ import kotlin.random.Random ...@@ -13,19 +13,6 @@ import kotlin.random.Random
*/ */
object BackgroundWorkHelper { 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 * Get background noise one time work delay
* The periodic job is already delayed by MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION * The periodic job is already delayed by MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
......
package de.rki.coronawarnapp.worker package de.rki.coronawarnapp.worker
import de.rki.coronawarnapp.coronatest.CoronaTestRepository 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.deniability.NoiseScheduler
import de.rki.coronawarnapp.risk.execution.RiskWorkScheduler import de.rki.coronawarnapp.risk.execution.RiskWorkScheduler
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
...@@ -21,7 +21,7 @@ import javax.inject.Singleton ...@@ -21,7 +21,7 @@ import javax.inject.Singleton
class BackgroundWorkScheduler @Inject constructor( class BackgroundWorkScheduler @Inject constructor(
private val riskWorkScheduler: RiskWorkScheduler, private val riskWorkScheduler: RiskWorkScheduler,
private val coronaTestRepository: CoronaTestRepository, private val coronaTestRepository: CoronaTestRepository,
private val testResultScheduler: TestResultScheduler, private val testResultScheduler: PCRResultScheduler,
private val noiseScheduler: NoiseScheduler, private val noiseScheduler: NoiseScheduler,
) { ) {
...@@ -36,14 +36,14 @@ class BackgroundWorkScheduler @Inject constructor( ...@@ -36,14 +36,14 @@ class BackgroundWorkScheduler @Inject constructor(
val hasPendingTests = coronatests.any { !it.isResultAvailableNotificationSent } val hasPendingTests = coronatests.any { !it.isResultAvailableNotificationSent }
if (!isSubmissionSuccessful && hasPendingTests) { if (!isSubmissionSuccessful && hasPendingTests) {
testResultScheduler.setPeriodicTestPolling(enabled = true) testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = true)
} }
} }
fun stopWorkScheduler() { fun stopWorkScheduler() {
noiseScheduler.setPeriodicNoise(enabled = false) noiseScheduler.setPeriodicNoise(enabled = false)
riskWorkScheduler.setPeriodicRiskCalculation(enabled = false) riskWorkScheduler.setPeriodicRiskCalculation(enabled = false)
testResultScheduler.setPeriodicTestPolling(enabled = false) testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false)
Timber.d("All Background Jobs Stopped") Timber.d("All Background Jobs Stopped")
} }
} }
...@@ -7,7 +7,8 @@ import dagger.Component ...@@ -7,7 +7,8 @@ import dagger.Component
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import de.rki.coronawarnapp.coronatest.CoronaTestRepository 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.Analytics
import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler
import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
...@@ -162,5 +163,8 @@ class MockProvider { ...@@ -162,5 +163,8 @@ class MockProvider {
fun noiseScheduler(): NoiseScheduler = mockk() fun noiseScheduler(): NoiseScheduler = mockk()
@Provides @Provides
fun testResultScheduler(): TestResultScheduler = mockk() fun pcrTestResultScheduler(): PCRResultScheduler = mockk()
@Provides
fun ratResultScheduler(): RAResultScheduler = mockk()
} }
package de.rki.coronawarnapp.worker package de.rki.coronawarnapp.worker
import androidx.work.NetworkType import androidx.work.NetworkType
import de.rki.coronawarnapp.coronatest.worker.execution.PCRResultScheduler
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
...@@ -9,7 +10,7 @@ class BackgroundWorkHelperTest { ...@@ -9,7 +10,7 @@ class BackgroundWorkHelperTest {
@Test @Test
fun getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval() { fun getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval() {
Assert.assertEquals( Assert.assertEquals(
BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(), PCRResultScheduler.getPcrTestResultRetrievalPeriodicWorkTimeInterval(),
120 120
) )
} }
......
...@@ -5,10 +5,11 @@ import androidx.work.ListenableWorker ...@@ -5,10 +5,11 @@ import androidx.work.ListenableWorker
import androidx.work.WorkRequest import androidx.work.WorkRequest
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import de.rki.coronawarnapp.coronatest.CoronaTestRepository 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.server.CoronaTestResult
import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTest
import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest 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.GeneralNotifications
import de.rki.coronawarnapp.notification.NotificationConstants import de.rki.coronawarnapp.notification.NotificationConstants
import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService
...@@ -48,7 +49,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -48,7 +49,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
@MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool @MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool
@MockK lateinit var timeStamper: TimeStamper @MockK lateinit var timeStamper: TimeStamper
@MockK lateinit var coronaTestRepository: CoronaTestRepository @MockK lateinit var coronaTestRepository: CoronaTestRepository
@MockK lateinit var testResultScheduler: TestResultScheduler @MockK lateinit var testResultScheduler: PCRResultScheduler
@RelaxedMockK lateinit var workerParams: WorkerParameters @RelaxedMockK lateinit var workerParams: WorkerParameters
private val currentInstant = Instant.ofEpochSecond(1611764225) private val currentInstant = Instant.ofEpochSecond(1611764225)
...@@ -66,7 +67,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -66,7 +67,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory
every { appComponent.errorResetTool } returns encryptionErrorResetTool 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 every { notificationHelper.cancelCurrentNotification(any()) } just Runs
...@@ -94,7 +95,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -94,7 +95,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
} }
} }
private fun createWorker() = DiagnosisTestResultRetrievalPeriodicWorker( private fun createWorker() = PCRResultRetrievalWorker(
context = context, context = context,
workerParams = workerParams, workerParams = workerParams,
testResultAvailableNotificationService = testResultAvailableNotificationService, testResultAvailableNotificationService = testResultAvailableNotificationService,
...@@ -111,7 +112,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -111,7 +112,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
val result = createWorker().doWork() val result = createWorker().doWork()
coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } 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() result shouldBe ListenableWorker.Result.success()
} }
...@@ -122,7 +123,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -122,7 +123,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
val result = createWorker().doWork() val result = createWorker().doWork()
coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } 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() result shouldBe ListenableWorker.Result.success()
} }
...@@ -135,7 +136,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -135,7 +136,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
val result = createWorker().doWork() val result = createWorker().doWork()
coVerify(exactly = 0) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } 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() result shouldBe ListenableWorker.Result.success()
} }
...@@ -236,7 +237,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -236,7 +237,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
notificationHelper.cancelCurrentNotification( notificationHelper.cancelCurrentNotification(
NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
) )
testResultScheduler.setPeriodicTestPolling(enabled = false) testResultScheduler.setPcrPeriodicTestPollingEnabled(enabled = false)
} }
result shouldBe ListenableWorker.Result.success() result shouldBe ListenableWorker.Result.success()
...@@ -250,7 +251,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { ...@@ -250,7 +251,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() {
val result = createWorker().doWork() val result = createWorker().doWork()
coVerify(exactly = 1) { coronaTestRepository.refresh(type = CoronaTest.Type.PCR) } 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() result shouldBe ListenableWorker.Result.retry()
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment