aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/Devices.java
blob: 2883025f308c0682cbe5f4d4d1fc228089853c36 (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
package invalid.lena.scrcpy;

import android.content.Context;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.File;
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) {
        File f = new File(ctx.getFilesDir(), FILE);
        try {
            AtomicFiles.write(f, serialize(devices).getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            Log.e(e, "devices: save failed");
        }
    }

    // Add or replace by host+port. Context-rooted; the in-place helper
    // is the test seam.
    public static List<Device> upsert(Context ctx, Device d) {
        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 List<Device> remove(Context ctx, Device d) {
        List<Device> list = load(ctx);
        list.removeIf(d::equals);
        save(ctx, list);
        return list;
    }
}