diff --git a/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerImpl.kt b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerImpl.kt index 62c2803adfc..678b6ebdac3 100644 --- a/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerImpl.kt +++ b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerImpl.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.content.pm.PackageManager.NameNotFoundException +import android.os.Build import android.os.IBinder import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner @@ -18,6 +19,7 @@ import com.bitwarden.authenticatorbridge.provider.AuthenticatorBridgeCallbackPro import com.bitwarden.authenticatorbridge.provider.StubAuthenticatorBridgeCallbackProvider import com.bitwarden.authenticatorbridge.provider.SymmetricKeyStorageProvider import com.bitwarden.authenticatorbridge.util.decrypt +import com.bitwarden.authenticatorbridge.util.isBuildVersionBelow import com.bitwarden.authenticatorbridge.util.toFingerprint import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -58,10 +60,10 @@ internal class AuthenticatorBridgeManagerImpl( */ private val mutableSharedAccountsStateFlow: MutableStateFlow = MutableStateFlow( - if (isBitwardenAppInstalled()) { - AccountSyncState.Loading - } else { - AccountSyncState.AppNotInstalled + when { + isBuildVersionBelow(Build.VERSION_CODES.S) -> AccountSyncState.OsVersionNotSupported + !isBitwardenAppInstalled() -> AccountSyncState.AppNotInstalled + else -> AccountSyncState.Loading } ) @@ -102,6 +104,10 @@ internal class AuthenticatorBridgeManagerImpl( } private fun bindService() { + if (isBuildVersionBelow(Build.VERSION_CODES.S)) { + mutableSharedAccountsStateFlow.value = AccountSyncState.OsVersionNotSupported + return + } if (!isBitwardenAppInstalled()) { mutableSharedAccountsStateFlow.value = AccountSyncState.AppNotInstalled return diff --git a/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/model/AccountSyncState.kt b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/model/AccountSyncState.kt index e8e47947014..f69d319461a 100644 --- a/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/model/AccountSyncState.kt +++ b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/manager/model/AccountSyncState.kt @@ -27,6 +27,11 @@ sealed class AccountSyncState { */ data object Loading : AccountSyncState() + /** + * OS version can't support account syncing. + */ + data object OsVersionNotSupported: AccountSyncState() + /** * Accounts successfully synced. */ diff --git a/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/util/AndroidBuildUtils.kt b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/util/AndroidBuildUtils.kt new file mode 100644 index 00000000000..3372d99c167 --- /dev/null +++ b/authenticatorbridge/src/main/java/com/bitwarden/authenticatorbridge/util/AndroidBuildUtils.kt @@ -0,0 +1,10 @@ +package com.bitwarden.authenticatorbridge.util + +import android.os.Build + +/** + * Returns true if the current OS build version is below the provided [version]. + * + * @see Build.VERSION_CODES + */ +fun isBuildVersionBelow(version: Int): Boolean = version > Build.VERSION.SDK_INT diff --git a/authenticatorbridge/src/test/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerTest.kt b/authenticatorbridge/src/test/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerTest.kt index bbcb1740e53..862661784dc 100644 --- a/authenticatorbridge/src/test/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerTest.kt +++ b/authenticatorbridge/src/test/java/com/bitwarden/authenticatorbridge/manager/AuthenticatorBridgeManagerTest.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.content.pm.PackageManager.NameNotFoundException +import android.os.Build import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeServiceCallback import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState @@ -15,6 +16,7 @@ import com.bitwarden.authenticatorbridge.util.FakeSymmetricKeyStorageProvider import com.bitwarden.authenticatorbridge.util.TestAuthenticatorBridgeCallbackProvider import com.bitwarden.authenticatorbridge.util.decrypt import com.bitwarden.authenticatorbridge.util.generateSecretKey +import com.bitwarden.authenticatorbridge.util.isBuildVersionBelow import com.bitwarden.authenticatorbridge.util.toFingerprint import com.bitwarden.authenticatorbridge.util.toSymmetricEncryptionKeyData import io.mockk.every @@ -45,23 +47,27 @@ class AuthenticatorBridgeManagerTest { private val fakeSymmetricKeyStorageProvider = FakeSymmetricKeyStorageProvider() private val testAuthenticatorBridgeCallbackProvider = TestAuthenticatorBridgeCallbackProvider() - private val manager: AuthenticatorBridgeManagerImpl = AuthenticatorBridgeManagerImpl( - context = context, - connectionType = AuthenticatorBridgeConnectionType.DEV, - symmetricKeyStorageProvider = fakeSymmetricKeyStorageProvider, - callbackProvider = testAuthenticatorBridgeCallbackProvider, - processLifecycleOwner = fakeLifecycleOwner, - ) + private lateinit var manager: AuthenticatorBridgeManagerImpl @BeforeEach fun setup() { + mockkStatic(::isBuildVersionBelow) + every { isBuildVersionBelow(Build.VERSION_CODES.S) } returns false mockkConstructor(Intent::class) mockkStatic(IAuthenticatorBridgeService.Stub::class) mockkStatic(EncryptedSharedAccountData::decrypt) + manager = AuthenticatorBridgeManagerImpl( + context = context, + connectionType = AuthenticatorBridgeConnectionType.DEV, + symmetricKeyStorageProvider = fakeSymmetricKeyStorageProvider, + callbackProvider = testAuthenticatorBridgeCallbackProvider, + processLifecycleOwner = fakeLifecycleOwner, + ) } @AfterEach fun teardown() { + mockkStatic(::isBuildVersionBelow) unmockkConstructor(Intent::class) unmockkStatic(IAuthenticatorBridgeService.Stub::class) unmockkStatic(EncryptedSharedAccountData::decrypt) @@ -87,6 +93,26 @@ class AuthenticatorBridgeManagerTest { assertEquals(AccountSyncState.AppNotInstalled, manager.accountSyncStateFlow.value) } + @Test + fun `initial AccountSyncState should be OsVersionNotSupported when OS level is below S`() { + every { isBuildVersionBelow(Build.VERSION_CODES.S) } returns true + val manager = AuthenticatorBridgeManagerImpl( + context = context, + connectionType = AuthenticatorBridgeConnectionType.DEV, + symmetricKeyStorageProvider = fakeSymmetricKeyStorageProvider, + callbackProvider = testAuthenticatorBridgeCallbackProvider, + processLifecycleOwner = fakeLifecycleOwner, + ) + assertEquals(AccountSyncState.OsVersionNotSupported, manager.accountSyncStateFlow.value) + } + + @Test + fun `onStart when OS level is below S should set state to OsVersionNotSupported`() { + every { isBuildVersionBelow(Build.VERSION_CODES.S) } returns true + fakeLifecycleOwner.lifecycle.dispatchOnStart() + assertEquals(AccountSyncState.OsVersionNotSupported, manager.accountSyncStateFlow.value) + } + @Test fun `onStart when bindService fails should set state to error`() { val mockIntent: Intent = mockk()