Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ dependencies {
// WorkManager
implementation "androidx.work:work-runtime-ktx:$workManager"
androidTestImplementation "androidx.work:work-testing:$workManager"
implementation "androidx.work:work-rxjava2:$workManager"

// Dagger
kapt "com.google.dagger:dagger-android-processor:$dagger"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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<WorkInfo>) {
assertSingleInstanceOfWork(workInfos)
assertEquals(WorkInfo.State.ENQUEUED, workInfos.first().state)
}

private fun assertSingleInstanceOfWork(workInfos: List<WorkInfo>) {
assertEquals(1, workInfos.size)
}

private fun initializeWorkManager() {
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.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<WorkInfo> {
return getWorkInfosByTag(APP_CONFIG_SYNC_WORK_TAG).get()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {

Expand All @@ -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")
}

Expand All @@ -70,6 +75,10 @@ class DaggerWorkerFactory(

}

private fun injectAppConfigurationWorker(worker: AppConfigurationWorker) {
worker.appConfigurationDownloader = configurationDownloader
}

private fun injectOfflinePixelWorker(worker: OfflinePixelScheduler.OfflinePixelWorker) {
worker.offlinePixelSender = offlinePixelSender
}
Expand Down
10 changes: 5 additions & 5 deletions app/src/main/java/com/duckduckgo/app/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 6 additions & 2 deletions app/src/main/java/com/duckduckgo/app/di/WorkerModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}") })
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AppConfigurationWorker>(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<Result> {
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()
}
}
}
40 changes: 0 additions & 40 deletions app/src/main/java/com/duckduckgo/app/global/job/JobBuilder.kt

This file was deleted.

Loading