diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt index 858fa8906..bdfc46af1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity +import android.os.Build import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope @@ -19,6 +20,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.BiometricsKeyResult import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault +import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.ui.platform.base.BaseViewModel @@ -71,9 +73,9 @@ class AccountSecurityViewModel @Inject constructor( ?.activeAccount ?.hasMasterPassword != false, isUnlockWithPinEnabled = settingsRepository.isUnlockWithPinEnabled, - shouldShowEnableAuthenticatorSync = featureFlagManager.getFeatureFlag( - key = FlagKey.AuthenticatorSync, - ), + shouldShowEnableAuthenticatorSync = + featureFlagManager.getFeatureFlag(FlagKey.AuthenticatorSync) && + !isBuildVersionBelow(Build.VERSION_CODES.S), userId = userId, vaultTimeout = settingsRepository.vaultTimeout, vaultTimeoutAction = settingsRepository.vaultTimeoutAction, @@ -372,9 +374,11 @@ class AccountSecurityViewModel @Inject constructor( private fun handleAuthenticatorSyncFeatureFlagUpdate( action: AccountSecurityAction.Internal.AuthenticatorSyncFeatureFlagUpdate, ) { + val shouldShowAuthenticatorSync = + action.isEnabled && !isBuildVersionBelow(Build.VERSION_CODES.S) mutableStateFlow.update { it.copy( - shouldShowEnableAuthenticatorSync = action.isEnabled, + shouldShowEnableAuthenticatorSync = shouldShowAuthenticatorSync, ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 50e9c553b..ce0057b3f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1001,7 +1001,7 @@ Do you want to switch to this account? Please restart registration or try logging in. You may already have an account. Restart registration Authenticator Sync - Allow Bitwarden Authenticator Syncing + Allow authenticator syncing There was an issue validating the registration token. Turn on autofill Use autofill to log into your accounts with a single tap. diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt index b1692e0da..09b2ba040 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt @@ -1465,7 +1465,7 @@ class AccountSecurityScreenTest : BaseComposeTest() { @Test fun `sync with Bitwarden authenticator UI should be displayed according to state`() { - val toggleText = "Allow Bitwarden Authenticator Syncing" + val toggleText = "Allow authenticator syncing" composeTestRule.onNodeWithText(toggleText).assertDoesNotExist() mutableStateFlow.update { DEFAULT_STATE.copy(shouldShowEnableAuthenticatorSync = true) } @@ -1486,7 +1486,7 @@ class AccountSecurityScreenTest : BaseComposeTest() { fun `sync with Bitwarden authenticator click should send AuthenticatorSyncToggle action`() { mutableStateFlow.update { DEFAULT_STATE.copy(shouldShowEnableAuthenticatorSync = true) } composeTestRule - .onNodeWithText("Allow Bitwarden Authenticator Syncing") + .onNodeWithText("Allow authenticator syncing") .performScrollTo() .performClick() verify { viewModel.trySendAction(AccountSecurityAction.AuthenticatorSyncToggle(true)) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt index 49ee1973c..3128445d5 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity +import android.os.Build import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.x8bit.bitwarden.R @@ -22,6 +23,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow +import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy @@ -35,7 +37,9 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.just import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.runs +import io.mockk.unmockkStatic import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow @@ -44,7 +48,9 @@ import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonObject +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import javax.crypto.Cipher @@ -91,6 +97,16 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { every { getFeatureFlag(FlagKey.AuthenticatorSync) } returns false } + @BeforeEach + fun setup() { + mockkStatic(::isBuildVersionBelow) + } + + @AfterEach + fun teardown() { + unmockkStatic(::isBuildVersionBelow) + } + @Test fun `initial state should be correct when saved state is set`() { val viewModel = createViewModel(initialState = DEFAULT_STATE) @@ -679,7 +695,8 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test - fun `when featureFlagManger returns true for AuthenticatorSync, should show authenticator sync UI`() { + fun `when featureFlagManger returns true for AuthenticatorSync, and version is at least 31 should show authenticator sync UI`() { + every { isBuildVersionBelow(Build.VERSION_CODES.S) } returns false val vm = createViewModel( initialState = null, featureFlagManager = mockk { @@ -693,8 +710,22 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `when featureFlagManger returns true for AuthenticatorSync, and version is under 31 should not show authenticator sync UI`() { + every { isBuildVersionBelow(Build.VERSION_CODES.S) } returns true + every { featureFlagManager.getFeatureFlag(FlagKey.AuthenticatorSync) } returns true + every { featureFlagManager.getFeatureFlagFlow(FlagKey.AuthenticatorSync) } returns emptyFlow() + val vm = createViewModel(initialState = null) + assertEquals( + DEFAULT_STATE, + vm.stateFlow.value, + ) + } + @Test fun `when featureFlagManger updates value AuthenticatorSync, should update UI`() = runTest { + every { isBuildVersionBelow(Build.VERSION_CODES.S) } returns false val featureFlagFlow = MutableStateFlow(false) val vm = createViewModel( initialState = null, @@ -712,6 +743,25 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { } } + @Test + @Suppress("MaxLineLength") + fun `when featureFlagManger updates value AuthenticatorSync, authenticator sync row should never show if below API 31`() = + runTest { + every { isBuildVersionBelow(Build.VERSION_CODES.S) } returns true + val featureFlagFlow = MutableStateFlow(false) + every { featureFlagManager.getFeatureFlag(FlagKey.AuthenticatorSync) } returns false + every { + featureFlagManager.getFeatureFlagFlow(FlagKey.AuthenticatorSync) + } returns featureFlagFlow + val vm = createViewModel(initialState = null) + vm.stateFlow.test { + assertEquals(DEFAULT_STATE, awaitItem()) + featureFlagFlow.value = true + featureFlagFlow.value = false + expectNoEvents() + } + } + @Test fun `when showUnlockBadgeFlow updates value, should update state`() = runTest { val viewModel = createViewModel() diff --git a/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/util/AndroidBuildUtils.kt b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/util/AndroidBuildUtils.kt index 3372d99c1..08f7b812b 100644 --- a/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/util/AndroidBuildUtils.kt +++ b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/util/AndroidBuildUtils.kt @@ -7,4 +7,4 @@ import android.os.Build * * @see Build.VERSION_CODES */ -fun isBuildVersionBelow(version: Int): Boolean = version > Build.VERSION.SDK_INT +internal fun isBuildVersionBelow(version: Int): Boolean = version > Build.VERSION.SDK_INT