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. Java only, no Kotlin, no NDK. ADB stack vendored from MuntashirAkon/libadb-android. scrcpy-server.jar is a pinned upstream binary in `app/src/main/assets/`. How to run it ------------- Prerequisites: Android Studio (Iguana or newer) or JDK 17 + Android SDK with platform-tools and platform 35. ./scripts/fetch-vendor # pull libadb-android into vendor/ ./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 For a signed release APK, see "How to build a release APK" below. On the target device: Settings -> Developer options -> Wireless debugging -> Pair device with pairing code. Note the displayed ip:port and 6-digit code. In the app on the source device: enter ip:port and the pairing code, tap Pair. Then tap the saved device row to connect and mirror. How to test it -------------- A `./test` entrypoint runs tests inside a project-local Docker image (JDK 17, Android cmdline-tools, platform 35, build-tools 35, x86_64 emulator). The image is built on first run 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. If `apksigner` is reachable (either on $PATH or under $ANDROID_SDK_ROOT/build-tools/) the script also runs a post-build `apksigner verify`. 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 (attempt ) (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. Layout ------ app/ Android module (Java) vendor/libadb-android/ vendored libadb module, pinned tag, plus licenses 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. Bundles libadb-android (dual GPL-3.0-or-later OR Apache-2.0, used here under Apache-2.0) and a pinned, checksum-verified scrcpy-server.jar from Genymobile/scrcpy (Apache-2.0).