plugins/plugin-computeruse/docs/ANDROID_CONSTRAINTS.md
| Capability | Permission | Notes |
|---|---|---|
| AccessibilityService — view tree + gesture dispatch | BIND_ACCESSIBILITY_SERVICE (user must enable in Settings) | Survives Advanced Protection Mode in most OEM ROMs |
| MediaProjection — screen frame capture | FOREGROUND_SERVICE_MEDIA_PROJECTION + user consent dialog | 1 Hz default; foreground service required |
| UsageStatsManager — app history + foreground app | PACKAGE_USAGE_STATS (user must enable in Settings > Usage Access, no runtime prompt) | 24-hour window; 5-min scan for foreground app |
| Camera2 — JPEG/RGBA frames | CAMERA (runtime permission) | Service-friendly; no Activity or SurfaceView needed |
| onTrimMemory → MemoryArbiter pressure | None (ComponentCallbacks2) | Fires at TRIM_MEMORY_RUNNING_LOW and TRIM_MEMORY_RUNNING_CRITICAL |
For normal Google Android and Play distribution, Eliza uses Google App Actions and Android shortcuts only:
packages/app-core/platforms/android/app/src/main/res/xml/shortcuts.xml
declares actions.intent.OPEN_APP_FEATURE, CREATE_MESSAGE,
and GET_THING.OPEN_APP_FEATURE is the static feature surface for chat/ask, voice,
LifeOps daily brief, LifeOps task creation, and LifeOps tasks.CREATE_MESSAGE and GET_THING route free-form ask/chat text to the
app's chat deep link.OPEN_APP_FEATURE inline inventory.actions.intent.CREATE_THING are intentionally
not declared; LifeOps task creation is a feature-open flow and any
mutation must still go through runtime confirmation and the ScheduledTask
path.Mapping by flow:
| Flow | Supported App Actions / shortcut entry |
|---|---|
| Ask/chat | CREATE_MESSAGE, GET_THING, eliza_app_action_chat |
| Voice chat | OPEN_APP_FEATURE inline inventory, eliza_app_action_voice |
| LifeOps daily brief | OPEN_APP_FEATURE inline inventory, eliza_app_action_daily_brief |
| Create LifeOps task | OPEN_APP_FEATURE inline inventory, eliza_app_action_new_task |
| View LifeOps tasks | OPEN_APP_FEATURE inline inventory, eliza_app_action_tasks |
There is no Play-compatible default-assistant handoff for the Pixel build.
Normal Google Android entry goes through App Actions/static shortcuts/deep
links. AOSP-only ROLE_ASSISTANT and ACTION_ASSIST behavior stays out of
the Play build.
The Play-compatible android-cloud build must not request or expose
default-assistant/system-only powers. Keep ACTION_ASSIST,
VOICE_COMMAND, ROLE_ASSISTANT, BIND_VOICE_INTERACTION,
usage-stats appop permissions, SMS/call default-role components, boot
receivers, battery-optimization exemption, MediaProjection foreground
services, and special-use foreground services out of that build.
AOSP/default-assistant behavior belongs only to android-system or
sideload-only validation builds.
Current Android docs describe App Actions as shortcuts.xml
capabilities registered on the launcher activity; Gemini/Assistant
interoperability for general apps is through those App Actions and
shortcuts. The navigation-app Gemini/Assistant intent formats are
navigation-specific and are not a general personal-assistant integration
surface for this app.
| Capability | Mechanism | Permission |
|---|---|---|
| High-fidelity screen capture | SurfaceControl.captureDisplay() | READ_FRAME_BUFFER (`signature |
| High-fidelity input injection | InputManager.injectInputEvent() | INJECT_EVENTS (`signature |
| Full process enumeration | IActivityManager.getRunningAppProcesses() via AIDL | REAL_GET_TASKS (signature) |
See AOSP_SYSTEM_APP.md for the privileged build path.
When Advanced Protection Mode (APM) is active on Pixel 9+ and some OEM variants:
featureAccessibility (not featureGeneric) survives.
MiladyAccessibilityService is registered correctly.adb logcat
for AccessibilityManagerService or android.safetycenter entries.The Linux low-memory killer daemon (lmkd) uses oom_score_adj to prioritize kills. Two mitigations are active:
ScreenCaptureService is a foreground service — lmkd ranks foreground services
below cached apps; they survive until memory is critically exhausted.onTrimMemory → capacitorPressureSource.dispatch() — WS1 MemoryArbiter receives
the pressure signal and proactively unloads lower-priority model handles
(transcribe, vision-describe) before the OOM killer fires.Run this against a physical Android device (API 24+ for gesture dispatch; API 29+ for
FOREGROUND_SERVICE_MEDIA_PROJECTION). Cuttlefish x86_64 emulator is acceptable for
smoke-testing, but the x86_64 JNI patch must be present (see WS4 llama-cpp-capacitor patch).
CAMERA runtime permission when prompted.MiladyAccessibilityService.instance is non-null via:
adb shell dumpsys accessibility | grep -i miladyMilady > enable Usage Access.
adb shell am start com.example.app # open any app
curl -X POST http://localhost:1337/api/computer-use/getAccessibilityTree
Expected: JSON array with [{id, role, label, bbox, actions}] entries.
Verify role values are Android class names (e.g. android.widget.Button).
curl -X POST http://localhost:1337/api/computer-use/dispatchGesture \
-d '{"type":"tap","x":540,"y":960}'
Expected: {"ok":true} and the tap is visible on screen.
curl -X POST http://localhost:1337/api/computer-use/dispatchGesture \
-d '{"type":"swipe","x":540,"y":1600,"x2":540,"y2":400,"durationMs":400}'
Expected: {"ok":true} and the list scrolls up.
curl -X POST http://localhost:1337/api/computer-use/performGlobalAction -d '{"action":"home"}'
curl -X POST http://localhost:1337/api/computer-use/performGlobalAction -d '{"action":"recents"}'
curl -X POST http://localhost:1337/api/computer-use/performGlobalAction -d '{"action":"back"}'
curl -X POST http://localhost:1337/api/computer-use/performGlobalAction -d '{"action":"notifications"}'
Expected: each action is visually confirmed on device.
curl -X POST http://localhost:1337/api/computer-use/startMediaProjection -d '{"fps":1}'
Expected: system consent dialog appears. Accept it.
curl -X GET http://localhost:1337/api/computer-use/captureFrame
Expected: {ok:true, data:{jpegBase64:"...", width:..., height:..., timestampMs:...}}.
Verify jpegBase64 decodes to a valid JPEG of the current screen.
curl -X POST http://localhost:1337/api/computer-use/stopMediaProjection
Expected: {ok:true, data:{stopped:true}}.
curl -X GET http://localhost:1337/api/computer-use/enumerateApps
Expected: JSON array of {packageName, label, lastUsedMs, totalForegroundMs, isForeground}.
Verify isForeground:true for the frontmost app.
If you receive {ok:false, code:"permission_denied"}, confirm Usage Access is enabled.
curl -X POST http://localhost:1337/api/computer-use/startCamera -d '{"fps":1}'
Expected: {ok:true, data:{cameras:"[...]"}} with at least one camera entry.
curl -X GET http://localhost:1337/api/computer-use/captureFrameCamera
Expected: {ok:true, data:{jpegBase64:"..."}}.
curl -X POST http://localhost:1337/api/computer-use/stopCamera
Open a memory-intensive app or use adb shell am send-trim-memory $(pidof ai.milady.milady) 80
to simulate TRIM_MEMORY_RUNNING_CRITICAL.
Expected: the JS console (or logcat for bridge events) shows:
[capacitorPressureSource] dispatching pressure: critical
followed by MemoryArbiter eviction log entries.
Verify via GET /api/training/auto/config that arbiter pressure state transitions to critical.
Use a Play/Assistant-capable Pixel or Google Android device signed into the same account used by the App Actions test tool.
@xml/shortcuts:
aapt dump xmltree app-release.aab base/manifest/AndroidManifest.xml.shortcuts.xml contains OPEN_APP_FEATURE,
CREATE_MESSAGE, and GET_THING, with no unsupported
actions.intent.CREATE_THING.ai.elizaos.app, app.eliza, or stale eliza://
value should remain after rewriting.ScheduledTask path.