Imported from learningequality/kolibri (
platforms/android/AGENTS.md). Install upstream withnpx skills add learningequality/kolibri --skill android. Copyright stays with the author.
Android Emulator Agent Guide
Instructions for Claude agents working autonomously with the Android emulator. Follow these sections in order when starting from scratch, or jump to the relevant section for ongoing work.
1. Emulator Setup and Launch
Check if the emulator is already running
adb devices
If you see emulator-5554 device, the emulator is ready — skip to section 2.
If the list is empty or shows offline, you need to start the emulator.
First-time setup (only needed once)
If no AVD has been created yet:
make setup
This downloads the Android SDK, system image, and creates the kolibri-test AVD.
Start the emulator
make emulator
This launches the emulator in the background. Wait for it to finish booting:
adb wait-for-device
adb shell getprop sys.boot_completed
Poll sys.boot_completed until it returns 1. The boot can take 30-60 seconds.
If the emulator segfaults or crashes, it's likely a GPU issue. Start with software rendering instead:
"${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}/emulator/emulator" -avd kolibri-test -gpu guest -no-snapshot &
2. Build and Install the App
This project uses a Makefile as the primary build interface. Run make help to see all available targets.
Build and install in one step
make install
This builds the debug APK via Gradle and installs it on the connected emulator. The first build takes several minutes; subsequent builds are faster.
Watch for:
- BUILD SUCCESSFUL: Proceed to install
- Compilation errors: Fix before continuing
- Python errors: Check Chaquopy output for syntax issues
Or just build without installing:
make kolibri.apk.unsigned
Verify the app is installed
adb shell pm list packages | grep kolibri
Should output: package:org.learningequality.Kolibri
Launch the app
adb shell am start -n org.learningequality.Kolibri/org.learningequality.Kolibri.WebViewActivity
Force stop the app
adb shell am force-stop org.learningequality.Kolibri
Clear app data (resets to fresh state)
adb shell pm clear org.learningequality.Kolibri
This is needed after Python code changes since Chaquopy caches bytecode.
Uninstall and reinstall (for signing key mismatches)
make uninstall && make install
Makefile reference
| Target | Description |
|---|---|
make setup |
Complete SDK + emulator setup (first time) |
make emulator |
Start the emulator |
make kolibri.apk.unsigned |
Build debug APK to dist/ |
make install |
Build and install debug APK |
make uninstall |
Uninstall app from device |
make logcat |
View filtered Kolibri logs |
make clean |
Clean build artifacts |
make test |
Run unit tests |
make lint |
Run Android linter |
Quick commands
Build + Install + Launch:
make install && adb shell am start -n org.learningequality.Kolibri/org.learningequality.Kolibri.WebViewActivity
Clear logs and monitor:
adb logcat -c && make logcat
3. Visual Inspect-Act Loop
This is the core workflow for autonomous UI interaction. Use /project:screenshot to run the full loop with instructions, or follow these steps:
Capture the screen
mkdir -p /tmp/claude
adb exec-out screencap -p > /tmp/claude/screenshot.png
Read the screenshot image at /tmp/claude/screenshot.png to see the screen visually.
Inspect: CDP vs uiautomator
Kolibri is a WebView app. WebView content and native Android UI require different tools:
| What you see | Tool | Why |
|---|---|---|
| Kolibri UI (buttons, forms, nav, text) | python3 scripts/cdp_helper.py dump |
WebView DOM is invisible to uiautomator |
| Native Android dialogs (permissions, system prompts) | adb shell uiautomator dump /sdcard/window_dump.xml && adb shell cat /sdcard/window_dump.xml |
System dialogs are invisible to CDP |
Rule of thumb: If a system dialog with rounded corners is overlaying the app, use uiautomator. For everything else, use CDP.
Interact: CDP vs adb input
WebView elements — click by text via CDP (no coordinate math needed):
python3 scripts/cdp_helper.py click "CONTINUE"
python3 scripts/cdp_helper.py click "EXPLORE"
Native elements — tap by coordinates from uiautomator bounds:
# bounds="[137,1177][943,1331]" → center at (540, 1254)
adb shell input tap 540 1254
Other interactions:
adb shell input text "<text>" # Type text (encode spaces as %s)
adb shell input swipe 540 1500 540 500 300 # Scroll down
adb shell input swipe 540 500 540 1500 300 # Scroll up
adb shell input keyevent 4 # Press BACK
adb shell input keyevent 66 # Press ENTER
adb shell input keyevent 3 # Press HOME
Verify
Take another screenshot after every interaction. Confirm the UI changed as expected before proceeding.
CDP helper reference
The CDP helper (scripts/cdp_helper.py) uses Chrome DevTools Protocol over ADB to access the WebView DOM. Requires websockets (uv pip install websockets).
python3 scripts/cdp_helper.py dump # List visible DOM elements as JSON
python3 scripts/cdp_helper.py click "Button" # Click element by exact text match
python3 scripts/cdp_helper.py js "expr" # Evaluate arbitrary JavaScript
Key event codes
| Code | Key | Code | Key |
|---|---|---|---|
| 3 | HOME | 4 | BACK |
| 19 | DPAD_UP | 20 | DPAD_DOWN |
| 21 | DPAD_LEFT | 22 | DPAD_RIGHT |
| 61 | TAB | 66 | ENTER |
| 67 | DEL | 111 | ESCAPE |
4. Maestro Flow Development
Maestro flows live in .maestro/. Use them for repeatable UI test sequences.
Develop a new flow
- Discover UI elements using the CDP helper:
python3 scripts/cdp_helper.py dump. Note thetextcontent — Maestro matches WebView elements by text whenandroidWebViewHierarchy: devtoolsis set. - Write the flow as a YAML file in
.maestro/:appId: org.learningequality.Kolibri androidWebViewHierarchy: devtools --- - launchApp - tapOn: "CONTINUE" - Run the flow:
~/.maestro/bin/maestro test .maestro/your-flow.yaml - Iterate: If the flow fails, screenshot to see the actual state, adjust selectors or add waits, re-run.
Install Maestro (if not present)
make maestro-install
Common Maestro commands
launchApp/clearState/clearKeychaintapOn: "text"/tapOn: { id: "resource-id" }inputText: "value"assertVisible: "text"/assertNotVisible: "text"extendedWaitUntil: { visible: "text", timeout: 30000 }scroll/swipeback/hideKeyboard
5. Log Inspection
Kolibri-filtered logs (streaming)
make logcat
Python stdout/stderr
adb logcat -s python.stdout:V python.stderr:V
Specific component tags
adb logcat -s KolibriWebView:V KolibriServer:V TaskWorkerImpl:V BaseTaskWorker:V
Crash logs
adb logcat -s AndroidRuntime:E
Recent log snapshot (non-streaming)
adb logcat -d -t 50
Clear log buffer
adb logcat -c
6. Troubleshooting
App crashes on startup
- Check crash logs:
adb logcat -s AndroidRuntime:E - Look for Python import errors:
adb logcat -s python.stderr:V
Python changes not appearing
Chaquopy caches Python bytecode. Clear app data:
adb shell pm clear org.learningequality.Kolibri
Or uninstall and reinstall:
make uninstall && make install
INSTALL_FAILED_UPDATE_INCOMPATIBLE
Signing key mismatch. Uninstall first:
make uninstall && make install
Service Worker issues
- Open Chrome DevTools:
chrome://inspect - Find the Kolibri WebView and inspect
- Check Application > Service Workers
WorkManager tasks not running
Check task logs:
adb logcat -s TaskWorkerImpl:V BaseTaskWorker:V WM-WorkerWrapper:V
Emulator not found
make setup # Creates SDK + AVD
make emulator
7. Iterating
- Make code changes
make install- Launch app and test
make logcatin another terminal- Repeat
For Python-only changes, builds are fast since Java doesn't need recompilation.
Key Facts
| Fact | Value |
|---|---|
| Package name | org.learningequality.Kolibri |
| Main activity | org.learningequality.Kolibri.WebViewActivity |
| AVD name | kolibri-test |
| JDK | Compilation pinned to 21 via app/build.gradle toolchain; daemon JVM pinned via gradle/gradle-daemon-jvm.properties. Set JAVA_HOME to a JDK 21 if Gradle can't auto-discover one. |
| GPU workaround | Use -gpu guest if default GPU segfaults |
| Python caching | Clear app data after Python changes (Chaquopy caches bytecode) |
| Build system | Gradle via Makefile wrappers — use make targets |
| CDP helper | python3 scripts/cdp_helper.py — requires websockets package |