aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/Sync.java
diff options
context:
space:
mode:
authorLena <lena@omega>2026-01-01 00:00:00 +0000
committerLena <lena@omega>2026-06-24 22:14:50 +0300
commiteb0c8951196c637e44daf3c0617b131b997d5d2c (patch)
tree2f83b6a41cee745467e53fc401942b82cea286ea /app/src/main/java/invalid/lena/scrcpy/Sync.java
downloadscrcpy-android-eb0c8951196c637e44daf3c0617b131b997d5d2c.tar.gz
scrcpy-android: mirror an Android device over wireless ADB0.1
Native Java app for Android 12+ that mirrors another Android device over wireless ADB, forwarding video, audio, touch input, and clipboard. Bundles a pinned scrcpy-server.jar and the vendored libadb-android stack. Supports h264/h265/av1 video and raw/opus audio with in-app codec selection. No NDK, no Kotlin. Includes JVM unit tests and a Docker-based emulator e2e rig.
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Sync.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/Sync.java78
1 files changed, 78 insertions, 0 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Sync.java b/app/src/main/java/invalid/lena/scrcpy/Sync.java
new file mode 100644
index 0000000..6e37523
--- /dev/null
+++ b/app/src/main/java/invalid/lena/scrcpy/Sync.java
@@ -0,0 +1,78 @@
+package invalid.lena.scrcpy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+// Pure-java adb sync v1 SEND framing. Extracted from Server.push() so
+// it can be unit-tested without an AdbStream or any android coupling.
+//
+// Layout on the wire (little-endian uint32 lengths):
+// "SEND" | u32(len(path,mode)) | path,mode
+// "DATA" | u32(chunk_size) | chunk_bytes (repeats, chunk <= 64K)
+// "DONE" | u32(mtime_seconds)
+// ----> response:
+// "OKAY" | u32(0)
+// or
+// "FAIL" | u32(msg_len) | msg
+public final class Sync {
+
+ public static final int CHUNK = 64 * 1024;
+
+ // FAIL responses carry a short human-readable message; cap what a
+ // corrupt or hostile length field can make us allocate.
+ private static final int MAX_FAIL_MSG = 4 * 1024;
+
+ private Sync() {}
+
+ // Push `src` to `remotePath` with the given mode and mtime, using the
+ // already-open `out` and `in` of an adb sync stream. Returns the
+ // total payload byte count.
+ public static long push(InputStream src, OutputStream out, InputStream in,
+ String remotePath, int mode, int mtimeSec)
+ throws IOException {
+ String header = remotePath + "," + mode;
+ byte[] hb = header.getBytes(StandardCharsets.UTF_8);
+
+ byte[] tag = new byte[8];
+ putTag(tag, 0, "SEND");
+ Wire.writeLe32(tag, 4, hb.length);
+ out.write(tag);
+ out.write(hb);
+
+ byte[] chunk = new byte[CHUNK];
+ byte[] dataHdr = new byte[8];
+ putTag(dataHdr, 0, "DATA");
+ long total = 0;
+ int n;
+ while ((n = src.read(chunk)) > 0) {
+ Wire.writeLe32(dataHdr, 4, n);
+ out.write(dataHdr);
+ out.write(chunk, 0, n);
+ total += n;
+ }
+
+ byte[] done = new byte[8];
+ putTag(done, 0, "DONE");
+ Wire.writeLe32(done, 4, mtimeSec);
+ out.write(done);
+ out.flush();
+
+ byte[] resp = new byte[8];
+ Wire.readFully(in, resp);
+ String code = new String(resp, 0, 4, StandardCharsets.US_ASCII);
+ int len = Wire.readLe32(resp, 4);
+ if ("OKAY".equals(code)) return total;
+
+ byte[] msg = new byte[Math.min(Math.max(0, len), MAX_FAIL_MSG)];
+ if (msg.length > 0) Wire.readFully(in, msg);
+ throw new IOException("sync " + code + ": "
+ + new String(msg, StandardCharsets.UTF_8));
+ }
+
+ private static void putTag(byte[] dst, int off, String tag) {
+ byte[] b = tag.getBytes(StandardCharsets.US_ASCII);
+ System.arraycopy(b, 0, dst, off, 4);
+ }
+}