* Android: fix Bitmap memory leaks in CanvasController snapshots snapshotPngBase64() and snapshotBase64() create bitmaps via captureBitmap() and scaleForMaxWidth() but never recycle them, leaking native memory on every canvas snapshot invocation. Wrap both methods in nested try/finally blocks: - outer: always recycles the captured bitmap - inner: recycles the scaled bitmap only when it differs from the captured one (scaleForMaxWidth returns `this` when no scaling needed) Made-with: Cursor * fix: note Android canvas snapshot bitmap leak in changelog (#41889) (thanks @Kaneki-x) --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us>
OpenClaw Android App
Status: extremely alpha. The app is actively being rebuilt from the ground up.
Rebuild Checklist
- New 4-step onboarding flow
- Connect tab with
Setup Code+Manualmodes - Encrypted persistence for gateway setup/auth state
- Chat UI restyled
- Settings UI restyled and de-duplicated (gateway controls moved to Connect)
- QR code scanning in onboarding
- Performance improvements
- Streaming support in chat UI
- Request camera/location and other permissions in onboarding/settings flow
- Push notifications for gateway/chat status updates
- Security hardening (biometric lock, token handling, safer defaults)
- Voice tab full functionality
- Screen tab full functionality
- Full end-to-end QA and release hardening
Open in Android Studio
- Open the folder
apps/android.
Build / Run
cd apps/android
./gradlew :app:assemblePlayDebug
./gradlew :app:installPlayDebug
./gradlew :app:testPlayDebugUnitTest
cd ../..
bun run android:bundle:release
Third-party debug flavor:
cd apps/android
./gradlew :app:assembleThirdPartyDebug
./gradlew :app:installThirdPartyDebug
./gradlew :app:testThirdPartyDebugUnitTest
bun run android:bundle:release auto-bumps Android versionName/versionCode in apps/android/app/build.gradle.kts, then builds two signed release bundles:
- Play build:
apps/android/build/release-bundles/openclaw-<version>-play-release.aab - Third-party build:
apps/android/build/release-bundles/openclaw-<version>-third-party-release.aab
Flavor-specific direct Gradle tasks:
cd apps/android
./gradlew :app:bundlePlayRelease
./gradlew :app:bundleThirdPartyRelease
Kotlin Lint + Format
pnpm android:lint
pnpm android:format
Android framework/resource lint (separate pass):
pnpm android:lint:android
Direct Gradle tasks:
cd apps/android
./gradlew :app:ktlintCheck :benchmark:ktlintCheck
./gradlew :app:ktlintFormat :benchmark:ktlintFormat
./gradlew :app:lintDebug
gradlew auto-detects the Android SDK at ~/Library/Android/sdk (macOS default) if ANDROID_SDK_ROOT / ANDROID_HOME are unset.
Macrobenchmark (Startup + Frame Timing)
cd apps/android
./gradlew :benchmark:connectedDebugAndroidTest
Reports are written under:
apps/android/benchmark/build/reports/androidTests/connected/
Perf CLI (low-noise)
Deterministic startup measurement + hotspot extraction with compact CLI output:
cd apps/android
./scripts/perf-startup-benchmark.sh
./scripts/perf-startup-hotspots.sh
Benchmark script behavior:
- Runs only
StartupMacrobenchmark#coldStartup(10 iterations). - Prints median/min/max/COV in one line.
- Writes timestamped snapshot JSON to
apps/android/benchmark/results/. - Auto-compares with previous local snapshot (or pass explicit baseline:
--baseline <old-benchmarkData.json>).
Hotspot script behavior:
- Ensures debug app installed, captures startup
simpleperfdata for.MainActivity. - Prints top DSOs, top symbols, and key app-path clues (Compose/MainActivity/WebView).
- Writes raw
perf.datapath for deeper follow-up if needed.
Run on a Real Android Phone (USB)
- On phone, enable Developer options + USB debugging.
- Connect by USB and accept the debugging trust prompt on phone.
- Verify ADB can see the device:
adb devices -l
- Install + launch debug build:
pnpm android:install
pnpm android:run
If adb devices -l shows unauthorized, re-plug and accept the trust prompt again.
USB-only gateway testing (no LAN dependency)
Use adb reverse so Android localhost:18789 tunnels to your laptop localhost:18789.
Terminal A (gateway):
pnpm openclaw gateway --port 18789 --verbose
Terminal B (USB tunnel):
adb reverse tcp:18789 tcp:18789
Then in app Connect → Manual:
- Host:
127.0.0.1 - Port:
18789 - TLS: off
Hot Reload / Fast Iteration
This app is native Kotlin + Jetpack Compose.
- For Compose UI edits: use Android Studio Live Edit on a debug build (works on physical devices; project
minSdk=31already meets API requirement). - For many non-structural code/resource changes: use Android Studio Apply Changes.
- For structural/native/manifest/Gradle changes: do full reinstall (
pnpm android:run). - Canvas web content already supports live reload when loaded from Gateway
__openclaw__/canvas/(seedocs/platforms/android.md).
Connect / Pair
- Start the gateway (on your main machine):
pnpm openclaw gateway --port 18789 --verbose
- In the Android app:
- Open the Connect tab.
- Use Setup Code or Manual mode to connect.
- Approve pairing (on the gateway machine):
openclaw devices list
openclaw devices approve <requestId>
More details: docs/platforms/android.md.
Permissions
- Discovery:
- Android 13+ (
API 33+):NEARBY_WIFI_DEVICES - Android 12 and below:
ACCESS_FINE_LOCATION(required for NSD scanning)
- Android 13+ (
- Foreground service notification (Android 13+):
POST_NOTIFICATIONS - Camera:
CAMERAforcamera.snapandcamera.clipRECORD_AUDIOforcamera.clipwhenincludeAudio=true
Google Play Restricted Permissions
As of March 19, 2026, these manifest permissions are the main Google Play policy risk for this app:
READ_SMSSEND_SMSREAD_CALL_LOG
Why these matter:
- Google Play treats SMS and Call Log access as highly restricted. In most cases, Play only allows them for the default SMS app, default Phone app, default Assistant, or a narrow policy exception.
- Review usually involves a
Permissions Declaration Form, policy justification, and demo video evidence in Play Console. - If we want a Play-safe build, these should be the first permissions removed behind a dedicated product flavor / variant.
Current OpenClaw Android implication:
- APK / sideload build can keep SMS and Call Log features.
- Google Play build should exclude SMS send/search and Call Log search unless the product is intentionally positioned and approved as a default-handler exception case.
- The repo now ships this split as Android product flavors:
play: removesREAD_SMS,SEND_SMS, andREAD_CALL_LOG, and hides SMS / Call Log surfaces in onboarding, settings, and advertised node capabilities.thirdParty: keeps the full permission set and the existing SMS / Call Log functionality.
Policy links:
- Google Play SMS and Call Log policy
- Google Play sensitive permissions policy hub
- Android default handlers guide
Other Play-restricted surfaces to watch if added later:
ACCESS_BACKGROUND_LOCATIONMANAGE_EXTERNAL_STORAGEQUERY_ALL_PACKAGESREQUEST_INSTALL_PACKAGESAccessibilityService
Reference links:
Integration Capability Test (Preconditioned)
This suite assumes setup is already done manually. It does not install/run/pair automatically.
Pre-req checklist:
- Gateway is running and reachable from the Android app.
- Android app is connected to that gateway and
openclaw nodes statusshows it as paired + connected. - App stays unlocked and in foreground for the whole run.
- Open the app Screen tab and keep it active during the run (canvas/A2UI commands require the canvas WebView attached there).
- Grant runtime permissions for capabilities you expect to pass (camera/mic/location/notification listener/location, etc.).
- No interactive system dialogs should be pending before test start.
- Canvas host is enabled and reachable from the device (do not run gateway with
OPENCLAW_SKIP_CANVAS_HOST=1; startup logs should includecanvas host mounted at .../__openclaw__/). - Local operator test client pairing is approved. If first run fails with
pairing required, approve latest pending device pairing request, then rerun: - For A2UI checks, keep the app on Screen tab; the node now auto-refreshes canvas capability once on first A2UI reachability failure (TTL-safe retry).
openclaw devices list
openclaw devices approve --latest
Run:
pnpm android:test:integration
Optional overrides:
OPENCLAW_ANDROID_GATEWAY_URL=ws://...(default: from your local OpenClaw config)OPENCLAW_ANDROID_GATEWAY_TOKEN=...OPENCLAW_ANDROID_GATEWAY_PASSWORD=...OPENCLAW_ANDROID_NODE_ID=...orOPENCLAW_ANDROID_NODE_NAME=...
What it does:
- Reads
node.describecommand list from the selected Android node. - Invokes advertised non-interactive commands.
- Skips
screen.recordin this suite (Android requires interactive per-invocation screen-capture consent). - Asserts command contracts (success or expected deterministic error for safe-invalid calls like
sms.sendandnotifications.actions).
Common failure quick-fixes:
pairing requiredbefore tests start:- approve pending device pairing (
openclaw devices approve --latest) and rerun.
- approve pending device pairing (
A2UI host not reachable/A2UI_HOST_NOT_CONFIGURED:- ensure gateway canvas host is running and reachable, keep the app on the Screen tab. The app will auto-refresh canvas capability once; if it still fails, reconnect app and rerun.
NODE_BACKGROUND_UNAVAILABLE: canvas unavailable:- app is not effectively ready for canvas commands; keep app foregrounded and Screen tab active.
Contributions
This Android app is currently being rebuilt. Maintainer: @obviyus. For issues/questions/contributions, please open an issue or reach out on Discord.