aboutsummaryrefslogtreecommitdiff
path: root/README
blob: a58e244fd6db4988d257b8bcc7f4899c68c4d1ed (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
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=<v> scid=<hex>
  openAbstract scrcpy_<scid> ok (attempt <n>)   (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.


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).