androjack-mcp 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
  3. package/.github/pull_request_template.md +16 -0
  4. package/CONTRIBUTING.md +27 -0
  5. package/LICENSE +21 -0
  6. package/README.md +592 -0
  7. package/SECURITY.md +26 -0
  8. package/assets/AndroJack banner.png +0 -0
  9. package/assets/killer_argument.png +0 -0
  10. package/build/constants.js +412 -0
  11. package/build/http-server.js +163 -0
  12. package/build/http.js +151 -0
  13. package/build/index.js +553 -0
  14. package/build/install.js +379 -0
  15. package/build/logger.js +57 -0
  16. package/build/tools/api-level.js +170 -0
  17. package/build/tools/api36-compliance.js +282 -0
  18. package/build/tools/architecture.js +75 -0
  19. package/build/tools/build-publish.js +362 -0
  20. package/build/tools/component.js +90 -0
  21. package/build/tools/debugger.js +82 -0
  22. package/build/tools/gradle.js +234 -0
  23. package/build/tools/kmp.js +348 -0
  24. package/build/tools/kotlin-patterns.js +500 -0
  25. package/build/tools/large-screen.js +366 -0
  26. package/build/tools/m3-expressive.js +447 -0
  27. package/build/tools/navigation3.js +331 -0
  28. package/build/tools/ondevice-ai.js +283 -0
  29. package/build/tools/permissions.js +404 -0
  30. package/build/tools/play-policy.js +221 -0
  31. package/build/tools/scalability.js +621 -0
  32. package/build/tools/search.js +89 -0
  33. package/build/tools/testing.js +439 -0
  34. package/build/tools/wear.js +337 -0
  35. package/build/tools/xr.js +274 -0
  36. package/config/antigravity_mcp.json +32 -0
  37. package/config/claude_desktop_config.json +17 -0
  38. package/config/cursor_mcp.json +21 -0
  39. package/config/jetbrains_mcp.json +28 -0
  40. package/config/kiro_mcp.json +40 -0
  41. package/config/vscode_mcp.json +24 -0
  42. package/config/windsurf_mcp.json +18 -0
  43. package/package.json +51 -0
  44. package/src/constants.ts +436 -0
  45. package/src/http-server.ts +186 -0
  46. package/src/http.ts +190 -0
  47. package/src/index.ts +702 -0
  48. package/src/install.ts +441 -0
  49. package/src/logger.ts +67 -0
  50. package/src/tools/api-level.ts +198 -0
  51. package/src/tools/api36-compliance.ts +289 -0
  52. package/src/tools/architecture.ts +94 -0
  53. package/src/tools/build-publish.ts +379 -0
  54. package/src/tools/component.ts +106 -0
  55. package/src/tools/debugger.ts +111 -0
  56. package/src/tools/gradle.ts +288 -0
  57. package/src/tools/kmp.ts +352 -0
  58. package/src/tools/kotlin-patterns.ts +534 -0
  59. package/src/tools/large-screen.ts +391 -0
  60. package/src/tools/m3-expressive.ts +473 -0
  61. package/src/tools/navigation3.ts +338 -0
  62. package/src/tools/ondevice-ai.ts +287 -0
  63. package/src/tools/permissions.ts +445 -0
  64. package/src/tools/play-policy.ts +229 -0
  65. package/src/tools/scalability.ts +646 -0
  66. package/src/tools/search.ts +112 -0
  67. package/src/tools/testing.ts +460 -0
  68. package/src/tools/wear.ts +343 -0
  69. package/src/tools/xr.ts +278 -0
  70. package/tsconfig.json +17 -0
