adb.js (5118B)
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 "use strict"; 6 7 const { clearInterval, setInterval } = ChromeUtils.importESModule( 8 "resource://gre/modules/Timer.sys.mjs" 9 ); 10 11 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 12 const { 13 adbProcess, 14 } = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js"); 15 const { 16 adbAddon, 17 } = require("resource://devtools/client/shared/remote-debugging/adb/adb-addon.js"); 18 const AdbDevice = require("resource://devtools/client/shared/remote-debugging/adb/adb-device.js"); 19 const { 20 AdbRuntime, 21 } = require("resource://devtools/client/shared/remote-debugging/adb/adb-runtime.js"); 22 const { 23 TrackDevicesCommand, 24 } = require("resource://devtools/client/shared/remote-debugging/adb/commands/track-devices.js"); 25 loader.lazyRequireGetter( 26 this, 27 "check", 28 "resource://devtools/client/shared/remote-debugging/adb/adb-running-checker.js", 29 true 30 ); 31 32 // Duration in milliseconds of the runtime polling. We resort to polling here because we 33 // have no event to know when a runtime started on an already discovered ADB device. 34 const UPDATE_RUNTIMES_INTERVAL = 3000; 35 36 class Adb extends EventEmitter { 37 constructor() { 38 super(); 39 40 this._trackDevicesCommand = new TrackDevicesCommand(); 41 42 this._isTrackingDevices = false; 43 this._isUpdatingRuntimes = false; 44 45 this._listeners = new Set(); 46 this._devices = new Map(); 47 this._runtimes = []; 48 49 this._updateAdbProcess = this._updateAdbProcess.bind(this); 50 this._onDeviceConnected = this._onDeviceConnected.bind(this); 51 this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this); 52 this._onNoDevicesDetected = this._onNoDevicesDetected.bind(this); 53 54 this._trackDevicesCommand.on("device-connected", this._onDeviceConnected); 55 this._trackDevicesCommand.on( 56 "device-disconnected", 57 this._onDeviceDisconnected 58 ); 59 this._trackDevicesCommand.on( 60 "no-devices-detected", 61 this._onNoDevicesDetected 62 ); 63 adbAddon.on("update", this._updateAdbProcess); 64 } 65 66 registerListener(listener) { 67 this._listeners.add(listener); 68 this.on("runtime-list-updated", listener); 69 this._updateAdbProcess(); 70 } 71 72 unregisterListener(listener) { 73 this._listeners.delete(listener); 74 this.off("runtime-list-updated", listener); 75 this._updateAdbProcess(); 76 } 77 78 async updateRuntimes() { 79 try { 80 const devices = [...this._devices.values()]; 81 const promises = devices.map(d => this._getDeviceRuntimes(d)); 82 const allRuntimes = await Promise.all(promises); 83 84 this._runtimes = allRuntimes.flat(); 85 this.emit("runtime-list-updated"); 86 } catch (e) { 87 console.error(e); 88 } 89 } 90 91 getRuntimes() { 92 return this._runtimes; 93 } 94 95 getDevices() { 96 return [...this._devices.values()]; 97 } 98 99 async isProcessStarted() { 100 return check(); 101 } 102 103 async _startTracking() { 104 this._isTrackingDevices = true; 105 await adbProcess.start(); 106 107 this._trackDevicesCommand.run(); 108 109 // Device runtimes are detected by running a shell command and checking for 110 // "firefox-debugger-socket" in the list of currently running processes. 111 this._timer = setInterval( 112 this.updateRuntimes.bind(this), 113 UPDATE_RUNTIMES_INTERVAL 114 ); 115 } 116 117 async _stopTracking() { 118 clearInterval(this._timer); 119 this._isTrackingDevices = false; 120 this._trackDevicesCommand.stop(); 121 122 this._devices = new Map(); 123 this._runtimes = []; 124 this.updateRuntimes(); 125 } 126 127 _shouldTrack() { 128 return adbAddon.status === "installed" && this._listeners.size > 0; 129 } 130 131 /** 132 * This method will emit "runtime-list-ready" to notify the consumer that the list of 133 * runtimes is ready to be retrieved. 134 */ 135 async _updateAdbProcess() { 136 if (!this._isTrackingDevices && this._shouldTrack()) { 137 const onRuntimesUpdated = this.once("runtime-list-updated"); 138 this._startTracking(); 139 // If we are starting to track runtimes, the list of runtimes will only be ready 140 // once the first "runtime-list-updated" event has been processed. 141 await onRuntimesUpdated; 142 } else if (this._isTrackingDevices && !this._shouldTrack()) { 143 this._stopTracking(); 144 } 145 this.emit("runtime-list-ready"); 146 } 147 148 async _onDeviceConnected(deviceId) { 149 const adbDevice = new AdbDevice(deviceId); 150 await adbDevice.initialize(); 151 this._devices.set(deviceId, adbDevice); 152 this.updateRuntimes(); 153 } 154 155 _onDeviceDisconnected(deviceId) { 156 this._devices.delete(deviceId); 157 this.updateRuntimes(); 158 } 159 160 _onNoDevicesDetected() { 161 this.updateRuntimes(); 162 } 163 164 async _getDeviceRuntimes(device) { 165 const socketPaths = [...(await device.getRuntimeSocketPaths())]; 166 const runtimes = []; 167 for (const socketPath of socketPaths) { 168 const runtime = new AdbRuntime(device, socketPath); 169 await runtime.init(); 170 runtimes.push(runtime); 171 } 172 return runtimes; 173 } 174 } 175 176 exports.adb = new Adb();