From 39eea4ecb5cb2877ad1b7fca0db8f1ec9c145dd9 Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Fri, 8 May 2020 13:42:27 +0100 Subject: [PATCH 1/5] Add WorkManager RxJava support --- app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle b/app/build.gradle index df5a19712903..174ae4e198e8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,6 +187,7 @@ dependencies { // WorkManager implementation "androidx.work:work-runtime-ktx:$workManager" + implementation "androidx.work:work-rxjava2:$workManager" // Dagger kapt "com.google.dagger:dagger-android-processor:$dagger" From e2b5eb9378b7d8f45e57290772d1632afafe06c4 Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Fri, 8 May 2020 13:45:22 +0100 Subject: [PATCH 2/5] Migrate app config sync from JobScheduler to WorkManager --- .../duckduckgo/app/di/DaggerWorkerFactory.kt | 9 +++ .../com/duckduckgo/app/di/NetworkModule.kt | 10 +-- .../com/duckduckgo/app/di/WorkerModule.kt | 8 ++- .../app/global/DuckDuckGoApplication.kt | 2 +- .../AppConfigurationSyncWorkRequestBuilder.kt | 61 +++++++++++++++++++ .../duckduckgo/app/global/job/JobBuilder.kt | 40 ------------ .../app/job/AppConfigurationJobService.kt | 44 +++---------- .../app/job/AppConfigurationSyncer.kt | 35 +++-------- 8 files changed, 101 insertions(+), 108 deletions(-) create mode 100644 app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt delete mode 100644 app/src/main/java/com/duckduckgo/app/global/job/JobBuilder.kt diff --git a/app/src/main/java/com/duckduckgo/app/di/DaggerWorkerFactory.kt b/app/src/main/java/com/duckduckgo/app/di/DaggerWorkerFactory.kt index e7866f1130a2..db2d0bcbe6eb 100644 --- a/app/src/main/java/com/duckduckgo/app/di/DaggerWorkerFactory.kt +++ b/app/src/main/java/com/duckduckgo/app/di/DaggerWorkerFactory.kt @@ -22,7 +22,10 @@ import androidx.work.ListenableWorker import androidx.work.WorkerFactory import androidx.work.WorkerParameters import com.duckduckgo.app.fire.DataClearingWorker +import com.duckduckgo.app.global.job.AppConfigurationWorker import com.duckduckgo.app.global.view.ClearDataAction +import com.duckduckgo.app.job.AppConfigurationDownloader +import com.duckduckgo.app.job.ConfigurationDownloader import com.duckduckgo.app.notification.NotificationScheduler.ClearDataNotificationWorker import com.duckduckgo.app.notification.NotificationScheduler.PrivacyNotificationWorker import com.duckduckgo.app.notification.NotificationFactory @@ -44,6 +47,7 @@ class DaggerWorkerFactory( private val notificationFactory: NotificationFactory, private val clearDataNotification: ClearDataNotification, private val privacyProtectionNotification: PrivacyProtectionNotification, + private val configurationDownloader: ConfigurationDownloader, private val pixel: Pixel ) : WorkerFactory() { @@ -59,6 +63,7 @@ class DaggerWorkerFactory( is DataClearingWorker -> injectDataClearWorker(instance) is ClearDataNotificationWorker -> injectClearDataNotificationWorker(instance) is PrivacyNotificationWorker -> injectPrivacyNotificationWorker(instance) + is AppConfigurationWorker -> injectAppConfigurationWorker(instance) else -> Timber.i("No injection required for worker $workerClassName") } @@ -70,6 +75,10 @@ class DaggerWorkerFactory( } + private fun injectAppConfigurationWorker(worker: AppConfigurationWorker) { + worker.appConfigurationDownloader = configurationDownloader + } + private fun injectOfflinePixelWorker(worker: OfflinePixelScheduler.OfflinePixelWorker) { worker.offlinePixelSender = offlinePixelSender } diff --git a/app/src/main/java/com/duckduckgo/app/di/NetworkModule.kt b/app/src/main/java/com/duckduckgo/app/di/NetworkModule.kt index ce5bb83db449..d8c231aa8d63 100644 --- a/app/src/main/java/com/duckduckgo/app/di/NetworkModule.kt +++ b/app/src/main/java/com/duckduckgo/app/di/NetworkModule.kt @@ -16,8 +16,8 @@ package com.duckduckgo.app.di -import android.app.job.JobScheduler import android.content.Context +import androidx.work.WorkManager import com.duckduckgo.app.autocomplete.api.AutoCompleteService import com.duckduckgo.app.brokensite.api.BrokenSiteSender import com.duckduckgo.app.brokensite.api.BrokenSiteSubmitter @@ -28,7 +28,7 @@ import com.duckduckgo.app.feedback.api.SubReasonApiMapper import com.duckduckgo.app.global.AppUrl.Url import com.duckduckgo.app.global.api.ApiRequestInterceptor import com.duckduckgo.app.global.api.NetworkApiCache -import com.duckduckgo.app.global.job.JobBuilder +import com.duckduckgo.app.global.job.AppConfigurationSyncWorkRequestBuilder import com.duckduckgo.app.httpsupgrade.api.HttpsUpgradeService import com.duckduckgo.app.job.AppConfigurationSyncer import com.duckduckgo.app.job.ConfigurationDownloader @@ -155,11 +155,11 @@ class NetworkModule { @Provides @Singleton fun appConfigurationSyncer( - jobBuilder: JobBuilder, - jobScheduler: JobScheduler, + appConfigurationSyncWorkRequestBuilder: AppConfigurationSyncWorkRequestBuilder, + workManager: WorkManager, appConfigurationDownloader: ConfigurationDownloader ): AppConfigurationSyncer { - return AppConfigurationSyncer(jobBuilder, jobScheduler, appConfigurationDownloader) + return AppConfigurationSyncer(appConfigurationSyncWorkRequestBuilder, workManager, appConfigurationDownloader) } companion object { diff --git a/app/src/main/java/com/duckduckgo/app/di/WorkerModule.kt b/app/src/main/java/com/duckduckgo/app/di/WorkerModule.kt index 9f7445d6945c..3531ec1ba556 100644 --- a/app/src/main/java/com/duckduckgo/app/di/WorkerModule.kt +++ b/app/src/main/java/com/duckduckgo/app/di/WorkerModule.kt @@ -22,6 +22,8 @@ import androidx.work.Configuration import androidx.work.WorkManager import androidx.work.WorkerFactory import com.duckduckgo.app.global.view.ClearDataAction +import com.duckduckgo.app.job.AppConfigurationDownloader +import com.duckduckgo.app.job.ConfigurationDownloader import com.duckduckgo.app.notification.NotificationFactory import com.duckduckgo.app.notification.db.NotificationDao import com.duckduckgo.app.notification.model.ClearDataNotification @@ -52,22 +54,24 @@ class WorkerModule { offlinePixelSender: OfflinePixelSender, settingsDataStore: SettingsDataStore, clearDataAction: ClearDataAction, - notficationManager: NotificationManagerCompat, + notificationManager: NotificationManagerCompat, notificationDao: NotificationDao, notificationFactory: NotificationFactory, clearDataNotification: ClearDataNotification, privacyProtectionNotification: PrivacyProtectionNotification, + configurationDownloader: ConfigurationDownloader, pixel: Pixel ): WorkerFactory { return DaggerWorkerFactory( offlinePixelSender, settingsDataStore, clearDataAction, - notficationManager, + notificationManager, notificationDao, notificationFactory, clearDataNotification, privacyProtectionNotification, + configurationDownloader, pixel ) } diff --git a/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt b/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt index 83097f16a618..d0e3ce528474 100644 --- a/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt +++ b/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt @@ -266,7 +266,7 @@ open class DuckDuckGoApplication : HasAndroidInjector, Application(), LifecycleO appConfigurationSyncer.scheduleImmediateSync() .subscribeOn(Schedulers.io()) .doAfterTerminate { - appConfigurationSyncer.scheduleRegularSync(this) + appConfigurationSyncer.scheduleRegularSync() } .subscribe({}, { Timber.w("Failed to download initial app configuration ${it.localizedMessage}") }) } diff --git a/app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt b/app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt new file mode 100644 index 000000000000..fc049b259905 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.global.job + +import android.content.Context +import androidx.work.* +import com.duckduckgo.app.job.ConfigurationDownloader +import io.reactivex.Single +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class AppConfigurationSyncWorkRequestBuilder @Inject constructor() { + + fun appConfigurationWork(): PeriodicWorkRequest { + return PeriodicWorkRequestBuilder(12, TimeUnit.HOURS) + .setConstraints(networkAvailable()) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 60, TimeUnit.MINUTES) + .addTag(APP_CONFIG_SYNC_WORK_TAG) + .build() + } + + private fun networkAvailable() = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build() + + companion object { + const val APP_CONFIG_SYNC_WORK_TAG = "AppConfigurationWorker" + } +} + +class AppConfigurationWorker(context: Context, workerParams: WorkerParameters) : RxWorker(context, workerParams) { + + @Inject + lateinit var appConfigurationDownloader: ConfigurationDownloader + + override fun createWork(): Single { + Timber.i("Running app config sync") + return appConfigurationDownloader.downloadTask() + .toSingle { + Timber.i("App configuration sync was successful") + Result.success() + } + .onErrorReturn { + Timber.w(it, "App configuration sync work failed") + Result.retry() + } + } +} diff --git a/app/src/main/java/com/duckduckgo/app/global/job/JobBuilder.kt b/app/src/main/java/com/duckduckgo/app/global/job/JobBuilder.kt deleted file mode 100644 index 26c7526113bf..000000000000 --- a/app/src/main/java/com/duckduckgo/app/global/job/JobBuilder.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2017 DuckDuckGo - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.duckduckgo.app.global.job - -import android.app.job.JobInfo -import android.app.job.JobInfo.BACKOFF_POLICY_EXPONENTIAL -import android.content.ComponentName -import android.content.Context -import com.duckduckgo.app.job.AppConfigurationJobService -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -const val APP_CONFIGURATION_JOB_ID = 1 - -class JobBuilder @Inject constructor() { - - fun appConfigurationJob(context: Context): JobInfo { - - return JobInfo.Builder(APP_CONFIGURATION_JOB_ID, ComponentName(context, AppConfigurationJobService::class.java)) - .setPeriodic(TimeUnit.HOURS.toMillis(12)) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) - .setBackoffCriteria(TimeUnit.MINUTES.toMillis(60), BACKOFF_POLICY_EXPONENTIAL) - .setPersisted(true) - .build() - } -} diff --git a/app/src/main/java/com/duckduckgo/app/job/AppConfigurationJobService.kt b/app/src/main/java/com/duckduckgo/app/job/AppConfigurationJobService.kt index 2138fb9ebe74..ef6ef771d393 100644 --- a/app/src/main/java/com/duckduckgo/app/job/AppConfigurationJobService.kt +++ b/app/src/main/java/com/duckduckgo/app/job/AppConfigurationJobService.kt @@ -17,20 +17,17 @@ package com.duckduckgo.app.job import android.app.job.JobParameters +import android.app.job.JobScheduler import android.app.job.JobService import dagger.android.AndroidInjection -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers import timber.log.Timber import javax.inject.Inject - +@Deprecated("This is the old sync service which uses JobScheduler. A new version, `AppConfigurationWorker` uses WorkManager and should be used going forwards.") class AppConfigurationJobService : JobService() { @Inject - lateinit var appConfigurationDownloader: ConfigurationDownloader - - private var downloadTask: Disposable? = null + lateinit var jobScheduler: JobScheduler override fun onCreate() { AndroidInjection.inject(this) @@ -38,37 +35,14 @@ class AppConfigurationJobService : JobService() { } override fun onStartJob(params: JobParameters?): Boolean { - Timber.i("onStartJob") - - downloadTask = appConfigurationDownloader.downloadTask() - .subscribeOn(Schedulers.io()) - .subscribe({ - Timber.i("Successfully downloaded all data") - jobFinishedSuccessfully(params) - }, { - Timber.w("Failed to download app configuration ${it.localizedMessage}") - jobFinishedFailed(params) - }) - - return true + Timber.i("Deprecated AppConfigurationJobService running. Unscheduling future syncs using this job") + jobScheduler.cancel(LEGACY_APP_CONFIGURATION_JOB_ID) + return false } - private fun jobFinishedSuccessfully(params: JobParameters?) { - Timber.i("Finished job successfully") - jobFinished(params, false) - } - - private fun jobFinishedFailed(params: JobParameters?) { - Timber.w("Error executing job") - jobFinished(params, true) - } - - override fun onStopJob(p0: JobParameters?): Boolean { - Timber.i("onStopJob") - - // job needs to be force stopped - downloadTask?.dispose() + override fun onStopJob(params: JobParameters?): Boolean = false - return true + companion object { + const val LEGACY_APP_CONFIGURATION_JOB_ID = 1 } } \ No newline at end of file diff --git a/app/src/main/java/com/duckduckgo/app/job/AppConfigurationSyncer.kt b/app/src/main/java/com/duckduckgo/app/job/AppConfigurationSyncer.kt index db1e8ebcf802..38aae7047301 100644 --- a/app/src/main/java/com/duckduckgo/app/job/AppConfigurationSyncer.kt +++ b/app/src/main/java/com/duckduckgo/app/job/AppConfigurationSyncer.kt @@ -16,17 +16,17 @@ package com.duckduckgo.app.job -import android.app.job.JobScheduler -import android.content.Context import androidx.annotation.CheckResult -import com.duckduckgo.app.global.job.APP_CONFIGURATION_JOB_ID -import com.duckduckgo.app.global.job.JobBuilder +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.WorkManager +import com.duckduckgo.app.global.job.AppConfigurationSyncWorkRequestBuilder +import com.duckduckgo.app.global.job.AppConfigurationSyncWorkRequestBuilder.Companion.APP_CONFIG_SYNC_WORK_TAG import io.reactivex.Completable import timber.log.Timber class AppConfigurationSyncer( - private val jobBuilder: JobBuilder, - private val jobScheduler: JobScheduler, + private val appConfigurationSyncWorkRequestBuilder: AppConfigurationSyncWorkRequestBuilder, + private val workManager: WorkManager, private val appConfigurationDownloader: ConfigurationDownloader ) { @@ -36,24 +36,9 @@ class AppConfigurationSyncer( return appConfigurationDownloader.downloadTask() } - /** - * Scheduling the same job again would kill the existing job if it was running. - * - * So this method can be used to first query if the job is already scheduled. - */ - fun jobScheduled(): Boolean { - return jobScheduler.allPendingJobs - .filter { APP_CONFIGURATION_JOB_ID == it.id } - .count() > 0 - } - - fun scheduleRegularSync(context: Context) { - val jobInfo = jobBuilder.appConfigurationJob(context) - - if (jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_SUCCESS) { - Timber.i("Job scheduled successfully") - } else { - Timber.e("Failed to schedule job") - } + fun scheduleRegularSync() { + Timber.i("Scheduling regular sync") + val workRequest = appConfigurationSyncWorkRequestBuilder.appConfigurationWork() + workManager.enqueueUniquePeriodicWork(APP_CONFIG_SYNC_WORK_TAG, ExistingPeriodicWorkPolicy.KEEP, workRequest) } } \ No newline at end of file From 2b4f6490cbb99dd08a190eb869ae4d2fc21d56fb Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Fri, 8 May 2020 14:00:41 +0100 Subject: [PATCH 3/5] Code tidy --- .../app/global/job/AppConfigurationSyncWorkRequestBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt b/app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt index fc049b259905..f393707baf61 100644 --- a/app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt +++ b/app/src/main/java/com/duckduckgo/app/global/job/AppConfigurationSyncWorkRequestBuilder.kt @@ -50,7 +50,7 @@ class AppConfigurationWorker(context: Context, workerParams: WorkerParameters) : Timber.i("Running app config sync") return appConfigurationDownloader.downloadTask() .toSingle { - Timber.i("App configuration sync was successful") + Timber.i("App configuration sync was successful") Result.success() } .onErrorReturn { From 1600ef6b611e349d899ab165e16e6ecd39052bcb Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Thu, 14 May 2020 11:42:02 +0100 Subject: [PATCH 4/5] Add sync configuration scheduling tests --- .../app/job/AppConfigurationSyncerTest.kt | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt diff --git a/app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt b/app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt new file mode 100644 index 000000000000..72d7d4ae160a --- /dev/null +++ b/app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.job + +import android.content.Context +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import androidx.work.* +import androidx.work.impl.utils.SynchronousExecutor +import androidx.work.testing.WorkManagerTestInitHelper +import com.duckduckgo.app.global.job.AppConfigurationSyncWorkRequestBuilder +import com.duckduckgo.app.global.job.AppConfigurationSyncWorkRequestBuilder.Companion.APP_CONFIG_SYNC_WORK_TAG +import com.duckduckgo.app.global.job.AppConfigurationWorker +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import io.reactivex.Completable +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class AppConfigurationSyncerTest { + + private lateinit var testee: AppConfigurationSyncer + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private lateinit var workManager: WorkManager + private val mockDownloader: ConfigurationDownloader = mock() + + @Before + fun setup() { + initializeWorkManager() + whenever(mockDownloader.downloadTask()).thenReturn(Completable.complete()) + testee = AppConfigurationSyncer(AppConfigurationSyncWorkRequestBuilder(), workManager, mockDownloader) + } + + @Test + fun whenInitializedThenNoWorkScheduled() { + assertTrue(workManager.getSyncWork().isEmpty()) + } + + @Test + fun whenSyncScheduledButNotYetRunThenWorkEnqueued() { + testee.scheduleRegularSync() + + val workInfos = workManager.getSyncWork() + assertWorkIsEnqueuedStatus(workInfos) + } + + @Test + fun whenSyncFinishedThenWorkStillScheduled() { + testee.scheduleRegularSync() + + executeWorker() + + val workInfos = workManager.getSyncWork() + assertWorkIsEnqueuedStatus(workInfos) + } + + private fun executeWorker() { + WorkManagerTestInitHelper.getTestDriver(context)?.setAllConstraintsMet(workManager.getSyncWork().first().id) + } + + private fun assertWorkIsEnqueuedStatus(workInfos: List) { + assertSingleInstanceOfWork(workInfos) + assertEquals(WorkInfo.State.ENQUEUED, workInfos.first().state) + } + + private fun assertSingleInstanceOfWork(workInfos: List) { + assertEquals(1, workInfos.size) + } + + private fun initializeWorkManager() { + val config = Configuration.Builder() + .setMinimumLoggingLevel(Log.DEBUG) + .setExecutor(SynchronousExecutor()) + .setWorkerFactory(object : WorkerFactory() { + override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? { + return AppConfigurationWorker(appContext, workerParameters).also { + it.appConfigurationDownloader = mockDownloader + } + } + }) + .build() + + WorkManagerTestInitHelper.initializeTestWorkManager(context, config) + workManager = WorkManager.getInstance(context) + } + + private fun WorkManager.getSyncWork(): List { + return getWorkInfosByTag(APP_CONFIG_SYNC_WORK_TAG).get() + } +} \ No newline at end of file From 891cabd35ac7db4c0ce65ad82a943b1e31ee9b9a Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Thu, 14 May 2020 11:43:30 +0100 Subject: [PATCH 5/5] Code tidy --- .../app/job/AppConfigurationSyncerTest.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt b/app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt index 72d7d4ae160a..f7dc12cb3168 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/job/AppConfigurationSyncerTest.kt @@ -87,19 +87,23 @@ class AppConfigurationSyncerTest { val config = Configuration.Builder() .setMinimumLoggingLevel(Log.DEBUG) .setExecutor(SynchronousExecutor()) - .setWorkerFactory(object : WorkerFactory() { - override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? { - return AppConfigurationWorker(appContext, workerParameters).also { - it.appConfigurationDownloader = mockDownloader - } - } - }) + .setWorkerFactory(testWorkerFactory()) .build() WorkManagerTestInitHelper.initializeTestWorkManager(context, config) workManager = WorkManager.getInstance(context) } + private fun testWorkerFactory(): WorkerFactory { + return object : WorkerFactory() { + override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? { + return AppConfigurationWorker(appContext, workerParameters).also { + it.appConfigurationDownloader = mockDownloader + } + } + } + } + private fun WorkManager.getSyncWork(): List { return getWorkInfosByTag(APP_CONFIG_SYNC_WORK_TAG).get() }