# OrganicAI — Android agent (Kotlin)

The mobile "hands and feet." A native Kotlin app that is **its own browser** and
runs the same organic-marketing automation the Chrome extension runs on desktop —
driven by the same Cloudflare Worker, registered in the same agent fleet.

## Why a Kotlin app at all (and not the extension on Edge)

We proved the desktop extension can't be dropped onto Android cleanly: Android
browsers install extensions **from a store by ID**, and ours is self-hosted (not
published). And the "drive your other apps" path via AccessibilityService is
**policy-dead** (see `docs/DECISIONS.md` ADR-43).

A Kotlin app sidesteps both. It embeds a **WebView** (the system Chromium) that we
fully control: it logs into facebook.com / linkedin.com / x.com **as the user** and
we run our automation by injecting JavaScript with `evaluateJavascript()`. This is
the exact same posture as the extension — a browser we own, driving websites — just
packaged as an app. No store extension, no accessibility, no driving other apps.

## How it maps to what already exists

The worker already speaks a device protocol the extension uses. The app is just
**another agent** on it:

| Worker contract (unchanged) | Android app |
|---|---|
| `POST /login` → 2FA → `/auth/otp/verify` → session token | `WorkerClient.login()` |
| `POST /devices/heartbeat` `{version, platform, capabilities}` | `WorkerClient.heartbeat()` every 30s from a foreground service |
| heartbeat returns pending `commands[]` | `AgentService` dispatches them |
| `BROWSER_NAVIGATE/EXTRACT/CLICK/TYPE/SCREENSHOT/WAIT/CLOSE` (ADR-42) | `AgentWebView` via `evaluateJavascript()` |
| `POST /devices/commands/:id/result` `{ok, result, error}` | `WorkerClient.reportResult()` |
| agent fleet (ADR-44) | registers as `platform: "android-app"` |

So the AI that drives the desktop agent drives the phone with the **same MCP
`browser.*` tools** — nothing new to learn on the orchestration side.

## Three wins the phone has over the desktop agent

1. **Screenshots are trivial.** A WebView draws straight to a `Bitmap` — no
   `chrome.debugger`, no background-tab problem. The flight recorder is easier here.
2. **Low latency (fixes P2).** A native app holds an **FCM** channel, so a command
   arrives in ~1s instead of waiting up to 30s for the next heartbeat. The phone can
   be the *fast* agent.
3. **On-device AI, free + private.** **Gemini Nano** via ML Kit GenAI generates
   post/reply text on the device — no Gemini API key, nothing leaves the phone.
   Removes the "needs a Gemini key" blocker entirely (`NanoAi.kt`).

## Layout

```
android/
  app/
    build.gradle.kts               # deps: WebView, coroutines, OkHttp, FCM, ML Kit GenAI
    src/main/AndroidManifest.xml   # INTERNET, FOREGROUND_SERVICE, POST_NOTIFICATIONS
    src/main/java/il/co/organicai/agent/
      MainActivity.kt              # hosts the WebView + a minimal login screen
      AgentWebView.kt              # the controllable browser: navigate/extract/click/type/screenshot
      WorkerClient.kt              # login (2FA), heartbeat, command poll, result report
      AgentService.kt              # foreground service — keeps the agent alive
      NanoAi.kt                    # Gemini Nano on-device generation (ML Kit GenAI)
```

## Status — BUILDS (compiles to a real APK)

✅ Compiles clean to `app-debug.apk` (~6 MB) with Gradle 8.9 / AGP 8.6 / Kotlin
2.0.20, compileSdk 34. The core agent is real: 2FA login → foreground service →
heartbeat + command loop → WebView primitives → result reporting.

### Commands the agent handles (via heartbeat, same protocol as desktop)
- `BROWSER_NAVIGATE / EXTRACT / CLICK / TYPE / SCREENSHOT / WAIT` — generic primitives.
- `PUBLISH_FACEBOOK { groupUrl?, text }` — real FB group text post (`assets/inject/fb_publish.js`).
- `PUBLISH_FEED { platform, text, feedUrl? }` — LinkedIn / X feed post (`assets/inject/feed_publish.js`).
- `GET_STATUS` — current url/title + logged-in heuristic.

The publish JS is ported from `content/*.js` and kept selector-compatible, so a DOM
change fixed on desktop is a one-line parallel fix here.

**Not done / next (in order):**
- **Install + run on the device** (`veux`, Android 13) — 2FA login, then drive one
  `PUBLISH_FACEBOOK` from the worker end-to-end. (Device disconnected mid-session.)
- **Port media / colored / poll / mentions** posts (text works; these reuse the
  same desktop selectors).
- **FCM** (push/P2): re-add the firebase dep + `PushService` + `google-services.json`
  (needs a Firebase project — user action).
- **Gemini Nano**: fill in `NanoAi` against ML Kit GenAI (stub; needs Nano-capable HW).

## Build

```
cd android
./gradlew :app:assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
```

Verified CLI build (no Android Studio needed) using the cached toolchain:
```
JAVA_HOME="…/Android Studio/jbr" ANDROID_HOME="…/Android/Sdk" \
  ./gradlew :app:assembleDebug
```
`local.properties` (git-ignored) must point `sdk.dir` at your Android SDK.
