From 93e0e12c79c588917b7b406823682587028d41cc Mon Sep 17 00:00:00 2001 From: Matthias Urhahn <matthias.urhahn@sap.com> Date: Fri, 30 Oct 2020 14:01:56 +0100 Subject: [PATCH] Dependency injection for workers (DEV) (#1503) * Allow WorkerManager's workers to be injected. * Remove static access for Playbook * Add mock for playbook so dagger can create the worker factory map. --- Corona-Warn-App/build.gradle | 3 + .../coronawarnapp/CoronaWarnApplication.kt | 8 +- .../nearby/ExposureStateUpdateWorker.kt | 12 ++- .../util/di/ApplicationComponent.kt | 5 +- .../util/worker/CWAWorkerFactory.kt | 37 +++++++++ .../util/worker/InjectedWorkerFactory.kt | 9 +++ .../util/worker/WorkManagerSetup.kt | 28 +++++++ .../coronawarnapp/util/worker/WorkerBinder.kt | 58 ++++++++++++++ .../coronawarnapp/util/worker/WorkerKey.kt | 10 +++ .../worker/BackgroundNoiseOneTimeWorker.kt | 18 +++-- .../worker/BackgroundNoisePeriodicWorker.kt | 15 ++-- .../DiagnosisKeyRetrievalOneTimeWorker.kt | 12 ++- .../DiagnosisKeyRetrievalPeriodicWorker.kt | 12 ++- ...gnosisTestResultRetrievalPeriodicWorker.kt | 27 ++++--- .../task/testtasks/timeout/BaseTimeoutTask.kt | 3 +- .../util/worker/WorkerBinderTest.kt | 76 +++++++++++++++++++ 16 files changed, 296 insertions(+), 37 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/CWAWorkerFactory.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/InjectedWorkerFactory.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkManagerSetup.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerKey.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 0848d3372..5ed563edf 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -329,6 +329,7 @@ dependencies { implementation 'com.google.dagger:dagger-android-support:2.28.1' kapt 'com.google.dagger:dagger-compiler:2.28.1' kapt 'com.google.dagger:dagger-android-processor:2.28.1' + kaptTest 'com.google.dagger:dagger-compiler:2.28.1' kaptAndroidTest 'com.google.dagger:dagger-compiler:2.28.1' compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2' @@ -366,6 +367,8 @@ dependencies { androidTestImplementation "io.kotest:kotest-assertions-core-jvm:4.3.0" androidTestImplementation "io.kotest:kotest-property-jvm:4.3.0" + testImplementation "io.github.classgraph:classgraph:4.8.90" + // Testing - Instrumentation androidTestImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test:runner:1.3.0' diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt index fa995ee84..473252103 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt @@ -7,8 +7,6 @@ import android.content.IntentFilter import android.os.Bundle import android.view.WindowManager import androidx.localbroadcastmanager.content.LocalBroadcastManager -import androidx.work.Configuration -import androidx.work.WorkManager import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector @@ -22,6 +20,7 @@ import de.rki.coronawarnapp.util.ForegroundState import de.rki.coronawarnapp.util.WatchdogService import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent +import de.rki.coronawarnapp.util.worker.WorkManagerSetup import de.rki.coronawarnapp.worker.BackgroundWorkHelper import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn @@ -42,6 +41,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { @Inject lateinit var watchdogService: WatchdogService @Inject lateinit var taskController: TaskController @Inject lateinit var foregroundState: ForegroundState + @Inject lateinit var workManagerSetup: WorkManagerSetup @LogHistoryTree @Inject lateinit var rollingLogHistory: Timber.Tree override fun onCreate() { @@ -55,9 +55,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { Timber.plant(rollingLogHistory) Timber.v("onCreate(): Initializing WorkManager") - Configuration.Builder() - .apply { setMinimumLoggingLevel(android.util.Log.DEBUG) }.build() - .let { WorkManager.initialize(this, it) } + workManagerSetup.setup() NotificationHelper.createNotificationChannel() 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 8af906afe..9fb027c19 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 @@ -5,16 +5,21 @@ import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.google.android.gms.common.api.ApiException import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.NoTokenException import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.storage.ExposureSummaryRepository import de.rki.coronawarnapp.transaction.RiskLevelTransaction +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import timber.log.Timber -class ExposureStateUpdateWorker(val context: Context, workerParams: WorkerParameters) : - CoroutineWorker(context, workerParams) { +class ExposureStateUpdateWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { companion object { private val TAG = ExposureStateUpdateWorker::class.simpleName } @@ -44,4 +49,7 @@ class ExposureStateUpdateWorker(val context: Context, workerParams: WorkerParame return Result.success() } + + @AssistedInject.Factory + interface Factory : InjectedWorkerFactory<ExposureStateUpdateWorker> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt index 1e6ce322a..ea9200f77 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt @@ -39,6 +39,7 @@ import de.rki.coronawarnapp.util.device.DeviceModule import de.rki.coronawarnapp.util.security.EncryptedPreferencesFactory import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool import de.rki.coronawarnapp.util.serialization.SerializationModule +import de.rki.coronawarnapp.util.worker.WorkerBinder import de.rki.coronawarnapp.verification.VerificationModule import javax.inject.Singleton @@ -66,8 +67,8 @@ import javax.inject.Singleton TaskModule::class, DeviceForTestersModule::class, BugReportingModule::class, - SerializationModule::class - + SerializationModule::class, + WorkerBinder::class ] ) interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/CWAWorkerFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/CWAWorkerFactory.kt new file mode 100644 index 000000000..9b491808f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/CWAWorkerFactory.kt @@ -0,0 +1,37 @@ +package de.rki.coronawarnapp.util.worker + +import android.content.Context +import androidx.work.ListenableWorker +import androidx.work.WorkerFactory +import androidx.work.WorkerParameters +import dagger.Reusable +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Provider + +@Reusable +class CWAWorkerFactory @Inject constructor( + private val factories: @JvmSuppressWildcards Map< + Class<out ListenableWorker>, Provider<InjectedWorkerFactory<out ListenableWorker>> + > +) : WorkerFactory() { + + init { + Timber.v("CWAWorkerFactory ready. Known factories: %s", factories) + } + + override fun createWorker( + appContext: Context, + workerClassName: String, + workerParameters: WorkerParameters + ): ListenableWorker? { + Timber.v("Looking up worker for %s", workerClassName) + val factory = factories.entries.find { + Class.forName(workerClassName).isAssignableFrom(it.key) + }?.value + + requireNotNull(factory) { "Unknown worker: $workerClassName" } + Timber.v("Creating worker for %s with %s", workerClassName, workerParameters) + return factory.get().create(appContext, workerParameters) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/InjectedWorkerFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/InjectedWorkerFactory.kt new file mode 100644 index 000000000..1986ef02b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/InjectedWorkerFactory.kt @@ -0,0 +1,9 @@ +package de.rki.coronawarnapp.util.worker + +import android.content.Context +import androidx.work.ListenableWorker +import androidx.work.WorkerParameters + +interface InjectedWorkerFactory<T : ListenableWorker> { + fun create(context: Context, workerParams: WorkerParameters): T +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkManagerSetup.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkManagerSetup.kt new file mode 100644 index 000000000..3e7c0ee22 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkManagerSetup.kt @@ -0,0 +1,28 @@ +package de.rki.coronawarnapp.util.worker + +import android.content.Context +import androidx.work.Configuration +import androidx.work.WorkManager +import de.rki.coronawarnapp.util.di.AppContext +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WorkManagerSetup @Inject constructor( + @AppContext private val context: Context, + private val cwaWorkerFactory: CWAWorkerFactory +) { + + fun setup() { + Timber.v("Setting up WorkManager.") + val configuration = Configuration.Builder().apply { + setMinimumLoggingLevel(android.util.Log.DEBUG) + setWorkerFactory(cwaWorkerFactory) + }.build() + + WorkManager.initialize(context, configuration) + + Timber.v("WorkManager setup done.") + } +} 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 new file mode 100644 index 000000000..7bae929f2 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerBinder.kt @@ -0,0 +1,58 @@ +package de.rki.coronawarnapp.util.worker + +import androidx.work.ListenableWorker +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import de.rki.coronawarnapp.nearby.ExposureStateUpdateWorker +import de.rki.coronawarnapp.worker.BackgroundNoiseOneTimeWorker +import de.rki.coronawarnapp.worker.BackgroundNoisePeriodicWorker +import de.rki.coronawarnapp.worker.DiagnosisKeyRetrievalOneTimeWorker +import de.rki.coronawarnapp.worker.DiagnosisKeyRetrievalPeriodicWorker +import de.rki.coronawarnapp.worker.DiagnosisTestResultRetrievalPeriodicWorker + +@Module +abstract class WorkerBinder { + + @Binds + @IntoMap + @WorkerKey(ExposureStateUpdateWorker::class) + abstract fun bindExposureStateUpdate( + factory: ExposureStateUpdateWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> + + @Binds + @IntoMap + @WorkerKey(BackgroundNoiseOneTimeWorker::class) + abstract fun backgroundNoiseOneTime( + factory: BackgroundNoiseOneTimeWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> + + @Binds + @IntoMap + @WorkerKey(BackgroundNoisePeriodicWorker::class) + abstract fun backgroundNoisePeriodic( + factory: BackgroundNoisePeriodicWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> + + @Binds + @IntoMap + @WorkerKey(DiagnosisKeyRetrievalOneTimeWorker::class) + abstract fun diagnosisKeyRetrievalOneTime( + factory: DiagnosisKeyRetrievalOneTimeWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> + + @Binds + @IntoMap + @WorkerKey(DiagnosisKeyRetrievalPeriodicWorker::class) + abstract fun diagnosisKeyRetrievalPeriodic( + factory: DiagnosisKeyRetrievalPeriodicWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> + + @Binds + @IntoMap + @WorkerKey(DiagnosisTestResultRetrievalPeriodicWorker::class) + abstract fun testResultRetrievalPeriodic( + factory: DiagnosisTestResultRetrievalPeriodicWorker.Factory + ): InjectedWorkerFactory<out ListenableWorker> +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerKey.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerKey.kt new file mode 100644 index 000000000..27d263540 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/worker/WorkerKey.kt @@ -0,0 +1,10 @@ +package de.rki.coronawarnapp.util.worker + +import androidx.work.ListenableWorker +import dagger.MapKey +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@MapKey +internal annotation class WorkerKey(val value: KClass<out ListenableWorker>) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoiseOneTimeWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoiseOneTimeWorker.kt index 724ac760c..4dfc187b4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoiseOneTimeWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoiseOneTimeWorker.kt @@ -3,22 +3,21 @@ package de.rki.coronawarnapp.worker import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.playbook.Playbook -import de.rki.coronawarnapp.util.di.AppInjector +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory /** * One time background noise worker * * @see BackgroundWorkScheduler */ -class BackgroundNoiseOneTimeWorker( - val context: Context, - workerParams: WorkerParameters -) : - CoroutineWorker(context, workerParams) { - +class BackgroundNoiseOneTimeWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters, private val playbook: Playbook - get() = AppInjector.component.playbook +) : CoroutineWorker(context, workerParams) { /** * Work execution @@ -41,4 +40,7 @@ class BackgroundNoiseOneTimeWorker( return result } + + @AssistedInject.Factory + interface Factory : InjectedWorkerFactory<BackgroundNoiseOneTimeWorker> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt index 3b9a93eb9..3869efb0a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/BackgroundNoisePeriodicWorker.kt @@ -3,7 +3,10 @@ package de.rki.coronawarnapp.worker import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import de.rki.coronawarnapp.worker.BackgroundWorkScheduler.stop import org.joda.time.DateTime import org.joda.time.DateTimeZone @@ -14,11 +17,10 @@ import timber.log.Timber * * @see BackgroundWorkScheduler */ -class BackgroundNoisePeriodicWorker( - val context: Context, - workerParams: WorkerParameters -) : - CoroutineWorker(context, workerParams) { +class BackgroundNoisePeriodicWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { companion object { private val TAG: String? = BackgroundNoisePeriodicWorker::class.simpleName @@ -61,4 +63,7 @@ class BackgroundNoisePeriodicWorker( private fun stopWorker() { BackgroundWorkScheduler.WorkType.BACKGROUND_NOISE_PERIODIC_WORK.stop() } + + @AssistedInject.Factory + interface Factory : InjectedWorkerFactory<BackgroundNoisePeriodicWorker> } 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 95cf66a25..275708013 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 @@ -3,7 +3,10 @@ package de.rki.coronawarnapp.worker import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import timber.log.Timber /** @@ -12,8 +15,10 @@ import timber.log.Timber * * @see BackgroundWorkScheduler */ -class DiagnosisKeyRetrievalOneTimeWorker(val context: Context, workerParams: WorkerParameters) : - CoroutineWorker(context, workerParams) { +class DiagnosisKeyRetrievalOneTimeWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { /** * Work execution @@ -59,4 +64,7 @@ class DiagnosisKeyRetrievalOneTimeWorker(val context: Context, workerParams: Wor Timber.d("$id: doWork() finished with %s", result) return result } + + @AssistedInject.Factory + interface Factory : InjectedWorkerFactory<DiagnosisKeyRetrievalOneTimeWorker> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt index f7baa0f08..f0dc4742c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisKeyRetrievalPeriodicWorker.kt @@ -3,6 +3,9 @@ package de.rki.coronawarnapp.worker import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import timber.log.Timber /** @@ -12,8 +15,10 @@ import timber.log.Timber * @see BackgroundWorkScheduler * @see DiagnosisKeyRetrievalOneTimeWorker */ -class DiagnosisKeyRetrievalPeriodicWorker(val context: Context, workerParams: WorkerParameters) : - CoroutineWorker(context, workerParams) { +class DiagnosisKeyRetrievalPeriodicWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { /** * Work execution @@ -60,4 +65,7 @@ class DiagnosisKeyRetrievalPeriodicWorker(val context: Context, workerParams: Wo Timber.d("$id: doWork() finished with %s", result) return result } + + @AssistedInject.Factory + interface Factory : InjectedWorkerFactory<DiagnosisKeyRetrievalPeriodicWorker> } 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/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt index 03543b63c..e89997277 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt @@ -4,6 +4,8 @@ import android.content.Context import androidx.core.app.NotificationCompat import androidx.work.CoroutineWorker import androidx.work.WorkerParameters +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R import de.rki.coronawarnapp.notification.NotificationHelper @@ -11,6 +13,7 @@ import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.TimeAndDateExtensions import de.rki.coronawarnapp.util.formatter.TestResult +import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import de.rki.coronawarnapp.worker.BackgroundWorkScheduler.stop import timber.log.Timber @@ -19,11 +22,10 @@ import timber.log.Timber * * @see BackgroundWorkScheduler */ -class DiagnosisTestResultRetrievalPeriodicWorker( - val context: Context, - workerParams: WorkerParameters -) : - CoroutineWorker(context, workerParams) { +class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( + @Assisted val context: Context, + @Assisted workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { companion object { private val TAG: String? = DiagnosisTestResultRetrievalPeriodicWorker::class.simpleName @@ -44,13 +46,15 @@ class DiagnosisTestResultRetrievalPeriodicWorker( Timber.d("Background job started. Run attempt: $runAttemptCount") BackgroundWorkHelper.sendDebugNotification( - "TestResult Executing: Start", "TestResult started. Run attempt: $runAttemptCount ") + "TestResult Executing: Start", "TestResult started. Run attempt: $runAttemptCount " + ) if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) { Timber.d("Background job failed after $runAttemptCount attempts. Rescheduling") BackgroundWorkHelper.sendDebugNotification( - "TestResult Executing: Failure", "TestResult failed with $runAttemptCount attempts") + "TestResult Executing: Failure", "TestResult failed with $runAttemptCount attempts" + ) BackgroundWorkScheduler.scheduleDiagnosisKeyPeriodicWork() return Result.failure() @@ -72,7 +76,8 @@ class DiagnosisTestResultRetrievalPeriodicWorker( } BackgroundWorkHelper.sendDebugNotification( - "TestResult Executing: End", "TestResult result: $result ") + "TestResult Executing: End", "TestResult result: $result " + ) return result } @@ -121,6 +126,10 @@ class DiagnosisTestResultRetrievalPeriodicWorker( BackgroundWorkScheduler.WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.stop() BackgroundWorkHelper.sendDebugNotification( - "TestResult Stopped", "TestResult Stopped") + "TestResult Stopped", "TestResult Stopped" + ) } + + @AssistedInject.Factory + interface Factory : InjectedWorkerFactory<DiagnosisTestResultRetrievalPeriodicWorker> } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/timeout/BaseTimeoutTask.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/timeout/BaseTimeoutTask.kt index 7879b015a..d5e513abb 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/timeout/BaseTimeoutTask.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/timeout/BaseTimeoutTask.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import timber.log.Timber -import javax.inject.Inject import javax.inject.Provider abstract class BaseTimeoutTask : Task<DefaultProgress, TimeoutTaskResult> { @@ -39,7 +38,7 @@ abstract class BaseTimeoutTask : Task<DefaultProgress, TimeoutTaskResult> { isCanceled = true } - abstract class Factory @Inject constructor( + abstract class Factory constructor( private val taskByDagger: Provider<BaseTimeoutTask> ) : TaskFactory<DefaultProgress, TimeoutTaskResult> { 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 new file mode 100644 index 000000000..1f41df012 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt @@ -0,0 +1,76 @@ +package de.rki.coronawarnapp.util.worker + +import androidx.work.ListenableWorker +import dagger.Component +import dagger.Module +import dagger.Provides +import de.rki.coronawarnapp.playbook.Playbook +import de.rki.coronawarnapp.util.di.AssistedInjectModule +import io.github.classgraph.ClassGraph +import io.kotest.matchers.collections.shouldContainAll +import io.mockk.mockk +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import timber.log.Timber +import javax.inject.Provider +import javax.inject.Singleton + +class WorkerBinderTest : BaseTest() { + + /** + * If one of our factories is not part of the factory map provided to **[CWAWorkerFactory]**, + * then the lookup will fail and an exception thrown. + * This can't be checked at compile-time and may create subtle errors that will not immediately be caught. + * + * This test uses the ClassGraph library to scan our package, find all worker classes, + * and makes sure that they are all bound into our factory map. + * Creating a new factory that is not registered or removing one from **[WorkerBinder]** + * will cause this test to fail. + */ + @Test + fun `all worker factory are bound into the factory map`() { + val component = DaggerWorkerTestComponent.factory().create() + val factories = component.factories + + Timber.v("We know %d worker factories.", factories.size) + factories.keys.forEach { + Timber.v("Registered: ${it.name}") + } + require(component.factories.isNotEmpty()) + + val scanResult = ClassGraph() + .acceptPackages("de.rki.coronawarnapp") + .enableClassInfo() + .scan() + + val ourWorkerClasses = scanResult + .getSubclasses("androidx.work.ListenableWorker") + .filterNot { it.name.startsWith("androidx.work") } + + Timber.v("Our project contains %d worker classes.", ourWorkerClasses.size) + ourWorkerClasses.forEach { Timber.v("Existing: ${it.name}") } + + val boundFactories = factories.keys.map { it.name } + val existingFactories = ourWorkerClasses.map { it.name } + boundFactories shouldContainAll existingFactories + } +} + +@Singleton +@Component(modules = [AssistedInjectModule::class, WorkerBinder::class, MockProvider::class]) +interface WorkerTestComponent { + + val factories: @JvmSuppressWildcards Map<Class<out ListenableWorker>, Provider<InjectedWorkerFactory<out ListenableWorker>>> + + @Component.Factory + interface Factory { + fun create(): WorkerTestComponent + } +} + +@Module +class MockProvider { + // For BackgroundNoiseOneTimeWorker + @Provides + fun playbook(): Playbook = mockk() +} -- GitLab