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.