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 @@ -187,6 +187,7 @@ dependencies {

// WorkManager
implementation "androidx.work:work-runtime-ktx:$workManager"
androidTestImplementation "androidx.work:work-testing:$workManager"

// Dagger
kapt "com.google.dagger:dagger-android-processor:$dagger"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ package com.duckduckgo.app.di
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.app.job.JobWorkItem
import androidx.work.WorkManager
import com.duckduckgo.app.job.AndroidJobCleaner
import com.duckduckgo.app.job.AndroidWorkScheduler
import com.duckduckgo.app.job.JobCleaner
import com.duckduckgo.app.job.WorkScheduler
import com.duckduckgo.app.notification.AndroidNotificationScheduler
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
Expand All @@ -44,4 +50,16 @@ class StubJobSchedulerModule {

}
}

@Singleton
@Provides
fun providesJobCleaner(workManager: WorkManager): JobCleaner {
return AndroidJobCleaner(workManager)
}

@Singleton
@Provides
fun providesWorkScheduler(notificationScheduler: AndroidNotificationScheduler, jobCleaner: JobCleaner): WorkScheduler {
return AndroidWorkScheduler(notificationScheduler, jobCleaner)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import retrofit2.Retrofit
import javax.inject.Named
import javax.inject.Singleton


@Singleton
@Component(
modules = [
Expand Down
127 changes: 127 additions & 0 deletions app/src/androidTest/java/com/duckduckgo/app/job/JobCleanerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.Configuration
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.impl.utils.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper
import com.duckduckgo.app.job.JobCleaner.Companion.allDeprecatedNotificationWorkTags
import com.duckduckgo.app.notification.NotificationScheduler
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.util.concurrent.TimeUnit

class JobCleanerTest {

private val context = InstrumentationRegistry.getInstrumentation().targetContext
private lateinit var workManager: WorkManager
private lateinit var testee: JobCleaner

@Before
fun before() {
initializeWorkManager()
testee = AndroidJobCleaner(workManager)
}

// https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing
private fun initializeWorkManager() {
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()

WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
workManager = WorkManager.getInstance(context)
}

@Test
fun whenStartedThenAllDeprecatedWorkIsCancelled() {
enqueueDeprecatedWorkers()
assertDeprecatedWorkersAreEnqueued()
testee.cleanDeprecatedJobs()
assertDeprecatedWorkersAreNotEnqueued()
}

@Test
fun whenStartedAndNoDeprecatedJobsAreScheduledThenNothingIsRemoved() {
enqueueNonDeprecatedWorkers()
assertNonDeprecatedWorkersAreEnqueued()
testee.cleanDeprecatedJobs()
assertNonDeprecatedWorkersAreEnqueued()
}

private fun enqueueDeprecatedWorkers() {
allDeprecatedNotificationWorkTags().forEach {
val requestBuilder = OneTimeWorkRequestBuilder<TestWorker>()
val request = requestBuilder
.addTag(it)
.setInitialDelay(10, TimeUnit.SECONDS)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we set this delay because if we don't the Worker will be immediately completed.

.build()
workManager.enqueue(request)
}
}

private fun enqueueNonDeprecatedWorkers() {
allDeprecatedNotificationWorkTags().forEach {
val requestBuilder = OneTimeWorkRequestBuilder<TestWorker>()
val request = requestBuilder
.addTag(NotificationScheduler.UNUSED_APP_WORK_REQUEST_TAG)
.setInitialDelay(10, TimeUnit.SECONDS)
.build()
workManager.enqueue(request)
}
}

private fun assertDeprecatedWorkersAreEnqueued() {
allDeprecatedNotificationWorkTags().forEach {
val scheduledWorkers = getScheduledWorkers(it)
assertFalse(scheduledWorkers.isEmpty())
}
}

private fun assertDeprecatedWorkersAreNotEnqueued() {
allDeprecatedNotificationWorkTags().forEach {
val scheduledWorkers = getScheduledWorkers(it)
assertTrue(scheduledWorkers.isEmpty())
}
}

private fun assertNonDeprecatedWorkersAreEnqueued() {
val scheduledWorkers = getScheduledWorkers(NotificationScheduler.UNUSED_APP_WORK_REQUEST_TAG)
assertFalse(scheduledWorkers.isEmpty())
}

private fun assertNonDeprecatedWorkersAreNotEnqueued() {
val scheduledWorkers = getScheduledWorkers(NotificationScheduler.UNUSED_APP_WORK_REQUEST_TAG)
assertTrue(scheduledWorkers.isEmpty())
}


private fun getScheduledWorkers(tag: String): List<WorkInfo> {
return workManager
.getWorkInfosByTag(tag)
.get()
.filter { it.state == WorkInfo.State.ENQUEUED }
}
}
27 changes: 27 additions & 0 deletions app/src/androidTest/java/com/duckduckgo/app/job/TestWorker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 androidx.work.Worker
import androidx.work.WorkerParameters

class TestWorker(context: Context, parameters: WorkerParameters) : Worker(context, parameters) {
override fun doWork(): Result {
return Result.success()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 com.duckduckgo.app.notification.AndroidNotificationScheduler
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test

class WorkSchedulerTest {

private val notificationScheduler: AndroidNotificationScheduler = mock()
private val jobCleaner: JobCleaner = mock()

private lateinit var testee: WorkScheduler

@Before
fun before() {
testee = AndroidWorkScheduler(
notificationScheduler,
jobCleaner
)
}

@Test
fun schedulesNextNotificationAndCleansDeprecatedJobs() = runBlocking<Unit> {
testee.scheduleWork()

verify(notificationScheduler).scheduleNextNotification()
verify(jobCleaner).cleanDeprecatedJobs()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@

package com.duckduckgo.app.notification

import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.Configuration
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.impl.utils.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper
import com.duckduckgo.app.CoroutineTestRule
import com.duckduckgo.app.notification.NotificationScheduler.ClearDataNotificationWorker
import com.duckduckgo.app.notification.NotificationScheduler.PrivacyNotificationWorker
Expand Down Expand Up @@ -50,11 +54,12 @@ class AndroidNotificationSchedulerTest {
private val privacyNotification: SchedulableNotification = mock()

private val context = InstrumentationRegistry.getInstrumentation().targetContext
private var workManager = WorkManager.getInstance(context)
private lateinit var workManager: WorkManager
private lateinit var testee: NotificationScheduler

@Before
fun before() {
initializeWorkManager()
whenever(variantManager.getVariant(any())).thenReturn(DEFAULT_VARIANT)
testee = NotificationScheduler(
workManager,
Expand All @@ -63,6 +68,17 @@ class AndroidNotificationSchedulerTest {
)
}

// https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing
private fun initializeWorkManager() {
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()

WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
workManager = WorkManager.getInstance(context)
}

@Test
fun whenPrivacyNotificationClearDataCanShowThenPrivacyNotificationIsScheduled() = runBlocking<Unit> {
whenever(privacyNotification.canShow()).thenReturn(true)
Expand Down Expand Up @@ -99,30 +115,6 @@ class AndroidNotificationSchedulerTest {
assertNoUnusedAppNotificationScheduled()
}

@Test
fun whenNotificationIsScheduledOldJobsAreCancelled() = runBlocking<Unit> {
whenever(privacyNotification.canShow()).thenReturn(false)
whenever(clearNotification.canShow()).thenReturn(false)

enqueueDeprecatedJobs()

testee.scheduleNextNotification()

NotificationScheduler.allDeprecatedNotificationWorkTags().forEach {
assertTrue(getScheduledWorkers(it).isEmpty())
}
}

private fun enqueueDeprecatedJobs() {
NotificationScheduler.allDeprecatedNotificationWorkTags().forEach {
val request = OneTimeWorkRequestBuilder<PrivacyNotificationWorker>()
.addTag(it)
.build()

workManager.enqueue(request)
}
}

private fun assertUnusedAppNotificationScheduled(workerName: String) {
assertTrue(getScheduledWorkers(NotificationScheduler.UNUSED_APP_WORK_REQUEST_TAG).any { it.tags.contains(workerName) })
}
Expand Down
18 changes: 17 additions & 1 deletion app/src/main/java/com/duckduckgo/app/di/JobsModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ package com.duckduckgo.app.di

import android.app.job.JobScheduler
import android.content.Context
import androidx.work.WorkManager
import com.duckduckgo.app.job.AndroidJobCleaner
import com.duckduckgo.app.job.AndroidWorkScheduler
import com.duckduckgo.app.job.JobCleaner
import com.duckduckgo.app.job.WorkScheduler
import com.duckduckgo.app.notification.AndroidNotificationScheduler
import dagger.Module
import dagger.Provides
import javax.inject.Singleton


@Module
class JobsModule {

Expand All @@ -32,4 +37,15 @@ class JobsModule {
return context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
}

@Singleton
@Provides
fun providesJobCleaner(workManager: WorkManager): JobCleaner {
return AndroidJobCleaner(workManager)
}

@Singleton
@Provides
fun providesWorkScheduler(notificationScheduler: AndroidNotificationScheduler, jobCleaner: JobCleaner): WorkScheduler {
return AndroidWorkScheduler(notificationScheduler, jobCleaner)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import com.duckduckgo.app.global.rating.AppEnjoymentLifecycleObserver
import com.duckduckgo.app.global.shortcut.AppShortcutCreator
import com.duckduckgo.app.httpsupgrade.HttpsUpgrader
import com.duckduckgo.app.job.AppConfigurationSyncer
import com.duckduckgo.app.notification.AndroidNotificationScheduler
import com.duckduckgo.app.job.WorkScheduler
import com.duckduckgo.app.notification.NotificationRegistrar
import com.duckduckgo.app.referral.AppInstallationReferrerStateListener
import com.duckduckgo.app.settings.db.SettingsDataStore
Expand Down Expand Up @@ -120,7 +120,7 @@ open class DuckDuckGoApplication : HasAndroidInjector, Application(), LifecycleO
lateinit var dataClearer: DataClearer

@Inject
lateinit var notificationScheduler: AndroidNotificationScheduler
lateinit var workScheduler: WorkScheduler

@Inject
lateinit var workerFactory: WorkerFactory
Expand Down Expand Up @@ -293,7 +293,7 @@ open class DuckDuckGoApplication : HasAndroidInjector, Application(), LifecycleO
fun onAppResumed() {
notificationRegistrar.updateStatus()
GlobalScope.launch {
notificationScheduler.scheduleNextNotification()
workScheduler.scheduleWork()
atbInitializer.initializeAfterReferrerAvailable()
}
}
Expand Down
Loading