diff --git a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFeature.kt b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFeature.kt index bf3b6892c6cf..161ceef47f8c 100644 --- a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFeature.kt +++ b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFeature.kt @@ -35,4 +35,11 @@ interface MaliciousSiteProtectionFeature { @Toggle.InternalAlwaysEnabled @Toggle.DefaultValue(false) fun self(): Toggle + + @Toggle.InternalAlwaysEnabled + @Toggle.DefaultValue(false) + fun visibleAndOnByDefault(): Toggle + + @Toggle.DefaultValue(true) + fun canUpdateDatasets(): Toggle } diff --git a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorker.kt b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorker.kt index 0971c00c7827..e35fdb1bc256 100644 --- a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorker.kt +++ b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorker.kt @@ -29,6 +29,7 @@ import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.AppScope import com.duckduckgo.malicioussiteprotection.impl.data.MaliciousSiteRepository +import com.duckduckgo.privacy.config.api.PrivacyConfigCallbackPlugin import com.squareup.anvil.annotations.ContributesMultibinding import dagger.SingleInstanceIn import java.util.concurrent.TimeUnit @@ -51,7 +52,7 @@ class MaliciousSiteProtectionFiltersUpdateWorker( override suspend fun doWork(): Result { return withContext(dispatcherProvider.io()) { - if (maliciousSiteProtectionFeature.isFeatureEnabled().not()) { + if (maliciousSiteProtectionFeature.isFeatureEnabled().not() || maliciousSiteProtectionFeature.canUpdateDatasets().not()) { return@withContext Result.success() } return@withContext if (maliciousSiteRepository.loadFilters().isSuccess) { @@ -63,6 +64,10 @@ class MaliciousSiteProtectionFiltersUpdateWorker( } } +@ContributesMultibinding( + scope = AppScope::class, + boundType = PrivacyConfigCallbackPlugin::class, +) @ContributesMultibinding( scope = AppScope::class, boundType = MainProcessLifecycleObserver::class, @@ -72,9 +77,21 @@ class MaliciousSiteProtectionFiltersUpdateWorkerScheduler @Inject constructor( private val workManager: WorkManager, private val maliciousSiteProtectionFeature: MaliciousSiteProtectionRCFeature, -) : MainProcessLifecycleObserver { +) : PrivacyConfigCallbackPlugin, MainProcessLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { + enqueuePeriodicWork() + } + + override fun onPrivacyConfigDownloaded() { + enqueuePeriodicWork() + } + + private fun enqueuePeriodicWork() { + if (maliciousSiteProtectionFeature.isFeatureEnabled().not() || maliciousSiteProtectionFeature.canUpdateDatasets().not()) { + workManager.cancelUniqueWork(MALICIOUS_SITE_PROTECTION_FILTERS_UPDATE_WORKER_TAG) + return + } val workerRequest = PeriodicWorkRequestBuilder( maliciousSiteProtectionFeature.getFilterSetUpdateFrequency(), TimeUnit.MINUTES, @@ -82,7 +99,11 @@ class MaliciousSiteProtectionFiltersUpdateWorkerScheduler @Inject constructor( .addTag(MALICIOUS_SITE_PROTECTION_FILTERS_UPDATE_WORKER_TAG) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES) .build() - workManager.enqueueUniquePeriodicWork(MALICIOUS_SITE_PROTECTION_FILTERS_UPDATE_WORKER_TAG, ExistingPeriodicWorkPolicy.UPDATE, workerRequest) + workManager.enqueueUniquePeriodicWork( + MALICIOUS_SITE_PROTECTION_FILTERS_UPDATE_WORKER_TAG, + ExistingPeriodicWorkPolicy.UPDATE, + workerRequest, + ) } companion object { diff --git a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorker.kt b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorker.kt index fe111bcb225a..891bc32f24af 100644 --- a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorker.kt +++ b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorker.kt @@ -29,6 +29,7 @@ import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.AppScope import com.duckduckgo.malicioussiteprotection.impl.data.MaliciousSiteRepository +import com.duckduckgo.privacy.config.api.PrivacyConfigCallbackPlugin import com.squareup.anvil.annotations.ContributesMultibinding import dagger.SingleInstanceIn import java.util.concurrent.TimeUnit @@ -51,7 +52,7 @@ class MaliciousSiteProtectionHashPrefixesUpdateWorker( override suspend fun doWork(): Result { return withContext(dispatcherProvider.io()) { - if (maliciousSiteProtectionFeature.isFeatureEnabled().not()) { + if (maliciousSiteProtectionFeature.isFeatureEnabled().not() || maliciousSiteProtectionFeature.canUpdateDatasets().not()) { return@withContext Result.success() } return@withContext if (maliciousSiteRepository.loadHashPrefixes().isSuccess) { @@ -63,6 +64,10 @@ class MaliciousSiteProtectionHashPrefixesUpdateWorker( } } +@ContributesMultibinding( + scope = AppScope::class, + boundType = PrivacyConfigCallbackPlugin::class, +) @ContributesMultibinding( scope = AppScope::class, boundType = MainProcessLifecycleObserver::class, @@ -72,9 +77,21 @@ class MaliciousSiteProtectionHashPrefixesUpdateWorkerScheduler @Inject construct private val workManager: WorkManager, private val maliciousSiteProtectionFeature: MaliciousSiteProtectionRCFeature, -) : MainProcessLifecycleObserver { +) : PrivacyConfigCallbackPlugin, MainProcessLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { + enqueuePeriodicWork() + } + + override fun onPrivacyConfigDownloaded() { + enqueuePeriodicWork() + } + + private fun enqueuePeriodicWork() { + if (maliciousSiteProtectionFeature.isFeatureEnabled().not() || maliciousSiteProtectionFeature.canUpdateDatasets().not()) { + workManager.cancelUniqueWork(MALICIOUS_SITE_PROTECTION_HASH_PREFIXES_UPDATE_WORKER_TAG) + return + } val workerRequest = PeriodicWorkRequestBuilder( maliciousSiteProtectionFeature.getHashPrefixUpdateFrequency(), TimeUnit.MINUTES, diff --git a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionRCFeature.kt b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionRCFeature.kt index 16db750c413d..97b7f5f954d8 100644 --- a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionRCFeature.kt +++ b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionRCFeature.kt @@ -31,6 +31,7 @@ import org.json.JSONObject interface MaliciousSiteProtectionRCFeature { fun isFeatureEnabled(): Boolean + fun canUpdateDatasets(): Boolean fun getHashPrefixUpdateFrequency(): Long fun getFilterSetUpdateFrequency(): Long } @@ -45,6 +46,7 @@ class RealMaliciousSiteProtectionRCFeature @Inject constructor( @AppCoroutineScope private val appCoroutineScope: CoroutineScope, ) : MaliciousSiteProtectionRCFeature, PrivacyConfigCallbackPlugin { private var isFeatureEnabled = false + private var canUpdateDatasets = false private var hashPrefixUpdateFrequency = 20L private var filterSetUpdateFrequency = 720L @@ -71,9 +73,15 @@ class RealMaliciousSiteProtectionRCFeature @Inject constructor( return isFeatureEnabled } + override fun canUpdateDatasets(): Boolean { + return canUpdateDatasets + } + private fun loadToMemory() { appCoroutineScope.launch(dispatchers.io()) { - isFeatureEnabled = maliciousSiteProtectionFeature.self().isEnabled() + isFeatureEnabled = maliciousSiteProtectionFeature.self().isEnabled() && + maliciousSiteProtectionFeature.visibleAndOnByDefault().isEnabled() + canUpdateDatasets = maliciousSiteProtectionFeature.canUpdateDatasets().isEnabled() maliciousSiteProtectionFeature.self().getSettings()?.let { JSONObject(it).let { settings -> hashPrefixUpdateFrequency = settings.getLong("hashPrefixUpdateFrequency") diff --git a/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorkerTest.kt b/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorkerTest.kt index 1e2c670c4484..be94c5f6955d 100644 --- a/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorkerTest.kt +++ b/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionFiltersUpdateWorkerTest.kt @@ -22,6 +22,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.kotlin.capture import org.mockito.kotlin.eq +import org.mockito.kotlin.never import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @@ -41,6 +42,7 @@ class MaliciousSiteProtectionFiltersUpdateWorkerTest { worker.maliciousSiteRepository = maliciousSiteRepository worker.dispatcherProvider = dispatcherProvider worker.maliciousSiteProtectionFeature = maliciousSiteProtectionFeature + whenever(maliciousSiteProtectionFeature.canUpdateDatasets()).thenReturn(true) } @Test @@ -49,6 +51,17 @@ class MaliciousSiteProtectionFiltersUpdateWorkerTest { val result = worker.doWork() + verify(maliciousSiteRepository, never()).loadFilters() + assertEquals(success(), result) + } + + @Test + fun doWork_returnsSuccessWhenCanUpdateDatasetsIsDisabled() = runTest { + whenever(maliciousSiteProtectionFeature.canUpdateDatasets()).thenReturn(false) + + val result = worker.doWork() + + verify(maliciousSiteRepository, never()).loadFilters() assertEquals(success(), result) } @@ -80,12 +93,12 @@ class MaliciousSiteProtectionFiltersUpdateWorkerSchedulerTest { private val scheduler = MaliciousSiteProtectionFiltersUpdateWorkerScheduler(workManager, maliciousSiteProtectionFeature) @Test - fun onCreate_schedulesWorkerWithUpdateFrequencyFromRCFlag() { + fun onPrivacyConfigDownloaded_schedulesWorkerWithUpdateFrequencyFromRCFlagAndUpdatePolicy() { val updateFrequencyMinutes = 15L whenever(maliciousSiteProtectionFeature.getFilterSetUpdateFrequency()).thenReturn(updateFrequencyMinutes) - scheduler.onCreate(mock()) + scheduler.onPrivacyConfigDownloaded() val workRequestCaptor = ArgumentCaptor.forClass(PeriodicWorkRequest::class.java) verify(workManager).enqueueUniquePeriodicWork( diff --git a/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorkerTest.kt b/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorkerTest.kt index 9046320a79e9..242a6296c89c 100644 --- a/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorkerTest.kt +++ b/malicious-site-protection/malicious-site-protection-impl/src/test/kotlin/com/duckduckgo/malicioussiteprotection/impl/MaliciousSiteProtectionHashPrefixesUpdateWorkerTest.kt @@ -22,6 +22,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.kotlin.capture import org.mockito.kotlin.eq +import org.mockito.kotlin.never import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @@ -41,6 +42,7 @@ class MaliciousSiteProtectionHashPrefixesUpdateWorkerTest { worker.maliciousSiteRepository = maliciousSiteRepository worker.dispatcherProvider = dispatcherProvider worker.maliciousSiteProtectionFeature = maliciousSiteProtectionFeature + whenever(maliciousSiteProtectionFeature.canUpdateDatasets()).thenReturn(true) } @Test @@ -49,6 +51,17 @@ class MaliciousSiteProtectionHashPrefixesUpdateWorkerTest { val result = worker.doWork() + verify(maliciousSiteRepository, never()).loadHashPrefixes() + assertEquals(success(), result) + } + + @Test + fun doWork_returnsSuccessWhenUpdateDatasetsIsDisabled() = runTest { + whenever(maliciousSiteProtectionFeature.canUpdateDatasets()).thenReturn(false) + + val result = worker.doWork() + + verify(maliciousSiteRepository, never()).loadHashPrefixes() assertEquals(success(), result) } @@ -80,12 +93,12 @@ class MaliciousSiteProtectionHashPrefixesUpdateWorkerSchedulerTest { private val scheduler = MaliciousSiteProtectionHashPrefixesUpdateWorkerScheduler(workManager, maliciousSiteProtectionFeature) @Test - fun onCreate_schedulesWorkerWithUpdateFrequencyFromRCFlag() { + fun onPrivacyConfigDownloaded_schedulesWorkerWithUpdateFrequencyFromRCFlag() { val updateFrequencyMinutes = 15L whenever(maliciousSiteProtectionFeature.getHashPrefixUpdateFrequency()).thenReturn(updateFrequencyMinutes) - scheduler.onCreate(mock()) + scheduler.onPrivacyConfigDownloaded() val workRequestCaptor = ArgumentCaptor.forClass(PeriodicWorkRequest::class.java) verify(workManager).enqueueUniquePeriodicWork(