mirror of
https://github.com/bitwarden/android.git
synced 2026-03-22 06:11:38 -05:00
PM-18314 & PM-18450 Check for Chrome browser 3rd party autofill. (#4752)
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
|
||||
<!-- Protect access to AuthenticatorBridgeService using this custom permission.
|
||||
|
||||
Note that each build type uses a different value for knownCerts.
|
||||
@@ -320,6 +320,11 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
</intent>
|
||||
<!-- To Query Chrome Beta: -->
|
||||
<package android:name="com.chrome.beta" />
|
||||
|
||||
<!-- To Query Chrome Stable: -->
|
||||
<package android:name="com.android.chrome" />
|
||||
</queries>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ChromeThirdPartyAutofillStatus>
|
||||
}
|
||||
@@ -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<ChromeThirdPartyAutofillStatus>
|
||||
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,
|
||||
),
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -44,6 +44,7 @@ sealed class FlagKey<out T : Any> {
|
||||
SingleTapPasskeyAuthentication,
|
||||
AnonAddySelfHostAlias,
|
||||
SimpleLoginSelfHostAlias,
|
||||
ChromeAutofill,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -222,6 +223,16 @@ sealed class FlagKey<out T : Any> {
|
||||
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<Boolean>() {
|
||||
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.
|
||||
|
||||
@@ -43,6 +43,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
|
||||
FlagKey.SingleTapPasskeyAuthentication,
|
||||
FlagKey.AnonAddySelfHostAlias,
|
||||
FlagKey.SimpleLoginSelfHostAlias,
|
||||
FlagKey.ChromeAutofill,
|
||||
-> BooleanFlagItem(
|
||||
label = flagKey.getDisplayLabel(),
|
||||
key = flagKey as FlagKey<Boolean>,
|
||||
@@ -101,4 +102,5 @@ private fun <T : Any> FlagKey<T>.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)
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@
|
||||
<string name="reset_coach_mark_tour_status">Reset all coach mark tours</string>
|
||||
<string name="anon_addy_self_hosted_aliases">AnonAddy self-hosted aliases</string>
|
||||
<string name="simple_login_self_hosted_aliases">SimpleLogin self-hosted aliases</string>
|
||||
<string name="enable_chrome_autofill">Enable chrome autofill</string>
|
||||
<!-- /Debug Menu -->
|
||||
</resources>
|
||||
|
||||
@@ -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<LifecycleCoroutineScope> {
|
||||
every { coroutineContext } returns UnconfinedTestDispatcher()
|
||||
}
|
||||
private val chromeThirdPartyAutofillManager = mockk<ChromeThirdPartyAutofillManager> {
|
||||
every { stableChromeAutofillStatus } returns DEFAULT_CHROME_AUTOFILL_DATA
|
||||
every { betaChromeAutofillStatus } returns DEFAULT_CHROME_AUTOFILL_DATA
|
||||
}
|
||||
|
||||
private val featureFlagManager = mockk<FeatureFlagManager> {
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -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<FeatureFlagManager> {
|
||||
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,
|
||||
)
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -136,6 +136,7 @@ private val DEFAULT_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||
FlagKey.SingleTapPasskeyAuthentication to true,
|
||||
FlagKey.AnonAddySelfHostAlias to true,
|
||||
FlagKey.SimpleLoginSelfHostAlias to true,
|
||||
FlagKey.ChromeAutofill to true,
|
||||
)
|
||||
|
||||
private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||
@@ -157,6 +158,7 @@ private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||
FlagKey.SingleTapPasskeyAuthentication to false,
|
||||
FlagKey.AnonAddySelfHostAlias to false,
|
||||
FlagKey.SimpleLoginSelfHostAlias to false,
|
||||
FlagKey.ChromeAutofill to false,
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE = DebugMenuState(
|
||||
|
||||
Reference in New Issue
Block a user