From f2769a3b29041ea8dd9deb34d3cac9bfa7b8e477 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:58:46 -0500 Subject: [PATCH] fix(build): isolate ML Kit GenAI to the Google flavor (fix F-Droid rb-check) (#5824) Co-authored-by: Claude Opus 4.8 --- androidApp/build.gradle.kts | 1 + .../kotlin/org/meshtastic/app/di/FdroidAiModule.kt | 4 ++++ .../kotlin/org/meshtastic/app/di/GoogleAiModule.kt | 7 +++++++ .../app/discovery}/GeminiNanoSummaryProvider.kt | 11 +++++++---- .../org/meshtastic/desktop/di/DesktopKoinModule.kt | 3 +++ feature/discovery/build.gradle.kts | 2 -- .../discovery/ai/AlgorithmicSummaryProvider.kt | 10 ++++++++-- 7 files changed, 30 insertions(+), 8 deletions(-) rename {feature/discovery/src/androidMain/kotlin/org/meshtastic/feature/discovery/ai => androidApp/src/google/kotlin/org/meshtastic/app/discovery}/GeminiNanoSummaryProvider.kt (90%) rename feature/discovery/src/{jvmMain => commonMain}/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt (76%) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 6de417594..ab72f7429 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -291,6 +291,7 @@ dependencies { googleImplementation(libs.firebase.ai) googleImplementation(libs.firebase.ai.ondevice) googleImplementation(libs.mlkit.translate) + googleImplementation(libs.mlkit.genai.prompt) googleImplementation(libs.androidx.appfunctions) googleImplementation(libs.androidx.appfunctions.service) diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt index 53edf9aca..2920bf22d 100644 --- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt +++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt @@ -18,6 +18,8 @@ package org.meshtastic.app.di import org.koin.core.annotation.Module import org.koin.core.annotation.Single +import org.meshtastic.feature.discovery.ai.AlgorithmicSummaryProvider +import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider import org.meshtastic.feature.docs.ai.AIDocAssistant import org.meshtastic.feature.docs.ai.KeywordFallbackAssistant import org.meshtastic.feature.docs.translation.DocTranslationService @@ -28,5 +30,7 @@ import org.meshtastic.feature.docs.translation.NoOpDocTranslator class FdroidAiModule { @Single fun aiDocAssistant(fallback: KeywordFallbackAssistant): AIDocAssistant = fallback + @Single fun discoverySummaryAiProvider(fallback: AlgorithmicSummaryProvider): DiscoverySummaryAiProvider = fallback + @Single fun docTranslationService(): DocTranslationService = NoOpDocTranslator() } diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt b/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt index 0828a9493..121164eca 100644 --- a/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt +++ b/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt @@ -22,8 +22,11 @@ import okio.Path.Companion.toOkioPath import org.koin.core.annotation.Module import org.koin.core.annotation.Single import org.meshtastic.app.ai.GeminiNanoDocAssistant +import org.meshtastic.app.discovery.GeminiNanoSummaryProvider import org.meshtastic.app.translation.MlKitDocTranslator import org.meshtastic.core.repository.NodeRepository +import org.meshtastic.feature.discovery.DiscoverySummaryGenerator +import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider import org.meshtastic.feature.docs.ai.AIDocAssistant import org.meshtastic.feature.docs.data.DocBundleLoader import org.meshtastic.feature.docs.data.KeywordSearchEngine @@ -44,6 +47,10 @@ class GoogleAiModule { nodeRepository: NodeRepository, ): AIDocAssistant = GeminiNanoDocAssistant(searchEngine, bundleLoader, nodeRepository) + @Single + fun discoverySummaryAiProvider(generator: DiscoverySummaryGenerator): DiscoverySummaryAiProvider = + GeminiNanoSummaryProvider(generator) + @Single fun docTranslationCache(context: Context): DocTranslationCache = DocTranslationCache(cacheDir = context.cacheDir.toOkioPath(), fileSystem = FileSystem.SYSTEM) diff --git a/feature/discovery/src/androidMain/kotlin/org/meshtastic/feature/discovery/ai/GeminiNanoSummaryProvider.kt b/androidApp/src/google/kotlin/org/meshtastic/app/discovery/GeminiNanoSummaryProvider.kt similarity index 90% rename from feature/discovery/src/androidMain/kotlin/org/meshtastic/feature/discovery/ai/GeminiNanoSummaryProvider.kt rename to androidApp/src/google/kotlin/org/meshtastic/app/discovery/GeminiNanoSummaryProvider.kt index 6fc800ef4..50395fc72 100644 --- a/feature/discovery/src/androidMain/kotlin/org/meshtastic/feature/discovery/ai/GeminiNanoSummaryProvider.kt +++ b/androidApp/src/google/kotlin/org/meshtastic/app/discovery/GeminiNanoSummaryProvider.kt @@ -14,26 +14,29 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.meshtastic.feature.discovery.ai +package org.meshtastic.app.discovery import co.touchlab.kermit.Logger import com.google.mlkit.genai.prompt.Generation import com.google.mlkit.genai.prompt.GenerativeModel import com.google.mlkit.genai.prompt.TextPart import com.google.mlkit.genai.prompt.generateContentRequest -import org.koin.core.annotation.Single import org.meshtastic.core.database.entity.DiscoveryPresetResultEntity import org.meshtastic.core.database.entity.DiscoverySessionEntity import org.meshtastic.feature.discovery.DiscoverySummaryGenerator +import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider /** - * Android provider that uses Gemini Nano via ML Kit GenAI Prompt API for on-device AI summaries. + * Google-flavor provider that uses Gemini Nano via the ML Kit GenAI Prompt API for on-device AI summaries. + * + * Lives in the Google flavor source set (not the shared `:feature:discovery` module) so the proprietary ML Kit GenAI + * dependency never reaches the F-Droid build. The F-Droid flavor binds + * [org.meshtastic.feature.discovery.ai. AlgorithmicSummaryProvider] instead. * * Falls back to [DiscoverySummaryGenerator] when: * - The on-device model is unavailable (unsupported hardware or not downloaded) * - Generation fails for any reason */ -@Single(binds = [DiscoverySummaryAiProvider::class]) class GeminiNanoSummaryProvider(private val generator: DiscoverySummaryGenerator) : DiscoverySummaryAiProvider { private val log = Logger.withTag("GeminiNanoSummary") diff --git a/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt b/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt index c47a1d4b0..3e7826074 100644 --- a/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt +++ b/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt @@ -87,6 +87,8 @@ import org.meshtastic.desktop.stub.NoopMeshLocationManager import org.meshtastic.desktop.stub.NoopMeshWorkerManager import org.meshtastic.desktop.stub.NoopPhoneLocationProvider import org.meshtastic.desktop.stub.NoopPlatformAnalytics +import org.meshtastic.feature.discovery.ai.AlgorithmicSummaryProvider +import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider import org.meshtastic.feature.docs.ai.AIDocAssistant import org.meshtastic.feature.docs.ai.KeywordFallbackAssistant import org.meshtastic.feature.docs.translation.DocTranslationService @@ -227,6 +229,7 @@ private fun desktopPlatformStubsModule() = module { // AI assistant: keyword-only fallback on desktop (no on-device model) single { get() } + single { get() } single { NoOpDocTranslator() } // Desktop uses the real ApiService implementation (no flavor stub needed) diff --git a/feature/discovery/build.gradle.kts b/feature/discovery/build.gradle.kts index 8fa4407bc..bc26d4d78 100644 --- a/feature/discovery/build.gradle.kts +++ b/feature/discovery/build.gradle.kts @@ -51,7 +51,5 @@ kotlin { } commonTest.dependencies { implementation(projects.core.testing) } - - androidMain.dependencies { implementation(libs.mlkit.genai.prompt) } } } diff --git a/feature/discovery/src/jvmMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt b/feature/discovery/src/commonMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt similarity index 76% rename from feature/discovery/src/jvmMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt rename to feature/discovery/src/commonMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt index 3fa5a96b5..c8d89eb62 100644 --- a/feature/discovery/src/jvmMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt +++ b/feature/discovery/src/commonMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt @@ -21,8 +21,14 @@ import org.meshtastic.core.database.entity.DiscoveryPresetResultEntity import org.meshtastic.core.database.entity.DiscoverySessionEntity import org.meshtastic.feature.discovery.DiscoverySummaryGenerator -/** JVM/Desktop fallback that delegates to the algorithmic [DiscoverySummaryGenerator]. */ -@Single(binds = [DiscoverySummaryAiProvider::class]) +/** + * Algorithmic [DiscoverySummaryAiProvider] that delegates to the deterministic [DiscoverySummaryGenerator]. + * + * Used wherever no on-device AI model is available: Desktop, iOS, and the Android F-Droid flavor. Registered with + * `binds = []` so it is injectable by concrete type but is not auto-bound to [DiscoverySummaryAiProvider]; each + * platform binds the interface explicitly (the Google flavor binds the Gemini Nano provider instead). + */ +@Single(binds = []) class AlgorithmicSummaryProvider(private val generator: DiscoverySummaryGenerator) : DiscoverySummaryAiProvider { override val isAvailable: Boolean = true