aboutsummaryrefslogtreecommitdiff
path: root/README
blob: 707f1f64cdcb0182818f1e1da036aef7df39db6c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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.


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.