
Android Emulator Skill
- 2 installs
- 910 repo stars
- Updated July 27, 2026
- new-silvermoon/awesome-android-skills
Provides scripts to build, test, and automate Android apps on an emulator using accessibility-driven UI navigation, log monitoring, and emulator lifecycle management.
About
Automates Android app testing and building via bundled scripts for emulator health checks, app launching, semantic screen mapping, and text/type-based UI navigation instead of pixel coordinates. A developer or AI agent uses it to drive Android emulators with structured, low-token output.
- Accessibility-driven navigation by text/type rather than pixels
- Cross-platform scripts for emulator health, launch, screen mapping, and logs
Android Emulator Skill by the numbers
- 2 all-time installs (skills.sh)
- Ranked #942 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/new-silvermoon/awesome-android-skills --skill android-emulator-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 910 |
| Last updated | July 27, 2026 |
| Repository | new-silvermoon/awesome-android-skills ↗ |
What it does
Provides scripts to build, test, and automate Android apps on an emulator using accessibility-driven UI navigation, log monitoring, and emulator lifecycle management.
Files
Android Emulator Skill
Build, test, and automate Android applications using accessibility-driven navigation and structured data instead of pixel coordinates.
Quick Start
# 1. Check environment (use .sh on macOS/Linux, .ps1 on Windows)
bash scripts/emu_health_check.sh
# or on Windows: .\scripts\emu_health_check.ps1
# 2. Launch app
python scripts/app_launcher.py --launch com.example.app
# 3. Map screen to see elements
python scripts/screen_mapper.py
# 4. Tap button
python scripts/navigator.py --find-text "Login" --tap
# 5. Enter text
python scripts/navigator.py --find-type EditText --enter-text "user@example.com"All scripts support --help for detailed options and --json for machine-readable output.
Production Scripts
Build & Development
1. build_and_test.py - Build Android projects, run tests, parse results
- Wrapper around Gradle
- Support for assemble, install, and connectedCheck
- Parse build errors and test results
- Options:
--task,--clean,--json
2. log_monitor.py - Real-time log monitoring with intelligent filtering
- Wrapper around
adb logcat - Filter by tag, priority, or PID
- Deduplicate repeated messages
- Options:
--package,--tag,--priority,--duration,--json
Navigation & Interaction
3. screen_mapper.py - Analyze current screen and list interactive elements
- Dump UI hierarchy using
uiautomator - Parse XML to identify buttons, text fields, etc.
- Options:
--verbose,--json
4. navigator.py - Find and interact with elements semantically
- Find by text (fuzzy matching), resource-id, or class name
- Interactive tapping and text entry
- Options:
--find-text,--find-id,--tap,--enter-text,--json
5. gesture.py - Perform swipes, scrolls, and other gestures
- Swipe up/down/left/right
- Scroll lists
- Options:
--swipe,--scroll,--duration,--json
6. keyboard.py - Key events and hardware buttons
- Input key events (Home, Back, Enter, Tab)
- Type text via ADB
- Options:
--key,--text,--json
7. app_launcher.py - App lifecycle management
- Launch apps (
adb shell am start) - Terminate apps (
adb shell am force-stop) - Install/Uninstall APKs
- List installed packages
- Options:
--launch,--terminate,--install,--uninstall,--list,--json
Emulator Lifecycle Management
8. emulator_manage.py - Manage Android Virtual Devices (AVDs)
- List available AVDs
- Boot emulators
- Shutdown emulators
- Options:
--list,--boot,--shutdown,--json
9. emu_health_check - Verify environment is properly configured
- Use
emu_health_check.shon macOS/Linux andemu_health_check.ps1on Windows - Check ADB, Emulator, Java, Gradle, ANDROID_HOME
- List connected devices
Common Patterns
Auto-Device Detection: Scripts target the single connected device/emulator if only one is present, or require -s <serial> if multiple are connected.
Output Formats: Default is concise human-readable output. Use --json for machine-readable output.
Requirements
- Android SDK Platform-Tools (adb, fastboot)
- Android Emulator
- Java / OpenJDK
- Python 3
Key Design Principles
Semantic Navigation: Find elements by text, resource-id, or content-description.
Token Efficiency: Concise default output with optional verbose and JSON modes.
Zero Configuration: Works with standard Android SDK installation.
#!/usr/bin/env python3
"""
Android App Launcher - App Lifecycle Control
Launches, terminates, and manages Android apps on the emulator/device.
"""
import argparse
import sys
import time
import subprocess
from common import resolve_serial, run_adb_command
class AppLauncher:
"""Controls app lifecycle on Android."""
def __init__(self, serial: str = None):
self.serial = serial
def launch(self, package: str, activity: str = None) -> bool:
"""
Launch an app.
If activity is provided, uses explicitly.
If not, tries to launch main activity via monkey (more robust than guessing).
"""
if activity:
cmd = ["shell", "am", "start", "-n", f"{package}/{activity}"]
else:
# Use monkey to launch the main activity of the package
cmd = ["shell", "monkey", "-p", package, "-c", "android.intent.category.LAUNCHER", "1"]
try:
run_adb_command(cmd, self.serial)
return True
except subprocess.CalledProcessError as e:
print(f"Error launching app: {e}")
return False
def terminate(self, package: str) -> bool:
"""Terminate an app."""
try:
run_adb_command(["shell", "am", "force-stop", package], self.serial)
return True
except subprocess.CalledProcessError:
return False
def install(self, apk_path: str) -> bool:
"""Install an APK."""
try:
run_adb_command(["install", "-r", apk_path], self.serial)
return True
except subprocess.CalledProcessError as e:
print(f"Error installing APK: {e}")
return False
def uninstall(self, package: str) -> bool:
"""Uninstall an app."""
try:
run_adb_command(["uninstall", package], self.serial)
return True
except subprocess.CalledProcessError:
return False
def list_packages(self, filter_str: str = None) -> list[str]:
"""List installed packages."""
try:
cmd = ["shell", "pm", "list", "packages"]
if filter_str:
cmd.append(filter_str)
result = run_adb_command(cmd, self.serial)
packages = []
for line in result.stdout.splitlines():
if line.startswith("package:"):
packages.append(line.replace("package:", "").strip())
return packages
except subprocess.CalledProcessError:
return []
def get_app_state(self, package: str) -> str:
"""Get app state (running or not running)."""
try:
# Check if process exists
result = run_adb_command(["shell", "pidof", package], self.serial, check=False)
if result.returncode == 0 and result.stdout.strip():
return "running"
return "not running"
except Exception:
return "unknown"
def main():
parser = argparse.ArgumentParser(description="Control Android app lifecycle")
# Actions
parser.add_argument("--launch", help="Launch app by package name")
parser.add_argument("--activity", help="Specific activity to launch (optional)")
parser.add_argument("--terminate", help="Terminate app by package name")
parser.add_argument("--install", help="Install app from APK path")
parser.add_argument("--uninstall", help="Uninstall app by package name")
parser.add_argument("--list", action="store_true", help="List installed packages")
parser.add_argument("--state", help="Get app state by package name")
# Options
parser.add_argument("--serial", "-s", help="Device serial (optional)")
parser.add_argument("--json", action="store_true", help="Output JSON (TODO)")
args = parser.parse_args()
try:
serial = resolve_serial(args.serial)
except RuntimeError as e:
print(f"Error: {e}")
sys.exit(1)
launcher = AppLauncher(serial)
if args.launch:
if launcher.launch(args.launch, args.activity):
print(f"Launched {args.launch}")
else:
sys.exit(1)
elif args.terminate:
if launcher.terminate(args.terminate):
print(f"Terminated {args.terminate}")
else:
sys.exit(1)
elif args.install:
if launcher.install(args.install):
print(f"Installed {args.install}")
else:
sys.exit(1)
elif args.uninstall:
if launcher.uninstall(args.uninstall):
print(f"Uninstalled {args.uninstall}")
else:
sys.exit(1)
elif args.list:
packages = launcher.list_packages()
for pkg in packages:
print(pkg)
elif args.state:
print(f"{args.state}: {launcher.get_app_state(args.state)}")
else:
parser.print_help()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Android Build & Test - Gradle Wrapper
Builds projects and runs tests with parsed output.
"""
import argparse
import sys
import subprocess
import os
def find_gradlew():
"""Find gradlew in current or parent directories."""
cwd = os.getcwd()
while cwd != "/":
path = os.path.join(cwd, "gradlew")
if os.path.exists(path):
return path
cwd = os.path.dirname(cwd)
return None
def run_gradle_task(task, clean=False, verbose=False):
gradlew = find_gradlew()
if not gradlew:
print("Error: gradlew not found in current directory tree.")
return False
cmd = [gradlew, task]
if clean:
cmd.insert(1, "clean")
if not verbose:
cmd.append("-q") # Quiet mode
print(f"Running: {' '.join(cmd)}")
try:
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
# Stream output
output_lines = []
for line in process.stdout:
output_lines.append(line)
if verbose:
print(line, end="")
process.wait()
if process.returncode == 0:
print(f"✅ Build Successful: {task}")
return True
else:
print(f"❌ Build Failed: {task}")
# Print last 20 lines of error if not verbose
if not verbose:
print("Error details (last 20 lines):")
print("".join(output_lines[-20:]))
return False
except Exception as e:
print(f"Error running gradle: {e}")
return False
def main():
parser = argparse.ArgumentParser(description="Build and Test Android Project")
parser.add_argument("--task", default="assembleDebug", help="Gradle task to run")
parser.add_argument("--test", action="store_true", help="Run connectedAndroidTest")
parser.add_argument("--clean", action="store_true", help="Run clean before task")
parser.add_argument("--verbose", action="store_true", help="Show full gradle output")
parser.add_argument("--json", action="store_true", help="Output JSON (TODO)")
args = parser.parse_args()
task = args.task
if args.test:
task = "connectedAndroidTest"
if run_gradle_task(task, args.clean, args.verbose):
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Common utilities for Android Emulator Skill scripts.
Handles ADB command execution and device resolution.
"""
import os
import subprocess
import sys
from typing import List, Optional, Tuple
def get_adb_path() -> str:
"""Get the path to the adb executable."""
# Check environment variable first
android_home = os.environ.get("ANDROID_HOME")
if android_home:
adb_path = os.path.join(android_home, "platform-tools", "adb")
if os.path.exists(adb_path):
return adb_path
# Check if adb is in PATH
try:
subprocess.run(["adb", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
return "adb"
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# Standard locations
home = os.path.expanduser("~")
possible_paths = [
os.path.join(home, "Library/Android/sdk/platform-tools/adb"),
os.path.join(home, "Android/Sdk/platform-tools/adb"),
]
for path in possible_paths:
if os.path.exists(path):
return path
return "adb" # Hope for the best
ADB_PATH = get_adb_path()
def run_adb_command(cmd: List[str], serial: Optional[str] = None, check: bool = True) -> subprocess.CompletedProcess:
"""
Run an ADB command.
Args:
cmd: List of command arguments (e.g. ["shell", "ls"])
serial: Optional device serial number
check: Whether to raise an exception on failure
Returns:
CompletedProcess object
"""
full_cmd = [ADB_PATH]
if serial:
full_cmd.extend(["-s", serial])
full_cmd.extend(cmd)
return subprocess.run(full_cmd, capture_output=True, text=True, check=check)
def get_connected_devices() -> List[str]:
"""Get a list of connected device serials."""
result = run_adb_command(["devices"])
devices = []
# Skip first line (List of devices attached)
lines = result.stdout.strip().splitlines()[1:]
for line in lines:
if not line.strip():
continue
parts = line.split()
if len(parts) >= 2 and parts[1] == "device":
devices.append(parts[0])
return devices
def resolve_serial(serial: Optional[str] = None) -> str:
"""
Resolve the device serial to use.
If serial is provided, verifies it exists.
If not provided:
- If 1 device connected, returns it.
- If multiple, raises RuntimeError.
- If none, raises RuntimeError.
"""
devices = get_connected_devices()
if serial:
if serial not in devices:
raise RuntimeError(f"Device '{serial}' not found or not connected.")
return serial
if not devices:
raise RuntimeError("No Android devices connected or emulators running.")
if len(devices) == 1:
return devices[0]
raise RuntimeError(f"Multiple devices connected: {', '.join(devices)}. Please specify one with --serial.")
def get_screen_size(serial: str) -> Tuple[int, int]:
"""Get screen width and height in pixels."""
result = run_adb_command(["shell", "wm", "size"], serial=serial)
# Output: Physical size: 1080x2400
try:
if result.stdout:
line = result.stdout.strip().splitlines()[0]
if "Physical size:" in line:
size_str = line.split(":")[-1].strip()
width, height = map(int, size_str.split("x"))
return width, height
except Exception:
pass
return (1080, 1920) # Default fallback
`<#
.SYNOPSIS
Android Emulator Testing Environment Health Check
.DESCRIPTION
Verifies that all required tools and dependencies are properly installed
and configured for Android emulator testing.
.EXAMPLE
.\emu_health_check.ps1 -Help
#>
param (
[switch]$Help
)
$ErrorActionPreference = "Stop"
if ($Help) {
Write-Host "Android Emulator Testing - Environment Health Check`n"
Write-Host "Verifies that your environment is properly configured for Android emulator testing.`n"
Write-Host "Usage: .\emu_health_check.ps1 [options]`n"
Write-Host "Options:"
Write-Host " -Help Show this help message`n"
Write-Host "This script checks for:"
Write-Host " - Android SDK availability (ANDROID_HOME)"
Write-Host " - ADB (Android Debug Bridge) installation"
Write-Host " - Emulator executable availability"
Write-Host " - Java Development Kit (JDK)"
Write-Host " - Connected Android devices/emulators"
Write-Host " - Python 3 installation (for scripts)`n"
Write-Host "Exit codes:"
Write-Host " 0 - All checks passed"
Write-Host " 1 - One or more checks failed (see output for details)"
exit 0
}
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host " Android Emulator Testing - Environment Health Check" -ForegroundColor Cyan
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`n" -ForegroundColor Cyan
$ChecksPassed = 0
$ChecksFailed = 0
function Check-Passed {
param([string]$Message)
Write-Host "✓ $Message" -ForegroundColor Green
$script:ChecksPassed++
}
function Check-Failed {
param([string]$Message)
Write-Host "✗ $Message" -ForegroundColor Red
$script:ChecksFailed++
}
function Check-Warning {
param([string]$Message)
Write-Host "⚠ $Message" -ForegroundColor Yellow
}
# Check 1: ANDROID_HOME
Write-Host "[1/6] " -ForegroundColor Cyan -NoNewline
Write-Host "Checking ANDROID_HOME..."
$envAndroidHome = $env:ANDROID_HOME
if ([string]::IsNullOrWhiteSpace($envAndroidHome)) {
# Try to guess standard locations
$userProfile = $env:USERPROFILE
if ($null -eq $userProfile) {
$userProfile = $env:HOME
}
$commonPath = Join-Path $userProfile 'AppData\Local\Android\Sdk'
$macPath = Join-Path $userProfile 'Library\Android\sdk'
if (Test-Path $commonPath) {
$env:ANDROID_HOME = $commonPath
Check-Warning "ANDROID_HOME not set, but found valid SDK at $commonPath"
Write-Host " Exporting for this session."
} elseif (Test-Path $macPath) {
$env:ANDROID_HOME = $macPath
Check-Warning "ANDROID_HOME not set, but found valid SDK at $macPath"
Write-Host " Exporting for this session."
} else {
Check-Failed "ANDROID_HOME environment variable not set"
Write-Host " Please set ANDROID_HOME to your Android SDK location."
}
} else {
Check-Passed "ANDROID_HOME is set to $envAndroidHome"
}
Write-Host ""
# Check 2: ADB
Write-Host "[2/6] " -ForegroundColor Cyan -NoNewline
Write-Host "Checking ADB (Android Debug Bridge)..."
if (Get-Command adb -ErrorAction SilentlyContinue) {
try {
$adbVersion = (adb --version | Select-Object -First 1)
Check-Passed "ADB is installed ($adbVersion)"
$adbPath = (Get-Command adb).Source
Write-Host " Path: $adbPath"
} catch {
Check-Failed "ADB command found but failed to run."
}
} else {
if (-not [string]::IsNullOrWhiteSpace($env:ANDROID_HOME)) {
$platformToolsPath = Join-Path $env:ANDROID_HOME 'platform-tools'
if ((Test-Path (Join-Path $platformToolsPath 'adb.exe')) -or (Test-Path (Join-Path $platformToolsPath 'adb'))) {
$env:PATH += ";$platformToolsPath"
Check-Warning "ADB found in SDK but not in PATH. Adding it temporarily."
Check-Passed "ADB is installed"
} else {
Check-Failed "ADB command not found"
Write-Host " Ensure platform-tools is in your PATH."
}
} else {
Check-Failed "ADB command not found"
Write-Host " Ensure platform-tools is in your PATH."
}
}
Write-Host ""
# Check 3: Emulator
Write-Host "[3/6] " -ForegroundColor Cyan -NoNewline
Write-Host "Checking Android Emulator..."
if (Get-Command emulator -ErrorAction SilentlyContinue) {
try {
$emuVersion = (emulator -version | Select-Object -First 1)
Check-Passed "Emulator is installed ($emuVersion)"
} catch {
Check-Failed "Emulator command found but failed to run."
}
} else {
if (-not [string]::IsNullOrWhiteSpace($env:ANDROID_HOME)) {
$emulatorPath = Join-Path $env:ANDROID_HOME 'emulator'
if ((Test-Path (Join-Path $emulatorPath 'emulator.exe')) -or (Test-Path (Join-Path $emulatorPath 'emulator'))) {
$env:PATH += ";$emulatorPath"
Check-Warning "Emulator found in SDK but not in PATH. Adding it temporarily."
Check-Passed "Emulator is installed"
} else {
Check-Failed "Emulator command not found"
Write-Host " Ensure emulator is in your PATH."
}
} else {
Check-Failed "Emulator command not found"
Write-Host " Ensure emulator is in your PATH."
}
}
Write-Host ""
# Check 4: Java
Write-Host "[4/6] " -ForegroundColor Cyan -NoNewline
Write-Host "Checking Java..."
if (Get-Command java -ErrorAction SilentlyContinue) {
try {
$javaVersion = (& java -version 2>&1 | Select-Object -First 1)
Check-Passed "Java is installed ($javaVersion)"
} catch {
Check-Failed "Java command found but failed to run."
}
} else {
Check-Failed "Java not found"
Write-Host " A JDK is required for Android development."
}
Write-Host ""
# Check 5: Python 3
Write-Host "[5/6] " -ForegroundColor Cyan -NoNewline
Write-Host "Checking Python 3..."
if (Get-Command python3 -ErrorAction SilentlyContinue) {
try {
$pythonVersion = (python3 --version | Select-Object -First 1)
Check-Passed "Python 3 is installed ($pythonVersion)"
} catch {
Check-Failed "Python 3 command found but failed to run."
}
} elseif (Get-Command python -ErrorAction SilentlyContinue) {
try {
$pythonVersion = (python --version | Select-Object -First 1)
if ($pythonVersion -match "Python 3") {
Check-Passed "Python 3 is installed ($pythonVersion)"
} else {
Check-Failed "Python 3 not found, found $pythonVersion instead"
Write-Host " Required for skill scripts."
}
} catch {
Check-Failed "Python command found but failed to run."
}
} else {
Check-Failed "Python 3 not found"
Write-Host " Required for skill scripts."
}
Write-Host ""
# Check 6: Connected Devices
Write-Host "[6/6] " -ForegroundColor Cyan -NoNewline
Write-Host "Checking connected devices..."
if (Get-Command adb -ErrorAction SilentlyContinue) {
$devices = (adb devices | Select-String -Pattern "device$")
$deviceCount = if ($null -ne $devices) { @($devices).Count } else { 0 }
if ($deviceCount -gt 0) {
Check-Passed "Found $deviceCount connected device(s)"
Write-Host ""
Write-Host " Connected devices:"
foreach ($device in $devices) {
$line = $device.ToString().Trim()
Write-Host " - $line"
}
} else {
Check-Warning "No devices connected or emulators booted"
Write-Host " Boot an emulator to begin testing."
Write-Host " Use 'emulator -list-avds' to see available AVDs."
}
} else {
Check-Failed "Cannot check devices (adb not found)"
}
Write-Host ""
# Summary
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host " Summary" -ForegroundColor Cyan
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`n" -ForegroundColor Cyan
Write-Host "Checks passed: " -NoNewline
Write-Host $ChecksPassed -ForegroundColor Green
if ($ChecksFailed -gt 0) {
Write-Host "Checks failed: " -NoNewline
Write-Host $ChecksFailed -ForegroundColor Red
Write-Host ""
Write-Host "Action required: " -ForegroundColor Yellow -NoNewline
Write-Host "Fix the failed checks above before testing"
exit 1
} else {
Write-Host ""
Write-Host "✓ Environment is ready for Android emulator testing" -ForegroundColor Green
exit 0
}
#!/usr/bin/env bash
#
# Android Emulator Testing Environment Health Check
#
# Verifies that all required tools and dependencies are properly installed
# and configured for Android emulator testing.
#
# Usage: bash scripts/emu_health_check.sh [--help]
set -e
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Check flags
SHOW_HELP=false
# Parse arguments
for arg in "$@"; do
case $arg in
--help|-h)
SHOW_HELP=true
shift
;;
esac
done
if [ "$SHOW_HELP" = true ]; then
cat <<EOF
Android Emulator Testing - Environment Health Check
Verifies that your environment is properly configured for Android emulator testing.
Usage: bash scripts/emu_health_check.sh [options]
Options:
--help, -h Show this help message
This script checks for:
- Android SDK availability (ANDROID_HOME)
- ADB (Android Debug Bridge) installation
- Emulator executable availability
- Java Development Kit (JDK)
- Connected Android devices/emulators
- Python 3 installation (for scripts)
Exit codes:
0 - All checks passed
1 - One or more checks failed (see output for details)
EOF
exit 0
fi
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} Android Emulator Testing - Environment Health Check${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
CHECKS_PASSED=0
CHECKS_FAILED=0
# Function to print check status
check_passed() {
echo -e "${GREEN}✓${NC} $1"
((CHECKS_PASSED++))
}
check_failed() {
echo -e "${RED}✗${NC} $1"
((CHECKS_FAILED++))
}
check_warning() {
echo -e "${YELLOW}⚠${NC} $1"
}
# Check 1: ANDROID_HOME
echo -e "${BLUE}[1/6]${NC} Checking ANDROID_HOME..."
if [ -n "$ANDROID_HOME" ]; then
check_passed "ANDROID_HOME is set to $ANDROID_HOME"
else
# Try to guess standard locations
if [ -d "$HOME/Library/Android/sdk" ]; then
export ANDROID_HOME="$HOME/Library/Android/sdk"
check_warning "ANDROID_HOME not set, but found valid SDK at $ANDROID_HOME"
echo " Exporting for this session."
else
check_failed "ANDROID_HOME environment variable not set"
echo " Please set ANDROID_HOME to your Android SDK location."
fi
fi
echo ""
# Check 2: ADB
echo -e "${BLUE}[2/6]${NC} Checking ADB (Android Debug Bridge)..."
if command -v adb &> /dev/null; then
ADB_VERSION=$(adb --version | head -n 1)
check_passed "ADB is installed ($ADB_VERSION)"
echo " Path: $(which adb)"
else
# Check if inside standard path
if [ -f "$ANDROID_HOME/platform-tools/adb" ]; then
export PATH="$PATH:$ANDROID_HOME/platform-tools"
check_warning "ADB found in SDK but not in PATH. Adding it temporarily."
check_passed "ADB is installed"
else
check_failed "ADB command not found"
echo " Ensure platform-tools is in your PATH."
fi
fi
echo ""
# Check 3: Emulator
echo -e "${BLUE}[3/6]${NC} Checking Android Emulator..."
if command -v emulator &> /dev/null; then
EMULATOR_VERSION=$(emulator -version | head -n 1)
check_passed "Emulator is installed ($EMULATOR_VERSION)"
else
if [ -f "$ANDROID_HOME/emulator/emulator" ]; then
export PATH="$PATH:$ANDROID_HOME/emulator"
check_warning "Emulator found in SDK but not in PATH. Adding it temporarily."
check_passed "Emulator is installed"
else
check_failed "Emulator command not found"
echo " Ensure emulator is in your PATH."
fi
fi
echo ""
# Check 4: Java
echo -e "${BLUE}[4/6]${NC} Checking Java..."
if command -v java &> /dev/null; then
JAVA_VERSION=$(java -version 2>&1 | head -n 1)
check_passed "Java is installed ($JAVA_VERSION)"
else
check_failed "Java not found"
echo " A JDK is required for Android development."
fi
echo ""
# Check 5: Python 3
echo -e "${BLUE}[5/6]${NC} Checking Python 3..."
if command -v python3 &> /dev/null; then
PYTHON_VERSION=$(python3 --version)
check_passed "Python 3 is installed ($PYTHON_VERSION)"
else
check_failed "Python 3 not found"
echo " Required for skill scripts."
fi
echo ""
# Check 6: Connected Devices
echo -e "${BLUE}[6/6]${NC} Checking connected devices..."
if command -v adb &> /dev/null; then
DEVICE_COUNT=$(adb devices | grep -E "device$" | wc -l | tr -d ' ')
if [ "$DEVICE_COUNT" -gt 0 ]; then
check_passed "Found $DEVICE_COUNT connected device(s)"
echo ""
echo " Connected devices:"
adb devices | grep -E "device$" | while read -r line; do
echo " - $line"
done
else
check_warning "No devices connected or emulators booted"
echo " Boot an emulator to begin testing."
echo " Use 'emulator -list-avds' to see available AVDs."
fi
else
check_failed "Cannot check devices (adb not found)"
fi
echo ""
# Summary
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} Summary${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "Checks passed: ${GREEN}$CHECKS_PASSED${NC}"
if [ "$CHECKS_FAILED" -gt 0 ]; then
echo -e "Checks failed: ${RED}$CHECKS_FAILED${NC}"
echo ""
echo -e "${YELLOW}Action required:${NC} Fix the failed checks above before testing"
exit 1
else
echo ""
echo -e "${GREEN}✓ Environment is ready for Android emulator testing${NC}"
exit 0
fi
#!/usr/bin/env python3
"""
Android Emulator Manager - AVD Lifecycle
List, boot, and shutdown Android Virtual Devices.
"""
import argparse
import sys
import subprocess
import os
from common import resolve_serial, run_adb_command
def get_emulator_path():
"""Get path to emulator executable."""
android_home = os.environ.get("ANDROID_HOME")
if android_home:
path = os.path.join(android_home, "emulator", "emulator")
if os.path.exists(path):
return path
# Check if in PATH
try:
subprocess.run(["emulator", "-version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return "emulator"
except Exception:
pass
return "emulator"
def list_avds():
"""List available AVDs."""
emu = get_emulator_path()
try:
res = subprocess.run([emu, "-list-avds"], capture_output=True, text=True, check=True)
avds = [line.strip() for line in res.stdout.splitlines() if line.strip()]
return avds
except RuntimeError:
return []
def boot_avd(avd_name):
"""Boot an AVD."""
emu = get_emulator_path()
print(f"Booting {avd_name}...")
# Launch in background
try:
subprocess.Popen([emu, "-avd", avd_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print(f"Emulator {avd_name} started.")
return True
except Exception as e:
print(f"Failed to boot: {e}")
return False
def shutdown_emulator(serial):
"""Shutdown an emulator instance."""
try:
run_adb_command(["emu", "kill"], serial)
print(f"Shutdown signal sent to {serial}")
return True
except subprocess.CalledProcessError:
print(f"Failed to shutdown {serial}")
return False
def main():
parser = argparse.ArgumentParser(description="Manage Android Emulators")
parser.add_argument("--list", action="store_true", help="List available AVDs")
parser.add_argument("--boot", help="Boot AVD by name")
parser.add_argument("--shutdown", help="Shutdown emulator by serial")
parser.add_argument("--json", action="store_true", help="Output JSON (TODO)")
args = parser.parse_args()
if args.list:
avds = list_avds()
print("Available AVDs:")
for avd in avds:
print(f" - {avd}")
elif args.boot:
boot_avd(args.boot)
elif args.shutdown:
# If shutdown arg is provided, treat it as serial if likely
serial = args.shutdown
shutdown_emulator(serial)
else:
parser.print_help()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Android Gesture - Swipe and Scroll
Perform gestures on the Android device.
"""
import argparse
import sys
import subprocess
from common import resolve_serial, run_adb_command, get_device_screen_size as get_size
def perform_swipe(serial, direction, duration=300):
"""
Perform checks logic:
- Up: swipe from bottom to top
- Down: swipe from top to bottom
- Left: swipe from right to left
- Right: swipe from left to right
"""
width, height = get_size(serial)
# Safe margins (10%)
w_min, w_max = int(width * 0.1), int(width * 0.9)
h_min, h_max = int(height * 0.1), int(height * 0.9)
# Centers
cx = width // 2
cy = height // 2
start_x, start_y, end_x, end_y = 0, 0, 0, 0
if direction == "up":
start_x, start_y = cx, h_max
end_x, end_y = cx, h_min
elif direction == "down":
start_x, start_y = cx, h_min
end_x, end_y = cx, h_max
elif direction == "left":
start_x, start_y = w_max, cy
end_x, end_y = w_min, cy
elif direction == "right":
start_x, start_y = w_min, cy
end_x, end_y = w_max, cy
cmd = ["shell", "input", "swipe", str(start_x), str(start_y), str(end_x), str(end_y), str(duration)]
try:
run_adb_command(cmd, serial)
print(f"Swiped {direction}")
except subprocess.CalledProcessError:
print(f"Failed to swipe {direction}")
def main():
parser = argparse.ArgumentParser(description="Perform gestures on Android")
parser.add_argument("--swipe", choices=["up", "down", "left", "right"], help="Swipe direction")
parser.add_argument("--scroll", choices=["up", "down", "left", "right"], help="Scroll direction (same as swipe but inverse logic usually, but here mapped 1:1 to swipe direction)")
parser.add_argument("--duration", type=int, default=300, help="Duration in ms")
parser.add_argument("--serial", "-s", help="Device serial")
args = parser.parse_args()
try:
serial = resolve_serial(args.serial)
except RuntimeError as e:
print(f"Error: {e}")
sys.exit(1)
if args.swipe:
perform_swipe(serial, args.swipe, args.duration)
elif args.scroll:
# Scroll down content usually means swiping up finger, but 'scroll down' command usually implies moving content down (swiping down)
# We'll just map scroll to swipe for now to keep it simple
perform_swipe(serial, args.scroll, args.duration)
else:
parser.print_help()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Android Keyboard - Input text and key events
Type text and press hardware buttons.
"""
import argparse
import sys
import shlex
import subprocess
from common import resolve_serial, run_adb_command
KEYCODES = {
"home": 3,
"back": 4,
"call": 5,
"endcall": 6,
"enter": 66,
"tab": 61,
"delete": 67,
"power": 26,
"camera": 27,
"volume_up": 24,
"volume_down": 25,
"menu": 82,
"search": 84,
}
def press_key(serial, key):
keycode = KEYCODES.get(key.lower())
if not keycode:
# Try as integer
try:
keycode = int(key)
except ValueError:
print(f"Unknown key: {key}")
return False
try:
run_adb_command(["shell", "input", "keyevent", str(keycode)], serial)
return True
except subprocess.CalledProcessError:
return False
def type_text(serial, text):
try:
safe_text = shlex.quote(text).replace(" ", "%s")
run_adb_command(["shell", "input", "text", safe_text], serial)
return True
except subprocess.CalledProcessError:
return False
def main():
parser = argparse.ArgumentParser(description="Android Keyboard Input")
parser.add_argument("--key", help="Key to press (home, back, enter, tab, delete, or keycode)")
parser.add_argument("--text", help="Text to type")
parser.add_argument("--serial", "-s", help="Device serial")
args = parser.parse_args()
try:
serial = resolve_serial(args.serial)
except RuntimeError as e:
print(f"Error: {e}")
sys.exit(1)
if args.key:
if press_key(serial, args.key):
print(f"Pressed {args.key}")
else:
sys.exit(1)
elif args.text:
if type_text(serial, args.text):
print(f"Typed: {args.text}")
else:
sys.exit(1)
else:
parser.print_help()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Android Log Monitor - ADB Logcat Wrapper
Monitor device logs with filtering.
"""
import argparse
import sys
import subprocess
import signal
from common import resolve_serial, run_adb_command
def main():
parser = argparse.ArgumentParser(description="Monitor Android Logs")
parser.add_argument("--package", help="Filter by package name (requires app to be running)")
parser.add_argument("--tag", help="Filter by tag")
parser.add_argument("--priority", choices=["V", "D", "I", "W", "E", "F"], default="V", help="Minimum priority")
parser.add_argument("--grep", help="Grep filter")
parser.add_argument("--clear", "-c", action="store_true", help="Clear logs first")
parser.add_argument("--serial", "-s", help="Device serial")
args = parser.parse_args()
try:
serial = resolve_serial(args.serial)
except RuntimeError as e:
print(f"Error: {e}")
sys.exit(1)
if args.clear:
run_adb_command(["logcat", "-c"], serial)
print("Logs cleared.")
cmd = ["logcat", "-v", "color", f"*:{args.priority}"]
if args.tag:
cmd = ["logcat", "-v", "color", "-s", args.tag]
full_cmd = ["adb"]
if serial:
full_cmd.extend(["-s", serial])
full_cmd.extend(cmd)
if args.package:
# Get PID of package
try:
res = run_adb_command(["shell", "pidof", args.package], serial, check=False)
pid = res.stdout.strip()
if pid:
print(f"Filtering for package {args.package} (PID: {pid})")
full_cmd.append(f"--pid={pid}")
else:
print(f"Package {args.package} not running. Showing all logs.")
except Exception:
pass
if args.grep:
full_cmd.extend(["|", "grep", args.grep])
print(f"Running: {' '.join(full_cmd)}")
try:
# Use subprocess directly to stream
process = subprocess.Popen(full_cmd, stdout=sys.stdout, stderr=sys.stderr)
process.wait()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Android Navigator - Smart Element Finder and Interactor
Finds and interacts with UI elements using accessibility data.
"""
import argparse
import sys
import shlex
import subprocess
from common import resolve_serial, run_adb_command
from screen_mapper import ScreenMapper
class Navigator:
def __init__(self, serial=None):
self.serial = serial
self.mapper = ScreenMapper(serial)
def find_element(self, text=None, resource_id=None, element_class=None, index=0):
"""Find element in current screen hierarchy."""
analysis = self.mapper.analyze()
if "error" in analysis:
return None
candidates = []
for elem in analysis["all_elements"]:
match = True
if text:
# Fuzzy match text or content-desc
elem_text = (elem.get("text") or "").lower()
elem_desc = (elem.get("content-desc") or "").lower()
search = text.lower()
if search not in elem_text and search not in elem_desc:
match = False
if resource_id and resource_id not in elem.get("resource-id", ""):
match = False
if element_class and element_class not in elem.get("class", ""):
match = False
if match:
candidates.append(elem)
if index < len(candidates):
return candidates[index]
return None
def tap(self, x, y):
"""Tap at coordinates."""
try:
run_adb_command(["shell", "input", "tap", str(x), str(y)], self.serial)
return True
except subprocess.CalledProcessError:
return False
def enter_text(self, text):
"""Enter text (escaped)."""
try:
# Escape text for shell
safe_text = shlex.quote(text).replace(" ", "%s")
run_adb_command(["shell", "input", "text", safe_text], self.serial)
return True
except subprocess.CalledProcessError:
return False
def main():
parser = argparse.ArgumentParser(description="Navigate Android apps")
# Finding options
parser.add_argument("--find-text", help="Find element by text (fuzzy)")
parser.add_argument("--find-id", help="Find element by resource-id")
parser.add_argument("--find-class", help="Find element by class name")
parser.add_argument("--index", type=int, default=0, help="Index of match")
# Action options
parser.add_argument("--tap", action="store_true", help="Tap the found element")
parser.add_argument("--enter-text", help="Enter text into found element")
parser.add_argument("--tap-at", help="Tap at coords x,y")
parser.add_argument("--serial", "-s", help="Device serial")
args = parser.parse_args()
try:
serial = resolve_serial(args.serial)
except RuntimeError as e:
print(f"Error: {e}")
sys.exit(1)
navigator = Navigator(serial)
# Tap at coordinates
if args.tap_at:
x, y = map(int, args.tap_at.split(","))
if navigator.tap(x, y):
print(f"Tapped at {x},{y}")
else:
sys.exit(1)
return
# Find element
if args.find_text or args.find_id or args.find_class:
element = navigator.find_element(
text=args.find_text,
resource_id=args.find_id,
element_class=args.find_class,
index=args.index
)
if not element:
print("Element not found")
sys.exit(1)
print(f"Found: {element.get('class')} '{element.get('text')}' at {element.get('bounds')}")
if args.tap:
bounds = element.get("bounds")
if bounds:
cx, cy = bounds["center_x"], bounds["center_y"]
if navigator.tap(cx, cy):
print(f"Tapped at {cx},{cy}")
else:
print("Failed to tap")
sys.exit(1)
else:
print("Element has no bounds")
sys.exit(1)
if args.enter_text:
# Tap first to focus if needed (optional, but good practice)
if args.tap:
time.sleep(0.5)
if navigator.enter_text(args.enter_text):
print(f"Entered text: {args.enter_text}")
else:
print("Failed to enter text")
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Android Screen Mapper - Current Screen Analyzer
Maps the current screen's UI elements for navigation decisions.
"""
import argparse
import json
import os
import re
import sys
import tempfile
import xml.etree.ElementTree as ET
from common import resolve_serial, run_adb_command
class ScreenMapper:
def __init__(self, serial=None):
self.serial = serial
self.temp_file = os.path.join(tempfile.gettempdir(), "window_dump.xml")
def dump_ui(self):
"""Dump UI hierarchy to local file."""
# Dump to device
run_adb_command(["shell", "uiautomator", "dump", "/sdcard/window_dump.xml"], self.serial)
# Pull to local
run_adb_command(["pull", "/sdcard/window_dump.xml", self.temp_file], self.serial)
def parse_bounds(self, bounds_str):
"""Parse bounds string '[x1,y1][x2,y2]' to {'x':, 'y':, 'width':, 'height':}"""
match = re.match(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds_str)
if match:
x1, y1, x2, y2 = map(int, match.groups())
return {
"x": x1,
"y": y1,
"width": x2 - x1,
"height": y2 - y1,
"center_x": (x1 + x2) // 2,
"center_y": (y1 + y2) // 2
}
return None
def analyze(self):
"""Analyze the UI hierarchy."""
self.dump_ui()
if not os.path.exists(self.temp_file):
return {"error": "Failed to dump UI"}
tree = ET.parse(self.temp_file)
root = tree.getroot()
analysis = {
"buttons": [],
"text_fields": [],
"interactive": [],
"all_elements": []
}
def process_node(node):
bounds = self.parse_bounds(node.get("bounds", ""))
element = {
"class": node.get("class", ""),
"text": node.get("text", ""),
"resource-id": node.get("resource-id", ""),
"content-desc": node.get("content-desc", ""),
"package": node.get("package", ""),
"clickable": node.get("clickable") == "true",
"enabled": node.get("enabled") == "true",
"focused": node.get("focused") == "true",
"scrollable": node.get("scrollable") == "true",
"bounds": bounds
}
# Identify specific types
if element["class"].endswith("Button") or element["clickable"]:
label = element["text"] or element["content-desc"] or element["resource-id"]
if label:
analysis["buttons"].append(label)
if element["class"].endswith("EditText"):
analysis["text_fields"].append(element)
if element["clickable"] or element["scrollable"] or element["class"].endswith("EditText"):
analysis["interactive"].append(element)
analysis["all_elements"].append(element)
for child in node:
process_node(child)
process_node(root)
# Deduplicate buttons
analysis["buttons"] = list(set(analysis["buttons"]))
return analysis
def format_summary(self, analysis):
"""Format analysis as text summary."""
lines = []
lines.append(f"Screen: {len(analysis['all_elements'])} elements ({len(analysis['interactive'])} interactive)")
if analysis["buttons"]:
buttons = analysis["buttons"][:5]
lines.append(f"Buttons: {', '.join(buttons)}")
if len(analysis["buttons"]) > 5:
lines.append(f" ... +{len(analysis['buttons']) - 5} more")
if analysis["text_fields"]:
lines.append(f"TextFields: {len(analysis['text_fields'])}")
for tf in analysis["text_fields"]:
lines.append(f" - {tf.get('text') or tf.get('resource-id') or 'Unnamed'}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Map Android UI elements")
parser.add_argument("--json", action="store_true", help="Output JSON")
parser.add_argument("--verbose", action="store_true", help="Detailed output")
parser.add_argument("--serial", "-s", help="Device serial")
args = parser.parse_args()
try:
serial = resolve_serial(args.serial)
except RuntimeError as e:
print(f"Error: {e}")
sys.exit(1)
mapper = ScreenMapper(serial)
analysis = mapper.analyze()
if args.json:
print(json.dumps(analysis, indent=2))
else:
print(mapper.format_summary(analysis))
if __name__ == "__main__":
main()