Highlights
Project snapshot
- No-root pipeline pairing Android MediaProjection capture with an AccessibilityService input path, so a browser can both watch and tap/swipe/type on the phone with zero rooting.
- Phone dials outbound over wss:// to a ~210-line Node relay (room + constant-time token auth), so it works across the internet with no port-forwarding on the device.
- Dual codec path — MJPEG by default and opt-in hardware H.264 (MediaCodec → WebCodecs) at ~30 fps — with GOP-aware frame dropping and backpressure at three layers.
Overview
Browser-based view and control of an Android phone, no root
ScreenLink lets you watch and remotely control an Android phone from any browser — on a computer or a second phone — without rooting the device. A host app captures the screen with MediaProjection and injects taps, swipes, and text through an AccessibilityService; a small relay pairs the phone with viewers; and the viewer is just a web page.
It is built as three cooperating parts: a Kotlin host app (six source files), a ~210-line Node relay using Express and ws, and a dependency-free browser viewer rendering to a Canvas. The phone connects outbound over an encrypted WebSocket, so there is nothing to port-forward, and the whole thing runs on free always-on infrastructure.
Problem
Remote phone access usually means root, cables, or paid tools
- Most screen-mirroring and control tools require root, a permanent USB/ADB tether, or a paid SaaS subscription.
- Android intentionally blocks synthetic input and per-session screen capture without explicit user consent, so a no-root path has to work within those guardrails.
- A phone behind mobile NAT has no public IP, so a naive peer-to-peer connection can't reach it from the internet.
- Android 13+ greys out the Accessibility toggle for sideloaded apps ("Restricted settings"), which trips up every manual setup.
Solution
An outbound-only relay with consent-based capture and input
- Capture the screen with MediaProjection and inject gestures with an AccessibilityService — the only non-root input path — keeping the user's own consent in the loop.
- Have the phone dial out over wss:// to a relay that pairs host and viewers by room code and a constant-time-checked token, so it traverses NAT with no port-forwarding.
- Automate the Accessibility "Restricted settings" unlock over ADB (settings put secure), since ADB is exempt from the Android 13+ block.
Core Features
Capture, control, and a tunable viewer
Screen Capture
- MediaProjection → VirtualDisplay → ImageReader → JPEG (MJPEG) by default
- Opt-in hardware H.264 via MediaCodec with cached SPS/PPS prepended to key frames
- Whole-screen capture forced on Android 14+ so remote taps map correctly
Remote Control
- AccessibilityService injects taps, long-presses, and swipes via GestureDescription
- Global actions (Back / Home / Recents / Notification shade) and best-effort text entry
- Watch-only mode by toggling Control off in the viewer
Viewer & Quality
- Dependency-free browser viewer renders frames to a Canvas
- WebCodecs VideoDecoder path for H.264, with automatic fallback to MJPEG
- Quality dropdown — Data saver / Balanced / Sharp — adjusts fps and compression live
Always-On Operation
- Foreground service with keep-awake overlay to survive Android 15+ lock-stop
- Auto-reconnect with backoff and best-effort resume after reboot
- ADB setup scripts (PowerShell + Bash) provision the phone in one shot
Architecture
Outbound host, fan-out relay, thin browser viewer
- The relay keeps a per-room map of one host and many viewers, forwards binary frames host→viewers and JSON control viewer→host, and replays cached stream metadata to late joiners.
- Hosted on Oracle Cloud Always Free with a DuckDNS hostname and Caddy for automatic TLS/WSS; a Render blueprint and a local ngrok launcher are included as alternatives.
Engineering Highlights
Resilience, backpressure, and platform-correct security
Streaming & Backpressure
- Capture defaults of 1024px max edge, JPEG quality 45, and 8 fps, all adjustable at runtime
- Backpressure handled at three layers: host socket queue, relay per-viewer buffer, and viewer decode queue
- GOP-aware H.264: congested viewers resume only on a key frame, and the relay requests a fresh I-frame from the host
Reliability
- OkHttp WebSocket with 20s pings and auto-reconnect (linear backoff capped at 10s)
- H.264 encoder exposes onError so the service can fall back to MJPEG if the codec fails
- Foreground media-projection service with a deliberately Stop-less notification to prevent accidental kills
Security & Platform
- wss:// enforced in-app, cleartext traffic disabled, constant-time token comparison on the relay (timingSafeEqual)
- Per-IP (20) and total (200) socket caps plus an 8MB per-frame size cap
- CI pins Gradle 8.7 / AGP 8.5.2 / JDK 17 / compileSdk 34 (minSdk 29) and builds the APK in GitHub Actions
Impact
A free, self-hosted alternative to paid remote-control tools
- Delivers internet-reachable phone viewing and control with no root, no port-forwarding, and no subscription.
- Runs entirely on free-tier infrastructure (Oracle Always Free + DuckDNS + Caddy), streaming ~0.5–1.5 Mbit/s at default quality.
- Stays transparent and consent-driven by design — Android's own cast indicator is always visible and capture consent is re-confirmed each session.
