aboutsummaryrefslogtreecommitdiff
path: root/README
diff options
context:
space:
mode:
Diffstat (limited to 'README')
-rw-r--r--README335
1 files changed, 153 insertions, 182 deletions
diff --git a/README b/README
index 11de229..707f1f6 100644
--- a/README
+++ b/README
@@ -1,197 +1,168 @@
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.
-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.
+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
-How to run it
+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
-------------
-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
+`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 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=<sha256>`; normal builds verify the tracked upstream
-binary checksum.
+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:
-For a signed release APK, see "How to build a release APK" below.
+ 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
-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 screenshots
- # Boots a pixel_6 AVD (natively 1080x2400), captures
- # Main and Settings, and overwrites the tracked PNGs
- # under fastlane/metadata/android/en-US/images/
- # phoneScreenshots. Refuses to write a shot unless the
- # app owns the focused window and the PNG has the
- # expected geometry. Review the diff before
- # committing. 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=<v> scid=<hex>
- openAbstract scrcpy_<scid> ok (x3)
- video meta codec=h264
- audio meta codec=raw (or opus if selected in Settings)
- video session meta <w>x<h> 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.
+Vendor updates are manual because the ADB transport carries overlapping local
+security and flow-control changes. See `vendor/libadb-android/README.md`.
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.
+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.