/* 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();