Skip to content

feat: AI-powered Reply Drafter#2258

Merged
enaboapps merged 9 commits into
mainfrom
feature/reply-drafter-2256
May 21, 2026
Merged

feat: AI-powered Reply Drafter#2258
enaboapps merged 9 commits into
mainfrom
feature/reply-drafter-2256

Conversation

@enaboapps

Copy link
Copy Markdown
Collaborator

Summary

On-device AI Reply Drafter (issue #2256): from the Main Menu, capture a
screenshot of the conversation and use Gemma 3n (via MediaPipe GenAI) to
suggest short replies, entirely on-device.

  • Suggestions render in a new switch-scannable linear menu layout
    (MenuLayoutType.LINEAR) so switch users can scan and select them;
    selecting one inserts it into the focused text field.
  • In-app model download — a resumable WorkManager foreground worker pulls
    the ~3 GB Gemma model; size-verified, free-space pre-checked.
  • Gemma Terms of Use acceptance gate before download.
  • Pro-gated and API 30+ only.
  • The model URL is required build-time config (REPLY_DRAFTER_MODEL_URL
    env var / replyDrafter.modelUrl in local.properties / CI secret) —
    not committed to source.

Test plan

  • Build with REPLY_DRAFTER_MODEL_URL provided
  • API 30+, Pro: Reply Drafter settings -> accept Gemma terms -> download
    the model -> completes and shows "ready"
  • Focus a text field in a chat app -> Main Menu -> Reply Drafter ->
    suggestions appear in the scannable linear menu; selecting one inserts it
  • Existing radial menus scan and paginate unchanged
  • ./gradlew testDebugUnitTest passes

Closes #2256

Implement issue #2256: from the Main Menu, capture a screenshot and use
an on-device LLM (Gemma 3n via MediaPipe GenAI) to suggest replies that
insert into the focused text field.

- Add LlmManager for MediaPipe GenAI vision inference
- Add ReplyDrafterManager and ReplyDrafterHUD for the screenshot-to-reply flow
- Add ModelManager and a WorkManager-based downloader for the on-device model
- Add ReplyDrafterSettingsScreen to download and manage the model
- Add a Pro-gated Reply Drafter action to the Main Menu

🤖 Auto-generated
isModelReady now treats a model file of the expected size as ready, so a
model provisioned by any means (in-app download or manual sideload) is
detected. Set EXPECTED_SIZE_BYTES to the gemma-3n-E2B-it-int4 size,
which also activates the size and integrity check.

🤖 Auto-generated
AccessibilityService.takeScreenshot() yields a HARDWARE-config bitmap
backed by a HardwareBuffer that ScreenshotManager releases once the
callback returns; MediaPipe's addImage requires ARGB_8888. Copy the
screenshot into a software ARGB_8888 bitmap synchronously in the
callback, before the buffer is released.

🤖 Auto-generated
ModelDownloadWorker runs as a foreground worker that requests the
dataSync service type, but the manifest never declared that type on
WorkManager's SystemForegroundService. Since Android 14, the type passed
to startForeground must be a subset of the type declared in the manifest,
so the process crashed with an IllegalArgumentException the moment a
model download started.

Merge android:foregroundServiceType="dataSync" onto the WorkManager
service via the app manifest. The FOREGROUND_SERVICE_DATA_SYNC permission
was already present.

🤖 Auto-generated
Reply Drafter suggestions rendered in ReplyDrafterHUD, a plain overlay
that switch users could not reach by scanning. Route the suggestions
through the menu and scanning pipeline so they are selectable.

- Add a linear menu layout (MenuLayoutType.LINEAR) alongside the radial
  one; suggestions render as scannable MenuItem rows via inflateAsRow
- Replace ReplyDrafterHUD with ReplyDrafterMenu, opened through
  MenuManager; move text insertion into ReplyDrafterTextInserter
- Add a Gemma Terms of Use acceptance gate before the model download
- Point MODEL_URL at the hosted model to enable the in-app download

🤖 Auto-generated
LlmManager is an object with mutable LlmInference state. close() runs
on the service-destroy thread while generateReplySuggestions() runs on
a background thread; if the service was destroyed mid-inference, close()
freed the native model while generateResponse() was still using it.

Coordinate initialize/generate/close through a lock. The inference still
runs outside the lock so close() never blocks the main thread; a close()
arriving mid-inference defers the native release to the generate call's
finally block.

🤖 Auto-generated
The radial menu page reserves vertical space for its overhead and clamps
the ring to fit; the linear page did neither, so long suggestion rows
could push the nav row, and its close button, off the bottom of the
screen, leaving a switch user unable to dismiss the menu.

buildLinearLayout now reserves the same overhead and caps the list's
measured height to what is left.

🤖 Auto-generated
markModelReady() and clearModelState() wrote the model-downloaded and
model-path preferences, but nothing has read them since model detection
became file-based. Remove the two methods, the two preference keys, and
ModelManager's now-unused PreferenceManager dependency.

🤖 Auto-generated
The model URL was hardcoded in ReplyDrafterModelConfig. Inject it at
build time instead, as a required value (env var or local.properties),
matching how the other secrets are handled.

- build.gradle.kts: add the REPLY_DRAFTER_MODEL_URL buildConfigField via
  the existing configValue helper; the build fails if it is unset
- ReplyDrafterModelConfig: MODEL_URL now reads BuildConfig
- Drop isDownloadConfigured() and the reply_drafter_not_configured
  state, now unreachable with the URL always present
- play-release.yml: pass REPLY_DRAFTER_MODEL_URL to the release build

🤖 Auto-generated
@enaboapps enaboapps force-pushed the feature/reply-drafter-2256 branch from b21cc09 to 23ae871 Compare May 21, 2026 14:05
@enaboapps enaboapps merged commit 3f4e1a5 into main May 21, 2026
@enaboapps enaboapps deleted the feature/reply-drafter-2256 branch May 21, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement AI-powered Reply Drafter

2 participants