Compare commits

...

1 Commits

Author SHA1 Message Date
Patrick Honkonen
eace3123af [PM-36867] fix: Disable card scanner on F-Droid builds (hotfix v2026.4.1) (#6890) 2026-05-08 11:00:39 -05:00
6 changed files with 69 additions and 9 deletions

View File

@@ -294,10 +294,11 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
// Standard-specific flavor dependencies
standardImplementation(libs.google.firebase.cloud.messaging)
standardImplementation(platform(libs.google.firebase.bom))
standardImplementation(libs.google.firebase.crashlytics)
standardImplementation(libs.google.billing)
standardImplementation(platform(libs.google.firebase.bom))
standardImplementation(libs.google.firebase.cloud.messaging)
standardImplementation(libs.google.firebase.crashlytics)
standardImplementation(libs.google.mlkit.text.recognition)
standardImplementation(libs.google.play.review)
// Pull in test fixtures from other modules

View File

@@ -0,0 +1,27 @@
package com.bitwarden.ui.platform.feature.cardscanner.util
import androidx.camera.core.ImageProxy
import com.bitwarden.annotation.OmitFromCoverage
/**
* No-op [CardTextAnalyzer] for the F-Droid build flavor.
*
* Google ML Kit is not permitted in F-Droid releases, so this stub replaces the
* standard analyzer at build time. The Scan Card UI is hidden via
* `BuildInfoManager.isFdroid`; this implementation exists solely to satisfy the
* flavor-uniform construction path used by `LocalManagerProvider`. The
* `cardDataParser` argument is unused, retained so the constructor signature
* matches the standard flavor and call sites remain identical.
*/
@OmitFromCoverage
@Suppress("UnusedParameter")
class CardTextAnalyzerImpl(
cardDataParser: CardDataParser,
) : CardTextAnalyzer {
override lateinit var onCardScanned: (CardScanData) -> Unit
override fun analyze(image: ImageProxy) {
image.close()
}
}

View File

@@ -5,6 +5,7 @@ import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.provider.CallingAppInfo
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.manager.model.FlagKey
import com.bitwarden.core.data.manager.toast.ToastManager
import com.bitwarden.core.data.repository.model.DataState
@@ -126,6 +127,7 @@ class VaultAddEditViewModel @Inject constructor(
featureFlagManager: FeatureFlagManager,
generatorRepository: GeneratorRepository,
cardScanManager: CardScanManager,
private val buildInfoManager: BuildInfoManager,
private val snackbarRelayManager: SnackbarRelayManager<SnackbarRelay>,
private val toastManager: ToastManager,
private val authRepository: AuthRepository,
@@ -183,7 +185,8 @@ class VaultAddEditViewModel @Inject constructor(
VaultAddEditState(
isArchiveEnabled = featureFlagManager.getFeatureFlag(FlagKey.ArchiveItems),
isCardScannerEnabled = featureFlagManager.getFeatureFlag(FlagKey.CardScanner),
isCardScannerEnabled = featureFlagManager
.getFeatureFlag(FlagKey.CardScanner) && !buildInfoManager.isFdroid,
vaultAddEditType = vaultAddEditType,
cipherType = vaultCipherType,
viewState = when (vaultAddEditType) {
@@ -1929,7 +1932,9 @@ class VaultAddEditViewModel @Inject constructor(
private fun handleCardScannerFlagUpdateReceive(
action: VaultAddEditAction.Internal.CardScannerFlagUpdateReceive,
) {
mutableStateFlow.update { it.copy(isCardScannerEnabled = action.isEnabled) }
mutableStateFlow.update {
it.copy(isCardScannerEnabled = action.isEnabled && !buildInfoManager.isFdroid)
}
}
private fun handleCardScanResultReceive(

View File

@@ -13,6 +13,9 @@ import java.util.concurrent.atomic.AtomicBoolean
* [CardTextAnalyzer] implementation that uses ML Kit Text Recognition
* to detect credit card details from camera frames.
*
* Only used in the standard build flavor. The F-Droid flavor provides a no-op
* stub because Google ML Kit is not permitted in F-Droid builds.
*
* @property cardDataParser The parser used to extract card data from
* recognized text.
*/
@@ -23,9 +26,10 @@ class CardTextAnalyzerImpl(
private val isInAnalysis = AtomicBoolean(false)
private val recognizer = TextRecognition.getClient(
TextRecognizerOptions.DEFAULT_OPTIONS,
)
// Lazy so ML Kit is only touched once a scan begins, never during construction.
private val recognizer by lazy {
TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
}
override lateinit var onCardScanned: (CardScanData) -> Unit

View File

@@ -7,6 +7,7 @@ import androidx.credentials.provider.ProviderCreateCredentialRequest
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.collections.CollectionView
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
import com.bitwarden.core.data.manager.model.FlagKey
import com.bitwarden.core.data.manager.toast.ToastManager
@@ -239,6 +240,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
every { getFeatureFlag(FlagKey.CardScanner) } answers { mutableCardScannerFlow.value }
every { getFeatureFlagFlow(FlagKey.CardScanner) } returns mutableCardScannerFlow
}
private val buildInfoManager: BuildInfoManager = mockk {
every { isFdroid } returns false
}
@BeforeEach
fun setup() {
@@ -5142,6 +5146,25 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
)
}
@Test
fun `isCardScannerEnabled should remain false on F-Droid even when flag is on`() =
runTest {
every { buildInfoManager.isFdroid } returns true
mutableCardScannerFlow.value = true
val initState = createVaultAddItemState()
val viewModel = createAddVaultItemViewModel()
assertEquals(
initState.copy(isCardScannerEnabled = false),
viewModel.stateFlow.value,
)
mutableCardScannerFlow.value = false
mutableCardScannerFlow.value = true
assertEquals(
initState.copy(isCardScannerEnabled = false),
viewModel.stateFlow.value,
)
}
@Test
fun `CardScanResultReceive with Success should update card fields and focus name`() =
runTest {
@@ -5523,6 +5546,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
savedStateHandle = savedStateHandle,
featureFlagManager = featureFlagManager,
authRepository = authRepository,
buildInfoManager = buildInfoManager,
clipboardManager = bitwardenClipboardManager,
cardScanManager = cardScanManager,
policyManager = policyManager,

View File

@@ -76,7 +76,6 @@ dependencies {
implementation(libs.androidx.credentials)
implementation(libs.androidx.navigation.compose)
implementation(libs.bumptech.glide)
implementation(libs.google.mlkit.text.recognition)
implementation(libs.kotlinx.serialization)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.collections.immutable)