/* 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/. */ "use strict"; const { clearInterval, setInterval } = ChromeUtils.importESModule( "resource://gre/modules/Timer.sys.mjs" ); const EventEmitter = require("resource://devtools/shared/event-emitter.js"); const { adbProcess, } = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js"); const { adbAddon, } = require("resource://devtools/client/shared/remote-debugging/adb/adb-addon.js"); const AdbDevice = require("resource://devtools/client/shared/remote-debugging/adb/adb-device.js"); const { AdbRuntime, } = require("resource://devtools/client/shared/remote-debugging/adb/adb-runtime.js"); const { TrackDevicesCommand, } = require("resource://devtools/client/shared/remote-debugging/adb/commands/track-devices.js"); loader.lazyRequireGetter( this, "check", "resource://devtools/client/shared/remote-debugging/adb/adb-running-checker.js", true ); // Duration in milliseconds of the runtime polling. We resort to polling here because we // have no event to know when a runtime started on an already discovered ADB device. const UPDATE_RUNTIMES_INTERVAL = 3000; class Adb extends EventEmitter { constructor() { super(); this._trackDevicesCommand = new TrackDevicesCommand(); this._isTrackingDevices = false; this._isUpdatingRuntimes = false; this._listeners = new Set(); this._devices = new Map(); this._runtimes = []; this._updateAdbProcess = this._updateAdbProcess.bind(this); this._onDeviceConnected = this._onDeviceConnected.bind(this); this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this); this._onNoDevicesDetected = this._onNoDevicesDetected.bind(this); this._trackDevicesCommand.on("device-connected", this._onDeviceConnected); this._trackDevicesCommand.on( "device-disconnected", this._onDeviceDisconnected ); this._trackDevicesCommand.on( "no-devices-detected", this._onNoDevicesDetected ); adbAddon.on("update", this._updateAdbProcess); } registerListener(listener) { this._listeners.add(listener); this.on("runtime-list-updated", listener); this._updateAdbProcess(); } unregisterListener(listener) { this._listeners.delete(listener); this.off("runtime-list-updated", listener); this._updateAdbProcess(); } async updateRuntimes() { try { const devices = [...this._devices.values()]; const promises = devices.map(d => this._getDeviceRuntimes(d)); const allRuntimes = await Promise.all(promises); this._runtimes = allRuntimes.flat(); this.emit("runtime-list-updated"); } catch (e) { console.error(e); } } getRuntimes() { return this._runtimes; } getDevices() { return [...this._devices.values()]; } async isProcessStarted() { return check(); } async _startTracking() { this._isTrackingDevices = true; await adbProcess.start(); this._trackDevicesCommand.run(); // Device runtimes are detected by running a shell command and checking for // "firefox-debugger-socket" in the list of currently running processes. this._timer = setInterval( this.updateRuntimes.bind(this), UPDATE_RUNTIMES_INTERVAL ); } async _stopTracking() { clearInterval(this._timer); this._isTrackingDevices = false; this._trackDevicesCommand.stop(); this._devices = new Map(); this._runtimes = []; this.updateRuntimes(); } _shouldTrack() { return adbAddon.status === "installed" && this._listeners.size > 0; } /** * This method will emit "runtime-list-ready" to notify the consumer that the list of * runtimes is ready to be retrieved. */ async _updateAdbProcess() { if (!this._isTrackingDevices && this._shouldTrack()) { const onRuntimesUpdated = this.once("runtime-list-updated"); this._startTracking(); // If we are starting to track runtimes, the list of runtimes will only be ready // once the first "runtime-list-updated" event has been processed. await onRuntimesUpdated; } else if (this._isTrackingDevices && !this._shouldTrack()) { this._stopTracking(); } this.emit("runtime-list-ready"); } async _onDeviceConnected(deviceId) { const adbDevice = new AdbDevice(deviceId); await adbDevice.initialize(); this._devices.set(deviceId, adbDevice); this.updateRuntimes(); } _onDeviceDisconnected(deviceId) { this._devices.delete(deviceId); this.updateRuntimes(); } _onNoDevicesDetected() { this.updateRuntimes(); } async _getDeviceRuntimes(device) { const socketPaths = [...(await device.getRuntimeSocketPaths())]; const runtimes = []; for (const socketPath of socketPaths) { const runtime = new AdbRuntime(device, socketPath); await runtime.init(); runtimes.push(runtime); } return runtimes; } } exports.adb = new Adb();