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 parse(String json) { List 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 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 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 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 upsert(Context ctx, Device d) throws IOException { List 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 remove(Context ctx, Device d) throws IOException { List list = load(ctx); list.removeIf(d::equals); save(ctx, list); return list; } }