diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/FillAssistModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/FillAssistModule.kt index e8480610b2..7a08c3ec2e 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/FillAssistModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/FillAssistModule.kt @@ -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, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt index bbd2bf506b..070495a8a4 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt @@ -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 diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt index cbaf366a29..adfd188efc 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt @@ -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]. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt index 7d67a37810..0c4016215b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt @@ -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? = getString(key = BLOCKED_AUTOFILL_URIS_KEY.appendIdentifier(userId))?.let { json.decodeFromStringOrNull(it) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt index 67a7a9f796..2511de6393 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt @@ -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. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt index a796f97cbc..edcecb499d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt @@ -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) } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt index f530a1044b..88c5a7a75a 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt @@ -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(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 diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt index 48f992c905..88120da927 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt @@ -72,6 +72,7 @@ class FakeSettingsDiskSource( private val storedUpgradedToPremiumCardPending = mutableMapOf() private val storedPremiumUpgradePending = mutableMapOf() private val storedInlineAutofillEnabled = mutableMapOf() + private val storedFillAssistEnabled = mutableMapOf() private val storedBlockedAutofillUris = mutableMapOf?>() 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? = storedBlockedAutofillUris[userId]