[PM-39807] fix: Gate fill-assist rules sync on user settings (#7123)

This commit is contained in:
aj-rosado
2026-06-30 19:14:08 +00:00
committed by GitHub
parent d468197ebc
commit 7124ccbeb1
8 changed files with 76 additions and 2 deletions
@@ -11,6 +11,7 @@ import com.x8bit.bitwarden.data.autofill.manager.FillAssistManager
import com.x8bit.bitwarden.data.autofill.manager.FillAssistManagerImpl
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -44,6 +45,7 @@ object FillAssistModule {
fillAssistDiskSource: FillAssistDiskSource,
featureFlagManager: FeatureFlagManager,
serverConfigRepository: ServerConfigRepository,
settingsRepository: SettingsRepository,
environmentDiskSource: EnvironmentDiskSource,
clock: Clock,
dispatcherManager: DispatcherManager,
@@ -53,6 +55,7 @@ object FillAssistModule {
fillAssistDiskSource = fillAssistDiskSource,
featureFlagManager = featureFlagManager,
serverConfigRepository = serverConfigRepository,
settingsRepository = settingsRepository,
environmentDiskSource = environmentDiskSource,
clock = clock,
dispatcherManager = dispatcherManager,
@@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.autofill.model.FillAssistRules
import com.x8bit.bitwarden.data.autofill.model.FillAssistRules.SelectorClause
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.filterNotNull
@@ -52,6 +53,7 @@ class FillAssistManagerImpl(
private val fillAssistDiskSource: FillAssistDiskSource,
private val featureFlagManager: FeatureFlagManager,
private val serverConfigRepository: ServerConfigRepository,
private val settingsRepository: SettingsRepository,
private val environmentDiskSource: EnvironmentDiskSource,
private val clock: Clock,
dispatcherManager: DispatcherManager,
@@ -73,7 +75,11 @@ class FillAssistManagerImpl(
}
override fun syncIfNecessary() {
if (!featureFlagManager.getFeatureFlag(FlagKey.FillAssistTargetingRules)) return
if (!featureFlagManager.getFeatureFlag(FlagKey.FillAssistTargetingRules) ||
!settingsRepository.isFillAssistEnabled
) {
return
}
val serverUrl = serverConfigRepository
.serverConfigStateFlow
.value
@@ -357,6 +357,16 @@ interface SettingsDiskSource : FlightRecorderDiskSource {
*/
fun storeInlineAutofillEnabled(userId: String, isInlineAutofillEnabled: Boolean?)
/**
* Gets the value determining if fill assist is enabled for the given [userId].
*/
fun getFillAssistEnabled(userId: String): Boolean?
/**
* Stores the given [isFillAssistEnabled] value for the given [userId].
*/
fun storeFillAssistEnabled(userId: String, isFillAssistEnabled: Boolean?)
/**
* Gets a list of blocked autofill URI's for the given [userId].
*/
@@ -21,6 +21,7 @@ private const val APP_LANGUAGE_KEY = "appLocale"
private const val APP_THEME_KEY = "theme"
private const val PULL_TO_REFRESH_KEY = "syncOnRefresh"
private const val INLINE_AUTOFILL_ENABLED_KEY = "inlineAutofillEnabled"
private const val FILL_ASSIST_ENABLED_KEY = "fillAssistEnabled"
private const val BLOCKED_AUTOFILL_URIS_KEY = "autofillBlacklistedUris"
private const val VAULT_LAST_SYNC_TIME = "vaultLastSyncTime"
private const val VAULT_TIMEOUT_ACTION_KEY = "vaultTimeoutAction"
@@ -266,6 +267,7 @@ class SettingsDiskSourceImpl(
storeAutofillSavePromptDisabled(userId = userId, isAutofillSavePromptDisabled = null)
storePullToRefreshEnabled(userId = userId, isPullToRefreshEnabled = null)
storeInlineAutofillEnabled(userId = userId, isInlineAutofillEnabled = null)
storeFillAssistEnabled(userId = userId, isFillAssistEnabled = null)
storeBlockedAutofillUris(userId = userId, blockedAutofillUris = null)
storeLastSyncTime(userId = userId, lastSyncTime = null)
storeClearClipboardFrequencySeconds(userId = userId, frequency = null)
@@ -543,6 +545,16 @@ class SettingsDiskSourceImpl(
)
}
override fun getFillAssistEnabled(userId: String): Boolean? =
getBoolean(key = FILL_ASSIST_ENABLED_KEY.appendIdentifier(userId))
override fun storeFillAssistEnabled(userId: String, isFillAssistEnabled: Boolean?) {
putBoolean(
key = FILL_ASSIST_ENABLED_KEY.appendIdentifier(userId),
value = isFillAssistEnabled,
)
}
override fun getBlockedAutofillUris(userId: String): List<String>? =
getString(key = BLOCKED_AUTOFILL_URIS_KEY.appendIdentifier(userId))?.let {
json.decodeFromStringOrNull(it)
@@ -145,6 +145,11 @@ interface SettingsRepository : FlightRecorderManager {
*/
var isInlineAutofillEnabled: Boolean
/**
* Whether fill assist is enabled for the current user.
*/
var isFillAssistEnabled: Boolean
/**
* Whether the auto copying totp when autofilling is disabled for the current user.
*/
@@ -47,7 +47,7 @@ private val DEFAULT_IS_SCREEN_CAPTURE_ALLOWED = BuildConfig.DEBUG
/**
* Primary implementation of [SettingsRepository].
*/
@Suppress("TooManyFunctions", "LongParameterList")
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
class SettingsRepositoryImpl(
private val autofillManager: AutofillManager,
private val autofillEnabledManager: AutofillEnabledManager,
@@ -316,6 +316,18 @@ class SettingsRepositoryImpl(
)
}
override var isFillAssistEnabled: Boolean
get() = activeUserId
?.let { settingsDiskSource.getFillAssistEnabled(userId = it) }
?: false
set(value) {
val userId = activeUserId ?: return
settingsDiskSource.storeFillAssistEnabled(
userId = userId,
isFillAssistEnabled = value,
)
}
override var isAutoCopyTotpDisabled: Boolean
get() = activeUserId
?.let { settingsDiskSource.getAutoCopyTotpDisabled(userId = it) }
@@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.autofill.datasource.disk.FillAssistDiskSource
import com.x8bit.bitwarden.data.autofill.model.FillAssistRules
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
@@ -51,6 +52,10 @@ class FillAssistManagerTest {
every { getFeatureFlag(FlagKey.FillAssistTargetingRules) } returns true
}
private val settingsRepository: SettingsRepository = mockk {
every { isFillAssistEnabled } returns true
}
private val serverConfigFlow = MutableStateFlow<ServerConfig?>(SERVER_CONFIG)
private val serverConfigRepository: ServerConfigRepository = mockk {
@@ -80,6 +85,7 @@ class FillAssistManagerTest {
fillAssistDiskSource = fillAssistDiskSource,
featureFlagManager = featureFlagManager,
serverConfigRepository = serverConfigRepository,
settingsRepository = settingsRepository,
environmentDiskSource = environmentDiskSource,
clock = FIXED_CLOCK,
dispatcherManager = FakeDispatcherManager(),
@@ -114,6 +120,17 @@ class FillAssistManagerTest {
verify(exactly = 0) { fillAssistDiskSource.storeFillAssistRules(any(), any()) }
}
@Test
fun `sync returns success and does nothing when fill assist is disabled in settings`() =
runTest {
every { settingsRepository.isFillAssistEnabled } returns false
manager.syncIfNecessary()
coVerify(exactly = 0) { fillAssistService.getManifest() }
verify(exactly = 0) { fillAssistDiskSource.storeFillAssistRules(any(), any()) }
}
@Test
fun `sync returns success and does nothing when fillAssistRulesUrl is null`() = runTest {
serverConfigFlow.value = null
@@ -72,6 +72,7 @@ class FakeSettingsDiskSource(
private val storedUpgradedToPremiumCardPending = mutableMapOf<String, Boolean?>()
private val storedPremiumUpgradePending = mutableMapOf<String, Boolean?>()
private val storedInlineAutofillEnabled = mutableMapOf<String, Boolean?>()
private val storedFillAssistEnabled = mutableMapOf<String, Boolean?>()
private val storedBlockedAutofillUris = mutableMapOf<String, List<String>?>()
private var storedIsIconLoadingDisabled: Boolean? = null
private var storedIsCrashLoggingEnabled: Boolean? = null
@@ -262,6 +263,7 @@ class FakeSettingsDiskSource(
storedDisableAutofillSavePrompt.remove(userId)
storedPullToRefreshEnabled.remove(userId)
storedInlineAutofillEnabled.remove(userId)
storedFillAssistEnabled.remove(userId)
storedBlockedAutofillUris.remove(userId)
storedClearClipboardFrequency.remove(userId)
@@ -430,6 +432,13 @@ class FakeSettingsDiskSource(
storedInlineAutofillEnabled[userId] = isInlineAutofillEnabled
}
override fun getFillAssistEnabled(userId: String): Boolean? =
storedFillAssistEnabled[userId]
override fun storeFillAssistEnabled(userId: String, isFillAssistEnabled: Boolean?) {
storedFillAssistEnabled[userId] = isFillAssistEnabled
}
override fun getBlockedAutofillUris(userId: String): List<String>? =
storedBlockedAutofillUris[userId]