summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/remote-debugging/adb/commands/track-devices.js
blob: 2d796668ea43302ea9fce442df36e4cb4a09b25d (plain)
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Wrapper around the ADB utility.

"use strict";

const EventEmitter = require("resource://devtools/shared/event-emitter.js");
const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js");
const { setTimeout } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);

const {
  adbProcess,
} = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js");
const client = require("resource://devtools/client/shared/remote-debugging/adb/adb-client.js");

const ADB_STATUS_OFFLINE = "offline";
const OKAY = 0x59414b4f;

// Start tracking devices connecting and disconnecting from the host.
// We can't reuse runCommand here because we keep the socket alive.
class TrackDevicesCommand extends EventEmitter {
  run() {
    this._waitForFirst = true;
    // Hold device statuses. key: device id, value: status.
    this._devices = new Map();
    this._socket = client.connect();

    this._socket.s.onopen = this._onOpen.bind(this);
    this._socket.s.onerror = this._onError.bind(this);
    this._socket.s.onclose = this._onClose.bind(this);
    this._socket.s.ondata = this._onData.bind(this);
  }

  stop() {
    if (this._socket) {
      this._socket.close();

      this._socket.s.onopen = null;
      this._socket.s.onerror = null;
      this._socket.s.onclose = null;
      this._socket.s.ondata = null;
    }
  }

  _onOpen() {
    dumpn("trackDevices onopen");
    const req = client.createRequest("host:track-devices");
    this._socket.send(req);
  }

  _onError(event) {
    dumpn("trackDevices onerror: " + event);
  }

  _onClose() {
    dumpn("trackDevices onclose");

    // Report all devices as disconnected
    this._disconnectAllDevices();

    // When we lose connection to the server,
    // and the adb is still on, we most likely got our server killed
    // by local adb. So we do try to reconnect to it.

    // Give some time to the new adb to start
    setTimeout(() => {
      // Only try to reconnect/restart if the add-on is still enabled
      if (adbProcess.ready) {
        // try to connect to the new local adb server or spawn a new one
        adbProcess.start().then(() => {
          // Re-track devices
          this.run();
        });
      }
    }, 2000);
  }

  _onData(event) {
    dumpn("trackDevices ondata");
    const data = event.data;
    dumpn("length=" + data.byteLength);
    const dec = new TextDecoder();
    dumpn(dec.decode(new Uint8Array(data)).trim());

    // check the OKAY or FAIL on first packet.
    if (this._waitForFirst) {
      if (!client.checkResponse(data, OKAY)) {
        this._socket.close();
        return;
      }
    }

    const packet = client.unpackPacket(data, !this._waitForFirst);
    this._waitForFirst = false;

    if (packet.data == "") {
      // All devices got disconnected.
      this._disconnectAllDevices();
    } else {
      // One line per device, each line being $DEVICE\t(offline|device)
      const lines = packet.data.split("\n");
      const newDevices = new Map();
      lines.forEach(function (line) {
        if (!line.length) {
          return;
        }

        const [deviceId, status] = line.split("\t");
        newDevices.set(deviceId, status);
      });

      // Fire events if needed.
      const deviceIds = new Set([
        ...this._devices.keys(),
        ...newDevices.keys(),
      ]);
      for (const deviceId of deviceIds) {
        const currentStatus = this._devices.get(deviceId);
        const newStatus = newDevices.get(deviceId);
        this._fireConnectionEventIfNeeded(deviceId, currentStatus, newStatus);
      }

      // Update devices.
      this._devices = newDevices;
    }
  }

  _disconnectAllDevices() {
    if (this._devices.size === 0) {
      // If no devices were detected, fire an event to let consumer resume.
      this.emit("no-devices-detected");
    } else {
      for (const [deviceId, status] of this._devices.entries()) {
        if (status !== ADB_STATUS_OFFLINE) {
          this.emit("device-disconnected", deviceId);
        }
      }
    }
    this._devices = new Map();
  }

  _fireConnectionEventIfNeeded(deviceId, currentStatus, newStatus) {
    const isCurrentOnline = !!(
      currentStatus && currentStatus !== ADB_STATUS_OFFLINE
    );
    const isNewOnline = !!(newStatus && newStatus !== ADB_STATUS_OFFLINE);

    if (isCurrentOnline === isNewOnline) {
      return;
    }

    if (isNewOnline) {
      this.emit("device-connected", deviceId);
    } else {
      this.emit("device-disconnected", deviceId);
    }
  }
}
exports.TrackDevicesCommand = TrackDevicesCommand;