diff options
Diffstat (limited to 'app/src')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Devices.java | 44 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Main.java | 58 | ||||
| -rw-r--r-- | app/src/main/res/layout/main.xml | 43 | ||||
| -rw-r--r-- | app/src/main/res/values/strings.xml | 13 | ||||
| -rw-r--r-- | app/src/test/java/invalid/lena/scrcpy/DevicesTest.java | 42 |
5 files changed, 134 insertions, 66 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Devices.java b/app/src/main/java/invalid/lena/scrcpy/Devices.java index c3e5141..0ba61a3 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Devices.java +++ b/app/src/main/java/invalid/lena/scrcpy/Devices.java @@ -30,9 +30,13 @@ public final class Devices { this.port = port; } + // Bracket IPv6 literals so the port stays unambiguous. Round-trips + // through parseAddress(). @Override public String toString() { - return host + ":" + port; + return host.indexOf(':') < 0 + ? host + ":" + port + : "[" + host + "]:" + port; } @Override @@ -49,6 +53,44 @@ public final class Devices { private Devices() {} + // Parse "host:port" exactly as the target's Wireless debugging screen + // prints it. IPv6 literals must be bracketed ("[fe80::1]:5555") because + // the address itself contains colons; an unbracketed one is rejected + // rather than silently split at the wrong colon. Returns null on + // anything malformed - the only caller is a text field. + public static Device parseAddress(String s) { + if (s == null) return null; + s = s.trim(); + String host, port; + if (s.startsWith("[")) { + int end = s.indexOf(']'); + if (end < 0 || !s.startsWith("]:", end)) return null; + host = s.substring(1, end); + port = s.substring(end + 2); + } else { + int colon = s.indexOf(':'); + if (colon < 0 || colon != s.lastIndexOf(':')) return null; + host = s.substring(0, colon); + port = s.substring(colon + 1); + } + int p = parsePort(port); + if (host.isEmpty() || p < 0) return null; + return new Device(host, p); + } + + // Digits only, 1-65535. Returns -1 if it is not a usable port. + // Integer.parseInt() alone would accept "+5555" and " 5555". + public static int parsePort(String s) { + if (s == null) return -1; + s = s.trim(); + if (s.isEmpty() || s.length() > 5) return -1; + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) < '0' || s.charAt(i) > '9') return -1; + } + int p = Integer.parseInt(s); + return p >= 1 && p <= 65535 ? p : -1; + } + // Pure-java parse: returns whatever rows are well-formed; logs and // skips anything malformed instead of nuking the list. public static List<Device> parse(String json) { diff --git a/app/src/main/java/invalid/lena/scrcpy/Main.java b/app/src/main/java/invalid/lena/scrcpy/Main.java index eb8d400..28a0870 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Main.java +++ b/app/src/main/java/invalid/lena/scrcpy/Main.java @@ -18,12 +18,14 @@ import java.util.List; // Pairing form + saved-device list. // -// Layout: host, pair-port, pair-code, connect-port + a single "Pair and save" -// button. After a successful pair() against the daemon, the row is appended -// to devices.json with the *connect* port (different from the pair port on -// Android 11+). Tapping a saved row launches the Mirror activity with the -// target host/port; Mirror owns its own Adb instance loaded from the same -// on-disk keypair. +// Layout: device address ("ip:port"), pair-port, pair-code + a single +// "Pair and save" button. The address is the connect endpoint printed on +// the target's Wireless debugging screen; pairing happens on a different, +// short-lived port from the pairing dialog, against the same host. After a +// successful pair() against the daemon, the row is appended to devices.json +// with the *connect* port. Tapping a saved row launches the Mirror activity +// with the target host/port; Mirror owns its own Adb instance loaded from +// the same on-disk keypair. public final class Main extends Activity { private volatile Adb adb; @@ -41,10 +43,9 @@ public final class Main extends Activity { Ui.padForInsets(findViewById(R.id.root), WindowInsets.Type.systemBars() | WindowInsets.Type.ime()); - EditText host = findViewById(R.id.host); + EditText address = findViewById(R.id.device_address); EditText pairPort = findViewById(R.id.pair_port); EditText pairCode = findViewById(R.id.pair_code); - EditText connectPort = findViewById(R.id.connect_port); pairButton = findViewById(R.id.pair); View settingsBtn = findViewById(R.id.settings); ListView devices = findViewById(R.id.devices); @@ -89,7 +90,7 @@ public final class Main extends Activity { Devices.Device d = adapter.getItem(pos); new AlertDialog.Builder(this) .setTitle(R.string.forget_device) - .setMessage(d.host + ":" + d.port) + .setMessage(d.toString()) .setPositiveButton(android.R.string.ok, (dlg, w) -> { Log.i("forget device: %s", d); try { @@ -110,41 +111,36 @@ public final class Main extends Activity { pairButton.setOnClickListener(v -> { if (adb == null) return; // still initialising - String h = host.getText().toString().trim(); - String pp = pairPort.getText().toString().trim(); - String pc = pairCode.getText().toString().trim(); - String cp = connectPort.getText().toString().trim(); - if (TextUtils.isEmpty(h) || TextUtils.isEmpty(pp) - || TextUtils.isEmpty(pc) || TextUtils.isEmpty(cp)) { - Toast.makeText(this, "fill all four fields", Toast.LENGTH_SHORT).show(); + // The saved endpoint is the address field verbatim; pairing + // reuses its host with the pairing port. + Devices.Device target = Devices.parseAddress(address.getText().toString()); + if (target == null) { + Toast.makeText(this, R.string.bad_address, Toast.LENGTH_LONG).show(); return; } - int pairP, connP; - try { - pairP = Integer.parseInt(pp); - connP = Integer.parseInt(cp); - } catch (NumberFormatException e) { - Toast.makeText(this, "ports must be numeric", Toast.LENGTH_SHORT).show(); + int pairP = Devices.parsePort(pairPort.getText().toString()); + if (pairP < 0) { + Toast.makeText(this, R.string.bad_pair_port, Toast.LENGTH_LONG).show(); return; } - if (pairP < 1 || pairP > 65535 || connP < 1 || connP > 65535) { - Toast.makeText(this, "ports must be 1-65535", Toast.LENGTH_SHORT).show(); + String pc = pairCode.getText().toString().trim(); + if (TextUtils.isEmpty(pc)) { + Toast.makeText(this, R.string.bad_pair_code, Toast.LENGTH_LONG).show(); return; } pairButton.setEnabled(false); Toast.makeText(this, R.string.pairing, Toast.LENGTH_SHORT).show(); - new Thread(() -> pairAndSave(h, pairP, pc, connP, pairButton), "pair").start(); + new Thread(() -> pairAndSave(target, pairP, pc, pairButton), "pair").start(); }); } - private void pairAndSave(String host, int pairPort, String code, int connPort, Button btn) { + private void pairAndSave(Devices.Device target, int pairPort, String code, Button btn) { try { - Log.i("pair: %s:%d", host, pairPort); - boolean ok = adb.pair(host, pairPort, code); + Log.i("pair: %s:%d", target.host, pairPort); + boolean ok = adb.pair(target.host, pairPort, code); if (!ok) throw new IllegalStateException("pair returned false"); - Log.i("pair ok host=%s pair_port=%d", host, pairPort); - Devices.Device d = new Devices.Device(host, connPort); - List<Devices.Device> updated = Devices.upsert(this, d); + Log.i("pair ok host=%s pair_port=%d", target.host, pairPort); + List<Devices.Device> updated = Devices.upsert(this, target); runOnUiThread(() -> { adapter.clear(); adapter.addAll(updated); diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 2e722b0..0b1273b 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -44,8 +44,10 @@ </LinearLayout> - <!-- Pairing form. Fields are grouped and labelled by where their - value comes from on the target's Wireless debugging screen. --> + <!-- Pairing form. Each field is one value the target already prints, + copied verbatim: the Wireless debugging screen shows the device + address as a single "ip:port" string, and the pairing dialog adds + its own port plus the code. --> <LinearLayout style="@style/Card" android:layout_width="match_parent" @@ -67,15 +69,22 @@ style="@style/FieldLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/label_host"/> + android:text="@string/label_address"/> + + <TextView + style="@style/Caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/sub_address"/> <EditText - android:id="@+id/host" + android:id="@+id/device_address" style="@style/Field" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/host_hint" - android:inputType="text" + android:layout_marginTop="@dimen/space_xs" + android:hint="@string/address_hint" + android:inputType="textUri" android:autofillHints="postalAddress"/> <TextView @@ -120,28 +129,6 @@ </LinearLayout> - <TextView - style="@style/FieldLabel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/label_connect"/> - - <TextView - style="@style/Caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/sub_connect"/> - - <EditText - android:id="@+id/connect_port" - style="@style/Field" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/space_xs" - android:hint="@string/connect_port_hint" - android:inputType="number" - android:autofillHints="off"/> - <Button android:id="@+id/pair" style="@style/Button.Primary" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a9a552c..d7c45ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,16 +1,17 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">scrcpy</string> - <string name="host_hint">192.168.1.42</string> + <string name="address_hint">192.168.1.42:41234</string> <string name="pair_port_hint">37123</string> <string name="pair_code_hint">6 digits</string> - <string name="connect_port_hint">41234</string> <string name="pair_help">On the target: Settings > Developer options > Wireless debugging</string> - <string name="label_host">Host / IP address</string> + <string name="label_address">Device address</string> + <string name="sub_address">The “IP address & Port” on the Wireless debugging screen</string> <string name="label_pairing">Pairing port and code</string> - <string name="sub_pairing">From the “Pair device with pairing code” dialog</string> - <string name="label_connect">Connection port</string> - <string name="sub_connect">Shown on the Wireless debugging screen</string> + <string name="sub_pairing">From the “Pair device with pairing code” dialog. Same IP, different port</string> + <string name="bad_address">Enter the device address as 192.168.1.42:41234</string> + <string name="bad_pair_port">Pairing port must be a number from 1 to 65535</string> + <string name="bad_pair_code">Enter the 6-digit pairing code</string> <string name="pair_and_save">Pair and save</string> <string name="pair_a_device">Pair a device</string> <string name="saved_devices">Saved devices</string> diff --git a/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java b/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java index d1e4f02..dcd04ca 100644 --- a/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java +++ b/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java @@ -1,6 +1,7 @@ package invalid.lena.scrcpy; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -53,6 +54,47 @@ public class DevicesTest { } @Test + public void parseAddressSplitsHostAndPort() { + Devices.Device d = Devices.parseAddress(" 192.168.1.42:41234 "); + assertEquals("192.168.1.42", d.host); + assertEquals(41234, d.port); + + Devices.Device v6 = Devices.parseAddress("[fe80::1]:5555"); + assertEquals("fe80::1", v6.host); + assertEquals(5555, v6.port); + // Round-trips through the display form. + assertEquals("[fe80::1]:5555", v6.toString()); + } + + @Test + public void parseAddressRejectsMalformed() { + // No port, empty host, non-numeric and out-of-range ports. + assertNull(Devices.parseAddress(null)); + assertNull(Devices.parseAddress("")); + assertNull(Devices.parseAddress("192.168.1.42")); + assertNull(Devices.parseAddress(":5555")); + assertNull(Devices.parseAddress("192.168.1.42:")); + assertNull(Devices.parseAddress("192.168.1.42:port")); + assertNull(Devices.parseAddress("192.168.1.42:0")); + assertNull(Devices.parseAddress("192.168.1.42:65536")); + // Unbracketed IPv6 would otherwise split at the wrong colon. + assertNull(Devices.parseAddress("fe80::1:5555")); + assertNull(Devices.parseAddress("[fe80::1]5555")); + } + + @Test + public void parsePortRejectsNonDigits() { + assertEquals(5555, Devices.parsePort(" 5555 ")); + assertEquals(1, Devices.parsePort("1")); + assertEquals(65535, Devices.parsePort("65535")); + assertEquals(-1, Devices.parsePort("")); + assertEquals(-1, Devices.parsePort("+5555")); + assertEquals(-1, Devices.parsePort("-1")); + assertEquals(-1, Devices.parsePort("55x5")); + assertEquals(-1, Devices.parsePort("655360")); + } + + @Test public void deviceEqualsByHostAndPort() { Devices.Device a = new Devices.Device("1.1.1.1", 5555); Devices.Device b = new Devices.Device("1.1.1.1", 5555); |