aboutsummaryrefslogtreecommitdiff

scrcpy-android

Native Android client that mirrors and controls another Android device over Wireless debugging. It forwards video, audio, touch, keys, and clipboard data through the scrcpy server. Neither device needs root.

The source device running this app needs Android 12 or newer. The target needs Android 11 or newer with Wireless debugging enabled.

Run

Build requirements are JDK 17, Android SDK platform 36, and build-tools 35.

./scripts/update-server
./gradlew :app:assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk

On the target, open Settings, Developer options, Wireless debugging. Copy the displayed IP address and connection port. Open Pair device with pairing code and copy its separate pairing port and six-digit code.

Enter those values in the source app and tap Pair and save. Pairing authorizes this app's stable ADB key on the target.

Android may change the Wireless debugging port after Wireless debugging is toggled, adbd restarts, or the device reboots. Forget the stale row and pair again with the newly displayed address.

Test

The host gate checks the Gradle wrapper and server inputs, runs both JVM test suites, and runs Android lint:

./scripts/check

The Docker entrypoint supplies its own JDK and Android SDK:

./test unit
./test server
./test apk
./test e2e
./test all

unit and server need Docker only. server builds and tests the pinned scrcpy source, then compares its executable dex with the bundled release. apk signs and verifies a minified release with a throwaway key. e2e needs read/write access to /dev/kvm; it boots two clean API 36 emulators and exercises Android's real pairing-code UI, the minified release client, mandatory TLS, Opus playback, SurfaceView rendering, and touch, key, and clipboard forwarding. It also checks automatic reconnect across both scrcpy and adbd restarts.

e2e boots two emulators and drives real system UI, so it is slower and more timing-dependent than the rest of the gate. Every deadline is overridable if a host needs longer: E2E_BOOT_DEADLINE, E2E_SETTLE_DEADLINE, E2E_UI_DEADLINE, E2E_ROTATE_DEADLINE, E2E_RESIZE_DEADLINE. The disposable AVDs use a bounded 10 GiB tmpfs; allow roughly 16 GiB of available memory for the two emulators and their data.

The screenshot tool also needs /dev/kvm:

./test screenshots

It replaces the tracked Fastlane screenshots only after checking their owner and dimensions.

Release

Set all four signing variables and run the release script:

export KEYSTORE_PATH=/absolute/path/release.p12
export KEYSTORE_PASS=...
export KEY_ALIAS=...
export KEY_PASS=...
./scripts/build-apk

The script verifies the wrapper and scrcpy server, builds the minified APK, checks its signature, 16 KiB page alignment, SDK levels, exact permission set, and exact ABI set with build-tools 35, then prints its SHA-256. The APK contains arm64-v8a and x86_64 native libraries.

Keep the signing keystore outside this repository. .gitignore cannot protect an arbitrary keystore filename.

Debug

All application logs use one tag:

adb logcat -s scrcpy-android

A normal session includes these lines:

pair ok host=... pair_port=...
adb connect ok
push /data/local/tmp/scrcpy-server.jar bytes=...
spawn server ver=... scid=...
openAbstract scrcpy_... ok
video meta codec=h264
audio meta codec=opus
video sink: rendered frame n=1
audio sink: playback started

Target-side scrcpy output is copied into the same log with the server: prefix. Uncaught exceptions are also written under the app-specific external files directory as crash-*.log; only the five newest files are retained.

Security

The app creates one software-backed RSA ADB identity in its private files directory. Pairing authenticates the six-digit-code exchange with SPAKE2 and authorizes that client key on the target. Later connections require TLS and the target rejects clients whose keys were not authorized.

Android does not provide a stable target certificate to pin: its pairing server generates a fresh key and adbd uses a separate process-scoped key. Consequently the ADB protocol authenticates this client to the target, but does not cryptographically authenticate a later target to this client. TLS still protects against passive eavesdropping. Treat the saved address and the network carrying it as trusted.

Clipboard synchronization is enabled by default and works in both directions. Disable it in Settings before connecting to a target that should not receive the source device's clipboard. Only explicit text clipboard items are synced. On Android 10 and newer, a source-side copy made in another app is forwarded when this app regains focus, because Android blocks background clipboard reads.

Use a private network or VPN to reach a remote target. Do not expose a Wireless debugging port directly to the internet. The app performs no hidden network access; it connects only to the address selected by the user and downloads nothing at runtime.

Backups and device-to-device transfer are disabled because private app data contains the ADB identity and saved target addresses. Crash logs may contain addresses and diagnostic data and are removed when the app is uninstalled.

Source layout

app/ contains the Android application. vendor/libadb-android/ is a pruned, locally patched subset of libadb-android 3.1.1. vendor/scrcpy is a submodule pinned to the scrcpy release used for the server. test-rig/ contains the Docker and emulator tests.

The server binary is intentionally ignored by Git. scripts/update-server downloads the allow-listed scrcpy 4.1 artifact and verifies its SHA-256 before installing it. Every APK build verifies it again. The source-build equivalent used by F-Droid is explicit:

server=$(./scripts/build-server)
install -m 0644 "$server" app/src/main/assets/scrcpy-server.jar
sha256sum app/src/main/assets/scrcpy-server.jar \
    | awk '{ print $1 }' >app/src/main/assets/scrcpy-server.sha256
./gradlew :app:assembleRelease

Vendor updates are manual because the ADB transport carries overlapping local security and flow-control changes. See vendor/libadb-android/README.md.

License

The application is Apache-2.0; see LICENSE. Bundled component notices, source locations, and LGPL relinking information are in app/src/main/assets/THIRD_PARTY_NOTICES and in the in-app Licenses screen.