An offline-capable Android application wrapper designed to host a high-performance, local JavaScript watermark-removal engine. It integrates a Jetpack Compose native container hosting a full-screen, customized WebView that executes local asset files.
For development guidelines, see CLAUDE.md.
The project is structured as a hybrid application split into three main layers:
graph TD
A[Native Android Container] -->|Hosts full screen| B[WebView UI]
B -->|Bridges JavaScript API| A
C[android-js-src/android-app.js] -->|Bundled into| D[assets/app.js]
E[gemini-watermark-remover SDK] -->|Imported by| C
B -->|Loads local assets| D
-
Native Android Shell (
android/):- Built using Jetpack Compose.
- Consists of a single MainActivity hosting a
WebViewutilizing explicit layout parameters to ensure perfect sizing. - Exposes a native JS bridge class
WebAppInterfacenamedAndroidBridgeto support saving images to gallery viaMediaStore, copying images to clipboard, and native chooser sharing viaFileProvider.
-
Web Front-end (
android/app/src/main/assets/):- Contains the UI framework files: index.html and style.css.
- Serves the compiled asset bundle
app.jslocally. - Do not edit
app.jsdirectly; it is compiled automatically during the Gradle build pipeline.
-
JS Entry Point & Core Submodule (
android-js-src/&gemini-watermark-remover/):- The JS source file android-app.js orchestrates application logic, UI interaction, and touch gestures.
- Imports core watermark-removal capabilities from the
gemini-watermark-removersubmodule.
- Offline Processing: All watermark-removal computations are run entirely client-side using browser canvas APIs.
- Interactive Comparison: Toggle pills allow seamless switching between the original and cleaned version of images.
- Pinch-to-Zoom & Pan: Fullscreen modal supporting smooth multi-touch pinch gestures, drag/pan, and double-tap gestures to reset zoom.
- Native Operations:
- 💾 Save to Gallery: Saves the processed image to
Pictures/GeminiWatermarkRemovervia Android'sMediaStoreAPI. - 📋 Copy to Clipboard: Copies the processed PNG to the system clipboard.
- 📤 Share: Invokes the native Android share sheet to send the image to other apps.
- 💾 Save to Gallery: Saves the processed image to
- Build Timestamping: Displays a build time string at the bottom-right corner of the web UI to verify the active Javascript build.
- Android SDK: Set up Android SDK, build tools (target SDK 36), and JDK 17.
- Node.js Dependencies: The bundling tool
esbuildneeds to be initialized. Run the following command inside the submodule directory:cd gemini-watermark-remover && pnpm install
The project is configured to bundle the web assets automatically whenever the Android app is compiled. This dependency is defined in the build.gradle.kts preBuild task:
tasks.register<Exec>("bundleJsAssets") { ... }
tasks.named("preBuild") { dependsOn("bundleJsAssets") }- Compile Debug APK:
cd android .\gradlew.bat assembleDebug - Install on Connected Device:
adb install -r android\app\build\outputs\apk\debug\app-debug.apk
Before modifying layout styling or gesture logic, review these critical gotchas:
Inside Android's WebView, vh units can resolve to 0 if the WebView is measured before its final height is set.
- Rule: Never use
vhorvwunits for critical layout elements in style.css. - Solution: Use absolute/fixed layouts with
inset: 0or percentage heights (100%) to enforce correct layout sizing. Ensure the nativeAndroidViewin Compose configures itslayoutParamsexplicitly withMATCH_PARENT.
To allow custom JavaScript pinch-to-zoom to operate correctly without parent layout interference, the WebView listens to touches. When event.pointerCount > 1, it executes v.parent?.requestDisallowInterceptTouchEvent(true).
You can debug the running web context inside the Android WebView by using Google Chrome/Chromium DevTools:
- Retrieve the process ID of the app:
adb shell pidof me.wanghuan.geminiwatermarkremover
- Forward the debug socket to your local machine (replace
<pid>with the PID from the previous step):adb forward tcp:9222 localabstract:webview_devtools_remote_<pid>
- Verify connection:
curl -m 5 http://localhost:9222/json
- Connect from Chrome DevTools or a Playwright script targeting
http://127.0.0.1:9222.