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
|
package invalid.lena.scrcpy;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
// Tiny JSON-backed list of paired targets, stored at filesDir/devices.json.
// No SQLite, no Room. Read on demand, written whole-file on add/remove.
//
// parse() / serialize() are pure-java and android-free for unit tests.
// load() / save() add the Context-rooted file I/O.
public final class Devices {
private static final String FILE = "devices.json";
public static final class Device {
public final String host;
public final int port;
public Device(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public String toString() {
return host + ":" + port;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Device d)) return false;
return port == d.port && host.equals(d.host);
}
@Override
public int hashCode() {
return host.hashCode() * 31 + port;
}
}
private Devices() {}
// 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) {
List<Device> out = new ArrayList<>();
if (json == null) return out;
json = json.trim();
if (json.isEmpty()) return out;
JSONArray arr;
try { arr = new JSONArray(json); }
catch (Exception e) { Log.e(e, "devices: not a json array"); return out; }
for (int i = 0; i < arr.length(); i++) {
try {
JSONObject o = arr.getJSONObject(i);
out.add(new Device(o.getString("host"), o.getInt("port")));
} catch (Exception e) {
Log.w("devices: skipping malformed row %d: %s", i, e);
}
}
return out;
}
public static String serialize(List<Device> devices) {
try {
JSONArray arr = new JSONArray();
for (Device d : devices) {
JSONObject o = new JSONObject();
o.put("host", d.host);
o.put("port", d.port);
arr.put(o);
}
return arr.toString(2);
} catch (Exception e) {
Log.e(e, "devices: serialize failed");
return "[]";
}
}
public static List<Device> load(Context ctx) {
File f = new File(ctx.getFilesDir(), FILE);
if (!f.exists()) return new ArrayList<>();
try {
return parse(new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8));
} catch (Exception e) {
Log.e(e, "devices: load failed");
return new ArrayList<>();
}
}
public static void save(Context ctx, List<Device> devices) throws IOException {
File f = new File(ctx.getFilesDir(), FILE);
AtomicFiles.write(f, serialize(devices).getBytes(StandardCharsets.UTF_8));
}
// Add or replace by host+port. Context-rooted; the in-place helper
// is the test seam.
public static synchronized List<Device> upsert(Context ctx, Device d) throws IOException {
List<Device> list = load(ctx);
list.removeIf(d::equals);
list.add(d);
save(ctx, list);
return list;
}
// Remove the matching device (by host+port). Returns the updated list.
public static synchronized List<Device> remove(Context ctx, Device d) throws IOException {
List<Device> list = load(ctx);
list.removeIf(d::equals);
save(ctx, list);
return list;
}
}
|