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(