diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fc02932b6b..5972f16e33 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,7 +15,7 @@
-
+
+
+
+
+
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt
index 2a63df4441..d20579b21f 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt
@@ -8,6 +8,9 @@ import androidx.lifecycle.lifecycleScope
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManagerImpl
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillManager
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillManagerImpl
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import dagger.Module
import dagger.Provides
@@ -23,19 +26,32 @@ import dagger.hilt.android.scopes.ActivityScoped
@InstallIn(ActivityComponent::class)
object ActivityAutofillModule {
+ @ActivityScoped
+ @ActivityScopedManager
+ @Provides
+ fun provideActivityScopedChromeThirdPartyAutofillManager(
+ activity: Activity,
+ ): ChromeThirdPartyAutofillManager = ChromeThirdPartyAutofillManagerImpl(
+ context = activity.baseContext,
+ )
+
@ActivityScoped
@Provides
fun provideAutofillActivityManager(
@ActivityScopedManager autofillManager: AutofillManager,
+ @ActivityScopedManager chromeThirdPartyAutofillManager: ChromeThirdPartyAutofillManager,
appStateManager: AppStateManager,
autofillEnabledManager: AutofillEnabledManager,
lifecycleScope: LifecycleCoroutineScope,
+ chromeThirdPartyAutofillEnabledManager: ChromeThirdPartyAutofillEnabledManager,
): AutofillActivityManager =
AutofillActivityManagerImpl(
autofillManager = autofillManager,
+ chromeThirdPartyAutofillManager = chromeThirdPartyAutofillManager,
appStateManager = appStateManager,
autofillEnabledManager = autofillEnabledManager,
lifecycleScope = lifecycleScope,
+ chromeThirdPartyAutofillEnabledManager = chromeThirdPartyAutofillEnabledManager,
)
/**
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt
index e9f59922d2..830d845b71 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt
@@ -15,12 +15,15 @@ import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManagerImpl
import com.x8bit.bitwarden.data.autofill.manager.AutofillTotpManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillTotpManagerImpl
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManagerImpl
import com.x8bit.bitwarden.data.autofill.parser.AutofillParser
import com.x8bit.bitwarden.data.autofill.parser.AutofillParserImpl
import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessor
import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessorImpl
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProviderImpl
+import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
@@ -54,6 +57,15 @@ object AutofillModule {
fun providesAutofillEnabledManager(): AutofillEnabledManager =
AutofillEnabledManagerImpl()
+ @Singleton
+ @Provides
+ fun providesChromeAutofillEnabledManager(
+ featureFlagManager: FeatureFlagManager,
+ ): ChromeThirdPartyAutofillEnabledManager =
+ ChromeThirdPartyAutofillEnabledManagerImpl(
+ featureFlagManager = featureFlagManager,
+ )
+
@Singleton
@Provides
fun provideAutofillCompletionManager(
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt
index 154be4db91..3ec71f52f4 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt
@@ -2,6 +2,9 @@ package com.x8bit.bitwarden.data.autofill.manager
import android.view.autofill.AutofillManager
import androidx.lifecycle.LifecycleCoroutineScope
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillManager
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -11,19 +14,31 @@ import kotlinx.coroutines.flow.onEach
*/
class AutofillActivityManagerImpl(
private val autofillManager: AutofillManager,
- private val autofillEnabledManager: AutofillEnabledManager,
+ private val chromeThirdPartyAutofillManager: ChromeThirdPartyAutofillManager,
+ autofillEnabledManager: AutofillEnabledManager,
appStateManager: AppStateManager,
lifecycleScope: LifecycleCoroutineScope,
+ chromeThirdPartyAutofillEnabledManager: ChromeThirdPartyAutofillEnabledManager,
) : AutofillActivityManager {
private val isAutofillEnabledAndSupported: Boolean
get() = autofillManager.isEnabled &&
autofillManager.hasEnabledAutofillServices() &&
autofillManager.isAutofillSupported
+ private val chromeAutofillStatus: ChromeThirdPartyAutofillStatus
+ get() = ChromeThirdPartyAutofillStatus(
+ stableStatusData = chromeThirdPartyAutofillManager.stableChromeAutofillStatus,
+ betaChannelStatusData = chromeThirdPartyAutofillManager.betaChromeAutofillStatus,
+ )
+
init {
appStateManager
.appForegroundStateFlow
- .onEach { autofillEnabledManager.isAutofillEnabled = isAutofillEnabledAndSupported }
+ .onEach {
+ autofillEnabledManager.isAutofillEnabled = isAutofillEnabledAndSupported
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
+ chromeAutofillStatus
+ }
.launchIn(lifecycleScope)
}
}
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManager.kt
new file mode 100644
index 0000000000..4da6696fa4
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManager.kt
@@ -0,0 +1,22 @@
+package com.x8bit.bitwarden.data.autofill.manager.chrome
+
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Manager which provides whether specific Chrome versions have third party autofill available and
+ * enabled.
+ */
+interface ChromeThirdPartyAutofillEnabledManager {
+ /**
+ * Combined status for all concerned Chrome versions.
+ */
+ var chromeThirdPartyAutofillStatus: ChromeThirdPartyAutofillStatus
+
+ /**
+ * An observable [StateFlow] of the combined third party autofill status of all concerned
+ * chrome versions.
+ */
+ val chromeThirdPartyAutofillStatusFlow: Flow
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManagerImpl.kt
new file mode 100644
index 0000000000..23986a30a3
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManagerImpl.kt
@@ -0,0 +1,52 @@
+package com.x8bit.bitwarden.data.autofill.manager.chrome
+
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
+import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
+import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.update
+
+/**
+ * Default implementation of [ChromeThirdPartyAutofillEnabledManager].
+ */
+class ChromeThirdPartyAutofillEnabledManagerImpl(
+ private val featureFlagManager: FeatureFlagManager,
+) : ChromeThirdPartyAutofillEnabledManager {
+ override var chromeThirdPartyAutofillStatus: ChromeThirdPartyAutofillStatus = DEFAULT_STATUS
+ set(value) {
+ field = value
+ mutableChromeThirdPartyAutofillStatusStateFlow.update {
+ value
+ }
+ }
+
+ private val mutableChromeThirdPartyAutofillStatusStateFlow = MutableStateFlow(
+ chromeThirdPartyAutofillStatus,
+ )
+
+ override val chromeThirdPartyAutofillStatusFlow: Flow
+ get() = mutableChromeThirdPartyAutofillStatusStateFlow
+ .combine(
+ featureFlagManager.getFeatureFlagFlow(FlagKey.ChromeAutofill),
+ ) { data, enabled ->
+ if (enabled) {
+ data
+ } else {
+ DEFAULT_STATUS
+ }
+ }
+}
+
+private val DEFAULT_STATUS = ChromeThirdPartyAutofillStatus(
+ ChromeThirdPartyAutoFillData(
+ isAvailable = false,
+ isThirdPartyEnabled = false,
+ ),
+ ChromeThirdPartyAutoFillData(
+ isAvailable = false,
+ isThirdPartyEnabled = false,
+ ),
+)
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillManager.kt
new file mode 100644
index 0000000000..b3b1ded424
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillManager.kt
@@ -0,0 +1,20 @@
+package com.x8bit.bitwarden.data.autofill.manager.chrome
+
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
+
+/**
+ * Manager class used to determine if a device has installed versions of Chrome (either the
+ * stable release or beta channel) which support and require opt in to third party autofill.
+ */
+interface ChromeThirdPartyAutofillManager {
+
+ /**
+ * The data representing the status of the stable chrome version
+ */
+ val stableChromeAutofillStatus: ChromeThirdPartyAutoFillData
+
+ /**
+ * The data representing the status of the beta chrome version
+ */
+ val betaChromeAutofillStatus: ChromeThirdPartyAutoFillData
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillManagerImpl.kt
new file mode 100644
index 0000000000..a1ed46b8ab
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillManagerImpl.kt
@@ -0,0 +1,62 @@
+package com.x8bit.bitwarden.data.autofill.manager.chrome
+
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
+import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
+
+private const val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"
+private const val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"
+private const val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"
+
+/**
+ * Default implementation of the [ChromeThirdPartyAutofillManager] which uses a
+ * [ContentResolver] to determine if the installed Chrome packages support and enable
+ * third party autofill services.
+ *
+ * Based off of [this blog post](https://android-developers.googleblog.com/2025/02/chrome-3p-autofill-services-update.html)
+ */
+@OmitFromCoverage
+class ChromeThirdPartyAutofillManagerImpl(
+ private val context: Context,
+) : ChromeThirdPartyAutofillManager {
+ override val stableChromeAutofillStatus: ChromeThirdPartyAutoFillData
+ get() = getThirdPartyAutoFillStatusForChannel(ChromeReleaseChannel.STABLE)
+ override val betaChromeAutofillStatus: ChromeThirdPartyAutoFillData
+ get() = getThirdPartyAutoFillStatusForChannel(ChromeReleaseChannel.BETA)
+
+ private fun getThirdPartyAutoFillStatusForChannel(
+ releaseChannel: ChromeReleaseChannel,
+ ): ChromeThirdPartyAutoFillData {
+ val uri = Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(releaseChannel.packageName + CONTENT_PROVIDER_NAME)
+ .path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
+ .build()
+ val cursor = context
+ .contentResolver
+ .query(
+ /* uri = */ uri,
+ /* projection = */ arrayOf(THIRD_PARTY_MODE_COLUMN),
+ /* selection = */ null,
+ /* selectionArgs = */ null,
+ /* sortOrder = */ null,
+ )
+ var thirdPartyEnabled = false
+ val isThirdPartyAvailable = cursor
+ ?.let {
+ it.moveToFirst()
+ val columnIndex = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN)
+ thirdPartyEnabled = it.getInt(columnIndex) != 0
+ it.close()
+ true
+ }
+ ?: false
+ return ChromeThirdPartyAutoFillData(
+ isAvailable = isThirdPartyAvailable,
+ isThirdPartyEnabled = thirdPartyEnabled,
+ )
+ }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/chrome/ChromeReleaseChannel.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/chrome/ChromeReleaseChannel.kt
new file mode 100644
index 0000000000..6b52143672
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/chrome/ChromeReleaseChannel.kt
@@ -0,0 +1,14 @@
+package com.x8bit.bitwarden.data.autofill.model.chrome
+
+private const val BETA_CHANNEL_PACKAGE = "com.chrome.beta"
+private const val CHROME_CHANNEL_PACKAGE = "com.android.chrome"
+
+/**
+ * Enumerated values of each version of Chrome supported for third party autofill checks.
+ *
+ * @property packageName the package name of the release channel for the Chrome version.
+ */
+enum class ChromeReleaseChannel(val packageName: String) {
+ STABLE(CHROME_CHANNEL_PACKAGE),
+ BETA(BETA_CHANNEL_PACKAGE),
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/chrome/ChromeThirdPartyAutoFillData.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/chrome/ChromeThirdPartyAutoFillData.kt
new file mode 100644
index 0000000000..bd9ff9aaa3
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/chrome/ChromeThirdPartyAutoFillData.kt
@@ -0,0 +1,17 @@
+package com.x8bit.bitwarden.data.autofill.model.chrome
+
+/**
+ * Relevant data relating to the third party autofill status of a version of the Chrome browser app.
+ */
+data class ChromeThirdPartyAutoFillData(
+ val isAvailable: Boolean,
+ val isThirdPartyEnabled: Boolean,
+)
+
+/**
+ * The overall status for all relevant release channels of Chrome.
+ */
+data class ChromeThirdPartyAutofillStatus(
+ val stableStatusData: ChromeThirdPartyAutoFillData,
+ val betaChannelStatusData: ChromeThirdPartyAutoFillData,
+)
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt
index 0a8f529ab3..09c46cba89 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt
@@ -44,6 +44,7 @@ sealed class FlagKey {
SingleTapPasskeyAuthentication,
AnonAddySelfHostAlias,
SimpleLoginSelfHostAlias,
+ ChromeAutofill,
)
}
}
@@ -222,6 +223,16 @@ sealed class FlagKey {
override val isRemotelyConfigured: Boolean = true
}
+ /**
+ * Data object holding the feature flag key to enable the checking for Chrome's third party
+ * autofill.
+ */
+ data object ChromeAutofill : FlagKey() {
+ override val keyName: String = "enable-pm-chrome-autofill"
+ override val defaultValue: Boolean = false
+ override val isRemotelyConfigured: Boolean = true
+ }
+
//region Dummy keys for testing
/**
* Data object holding the key for a [Boolean] flag to be used in tests.
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt
index 10452d7ea7..9b99901269 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt
@@ -43,6 +43,7 @@ fun FlagKey.ListItemContent(
FlagKey.SingleTapPasskeyAuthentication,
FlagKey.AnonAddySelfHostAlias,
FlagKey.SimpleLoginSelfHostAlias,
+ FlagKey.ChromeAutofill,
-> BooleanFlagItem(
label = flagKey.getDisplayLabel(),
key = flagKey as FlagKey,
@@ -101,4 +102,5 @@ private fun FlagKey.getDisplayLabel(): String = when (this) {
stringResource(R.string.single_tap_passkey_authentication)
FlagKey.AnonAddySelfHostAlias -> stringResource(R.string.anon_addy_self_hosted_aliases)
FlagKey.SimpleLoginSelfHostAlias -> stringResource(R.string.simple_login_self_hosted_aliases)
+ FlagKey.ChromeAutofill -> stringResource(R.string.enable_chrome_autofill)
}
diff --git a/app/src/main/res/values/strings_non_localized.xml b/app/src/main/res/values/strings_non_localized.xml
index 8239a768f8..d92e33d8b3 100644
--- a/app/src/main/res/values/strings_non_localized.xml
+++ b/app/src/main/res/values/strings_non_localized.xml
@@ -28,5 +28,6 @@
Reset all coach mark tours
AnonAddy self-hosted aliases
SimpleLogin self-hosted aliases
+ Enable chrome autofill
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt
index 01ee3a0ce8..ea71296f3d 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt
@@ -3,8 +3,15 @@ package com.x8bit.bitwarden.data.autofill.manager
import android.view.autofill.AutofillManager
import androidx.lifecycle.LifecycleCoroutineScope
import app.cash.turbine.test
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManagerImpl
+import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillManager
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
+import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
+import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
@@ -13,6 +20,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@@ -34,6 +42,16 @@ class AutofillActivityManagerTest {
private val lifecycleScope = mockk {
every { coroutineContext } returns UnconfinedTestDispatcher()
}
+ private val chromeThirdPartyAutofillManager = mockk {
+ every { stableChromeAutofillStatus } returns DEFAULT_CHROME_AUTOFILL_DATA
+ every { betaChromeAutofillStatus } returns DEFAULT_CHROME_AUTOFILL_DATA
+ }
+
+ private val featureFlagManager = mockk {
+ every { getFeatureFlagFlow(FlagKey.ChromeAutofill) } returns MutableStateFlow(true)
+ }
+ private val chromeThirdPartyAutofillEnabledManager: ChromeThirdPartyAutofillEnabledManager =
+ ChromeThirdPartyAutofillEnabledManagerImpl(featureFlagManager = featureFlagManager)
// We will construct an instance here just to hook the various dependencies together internally
@Suppress("unused")
@@ -42,6 +60,8 @@ class AutofillActivityManagerTest {
appStateManager = appStateManager,
autofillEnabledManager = autofillEnabledManager,
lifecycleScope = lifecycleScope,
+ chromeThirdPartyAutofillManager = chromeThirdPartyAutofillManager,
+ chromeThirdPartyAutofillEnabledManager = chromeThirdPartyAutofillEnabledManager,
)
private var isAutofillEnabledAndSupported = false
@@ -70,4 +90,37 @@ class AutofillActivityManagerTest {
assertFalse(awaitItem())
}
}
+
+ @Suppress("MaxLineLength")
+ @Test
+ fun `changes in app foreground status should update the ChromeThirdPartyAutofillEnabledManager as necessary`() =
+ runTest {
+ val updatedBetaState =
+ DEFAULT_CHROME_AUTOFILL_DATA.copy(isAvailable = true)
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatusFlow.test {
+ assertEquals(
+ DEFAULT_EXPECTED_AUTOFILL_STATUS,
+ awaitItem(),
+ )
+ every { chromeThirdPartyAutofillManager.betaChromeAutofillStatus } returns
+ updatedBetaState
+ mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
+ assertEquals(
+ DEFAULT_EXPECTED_AUTOFILL_STATUS.copy(
+ betaChannelStatusData = updatedBetaState,
+ ),
+ awaitItem(),
+ )
+ }
+ }
}
+
+private val DEFAULT_CHROME_AUTOFILL_DATA = ChromeThirdPartyAutoFillData(
+ isAvailable = false,
+ isThirdPartyEnabled = false,
+)
+
+private val DEFAULT_EXPECTED_AUTOFILL_STATUS = ChromeThirdPartyAutofillStatus(
+ stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
+ betaChannelStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
+)
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManagerTest.kt
new file mode 100644
index 0000000000..2caf476232
--- /dev/null
+++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/chrome/ChromeThirdPartyAutofillEnabledManagerTest.kt
@@ -0,0 +1,114 @@
+package com.x8bit.bitwarden.data.autofill.manager.chrome
+
+import app.cash.turbine.test
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
+import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
+import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
+import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.test.runTest
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class ChromeThirdPartyAutofillEnabledManagerTest {
+ private val mutableChromeAutofillFeatureFlow = MutableStateFlow(true)
+ private val featureFlagManager = mockk {
+ every {
+ getFeatureFlagFlow(FlagKey.ChromeAutofill)
+ } returns mutableChromeAutofillFeatureFlow
+ }
+ private val chromeThirdPartyAutofillEnabledManager =
+ ChromeThirdPartyAutofillEnabledManagerImpl(featureFlagManager = featureFlagManager)
+
+ @Suppress("MaxLineLength")
+ @Test
+ fun `chromeThirdPartyAutofillStatusStateFlow should emit whenever isAutofillEnabled is set to a unique value`() =
+ runTest {
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatusFlow.test {
+ assertEquals(
+ DEFAULT_EXPECTED_AUTOFILL_STATUS,
+ awaitItem(),
+ )
+ val firstExpectedStatusChange = DEFAULT_EXPECTED_AUTOFILL_STATUS.copy(
+ stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA.copy(isAvailable = true),
+ )
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
+ firstExpectedStatusChange
+
+ assertEquals(
+ firstExpectedStatusChange,
+ awaitItem(),
+ )
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
+ firstExpectedStatusChange.copy()
+ expectNoEvents()
+
+ val secondExpectedStatusChange = firstExpectedStatusChange
+ .copy(
+ betaChannelStatusData = DEFAULT_CHROME_AUTOFILL_DATA.copy(
+ isThirdPartyEnabled = true,
+ ),
+ )
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
+ secondExpectedStatusChange
+
+ assertEquals(
+ secondExpectedStatusChange,
+ awaitItem(),
+ )
+ }
+ }
+
+ @Suppress("MaxLineLength")
+ @Test
+ fun `chromeThirdPartyAutofillStatusStateFlow should not emit whenever isAutofillEnabled is set to a unique value if the feature is off`() =
+ runTest {
+ mutableChromeAutofillFeatureFlow.update { false }
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatusFlow.test {
+ assertEquals(
+ DEFAULT_EXPECTED_AUTOFILL_STATUS,
+ awaitItem(),
+ )
+ val firstExpectedStatusChange = DEFAULT_EXPECTED_AUTOFILL_STATUS.copy(
+ stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA.copy(isAvailable = true),
+ )
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
+ firstExpectedStatusChange
+
+ assertEquals(
+ DEFAULT_EXPECTED_AUTOFILL_STATUS,
+ awaitItem(),
+ )
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
+ firstExpectedStatusChange.copy()
+ expectNoEvents()
+
+ val secondExpectedStatusChange = firstExpectedStatusChange
+ .copy(
+ betaChannelStatusData = DEFAULT_CHROME_AUTOFILL_DATA.copy(
+ isThirdPartyEnabled = true,
+ ),
+ )
+ chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
+ secondExpectedStatusChange
+
+ assertEquals(
+ DEFAULT_EXPECTED_AUTOFILL_STATUS,
+ awaitItem(),
+ )
+ }
+ }
+}
+
+private val DEFAULT_CHROME_AUTOFILL_DATA = ChromeThirdPartyAutoFillData(
+ isAvailable = false,
+ isThirdPartyEnabled = false,
+)
+
+private val DEFAULT_EXPECTED_AUTOFILL_STATUS = ChromeThirdPartyAutofillStatus(
+ stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
+ betaChannelStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
+)
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/FlagKeyTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/FlagKeyTest.kt
index d6367b3ab2..19d87d7ba2 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/FlagKeyTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/FlagKeyTest.kt
@@ -81,6 +81,10 @@ class FlagKeyTest {
FlagKey.AnonAddySelfHostAlias.keyName,
"anon-addy-self-host-alias",
)
+ assertEquals(
+ FlagKey.ChromeAutofill.keyName,
+ "enable-pm-chrome-autofill",
+ )
}
@Test
@@ -104,6 +108,7 @@ class FlagKeyTest {
FlagKey.AnonAddySelfHostAlias,
FlagKey.SimpleLoginSelfHostAlias,
FlagKey.CipherKeyEncryption,
+ FlagKey.ChromeAutofill,
).all {
!it.defaultValue
},
@@ -132,6 +137,7 @@ class FlagKeyTest {
FlagKey.MutualTls,
FlagKey.AnonAddySelfHostAlias,
FlagKey.SimpleLoginSelfHostAlias,
+ FlagKey.ChromeAutofill,
).all {
it.isRemotelyConfigured
},
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt
index b7b5c50014..21f011caa8 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt
@@ -136,6 +136,7 @@ private val DEFAULT_MAP_VALUE: Map, Any> = mapOf(
FlagKey.SingleTapPasskeyAuthentication to true,
FlagKey.AnonAddySelfHostAlias to true,
FlagKey.SimpleLoginSelfHostAlias to true,
+ FlagKey.ChromeAutofill to true,
)
private val UPDATED_MAP_VALUE: Map, Any> = mapOf(
@@ -157,6 +158,7 @@ private val UPDATED_MAP_VALUE: Map, Any> = mapOf(
FlagKey.SingleTapPasskeyAuthentication to false,
FlagKey.AnonAddySelfHostAlias to false,
FlagKey.SimpleLoginSelfHostAlias to false,
+ FlagKey.ChromeAutofill to false,
)
private val DEFAULT_STATE = DebugMenuState(