Skip to content

Commit acfee4c

Browse files
authored
Ensure FF check happens on io for 1st party cookie expiry work scheduling (#7081)
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1211876763499720?focus=true ### Description Moves the feature flag checks to `io` instead of `main`. This is triggered from `MainProcessLifecycleObserver` so is an important hot path for app launch / ANR prevention. ### Steps to test this PR - QA optional - [ ] [optional]: add a log statement to `FirstPartyCookiesModifierWorker.doWork()` and make sure it continues to fire with this change in place when the process is created - [ ] [optional]: verify the `StrictMode` violation warning no longer shows on app process creation Co-authored-by: Craig Russell <1336281+CDRussell@users.noreply.github.com>
1 parent ec07123 commit acfee4c

File tree

3 files changed

+41
-20
lines changed

3 files changed

+41
-20
lines changed

cookies/cookies-impl/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ dependencies {
6565
testImplementation Testing.robolectric
6666
testImplementation CashApp.turbine
6767
testImplementation AndroidX.work.testing
68-
68+
testImplementation "androidx.lifecycle:lifecycle-runtime-testing:_"
6969
testImplementation project(path: ':common-test')
7070

7171
coreLibraryDesugaring Android.tools.desugarJdkLibs

cookies/cookies-impl/src/main/java/com/duckduckgo/cookies/impl/features/firstparty/FirstPartyCookiesModifierWorker.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.duckduckgo.cookies.impl.features.firstparty
1818

1919
import android.content.Context
2020
import androidx.lifecycle.LifecycleOwner
21+
import androidx.lifecycle.lifecycleScope
2122
import androidx.work.BackoffPolicy
2223
import androidx.work.CoroutineWorker
2324
import androidx.work.ExistingPeriodicWorkPolicy
@@ -32,6 +33,7 @@ import com.duckduckgo.di.scopes.AppScope
3233
import com.duckduckgo.feature.toggles.api.FeatureToggle
3334
import com.squareup.anvil.annotations.ContributesMultibinding
3435
import dagger.SingleInstanceIn
36+
import kotlinx.coroutines.launch
3537
import kotlinx.coroutines.withContext
3638
import java.util.concurrent.TimeUnit
3739
import java.util.concurrent.TimeUnit.DAYS
@@ -64,6 +66,7 @@ class FirstPartyCookiesModifierWorker(
6466
class FirstPartyCookiesModifierWorkerScheduler @Inject constructor(
6567
private val workManager: WorkManager,
6668
private val toggle: FeatureToggle,
69+
private val dispatchers: DispatcherProvider,
6770
) : MainProcessLifecycleObserver {
6871

6972
private val workerRequest = PeriodicWorkRequestBuilder<FirstPartyCookiesModifierWorker>(1, DAYS)
@@ -72,18 +75,22 @@ class FirstPartyCookiesModifierWorkerScheduler @Inject constructor(
7275
.build()
7376

7477
override fun onStop(owner: LifecycleOwner) {
75-
if (isFeatureEnabled()) {
76-
workManager.enqueueUniquePeriodicWork(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG, ExistingPeriodicWorkPolicy.REPLACE, workerRequest)
77-
} else {
78-
workManager.cancelAllWorkByTag(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG)
78+
owner.lifecycleScope.launch(dispatchers.io()) {
79+
if (isFeatureEnabled()) {
80+
workManager.enqueueUniquePeriodicWork(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG, ExistingPeriodicWorkPolicy.REPLACE, workerRequest)
81+
} else {
82+
workManager.cancelAllWorkByTag(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG)
83+
}
7984
}
8085
}
8186

8287
override fun onStart(owner: LifecycleOwner) {
83-
if (isFeatureEnabled()) {
84-
workManager.enqueueUniquePeriodicWork(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG, ExistingPeriodicWorkPolicy.KEEP, workerRequest)
85-
} else {
86-
workManager.cancelAllWorkByTag(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG)
88+
owner.lifecycleScope.launch(dispatchers.io()) {
89+
if (isFeatureEnabled()) {
90+
workManager.enqueueUniquePeriodicWork(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG, ExistingPeriodicWorkPolicy.KEEP, workerRequest)
91+
} else {
92+
workManager.cancelAllWorkByTag(FIRST_PARTY_COOKIES_EXPIRE_WORKER_TAG)
93+
}
8794
}
8895
}
8996

cookies/cookies-impl/src/test/java/com/duckduckgo/cookies/impl/features/firstparty/FirstPartyCookiesModifierWorkerSchedulerTest.kt

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,65 +16,79 @@
1616

1717
package com.duckduckgo.cookies.impl.features.firstparty
1818

19-
import androidx.lifecycle.LifecycleOwner
19+
import androidx.lifecycle.Lifecycle.State.INITIALIZED
20+
import androidx.lifecycle.testing.TestLifecycleOwner
21+
import androidx.test.ext.junit.runners.AndroidJUnit4
2022
import androidx.work.ExistingPeriodicWorkPolicy.KEEP
2123
import androidx.work.ExistingPeriodicWorkPolicy.REPLACE
2224
import androidx.work.WorkManager
25+
import com.duckduckgo.common.test.CoroutineTestRule
2326
import com.duckduckgo.cookies.api.CookiesFeatureName
2427
import com.duckduckgo.feature.toggles.api.FeatureToggle
28+
import kotlinx.coroutines.test.runTest
2529
import org.junit.Before
30+
import org.junit.Rule
2631
import org.junit.Test
32+
import org.junit.runner.RunWith
2733
import org.mockito.kotlin.any
2834
import org.mockito.kotlin.eq
2935
import org.mockito.kotlin.mock
3036
import org.mockito.kotlin.verify
3137
import org.mockito.kotlin.whenever
3238

39+
@RunWith(AndroidJUnit4::class)
3340
class FirstPartyCookiesModifierWorkerSchedulerTest {
3441

3542
private val mockToggle: FeatureToggle = mock()
3643
private val mockWorkManager: WorkManager = mock()
37-
private val mockOwner: LifecycleOwner = mock()
44+
private val lifecycleOwner = TestLifecycleOwner(initialState = INITIALIZED)
3845

3946
lateinit var firstPartyCookiesModifierWorkerScheduler: FirstPartyCookiesModifierWorkerScheduler
4047

48+
@get:Rule
49+
val coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
50+
4151
@Before
4252
fun before() {
43-
firstPartyCookiesModifierWorkerScheduler = FirstPartyCookiesModifierWorkerScheduler(mockWorkManager, mockToggle)
53+
firstPartyCookiesModifierWorkerScheduler = FirstPartyCookiesModifierWorkerScheduler(
54+
workManager = mockWorkManager,
55+
toggle = mockToggle,
56+
dispatchers = coroutineTestRule.testDispatcherProvider,
57+
)
4458
}
4559

4660
@Test
47-
fun whenOnStopIfFeatureEnabledThenEnqueueWorkWithReplacePolicy() {
61+
fun whenOnStopIfFeatureEnabledThenEnqueueWorkWithReplacePolicy() = runTest {
4862
whenever(mockToggle.isFeatureEnabled(CookiesFeatureName.Cookie.value)).thenReturn(true)
4963

50-
firstPartyCookiesModifierWorkerScheduler.onStop(mockOwner)
64+
firstPartyCookiesModifierWorkerScheduler.onStop(lifecycleOwner)
5165

5266
verify(mockWorkManager).enqueueUniquePeriodicWork(any(), eq(REPLACE), any())
5367
}
5468

5569
@Test
56-
fun whenOnStopIfFeatureNotEnabledThenDeleteTag() {
70+
fun whenOnStopIfFeatureNotEnabledThenDeleteTag() = runTest {
5771
whenever(mockToggle.isFeatureEnabled(CookiesFeatureName.Cookie.value)).thenReturn(false)
5872

59-
firstPartyCookiesModifierWorkerScheduler.onStop(mockOwner)
73+
firstPartyCookiesModifierWorkerScheduler.onStop(lifecycleOwner)
6074

6175
verify(mockWorkManager).cancelAllWorkByTag(any())
6276
}
6377

6478
@Test
65-
fun whenOnStartIfFeatureEnabledThenEnqueueWorkWithKeepPolicy() {
79+
fun whenOnStartIfFeatureEnabledThenEnqueueWorkWithKeepPolicy() = runTest {
6680
whenever(mockToggle.isFeatureEnabled(CookiesFeatureName.Cookie.value)).thenReturn(true)
6781

68-
firstPartyCookiesModifierWorkerScheduler.onStart(mockOwner)
82+
firstPartyCookiesModifierWorkerScheduler.onStart(lifecycleOwner)
6983

7084
verify(mockWorkManager).enqueueUniquePeriodicWork(any(), eq(KEEP), any())
7185
}
7286

7387
@Test
74-
fun whenOnStartIfFeatureNotEnabledThenDeleteTag() {
88+
fun whenOnStartIfFeatureNotEnabledThenDeleteTag() = runTest {
7589
whenever(mockToggle.isFeatureEnabled(CookiesFeatureName.Cookie.value)).thenReturn(false)
7690

77-
firstPartyCookiesModifierWorkerScheduler.onStart(mockOwner)
91+
firstPartyCookiesModifierWorkerScheduler.onStart(lifecycleOwner)
7892

7993
verify(mockWorkManager).cancelAllWorkByTag(any())
8094
}

0 commit comments

Comments
 (0)