scrcpy-android ============== What it does ------------ Native Android app that mirrors a second Android device over Wireless ADB, the same way scrcpy(1) does. Source device runs this app; target is any Android 11+ device with Wireless Debugging enabled. Video, audio, touch, key, and clipboard are forwarded. No root required on either side. The application code is plain Java. Runtime dependencies include the native Conscrypt and SPAKE2 libraries. The ADB stack is vendored from MuntashirAkon/libadb-android. `scrcpy-server.jar` is a pinned upstream binary in `app/src/main/assets/` and every APK packaging task verifies its checksum. How to run it ------------- Prerequisites: JDK 17 and Android SDK with platform-tools and platform 35, or an Android Studio release supporting AGP 8.7. ./scripts/update-server # download + verify pinned scrcpy-server.jar ./gradlew :app:assembleDebug # build debug APK adb install app/build/outputs/apk/debug/app-debug.apk The vendored libadb-android tree is committed; scripts/fetch-vendor is only needed when bumping its pinned tag, not for building. F-Droid builds `scrcpy-server.jar` from the pinned source submodule. Its recipe must pass the locally built artifact's checksum as `-PscrcpyServerSha256=`; normal builds verify the tracked upstream binary checksum. For a signed release APK, see "How to build a release APK" below. On the target device: Settings -> Developer options -> Wireless debugging. Note the "IP address & Port" on that screen; it is the device address and the port is permanent. Then open "Pair device with pairing code" and note the port from its dialog (a different, short-lived one) and the 6-digit code. In the app on the source device: enter the device address as `ip:port`, then the pairing port and code, and tap Pair. Tap the saved device row to connect and mirror. How to test it -------------- A direct host/VM quality gate is available when JDK 17 and Android SDK 35 are installed: ./scripts/check # wrapper/server integrity, all JVM tests, Android lint A `./test` entrypoint runs tests inside project-local Docker images. The small unit target contains JDK 17, Android cmdline-tools, platform 35, and build-tools 35; emulator tiers add the x86_64 API-35 emulator. Images are built on first use and cached. ./test # JVM unit tests (Wire, Sync, ControlMessages, # Devices, plus piped-stream parser tests for # VideoStream, AudioStream, ControlStream). # ~30 s on warm cache. No KVM needed. ./test e2e # Single-emulator self-mirror end-to-end. # Boots the API-35 AOSP emulator, roots it, # installs the debug APK, drops the app's public # key into /data/misc/adb/adb_keys, launches # Mirror against 127.0.0.1:5555, then asserts # 'video frame n=1' lands in logcat and the # screencap is non-uniform. Then kills the server # on the target and asserts the session detects the # dropped link and auto-reconnects (a fresh # 'video frame n=1'). Needs /dev/kvm readable by the # invoking uid. ./test all # unit tests followed by e2e. Fails fast: if the # unit tier fails, the e2e tier is skipped. # Same /dev/kvm requirement as `./test e2e`. ./test record # Boots the emulator, runs a Mirror session, injects # taps/keys, and stitches a screen recording into # ./output.mp4. Needs /dev/kvm. ./test apk # Smoke build: generates a throwaway PKCS12 keystore # under .tools/, runs scripts/build-apk against it, # verifies the resulting APK is signed. No KVM. If /dev/kvm is not rw for your uid the script prints the one-line fix and exits - it does not auto-chmod. How to build a release APK -------------------------- Set four environment variables and run the release script: export KEYSTORE_PATH=/abs/path/to/release.jks export KEYSTORE_PASS=... export KEY_ALIAS=... export KEY_PASS=... ./scripts/build-apk The signed APK lands at `app/build/outputs/apk/release/app-release.apk` and its sha256 is printed at the end. Build-tools 35.0.0 `apksigner` must be available on `$PATH` or under `$ANDROID_SDK_ROOT`; the script fails unless independent signature verification succeeds. Generate a release keystore once with: keytool -genkey -v -keystore release.jks -storetype PKCS12 \ -alias scrcpy-android -keyalg RSA -keysize 2048 -validity 10000 Keep it OUTSIDE the repo. The .gitignore does not protect arbitrary filenames; the keystore is your secret. How to debug it --------------- Everything is logged with tag `scrcpy-android`: adb logcat -s scrcpy-android Expected log sequence on a successful connect (pair ok appears once, at pairing time): pair ok host=... pair_port=... adb connect ok push /data/local/tmp/scrcpy-server.jar bytes=... spawn server ver= scid= openAbstract scrcpy_ ok (x3) video meta codec=h264 audio meta codec=raw (or opus if selected in Settings) video session meta x client_resize=false video frame n=1 Server stdout/stderr is pumped into the same log stream prefixed with `server: ` so target-side errors are visible without an extra adb shell. Security and privacy -------------------- Pairing creates a software-backed ADB key under the app's private files directory. Revoke it from the target's Wireless debugging screen or clear the app's data. Clipboard synchronization is automatic in both directions while a session is connected; do not mirror an untrusted target while sensitive clipboard contents are present. Recordings and crash logs are written under the app-specific external files directory and are removed when the app is uninstalled. They may contain screen contents, entered text, addresses, and diagnostic data. The continuous foreground service is classified as `mediaPlayback` because it presents a live remote audio/video stream. This avoids Android 15's six-hour `dataSync` foreground-service timeout and adds no runtime permission prompt. Layout ------ app/ Android module (Java) vendor/libadb-android/ vendored libadb module, pinned tag, plus licenses vendor/scrcpy git submodule pinned at the scrcpy release the server jar comes from; F-Droid builds the jar from it. Not needed for local builds. fastlane/ F-Droid store metadata (description, icon, screenshots) scripts/ POSIX sh, set -eu gradle/, gradlew* Gradle 8.9 wrapper LICENSE Apache-2.0 Constraints ----------- - minSdk 31 (source device needs Android 12+); compileSdk = targetSdk = 35 (built against and targeting Android 15) - Wireless ADB only; no USB-OTG host mode - Multi-touch, soft keys, clipboard sync, and hardware Back (long-press the device Back key while Mirror is foreground) are forwarded. - Video codec (h264 / h265 / av1) and audio codec (raw / opus) are selectable from the in-app Settings screen; defaults are h264 + raw. License ------- Apache-2.0; see LICENSE. Bundled dependency licenses, source locations, and the LGPL replacement notice for SPAKE2 are recorded in `app/src/main/assets/THIRD_PARTY_NOTICES`, which is shipped in the APK.