diff --git a/cxf/build.gradle.kts b/cxf/build.gradle.kts index 7e3b080a6f7..250650a1855 100644 --- a/cxf/build.gradle.kts +++ b/cxf/build.gradle.kts @@ -33,6 +33,16 @@ configure { ) } } + flavorDimensions += listOf("mode") + productFlavors { + create("standard") { + isDefault = true + dimension = "mode" + } + create("fdroid") { + dimension = "mode" + } + } compileOptions { sourceCompatibility(libs.versions.jvmTarget.get()) targetCompatibility(libs.versions.jvmTarget.get()) @@ -46,6 +56,9 @@ kotlin { } dependencies { + fun standardImplementation(dependencyNotation: Any) { + add("standardImplementation", dependencyNotation) + } implementation(project(":annotation")) implementation(project(":core")) @@ -57,13 +70,16 @@ dependencies { implementation(libs.androidx.compose.runtime) implementation(libs.androidx.credentials) implementation(libs.androidx.credentials.providerevents) - implementation(libs.androidx.credentials.providerevents.play.services) implementation(libs.google.hilt.android) ksp(libs.google.hilt.compiler) implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.serialization) implementation(libs.timber) + // Play Services backend for ProviderEventsManager is not FOSS, so it is excluded from the + // F-Droid flavor. The F-Droid flavor supplies no-op stubs instead. + standardImplementation(libs.androidx.credentials.providerevents.play.services) + testImplementation(platform(libs.junit.bom)) testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.junit.jupiter) diff --git a/cxf/src/fdroid/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterImpl.kt b/cxf/src/fdroid/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterImpl.kt new file mode 100644 index 00000000000..c578fef3aab --- /dev/null +++ b/cxf/src/fdroid/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterImpl.kt @@ -0,0 +1,24 @@ +package com.bitwarden.cxf.importer + +import android.content.Context +import androidx.credentials.providerevents.exception.ImportCredentialsUnknownErrorException +import com.bitwarden.annotation.OmitFromCoverage +import com.bitwarden.cxf.importer.model.ImportCredentialsSelectionResult + +/** + * F-Droid implementation of [CredentialExchangeImporter]. Credential exchange relies on the + * Play Services backend, which is not available on F-Droid builds, so import is unsupported and + * always reports a failure if invoked. + */ +@OmitFromCoverage +@Suppress("UnusedParameter") +internal class CredentialExchangeImporterImpl( + activity: Context, +) : CredentialExchangeImporter { + + override suspend fun importCredentials( + credentialTypes: List, + ): ImportCredentialsSelectionResult = ImportCredentialsSelectionResult.Failure( + error = ImportCredentialsUnknownErrorException(), + ) +} diff --git a/cxf/src/fdroid/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt b/cxf/src/fdroid/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt new file mode 100644 index 00000000000..249a862e945 --- /dev/null +++ b/cxf/src/fdroid/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt @@ -0,0 +1,29 @@ +package com.bitwarden.cxf.registry + +import android.app.Application +import androidx.credentials.providerevents.transfer.ClearExportResponse +import androidx.credentials.providerevents.transfer.RegisterExportResponse +import com.bitwarden.annotation.OmitFromCoverage +import com.bitwarden.core.data.util.asFailure +import com.bitwarden.cxf.registry.model.RegistrationRequest + +/** + * F-Droid implementation of [CredentialExchangeRegistry]. Registration relies on the Play Services + * backend, which is not available on F-Droid builds, so registration is a no-op that always fails. + */ +@OmitFromCoverage +@Suppress("UnusedParameter") +internal class CredentialExchangeRegistryImpl( + application: Application, +) : CredentialExchangeRegistry { + + override suspend fun register( + registrationRequest: RegistrationRequest, + ): Result = + UnsupportedOperationException("Credential exchange is not supported on F-Droid builds.") + .asFailure() + + override suspend fun unregister(): Result = + UnsupportedOperationException("Credential exchange is not supported on F-Droid builds.") + .asFailure() +} diff --git a/cxf/src/main/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterImpl.kt b/cxf/src/standard/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterImpl.kt similarity index 100% rename from cxf/src/main/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterImpl.kt rename to cxf/src/standard/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterImpl.kt diff --git a/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt b/cxf/src/standard/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt similarity index 100% rename from cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt rename to cxf/src/standard/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt diff --git a/cxf/src/testFdroid/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterTest.kt b/cxf/src/testFdroid/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterTest.kt new file mode 100644 index 00000000000..d11bb07d272 --- /dev/null +++ b/cxf/src/testFdroid/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterTest.kt @@ -0,0 +1,22 @@ +package com.bitwarden.cxf.importer + +import android.app.Activity +import com.bitwarden.cxf.importer.model.ImportCredentialsSelectionResult +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class CredentialExchangeImporterTest { + + private val importer = CredentialExchangeImporterImpl( + activity = mockk(relaxed = true), + ) + + @Test + fun `importCredentials should return Failure`() = runTest { + val result = importer.importCredentials(listOf("basic-auth")) + + assertTrue(result is ImportCredentialsSelectionResult.Failure) + } +} diff --git a/cxf/src/test/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterTest.kt b/cxf/src/testStandard/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterTest.kt similarity index 100% rename from cxf/src/test/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterTest.kt rename to cxf/src/testStandard/kotlin/com/bitwarden/cxf/importer/CredentialExchangeImporterTest.kt diff --git a/detekt-config.yml b/detekt-config.yml index bf9bdd506d5..fa57c761e03 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -27,7 +27,7 @@ output-reports: comments: active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**', '**ServiceImpl.kt', '**/testFixtures/**' ] + excludes: [ '**/test/**', '**/testStandard/**', '**/testFdroid/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**', '**ServiceImpl.kt', '**/testFixtures/**' ] AbsentOrWrongFileLicense: active: false licenseTemplateFile: 'license.template' diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 36c8d5afbfc..138bf5e54da 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -143,7 +143,14 @@ platform :android do desc "Run library module tests for specified modules" lane :testLibraries do |options| - tasks = options[:target].split(" ").map { |mod| "#{mod}:testDebugUnitTest" } + # Modules with product flavors expose per-flavor test tasks instead of `testDebugUnitTest`. + # For those, run every flavor so both the standard and F-Droid sources are covered. + flavored_module_tasks = { + ":cxf" => ["testStandardDebugUnitTest", "testFdroidDebugUnitTest"], + } + tasks = options[:target].split(" ").flat_map { |mod| + (flavored_module_tasks[mod] || ["testDebugUnitTest"]).map { |task| "#{mod}:#{task}" } + } gradle(tasks: tasks) end