@@ -0,0 +1,331 @@
1
+ // Tool 14: Navigation 3 Guide
2
+ // Navigation 3 went stable November 2025 — complete architectural rethink from Nav2.
3
+ // This tool exists because AI tools hallucinate Nav2 code for new projects even with
4
+ // internet access (documented Atomic Robot case study, January 2026).
5
+ export async function androidNavigation3Guide(topic) {
6
+ const t = topic.toLowerCase().trim();
7
+ // ── Overview / default ─────────────────────────────────────────────────────
8
+ const overview = `
9
+ # Navigation 3 — Official Reference (Stable, November 2025)
10
+ Source: https://developer.android.com/guide/navigation/navigation-3
11
+
12
+ ## ⚠️ CRITICAL FOR AI TOOLS
13
+ Navigation 3 is the current official navigation library as of November 2025.
14
+ Do NOT generate Navigation 2 (NavController/NavHost/NavGraph) for new projects.
15
+ Google's Nav3 migration docs contain explicit "AI Agent:" annotations — this is how
16
+ aware Google is that AI tools hallucinate stale navigation code.
17
+
18
+ ## Why Nav3 Is a Breaking Paradigm Shift (Not Just a Version Bump)
19
+
20
+ | Concept | Navigation 2 | Navigation 3 |
21
+ |---------|-------------|-------------|
22
+ | Back stack | Internal library state | Plain Kotlin List<NavEntry<*>> |
23
+ | State management | NavController owns it | You own it (observable, testable) |
24
+ | Navigation trigger | NavController.navigate() | Add/remove from your back stack list |
25
+ | Graph definition | NavHost + composable() destinations | NavDisplay + type-safe NavKey |
26
+ | Adaptive layouts | Separate NavigationSuiteScaffold | Built-in via Scenes API |
27
+ | State persistence | rememberSaveable | rememberNavBackStack (custom serialization) |
28
+ | Testing | Hard — internal state | Easy — back stack is a plain list |
29
+
30
+ ## Core Setup
31
+
32
+ \`\`\`kotlin
33
+ // libs.versions.toml
34
+ [versions]
35
+ navigation3 = "1.0.0" // Stable November 2025
36
+
37
+ [libraries]
38
+ navigation3-compose = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" }
39
+ navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" }
40
+ \`\`\`
41
+
42
+ \`\`\`kotlin
43
+ // build.gradle.kts
44
+ implementation(libs.navigation3.compose)
45
+ implementation(libs.navigation3.runtime)
46
+ \`\`\`
47
+
48
+ ## NavKey — Type-Safe Destination Keys
49
+
50
+ \`\`\`kotlin
51
+ // Each destination is a serializable key — NOT a route string
52
+ @Serializable
53
+ data object HomeKey // Simple screen
54
+
55
+ @Serializable
56
+ data class ProfileKey(val userId: String) // Screen with args — type-safe, no stringly-typed routes
57
+
58
+ @Serializable
59
+ data class DetailKey(val itemId: Int, val title: String)
60
+ \`\`\`
61
+
62
+ ## rememberNavBackStack — Your Back Stack
63
+
64
+ \`\`\`kotlin
65
+ // The back stack is a plain Kotlin list you control
66
+ // NOT NavController.navigate() — you add/remove entries directly
67
+ val backStack = rememberNavBackStack(HomeKey) // Start with HomeKey
68
+
69
+ // Navigate forward
70
+ backStack.add(ProfileKey(userId = "abc123"))
71
+
72
+ // Go back
73
+ backStack.removeLastOrNull()
74
+
75
+ // Go back to specific destination (like popUpTo)
76
+ backStack.removeAll { it.key is ProfileKey }
77
+
78
+ // Replace current
79
+ backStack[backStack.lastIndex] = DetailKey(itemId = 1, title = "Item")
80
+ \`\`\`
81
+
82
+ ## NavDisplay — The Renderer
83
+
84
+ \`\`\`kotlin
85
+ @Composable
86
+ fun AppNavigation() {
87
+ val backStack = rememberNavBackStack(HomeKey)
88
+
89
+ NavDisplay(
90
+ backStack = backStack,
91
+ entryProvider = entryProvider {
92
+ entry<HomeKey> { HomeScreen(onNavigateToProfile = { id -> backStack.add(ProfileKey(id)) }) }
93
+ entry<ProfileKey> { key -> ProfileScreen(userId = key.userId) }
94
+ entry<DetailKey> { key -> DetailScreen(itemId = key.itemId, title = key.title) }
95
+ }
96
+ )
97
+ }
98
+ \`\`\`
99
+
100
+ ## State Persistence — rememberSerializable (NOT rememberSaveable)
101
+
102
+ \`\`\`kotlin
103
+ // ❌ WRONG — rememberSaveable does NOT work for NavBackStack
104
+ val backStack = rememberSaveable { mutableStateListOf(HomeKey) }
105
+
106
+ // ✅ CORRECT — rememberNavBackStack uses rememberSerializable internally
107
+ // All NavKey types MUST be @Serializable
108
+ val backStack = rememberNavBackStack(HomeKey)
109
+ \`\`\`
110
+
111
+ Source: https://developer.android.com/guide/navigation/navigation-3#state-persistence
112
+ `;
113
+ // ── Scenes API ─────────────────────────────────────────────────────────────
114
+ const scenes = `
115
+ # Navigation 3 — Scenes API (Adaptive Multi-Pane)
116
+ Source: https://developer.android.com/guide/navigation/navigation-3/adaptive
117
+
118
+ ## What Scenes Does
119
+
120
+ The Scenes API replaces the need to combine NavigationSuiteScaffold + separate adaptive
121
+ logic. Nav3 Scenes makes the back stack itself adaptive — the same back stack entries
122
+ automatically render as single-pane on phones or multi-pane on tablets/foldables.
123
+
124
+ \`\`\`kotlin
125
+ // Additional dependency for Scenes
126
+ implementation("androidx.navigation3:navigation3-adaptive:1.0.0")
127
+ \`\`\`
128
+
129
+ ## List-Detail with Scenes
130
+
131
+ \`\`\`kotlin
132
+ @Serializable data object InboxKey
133
+ @Serializable data class EmailKey(val emailId: String)
134
+
135
+ @Composable
136
+ fun AdaptiveMailApp() {
137
+ val backStack = rememberNavBackStack(InboxKey)
138
+ val adaptiveInfo = currentWindowAdaptiveInfo()
139
+
140
+ NavDisplay(
141
+ backStack = backStack,
142
+ // Scenes tells NavDisplay how many entries to show simultaneously
143
+ sceneStrategy = rememberListDetailSceneStrategy(),
144
+ entryProvider = entryProvider {
145
+ entry<InboxKey> {
146
+ InboxScreen(onEmailClick = { id -> backStack.add(EmailKey(id)) })
147
+ }
148
+ entry<EmailKey> { key ->
149
+ // On phone: full screen. On tablet: side-by-side with Inbox.
150
+ // Zero conditional logic here — Scenes handles it.
151
+ EmailDetailScreen(emailId = key.emailId)
152
+ }
153
+ }
154
+ )
155
+ }
156
+ \`\`\`
157
+
158
+ ## Custom Scene Strategy
159
+
160
+ \`\`\`kotlin
161
+ // Show up to 2 entries side-by-side on Expanded width
162
+ val sceneStrategy = remember {
163
+ TwoPaneSceneStrategy<Any>(
164
+ showTwoPanes = { adaptiveInfo ->
165
+ adaptiveInfo.windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED
166
+ }
167
+ )
168
+ }
169
+ \`\`\`
170
+
171
+ Source: https://developer.android.com/guide/navigation/navigation-3/adaptive
172
+ `;
173
+ // ── Migration from Nav2 ────────────────────────────────────────────────────
174
+ const migration = `
175
+ # Navigation 3 — Migrating from Navigation 2
176
+ Source: https://developer.android.com/guide/navigation/navigation-3/migrate
177
+
178
+ ## AI Agent: Read This Section Carefully
179
+ Google embedded "AI Agent:" annotations in the official Nav3 migration guide specifically
180
+ because AI tools were hallucinating outdated Nav3 code. This tool surfaces the key points.
181
+
182
+ ## API Mapping
183
+
184
+ | Navigation 2 | Navigation 3 Equivalent |
185
+ |-------------|------------------------|
186
+ | NavController | rememberNavBackStack (your list) |
187
+ | NavHost | NavDisplay |
188
+ | composable("route") { } | entry<MyKey> { } |
189
+ | navController.navigate("route") | backStack.add(MyKey) |
190
+ | navController.popBackStack() | backStack.removeLastOrNull() |
191
+ | navController.navigate("route") { popUpTo("other") } | backStack.removeAll { it.key is OtherKey }; backStack.add(MyKey) |
192
+ | arguments bundle | NavKey constructor params (type-safe) |
193
+ | NavDeepLink | Handled separately, not via NavKey |
194
+ | NavGraph nesting | Just add entries — no graph nesting concept |
195
+
196
+ ## Migration Steps
197
+
198
+ 1. **Replace string routes with @Serializable data class/object NavKeys**
199
+ \`\`\`kotlin
200
+ // Before (Nav2)
201
+ composable("profile/{userId}") { backStackEntry ->
202
+ val userId = backStackEntry.arguments?.getString("userId")
203
+ ProfileScreen(userId)
204
+ }
205
+
206
+ // After (Nav3)
207
+ @Serializable data class ProfileKey(val userId: String)
208
+ entry<ProfileKey> { key -> ProfileScreen(key.userId) }
209
+ \`\`\`
210
+
211
+ 2. **Replace NavController with your back stack**
212
+ \`\`\`kotlin
213
+ // Before
214
+ navController.navigate("profile/abc123")
215
+
216
+ // After
217
+ backStack.add(ProfileKey(userId = "abc123"))
218
+ \`\`\`
219
+
220
+ 3. **Remove NavHost, replace with NavDisplay**
221
+ \`\`\`kotlin
222
+ // Before
223
+ NavHost(navController, startDestination = "home") { ... }
224
+
225
+ // After
226
+ NavDisplay(backStack = backStack, entryProvider = entryProvider { ... })
227
+ \`\`\`
228
+
229
+ 4. **Update ViewModel navigation** — pass backStack as a parameter or use a shared state holder
230
+ \`\`\`kotlin
231
+ // ViewModel cannot hold NavBackStack directly (lifecycle reasons)
232
+ // Pass navigation callbacks instead
233
+ @Composable
234
+ fun HomeScreen(onNavigateToProfile: (String) -> Unit) { ... }
235
+
236
+ // In NavDisplay entry:
237
+ entry<HomeKey> {
238
+ HomeScreen(onNavigateToProfile = { id -> backStack.add(ProfileKey(id)) })
239
+ }
240
+ \`\`\`
241
+
242
+ ## What Does NOT Change in Nav3
243
+
244
+ - Bottom navigation / NavigationRail / NavigationDrawer — still use NavigationSuiteScaffold
245
+ (or use the Scenes API which does it automatically for the content area)
246
+ - Deep links — handled at the Activity level, then push the appropriate NavKey onto the stack
247
+ - ViewModel scoping — still use viewModel() inside entry { } blocks, scoped to that entry
248
+
249
+ Source: https://developer.android.com/guide/navigation/navigation-3/migrate
250
+ `;
251
+ // ── Testing ────────────────────────────────────────────────────────────────
252
+ const testing = `
253
+ # Navigation 3 — Testing
254
+ Source: https://developer.android.com/guide/navigation/navigation-3/testing
255
+
256
+ ## Why Nav3 Testing Is Easier Than Nav2
257
+
258
+ The back stack is a plain Kotlin list. You don't need a NavController mock.
259
+ You don't need NavHostFragment. You just observe the list.
260
+
261
+ \`\`\`kotlin
262
+ @Test
263
+ fun navigatingToProfile_addsProfileKeyToBackStack() {
264
+ // Arrange
265
+ val backStack = mutableStateListOf<Any>(HomeKey)
266
+
267
+ // Act
268
+ backStack.add(ProfileKey(userId = "user1"))
269
+
270
+ // Assert — direct list assertion, no NavController involved
271
+ assertThat(backStack.last()).isEqualTo(ProfileKey(userId = "user1"))
272
+ assertThat(backStack.size).isEqualTo(2)
273
+ }
274
+
275
+ @Test
276
+ fun backPress_removesLastEntry() {
277
+ val backStack = mutableStateListOf<Any>(HomeKey, ProfileKey("u1"))
278
+ backStack.removeLastOrNull()
279
+ assertThat(backStack.last()).isEqualTo(HomeKey)
280
+ }
281
+ \`\`\`
282
+
283
+ ## Compose UI Test with Nav3
284
+
285
+ \`\`\`kotlin
286
+ @get:Rule val composeTestRule = createComposeRule()
287
+
288
+ @Test
289
+ fun clickingProfile_showsProfileScreen() {
290
+ val backStack = mutableStateListOf<Any>(HomeKey)
291
+
292
+ composeTestRule.setContent {
293
+ NavDisplay(
294
+ backStack = backStack,
295
+ entryProvider = entryProvider {
296
+ entry<HomeKey> { HomeScreen(onNavigateToProfile = { backStack.add(ProfileKey(it)) }) }
297
+ entry<ProfileKey> { key -> ProfileScreen(userId = key.userId) }
298
+ }
299
+ )
300
+ }
301
+
302
+ composeTestRule.onNodeWithTag("profileButton").performClick()
303
+ composeTestRule.onNodeWithText("Profile").assertIsDisplayed()
304
+ assertThat(backStack.last()).isInstanceOf(ProfileKey::class.java)
305
+ }
306
+ \`\`\`
307
+
308
+ Source: https://developer.android.com/guide/navigation/navigation-3/testing
309
+ `;
310
+ // ── Route dispatch ─────────────────────────────────────────────────────────
311
+ if (t.includes("scene") || t.includes("adaptive") || t.includes("pane") || t.includes("tablet")) {
312
+ return scenes;
313
+ }
314
+ if (t.includes("migrat") || t.includes("nav2") || t.includes("upgrade")) {
315
+ return migration;
316
+ }
317
+ if (t.includes("test")) {
318
+ return testing;
319
+ }
320
+ if (t.includes("key") || t.includes("backstack") || t.includes("back stack") || t.includes("display")) {
321
+ return overview;
322
+ }
323
+ // Default: overview + scenes summary
324
+ return overview + "\n\n---\n\n" + scenes.split("\n## What Scenes Does")[0] +
325
+ "\n\n## Quick Scenes Example\n" +
326
+ "For adaptive list-detail navigation, use `rememberListDetailSceneStrategy()` — " +
327
+ "the same back stack renders as single-pane on phones and two-pane on tablets.\n" +
328
+ "Query 'scenes' for the full example.\n\n" +
329
+ "**Other topics:** 'migration' (from Nav2), 'testing', 'scenes' (adaptive multi-pane)\n\n" +
330
+ "Source: https://developer.android.com/guide/navigation/navigation-3";
331
+ }
@@ -0,0 +1,283 @@
1
+ // Tool 17: On-Device AI / AICore Guide
2
+ // Android 16 shipped AICore and ML Kit Gen AI API — local LLMs on Android, no server round-trip.
3
+ // Used by Gmail (Smart Reply), Google Photos (object detection), Pixel Screenshots (semantic search).
4
+ // No MCP server covers this. AI tools default to cloud API calls when on-device is now the answer.
5
+ export async function androidOnDeviceAiGuide(topic) {
6
+ const t = topic.toLowerCase().trim();
7
+ const overview = `
8
+ # On-Device AI — Android AICore & ML Kit Gen AI (2025)
9
+ Source: https://developer.android.com/ai/aicore
10
+ Source: https://developers.google.com/ml-kit/genai
11
+
12
+ ## What On-Device AI Means in 2026
13
+
14
+ Android 16 added AICore — a system-level service that manages on-device LLMs.
15
+ ML Kit Gen AI API lets apps access these models through a standard interface.
16
+
17
+ Key benefits vs cloud:
18
+ - **Zero latency** — no network round-trip
19
+ - **Privacy** — data never leaves the device
20
+ - **Offline** — works without internet
21
+ - **Cost** — no API billing
22
+
23
+ ## Which Google Apps Use It
24
+
25
+ - **Gmail** — Smart Reply and Smart Compose on Pixel devices (on-device)
26
+ - **Google Photos** — object detection, face grouping, on-device search
27
+ - **Google Docs** — local summarization on Pixel
28
+ - **Pixel Screenshots** — semantic search across screenshot history
29
+
30
+ ## Architecture Pattern — Repository Interface for Swappable Backends
31
+
32
+ The official Google pattern: wrap ML models behind a repository interface so the
33
+ implementation can switch between on-device (AICore) and cloud (Vertex AI) without
34
+ touching the UI layer. This is the same MVVM/repository pattern you already know.
35
+
36
+ \`\`\`kotlin
37
+ // Domain layer — platform-agnostic interface
38
+ interface AiTextRepository {
39
+ suspend fun summarize(text: String): Result<String>
40
+ suspend fun generateReply(context: String, options: List<String>): Result<String>
41
+ fun isAvailable(): Flow<Boolean>
42
+ }
43
+
44
+ // Data layer — on-device implementation
45
+ class OnDeviceAiRepository(
46
+ private val generativeModel: GenerativeModel // ML Kit Gen AI
47
+ ) : AiTextRepository {
48
+ override suspend fun summarize(text: String): Result<String> = runCatching {
49
+ val response = generativeModel.generateContent("Summarize: $text")
50
+ response.text ?: throw IllegalStateException("No response from on-device model")
51
+ }
52
+ override fun isAvailable(): Flow<Boolean> = flow {
53
+ emit(GenerativeModel.isAvailable())
54
+ }
55
+ }
56
+
57
+ // Data layer — cloud fallback implementation
58
+ class CloudAiRepository(
59
+ private val vertexAi: FirebaseVertexAI
60
+ ) : AiTextRepository {
61
+ override suspend fun summarize(text: String): Result<String> = runCatching {
62
+ val model = vertexAi.generativeModel("gemini-2.0-flash")
63
+ val response = model.generateContent("Summarize: $text")
64
+ response.text ?: throw IllegalStateException("No response from cloud model")
65
+ }
66
+ override fun isAvailable(): Flow<Boolean> = flowOf(true) // Cloud always available (with network)
67
+ }
68
+ \`\`\`
69
+
70
+ Source: https://developer.android.com/ai/aicore
71
+ `;
72
+ const setup = `
73
+ # On-Device AI — Setup & Initialization
74
+ Source: https://developer.android.com/ml-kit/genai/on-device
75
+
76
+ ## Dependencies
77
+
78
+ \`\`\`toml
79
+ # libs.versions.toml
80
+ [versions]
81
+ mlkit-genai = "0.1.1" # Check for latest at https://developers.google.com/ml-kit/genai
82
+
83
+ [libraries]
84
+ mlkit-genai-inference = { group = "com.google.mlkit", name = "genai-common", version.ref = "mlkit-genai" }
85
+ \`\`\`
86
+
87
+ \`\`\`kotlin
88
+ // build.gradle.kts
89
+ implementation(libs.mlkit.genai.inference)
90
+ \`\`\`
91
+
92
+ ## Check Model Availability Before Use
93
+
94
+ On-device models are only available on supported Pixel devices with the model downloaded.
95
+ Always check availability and provide a fallback.
96
+
97
+ \`\`\`kotlin
98
+ // Check if on-device AI is available on this device
99
+ class AiRepositoryFactory @Inject constructor(
100
+ @ApplicationContext private val context: Context,
101
+ private val cloudRepository: CloudAiRepository
102
+ ) {
103
+ suspend fun create(): AiTextRepository {
104
+ return if (isOnDeviceAvailable()) {
105
+ val model = GenerativeModel.getInstance(context)
106
+ OnDeviceAiRepository(model)
107
+ } else {
108
+ cloudRepository // Graceful fallback to cloud
109
+ }
110
+ }
111
+
112
+ private suspend fun isOnDeviceAvailable(): Boolean {
113
+ return try {
114
+ GenerativeModel.isAvailable(context)
115
+ } catch (e: Exception) {
116
+ false
117
+ }
118
+ }
119
+ }
120
+ \`\`\`
121
+
122
+ ## ViewModel Integration
123
+
124
+ \`\`\`kotlin
125
+ @HiltViewModel
126
+ class SummaryViewModel @Inject constructor(
127
+ private val aiRepository: AiTextRepository // Interface — doesn't know if on-device or cloud
128
+ ) : ViewModel() {
129
+
130
+ private val _uiState = MutableStateFlow<SummaryUiState>(SummaryUiState.Idle)
131
+ val uiState: StateFlow<SummaryUiState> = _uiState.asStateFlow()
132
+
133
+ fun summarize(text: String) {
134
+ viewModelScope.launch {
135
+ _uiState.value = SummaryUiState.Loading
136
+ aiRepository.summarize(text)
137
+ .onSuccess { summary -> _uiState.value = SummaryUiState.Success(summary) }
138
+ .onFailure { error -> _uiState.value = SummaryUiState.Error(error.message) }
139
+ }
140
+ }
141
+ }
142
+
143
+ sealed interface SummaryUiState {
144
+ data object Idle : SummaryUiState
145
+ data object Loading : SummaryUiState
146
+ data class Success(val summary: String) : SummaryUiState
147
+ data class Error(val message: String?) : SummaryUiState
148
+ }
149
+ \`\`\`
150
+
151
+ Source: https://developer.android.com/ml-kit/genai/on-device
152
+ `;
153
+ const smartReply = `
154
+ # On-Device AI — Smart Reply Pattern (Gmail-Style)
155
+ Source: https://developers.google.com/ml-kit/genai/on-device
156
+
157
+ ## Smart Reply Implementation
158
+
159
+ \`\`\`kotlin
160
+ // Domain interface
161
+ interface SmartReplyRepository {
162
+ suspend fun getSuggestedReplies(conversationHistory: List<Message>): Result<List<String>>
163
+ }
164
+
165
+ // On-device implementation using ML Kit
166
+ class OnDeviceSmartReplyRepository @Inject constructor(
167
+ private val generativeModel: GenerativeModel
168
+ ) : SmartReplyRepository {
169
+
170
+ override suspend fun getSuggestedReplies(
171
+ conversationHistory: List<Message>
172
+ ): Result<List<String>> = runCatching {
173
+ val prompt = buildPrompt(conversationHistory)
174
+ val response = generativeModel.generateContent(prompt)
175
+
176
+ // Parse the structured response into reply options
177
+ response.text
178
+ ?.lines()
179
+ ?.filter { it.isNotBlank() }
180
+ ?.take(3) // Limit to 3 suggestions like Gmail
181
+ ?: emptyList()
182
+ }
183
+
184
+ private fun buildPrompt(history: List<Message>): String {
185
+ val conversation = history.takeLast(5).joinToString("\n") { msg ->
186
+ "\${msg.sender}: \${msg.text}"
187
+ }
188
+ return """
189
+ Given this conversation:
190
+ $conversation
191
+
192
+ Suggest 3 short, natural reply options (one per line, no numbering, max 10 words each):
193
+ """.trimIndent()
194
+ }
195
+ }
196
+
197
+ // UI — chips that appear above keyboard
198
+ @Composable
199
+ fun SmartReplySuggestions(
200
+ suggestions: List<String>,
201
+ onSuggestionClick: (String) -> Unit
202
+ ) {
203
+ if (suggestions.isEmpty()) return
204
+
205
+ LazyRow(
206
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
207
+ contentPadding = PaddingValues(horizontal = 16.dp)
208
+ ) {
209
+ items(suggestions) { suggestion ->
210
+ SuggestionChip(
211
+ onClick = { onSuggestionClick(suggestion) },
212
+ label = { Text(suggestion) }
213
+ )
214
+ }
215
+ }
216
+ }
217
+ \`\`\`
218
+
219
+ Source: https://developers.google.com/ml-kit/genai
220
+ `;
221
+ const mlKit = `
222
+ # ML Kit — On-Device ML (Non-Generative)
223
+ Source: https://developers.google.com/ml-kit
224
+
225
+ ## ML Kit vs AICore — When to Use Which
226
+
227
+ | Use Case | Tool |
228
+ |----------|------|
229
+ | Text classification, entity extraction | ML Kit Text APIs |
230
+ | Image labeling, object detection | ML Kit Vision APIs |
231
+ | Face detection, pose estimation | ML Kit Vision APIs |
232
+ | Barcode/QR scanning | ML Kit Barcode Scanning |
233
+ | Translation | ML Kit Translation |
234
+ | Free-form text generation, summarization, Q&A | AICore / ML Kit Gen AI |
235
+
236
+ ## Common ML Kit Setup
237
+
238
+ \`\`\`toml
239
+ [versions]
240
+ mlkit = "19.0.3"
241
+
242
+ [libraries]
243
+ # Pick only what you need — each is a separate dependency
244
+ mlkit-text-recognition = { group = "com.google.mlkit", name = "text-recognition", version.ref = "mlkit" }
245
+ mlkit-image-labeling = { group = "com.google.mlkit", name = "image-labeling", version.ref = "mlkit" }
246
+ mlkit-barcode-scanning = { group = "com.google.mlkit", name = "barcode-scanning", version.ref = "mlkit" }
247
+ mlkit-face-detection = { group = "com.google.mlkit", name = "face-detection", version.ref = "mlkit" }
248
+ mlkit-translation = { group = "com.google.mlkit", name = "translate", version.ref = "mlkit" }
249
+ \`\`\`
250
+
251
+ ## Architecture Pattern — ML Models Behind Repository Interfaces
252
+
253
+ \`\`\`kotlin
254
+ // SAME pattern as AICore — ML models are implementation details, not domain concerns
255
+ interface ImageAnalysisRepository {
256
+ suspend fun labelImage(bitmap: Bitmap): Result<List<String>>
257
+ }
258
+
259
+ class MlKitImageAnalysisRepository @Inject constructor() : ImageAnalysisRepository {
260
+ private val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS)
261
+
262
+ override suspend fun labelImage(bitmap: Bitmap): Result<List<String>> = runCatching {
263
+ val inputImage = InputImage.fromBitmap(bitmap, 0)
264
+ labeler.process(inputImage).await()
265
+ .filter { it.confidence > 0.7f }
266
+ .map { it.text }
267
+ }
268
+ }
269
+ \`\`\`
270
+
271
+ Source: https://developers.google.com/ml-kit
272
+ `;
273
+ if (t.includes("smart reply") || t.includes("suggestion"))
274
+ return smartReply;
275
+ if (t.includes("setup") || t.includes("install") || t.includes("depend") || t.includes("init"))
276
+ return setup;
277
+ if (t.includes("ml kit") || t.includes("mlkit") || t.includes("vision") || t.includes("barcode") || t.includes("label"))
278
+ return mlKit;
279
+ return overview + "\n\n---\n\n" +
280
+ "**Query topics:** 'setup' (dependencies + initialization), 'smart reply' (Gmail-style suggestions), " +
281
+ "'ml kit' (non-generative on-device ML — vision, text, barcode)\n\n" +
282
+ "Source: https://developer.android.com/ai";
283
+ }