track-devices.js (5068B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // Wrapper around the ADB utility. 6 7 "use strict"; 8 9 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 10 const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); 11 const { setTimeout } = ChromeUtils.importESModule( 12 "resource://gre/modules/Timer.sys.mjs" 13 ); 14 15 const { 16 adbProcess, 17 } = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js"); 18 const client = require("resource://devtools/client/shared/remote-debugging/adb/adb-client.js"); 19 20 // All states considered as valid online states for ADB. 21 // Per https://android.googlesource.com/platform/packages/modules/adb/+/a8ab5ceccb41035804ad83bfa625adac4a8ccf83/adb.h#127 22 const ONLINE_STATES = new Set([ 23 "bootloader", 24 "device", 25 "host", 26 "recovery", 27 "rescue", 28 "sideload", 29 ]); 30 31 const OKAY = 0x59414b4f; 32 33 // Start tracking devices connecting and disconnecting from the host. 34 // We can't reuse runCommand here because we keep the socket alive. 35 class TrackDevicesCommand extends EventEmitter { 36 run() { 37 this._waitForFirst = true; 38 // Hold device statuses. key: device id, value: status. 39 this._devices = new Map(); 40 this._socket = client.connect(); 41 42 this._socket.s.onopen = this._onOpen.bind(this); 43 this._socket.s.onerror = this._onError.bind(this); 44 this._socket.s.onclose = this._onClose.bind(this); 45 this._socket.s.ondata = this._onData.bind(this); 46 } 47 48 stop() { 49 if (this._socket) { 50 this._socket.close(); 51 52 this._socket.s.onopen = null; 53 this._socket.s.onerror = null; 54 this._socket.s.onclose = null; 55 this._socket.s.ondata = null; 56 } 57 } 58 59 _onOpen() { 60 dumpn("trackDevices onopen"); 61 const req = client.createRequest("host:track-devices"); 62 this._socket.send(req); 63 } 64 65 _onError(event) { 66 dumpn("trackDevices onerror: " + event); 67 } 68 69 _onClose() { 70 dumpn("trackDevices onclose"); 71 72 // Report all devices as disconnected 73 this._disconnectAllDevices(); 74 75 // When we lose connection to the server, 76 // and the adb is still on, we most likely got our server killed 77 // by local adb. So we do try to reconnect to it. 78 79 // Give some time to the new adb to start 80 setTimeout(() => { 81 // Only try to reconnect/restart if the add-on is still enabled 82 if (adbProcess.ready) { 83 // try to connect to the new local adb server or spawn a new one 84 adbProcess.start().then(() => { 85 // Re-track devices 86 this.run(); 87 }); 88 } 89 }, 2000); 90 } 91 92 _onData(event) { 93 dumpn("trackDevices ondata"); 94 const data = event.data; 95 dumpn("length=" + data.byteLength); 96 const dec = new TextDecoder(); 97 dumpn(dec.decode(new Uint8Array(data)).trim()); 98 99 // check the OKAY or FAIL on first packet. 100 if (this._waitForFirst) { 101 if (!client.checkResponse(data, OKAY)) { 102 this._socket.close(); 103 return; 104 } 105 } 106 107 const packet = client.unpackPacket(data, !this._waitForFirst); 108 this._waitForFirst = false; 109 110 if (packet.data == "") { 111 // All devices got disconnected. 112 this._disconnectAllDevices(); 113 } else { 114 // One line per device, each line being $DEVICE\t(offline|device) 115 const lines = packet.data.split("\n"); 116 const newDevices = new Map(); 117 lines.forEach(function (line) { 118 if (!line.length) { 119 return; 120 } 121 122 const [deviceId, status] = line.split("\t"); 123 newDevices.set(deviceId, status); 124 }); 125 126 // Fire events if needed. 127 const deviceIds = new Set([ 128 ...this._devices.keys(), 129 ...newDevices.keys(), 130 ]); 131 for (const deviceId of deviceIds) { 132 const currentStatus = this._devices.get(deviceId); 133 const newStatus = newDevices.get(deviceId); 134 this._fireConnectionEventIfNeeded(deviceId, currentStatus, newStatus); 135 } 136 137 // Update devices. 138 this._devices = newDevices; 139 } 140 } 141 142 _disconnectAllDevices() { 143 if (this._devices.size === 0) { 144 // If no devices were detected, fire an event to let consumer resume. 145 this.emit("no-devices-detected"); 146 } else { 147 for (const [deviceId, status] of this._devices.entries()) { 148 if (!this._isStatusOnline(status)) { 149 this.emit("device-disconnected", deviceId); 150 } 151 } 152 } 153 this._devices = new Map(); 154 } 155 156 _fireConnectionEventIfNeeded(deviceId, currentStatus, newStatus) { 157 const isCurrentOnline = this._isStatusOnline(currentStatus); 158 const isNewOnline = this._isStatusOnline(newStatus); 159 160 if (isCurrentOnline === isNewOnline) { 161 return; 162 } 163 164 if (isNewOnline) { 165 this.emit("device-connected", deviceId); 166 } else { 167 this.emit("device-disconnected", deviceId); 168 } 169 } 170 171 _isStatusOnline(status) { 172 return ONLINE_STATES.has(status); 173 } 174 } 175 exports.TrackDevicesCommand = TrackDevicesCommand;