Bug 1492700 - Split adb.js in several files;r=daisuke
authorJulian Descottes <jdescottes@mozilla.com>
Wed, 05 Dec 2018 20:47:34 +0000
changeset 508736 9d8c5a3fc14e0df0dde204aaaa6c933df43ef57b
parent 508735 cceb6f9235a1fc81f1fe2e72be3fb93cd4f5e05c
child 508737 41384e20308d690d1bd65288a1b1af9da24851d9
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaisuke
bugs1492700
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1492700 - Split adb.js in several files;r=daisuke Differential Revision: https://phabricator.services.mozilla.com/D13473
devtools/client/aboutdebugging-new/src/modules/runtime-client-factory.js
devtools/shared/adb/adb-device.js
devtools/shared/adb/adb-runtime.js
devtools/shared/adb/adb-scanner.js
devtools/shared/adb/adb.js
devtools/shared/adb/commands/index.js
devtools/shared/adb/commands/list-devices.js
devtools/shared/adb/commands/moz.build
devtools/shared/adb/commands/prepare-tcp-connection.js
devtools/shared/adb/commands/run-command.js
devtools/shared/adb/commands/shell.js
devtools/shared/adb/commands/track-devices.js
devtools/shared/adb/moz.build
devtools/shared/adb/test/test_adb.js
--- a/devtools/client/aboutdebugging-new/src/modules/runtime-client-factory.js
+++ b/devtools/client/aboutdebugging-new/src/modules/runtime-client-factory.js
@@ -1,15 +1,15 @@
 /* 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 { ADB } = require("devtools/shared/adb/adb");
+const { prepareTCPConnection } = require("devtools/shared/adb/commands/index");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const { DebuggerServer } = require("devtools/server/main");
 const { ClientWrapper } = require("./client-wrapper");
 const { remoteClientManager } =
   require("devtools/client/shared/remote-debugging/remote-client-manager");
 
 const { RUNTIMES } = require("../constants");
 
@@ -24,17 +24,17 @@ async function createLocalClient() {
 async function createNetworkClient(host, port) {
   const transport = await DebuggerClient.socketConnect({ host, port });
   const client = new DebuggerClient(transport);
   await client.connect();
   return new ClientWrapper(client);
 }
 
 async function createUSBClient(socketPath) {
-  const port = await ADB.prepareTCPConnection(socketPath);
+  const port = await prepareTCPConnection(socketPath);
   return createNetworkClient("localhost", port);
 }
 
 async function createClientForRuntime(runtime) {
   const { extra, id, type } = runtime;
 
   if (type === RUNTIMES.THIS_FIREFOX) {
     return createLocalClient();
--- a/devtools/shared/adb/adb-device.js
+++ b/devtools/shared/adb/adb-device.js
@@ -1,44 +1,44 @@
 /* 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 { ADB } = require("devtools/shared/adb/adb");
+const { shell } = require("devtools/shared/adb/commands/index");
 
 /**
  * A Device instance is created and registered with the Devices module whenever
  * ADB notices a new device is connected.
  */
 class AdbDevice {
   constructor(id) {
     this.id = id;
   }
 
   async getModel() {
     if (this._model) {
       return this._model;
     }
-    const model = await ADB.shell("getprop ro.product.model");
+    const model = await shell("getprop ro.product.model");
     this._model = model.trim();
     return this._model;
   }
 
   // This method is not using any information from the instance, but in theory getting
   // runtime socket paths (as well as model) should be device specific. So we should use
   // information available on the instance when implementing multi device support.
   // See Bug 1507126.
   async getRuntimeSocketPaths() {
     // A matching entry looks like:
     // 00000000: 00000002 00000000 00010000 0001 01 6551588
     //  /data/data/org.mozilla.fennec/firefox-debugger-socket
     const query = "cat /proc/net/unix";
-    const rawSocketInfo = await ADB.shell(query);
+    const rawSocketInfo = await shell(query);
 
     // Filter to lines with "firefox-debugger-socket"
     let socketInfos = rawSocketInfo.split(/\r?\n/);
     socketInfos = socketInfos.filter(l => l.includes("firefox-debugger-socket"));
 
     // It's possible to have multiple lines with the same path, so de-dupe them
     const socketPaths = new Set();
     for (const socketInfo of socketInfos) {
--- a/devtools/shared/adb/adb-runtime.js
+++ b/devtools/shared/adb/adb-runtime.js
@@ -1,16 +1,16 @@
 /* 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 { RuntimeTypes } = require("devtools/client/webide/modules/runtime-types");
-const { ADB } = require("devtools/shared/adb/adb");
+const { prepareTCPConnection } = require("devtools/shared/adb/commands/index");
 
 class AdbRuntime {
   constructor(adbDevice, model, socketPath) {
     this.type = RuntimeTypes.USB;
 
     this._adbDevice = adbDevice;
     this._model = model;
     this._socketPath = socketPath;
@@ -28,17 +28,17 @@ class AdbRuntime {
     return `Firefox ${this._channel()}`;
   }
 
   get name() {
     return `Firefox ${this._channel()} on Android (${this.deviceName})`;
   }
 
   connect(connection) {
-    return ADB.prepareTCPConnection(this._socketPath).then(port => {
+    return prepareTCPConnection(this._socketPath).then(port => {
       connection.host = "localhost";
       connection.port = port;
       connection.connect();
     });
   }
 
   _channel() {
     const packageName = this._packageName();
--- a/devtools/shared/adb/adb-scanner.js
+++ b/devtools/shared/adb/adb-scanner.js
@@ -2,16 +2,17 @@
  * 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 EventEmitter = require("devtools/shared/event-emitter");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
 const { ADB } = require("devtools/shared/adb/adb");
+const { trackDevices } = require("devtools/shared/adb/commands/index");
 const { adbDevicesRegistry } = require("devtools/shared/adb/adb-devices-registry");
 const { AdbRuntime } = require("devtools/shared/adb/adb-runtime");
 
 loader.lazyRequireGetter(this, "AdbDevice", "devtools/shared/adb/adb-device");
 
 class ADBScanner extends EventEmitter {
   constructor() {
     super();
@@ -25,17 +26,17 @@ class ADBScanner extends EventEmitter {
   enable() {
     EventEmitter.on(ADB, "device-connected", this._onDeviceConnected);
     EventEmitter.on(ADB, "device-disconnected", this._onDeviceDisconnected);
 
     adbDevicesRegistry.on("register", this._updateRuntimes);
     adbDevicesRegistry.on("unregister", this._updateRuntimes);
 
     ADB.start().then(() => {
-      ADB.trackDevices();
+      trackDevices();
     });
     this._updateRuntimes();
   }
 
   disable() {
     EventEmitter.off(ADB, "device-connected", this._onDeviceConnected);
     EventEmitter.off(ADB, "device-disconnected", this._onDeviceDisconnected);
     adbDevicesRegistry.off("register", this._updateRuntimes);
--- a/devtools/shared/adb/adb.js
+++ b/devtools/shared/adb/adb.js
@@ -2,31 +2,27 @@
  * 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 { Cc, Ci } = require("chrome");
-const EventEmitter = require("devtools/shared/event-emitter");
-const client = require("./adb-client");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
 const { getFileForBinary } = require("./adb-binary");
 const { setTimeout } = require("resource://gre/modules/Timer.jsm");
 const { Services } = require("resource://gre/modules/Services.jsm");
-const { ConnectionManager } = require("devtools/shared/client/connection-manager");
+const { runCommand } = require("./commands/index");
 loader.lazyRequireGetter(this, "check",
                          "devtools/shared/adb/adb-running-checker", true);
 
 let ready = false;
 let didRunInitially = false;
 
-const OKAY = 0x59414b4f;
-
 const ADB = {
   get didRunInitially() {
     return didRunInitially;
   },
   set didRunInitially(newVal) {
     didRunInitially = newVal;
   },
 
@@ -132,293 +128,19 @@ const ADB = {
     await this.kill();
   },
 
   /**
    * Kill the ADB server.
    */
   async kill() {
     try {
-      await this.runCommand("host:kill");
+      await runCommand("host:kill");
     } catch (e) {
       dumpn("Failed to send host:kill command");
     }
     dumpn("adb server was terminated by host:kill");
     this.ready = false;
     this.didRunInitially = false;
   },
-
-  // Start tracking devices connecting and disconnecting from the host.
-  // We can't reuse runCommand here because we keep the socket alive.
-  // @return The socket used.
-  trackDevices() {
-    dumpn("trackDevices");
-    const socket = client.connect();
-    let waitForFirst = true;
-    const devices = {};
-
-    socket.s.onopen = function() {
-      dumpn("trackDevices onopen");
-      Services.obs.notifyObservers(null, "adb-track-devices-start");
-      const req = client.createRequest("host:track-devices");
-      socket.send(req);
-    };
-
-    socket.s.onerror = function(event) {
-      dumpn("trackDevices onerror: " + event);
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-    };
-
-    socket.s.onclose = function() {
-      dumpn("trackDevices onclose");
-
-      // Report all devices as disconnected
-      for (const dev in devices) {
-        devices[dev] = false;
-        EventEmitter.emit(ADB, "device-disconnected", dev);
-      }
-
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-
-      // 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.
-      setTimeout(function() { // Give some time to the new adb to start
-        if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
-          ADB.start().then(function() { // try to connect to the new local adb server
-                                         // or, spawn a new one
-            ADB.trackDevices(); // Re-track devices
-          });
-        }
-      }, 2000);
-    };
-
-    socket.s.ondata = function(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 (waitForFirst) {
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          return;
-        }
-      }
-
-      const packet = client.unpackPacket(data, !waitForFirst);
-      waitForFirst = false;
-
-      if (packet.data == "") {
-        // All devices got disconnected.
-        for (const dev in devices) {
-          devices[dev] = false;
-          EventEmitter.emit(ADB, "device-disconnected", dev);
-        }
-      } else {
-        // One line per device, each line being $DEVICE\t(offline|device)
-        const lines = packet.data.split("\n");
-        const newDev = {};
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-
-          const [dev, status] = line.split("\t");
-          newDev[dev] = status !== "offline";
-        });
-        // Check which device changed state.
-        for (const dev in newDev) {
-          if (devices[dev] != newDev[dev]) {
-            if (dev in devices || newDev[dev]) {
-              const topic = newDev[dev] ? "device-connected"
-                                        : "device-disconnected";
-              EventEmitter.emit(ADB, topic, dev);
-            }
-            devices[dev] = newDev[dev];
-          }
-        }
-      }
-    };
-  },
-
-  // Sends back an array of device names.
-  listDevices() {
-    dumpn("listDevices");
-
-    return this.runCommand("host:devices").then(
-      function onSuccess(data) {
-        const lines = data.split("\n");
-        const res = [];
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-          const [ device ] = line.split("\t");
-          res.push(device);
-        });
-        return res;
-      }
-    );
-  },
-
-  // sends adb forward localPort devicePort
-  forwardPort(localPort, devicePort) {
-    dumpn("forwardPort " + localPort + " -- " + devicePort);
-    // <host-prefix>:forward:<local>;<remote>
-
-    return this.runCommand("host:forward:" + localPort + ";" + devicePort)
-               .then(function onSuccess(data) {
-                 return data;
-               });
-  },
-
-  // Prepare TCP connection for provided socket path.
-  // The returned value is a port number of localhost for the connection.
-  async prepareTCPConnection(socketPath) {
-    const port = ConnectionManager.getFreeTCPPort();
-    const local = `tcp:${ port }`;
-    const remote = socketPath.startsWith("@")
-                     ? `localabstract:${ socketPath.substring(1) }`
-                     : `localfilesystem:${ socketPath }`;
-    await this.forwardPort(local, remote);
-    return port;
-  },
-
-  // Run a shell command
-  async shell(command) {
-    let state;
-    let stdout = "";
-
-    dumpn("shell " + command);
-
-    return new Promise((resolve, reject) => {
-      const shutdown = function() {
-        dumpn("shell shutdown");
-        socket.close();
-        reject("BAD_RESPONSE");
-      };
-
-      const runFSM = function runFSM(data) {
-        dumpn("runFSM " + state);
-        let req;
-        let ignoreResponseCode = false;
-        switch (state) {
-          case "start":
-            state = "send-transport";
-            runFSM();
-            break;
-          case "send-transport":
-            req = client.createRequest("host:transport-any");
-            socket.send(req);
-            state = "wait-transport";
-            break;
-          case "wait-transport":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "send-shell";
-            runFSM();
-            break;
-          case "send-shell":
-            req = client.createRequest("shell:" + command);
-            socket.send(req);
-            state = "rec-shell";
-            break;
-          case "rec-shell":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "decode-shell";
-            if (client.getBuffer(data).byteLength == 4) {
-              break;
-            }
-            ignoreResponseCode = true;
-            // eslint-disable-next-lined no-fallthrough
-          case "decode-shell":
-            const decoder = new TextDecoder();
-            const text = new Uint8Array(client.getBuffer(data),
-                                        ignoreResponseCode ? 4 : 0);
-            stdout += decoder.decode(text);
-            break;
-          default:
-            dumpn("shell Unexpected State: " + state);
-            reject("UNEXPECTED_STATE");
-        }
-      };
-
-      const socket = client.connect();
-      socket.s.onerror = function(event) {
-        dumpn("shell onerror");
-        reject("SOCKET_ERROR");
-      };
-
-      socket.s.onopen = function(event) {
-        dumpn("shell onopen");
-        state = "start";
-        runFSM();
-      };
-
-      socket.s.onclose = function(event) {
-        resolve(stdout);
-        dumpn("shell onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("shell ondata");
-        runFSM(event.data);
-      };
-    });
-  },
-
-  // Asynchronously runs an adb command.
-  // @param command The command as documented in
-  // http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
-  runCommand(command) {
-    dumpn("runCommand " + command);
-    return new Promise((resolve, reject) => {
-      if (!this.ready) {
-        setTimeout(function() {
-          reject("ADB_NOT_READY");
-        });
-        return;
-      }
-
-      const socket = client.connect();
-
-      socket.s.onopen = function() {
-        dumpn("runCommand onopen");
-        const req = client.createRequest(command);
-        socket.send(req);
-      };
-
-      socket.s.onerror = function() {
-        dumpn("runCommand onerror");
-        reject("NETWORK_ERROR");
-      };
-
-      socket.s.onclose = function() {
-        dumpn("runCommand onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("runCommand ondata");
-        const data = event.data;
-
-        const packet = client.unpackPacket(data, false);
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          dumpn("Error: " + packet.data);
-          reject("PROTOCOL_ERROR");
-          return;
-        }
-
-        resolve(packet.data);
-      };
-    });
-  },
 };
 
 exports.ADB = ADB;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/commands/index.js
@@ -0,0 +1,19 @@
+/* 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 { listDevices } = require("./list-devices");
+const { prepareTCPConnection } = require("./prepare-tcp-connection");
+const { runCommand } = require("./run-command");
+const { shell } = require("./shell");
+const { trackDevices } = require("./track-devices");
+
+module.exports = {
+  listDevices,
+  prepareTCPConnection,
+  runCommand,
+  shell,
+  trackDevices,
+};
copy from devtools/shared/adb/adb.js
copy to devtools/shared/adb/commands/list-devices.js
--- a/devtools/shared/adb/adb.js
+++ b/devtools/shared/adb/commands/list-devices.js
@@ -1,424 +1,31 @@
 /* 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 { Cc, Ci } = require("chrome");
-const EventEmitter = require("devtools/shared/event-emitter");
-const client = require("./adb-client");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
-const { getFileForBinary } = require("./adb-binary");
-const { setTimeout } = require("resource://gre/modules/Timer.jsm");
-const { Services } = require("resource://gre/modules/Services.jsm");
-const { ConnectionManager } = require("devtools/shared/client/connection-manager");
-loader.lazyRequireGetter(this, "check",
-                         "devtools/shared/adb/adb-running-checker", true);
-
-let ready = false;
-let didRunInitially = false;
-
-const OKAY = 0x59414b4f;
-
-const ADB = {
-  get didRunInitially() {
-    return didRunInitially;
-  },
-  set didRunInitially(newVal) {
-    didRunInitially = newVal;
-  },
-
-  get ready() {
-    return ready;
-  },
-  set ready(newVal) {
-    ready = newVal;
-  },
-
-  get adbFilePromise() {
-    if (this._adbFilePromise) {
-      return this._adbFilePromise;
-    }
-    this._adbFilePromise = getFileForBinary();
-    return this._adbFilePromise;
-  },
-
-  async _runProcess(process, params) {
-    return new Promise((resolve, reject) => {
-      process.runAsync(params, params.length, {
-        observe(subject, topic, data) {
-          switch (topic) {
-            case "process-finished":
-              resolve();
-              break;
-            case "process-failed":
-              reject();
-              break;
-          }
-        },
-      }, false);
-    });
-  },
-
-  // Waits until a predicate returns true or re-tries the predicate calls
-  // |retry| times, we wait for 100ms between each calls.
-  async _waitUntil(predicate, retry = 20) {
-    let count = 0;
-    while (count++ < retry) {
-      if (await predicate()) {
-        return true;
-      }
-      // Wait for 100 milliseconds.
-      await new Promise(resolve => setTimeout(resolve, 100));
-    }
-    // Timed out after trying too many times.
-    return false;
-  },
-
-  // We startup by launching adb in server mode, and setting
-  // the tcp socket preference to |true|
-  async start() {
-    return new Promise(async (resolve, reject) => {
-      const onSuccessfulStart = () => {
-        Services.obs.notifyObservers(null, "adb-ready");
-        this.ready = true;
-        resolve();
-      };
-
-      const isAdbRunning = await check();
-      if (isAdbRunning) {
-        dumpn("Found ADB process running, not restarting");
-        onSuccessfulStart();
-        return;
-      }
-      dumpn("Didn't find ADB process running, restarting");
+/**
+ * The listDevices command is currently unused in DevTools. We are keeping it while
+ * working on RemoteDebugging NG, in case it becomes needed later. We will remove it from
+ * the codebase if unused at the end of the project. See Bug 1511779.
+ */
+const listDevices = function() {
+  dumpn("listDevices");
 
-      this.didRunInitially = true;
-      const process = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Ci.nsIProcess);
-      // FIXME: Bug 1481691 - We should avoid extracting files every time.
-      const adbFile = await this.adbFilePromise;
-      process.init(adbFile);
-      // Hide command prompt window on Windows
-      process.startHidden = true;
-      process.noShell = true;
-      const params = ["start-server"];
-      let isStarted = false;
-      try {
-        await this._runProcess(process, params);
-        isStarted = await this._waitUntil(check);
-      } catch (e) {
-      }
-
-      if (isStarted) {
-        onSuccessfulStart();
-      } else {
-        this.ready = false;
-        reject();
-      }
-    });
-  },
-
-  /**
-   * Stop the ADB server, but only if we started it.  If it was started before
-   * us, we return immediately.
-   */
-  async stop() {
-    if (!this.didRunInitially) {
-      return; // We didn't start the server, nothing to do
-    }
-    await this.kill();
-  },
-
-  /**
-   * Kill the ADB server.
-   */
-  async kill() {
-    try {
-      await this.runCommand("host:kill");
-    } catch (e) {
-      dumpn("Failed to send host:kill command");
-    }
-    dumpn("adb server was terminated by host:kill");
-    this.ready = false;
-    this.didRunInitially = false;
-  },
-
-  // Start tracking devices connecting and disconnecting from the host.
-  // We can't reuse runCommand here because we keep the socket alive.
-  // @return The socket used.
-  trackDevices() {
-    dumpn("trackDevices");
-    const socket = client.connect();
-    let waitForFirst = true;
-    const devices = {};
-
-    socket.s.onopen = function() {
-      dumpn("trackDevices onopen");
-      Services.obs.notifyObservers(null, "adb-track-devices-start");
-      const req = client.createRequest("host:track-devices");
-      socket.send(req);
-    };
-
-    socket.s.onerror = function(event) {
-      dumpn("trackDevices onerror: " + event);
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-    };
-
-    socket.s.onclose = function() {
-      dumpn("trackDevices onclose");
-
-      // Report all devices as disconnected
-      for (const dev in devices) {
-        devices[dev] = false;
-        EventEmitter.emit(ADB, "device-disconnected", dev);
-      }
-
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-
-      // 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.
-      setTimeout(function() { // Give some time to the new adb to start
-        if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
-          ADB.start().then(function() { // try to connect to the new local adb server
-                                         // or, spawn a new one
-            ADB.trackDevices(); // Re-track devices
-          });
-        }
-      }, 2000);
-    };
-
-    socket.s.ondata = function(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 (waitForFirst) {
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
+  return this.runCommand("host:devices").then(
+    function onSuccess(data) {
+      const lines = data.split("\n");
+      const res = [];
+      lines.forEach(function(line) {
+        if (line.length == 0) {
           return;
         }
-      }
-
-      const packet = client.unpackPacket(data, !waitForFirst);
-      waitForFirst = false;
-
-      if (packet.data == "") {
-        // All devices got disconnected.
-        for (const dev in devices) {
-          devices[dev] = false;
-          EventEmitter.emit(ADB, "device-disconnected", dev);
-        }
-      } else {
-        // One line per device, each line being $DEVICE\t(offline|device)
-        const lines = packet.data.split("\n");
-        const newDev = {};
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-
-          const [dev, status] = line.split("\t");
-          newDev[dev] = status !== "offline";
-        });
-        // Check which device changed state.
-        for (const dev in newDev) {
-          if (devices[dev] != newDev[dev]) {
-            if (dev in devices || newDev[dev]) {
-              const topic = newDev[dev] ? "device-connected"
-                                        : "device-disconnected";
-              EventEmitter.emit(ADB, topic, dev);
-            }
-            devices[dev] = newDev[dev];
-          }
-        }
-      }
-    };
-  },
-
-  // Sends back an array of device names.
-  listDevices() {
-    dumpn("listDevices");
-
-    return this.runCommand("host:devices").then(
-      function onSuccess(data) {
-        const lines = data.split("\n");
-        const res = [];
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-          const [ device ] = line.split("\t");
-          res.push(device);
-        });
-        return res;
-      }
-    );
-  },
-
-  // sends adb forward localPort devicePort
-  forwardPort(localPort, devicePort) {
-    dumpn("forwardPort " + localPort + " -- " + devicePort);
-    // <host-prefix>:forward:<local>;<remote>
-
-    return this.runCommand("host:forward:" + localPort + ";" + devicePort)
-               .then(function onSuccess(data) {
-                 return data;
-               });
-  },
-
-  // Prepare TCP connection for provided socket path.
-  // The returned value is a port number of localhost for the connection.
-  async prepareTCPConnection(socketPath) {
-    const port = ConnectionManager.getFreeTCPPort();
-    const local = `tcp:${ port }`;
-    const remote = socketPath.startsWith("@")
-                     ? `localabstract:${ socketPath.substring(1) }`
-                     : `localfilesystem:${ socketPath }`;
-    await this.forwardPort(local, remote);
-    return port;
-  },
-
-  // Run a shell command
-  async shell(command) {
-    let state;
-    let stdout = "";
-
-    dumpn("shell " + command);
-
-    return new Promise((resolve, reject) => {
-      const shutdown = function() {
-        dumpn("shell shutdown");
-        socket.close();
-        reject("BAD_RESPONSE");
-      };
-
-      const runFSM = function runFSM(data) {
-        dumpn("runFSM " + state);
-        let req;
-        let ignoreResponseCode = false;
-        switch (state) {
-          case "start":
-            state = "send-transport";
-            runFSM();
-            break;
-          case "send-transport":
-            req = client.createRequest("host:transport-any");
-            socket.send(req);
-            state = "wait-transport";
-            break;
-          case "wait-transport":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "send-shell";
-            runFSM();
-            break;
-          case "send-shell":
-            req = client.createRequest("shell:" + command);
-            socket.send(req);
-            state = "rec-shell";
-            break;
-          case "rec-shell":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "decode-shell";
-            if (client.getBuffer(data).byteLength == 4) {
-              break;
-            }
-            ignoreResponseCode = true;
-            // eslint-disable-next-lined no-fallthrough
-          case "decode-shell":
-            const decoder = new TextDecoder();
-            const text = new Uint8Array(client.getBuffer(data),
-                                        ignoreResponseCode ? 4 : 0);
-            stdout += decoder.decode(text);
-            break;
-          default:
-            dumpn("shell Unexpected State: " + state);
-            reject("UNEXPECTED_STATE");
-        }
-      };
-
-      const socket = client.connect();
-      socket.s.onerror = function(event) {
-        dumpn("shell onerror");
-        reject("SOCKET_ERROR");
-      };
-
-      socket.s.onopen = function(event) {
-        dumpn("shell onopen");
-        state = "start";
-        runFSM();
-      };
-
-      socket.s.onclose = function(event) {
-        resolve(stdout);
-        dumpn("shell onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("shell ondata");
-        runFSM(event.data);
-      };
-    });
-  },
-
-  // Asynchronously runs an adb command.
-  // @param command The command as documented in
-  // http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
-  runCommand(command) {
-    dumpn("runCommand " + command);
-    return new Promise((resolve, reject) => {
-      if (!this.ready) {
-        setTimeout(function() {
-          reject("ADB_NOT_READY");
-        });
-        return;
-      }
-
-      const socket = client.connect();
-
-      socket.s.onopen = function() {
-        dumpn("runCommand onopen");
-        const req = client.createRequest(command);
-        socket.send(req);
-      };
-
-      socket.s.onerror = function() {
-        dumpn("runCommand onerror");
-        reject("NETWORK_ERROR");
-      };
-
-      socket.s.onclose = function() {
-        dumpn("runCommand onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("runCommand ondata");
-        const data = event.data;
-
-        const packet = client.unpackPacket(data, false);
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          dumpn("Error: " + packet.data);
-          reject("PROTOCOL_ERROR");
-          return;
-        }
-
-        resolve(packet.data);
-      };
-    });
-  },
+        const [ device ] = line.split("\t");
+        res.push(device);
+      });
+      return res;
+    }
+  );
 };
-
-exports.ADB = ADB;
+exports.listDevices = listDevices;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/commands/moz.build
@@ -0,0 +1,12 @@
+# 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/.
+
+DevToolsModules(
+    'index.js',
+    'list-devices.js',
+    'prepare-tcp-connection.js',
+    'run-command.js',
+    'shell.js',
+    'track-devices.js',
+)
copy from devtools/shared/adb/adb.js
copy to devtools/shared/adb/commands/prepare-tcp-connection.js
--- a/devtools/shared/adb/adb.js
+++ b/devtools/shared/adb/commands/prepare-tcp-connection.js
@@ -1,424 +1,33 @@
 /* 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 { Cc, Ci } = require("chrome");
-const EventEmitter = require("devtools/shared/event-emitter");
-const client = require("./adb-client");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
-const { getFileForBinary } = require("./adb-binary");
-const { setTimeout } = require("resource://gre/modules/Timer.jsm");
-const { Services } = require("resource://gre/modules/Services.jsm");
 const { ConnectionManager } = require("devtools/shared/client/connection-manager");
-loader.lazyRequireGetter(this, "check",
-                         "devtools/shared/adb/adb-running-checker", true);
-
-let ready = false;
-let didRunInitially = false;
-
-const OKAY = 0x59414b4f;
-
-const ADB = {
-  get didRunInitially() {
-    return didRunInitially;
-  },
-  set didRunInitially(newVal) {
-    didRunInitially = newVal;
-  },
-
-  get ready() {
-    return ready;
-  },
-  set ready(newVal) {
-    ready = newVal;
-  },
-
-  get adbFilePromise() {
-    if (this._adbFilePromise) {
-      return this._adbFilePromise;
-    }
-    this._adbFilePromise = getFileForBinary();
-    return this._adbFilePromise;
-  },
-
-  async _runProcess(process, params) {
-    return new Promise((resolve, reject) => {
-      process.runAsync(params, params.length, {
-        observe(subject, topic, data) {
-          switch (topic) {
-            case "process-finished":
-              resolve();
-              break;
-            case "process-failed":
-              reject();
-              break;
-          }
-        },
-      }, false);
-    });
-  },
-
-  // Waits until a predicate returns true or re-tries the predicate calls
-  // |retry| times, we wait for 100ms between each calls.
-  async _waitUntil(predicate, retry = 20) {
-    let count = 0;
-    while (count++ < retry) {
-      if (await predicate()) {
-        return true;
-      }
-      // Wait for 100 milliseconds.
-      await new Promise(resolve => setTimeout(resolve, 100));
-    }
-    // Timed out after trying too many times.
-    return false;
-  },
-
-  // We startup by launching adb in server mode, and setting
-  // the tcp socket preference to |true|
-  async start() {
-    return new Promise(async (resolve, reject) => {
-      const onSuccessfulStart = () => {
-        Services.obs.notifyObservers(null, "adb-ready");
-        this.ready = true;
-        resolve();
-      };
-
-      const isAdbRunning = await check();
-      if (isAdbRunning) {
-        dumpn("Found ADB process running, not restarting");
-        onSuccessfulStart();
-        return;
-      }
-      dumpn("Didn't find ADB process running, restarting");
-
-      this.didRunInitially = true;
-      const process = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Ci.nsIProcess);
-      // FIXME: Bug 1481691 - We should avoid extracting files every time.
-      const adbFile = await this.adbFilePromise;
-      process.init(adbFile);
-      // Hide command prompt window on Windows
-      process.startHidden = true;
-      process.noShell = true;
-      const params = ["start-server"];
-      let isStarted = false;
-      try {
-        await this._runProcess(process, params);
-        isStarted = await this._waitUntil(check);
-      } catch (e) {
-      }
-
-      if (isStarted) {
-        onSuccessfulStart();
-      } else {
-        this.ready = false;
-        reject();
-      }
-    });
-  },
-
-  /**
-   * Stop the ADB server, but only if we started it.  If it was started before
-   * us, we return immediately.
-   */
-  async stop() {
-    if (!this.didRunInitially) {
-      return; // We didn't start the server, nothing to do
-    }
-    await this.kill();
-  },
-
-  /**
-   * Kill the ADB server.
-   */
-  async kill() {
-    try {
-      await this.runCommand("host:kill");
-    } catch (e) {
-      dumpn("Failed to send host:kill command");
-    }
-    dumpn("adb server was terminated by host:kill");
-    this.ready = false;
-    this.didRunInitially = false;
-  },
-
-  // Start tracking devices connecting and disconnecting from the host.
-  // We can't reuse runCommand here because we keep the socket alive.
-  // @return The socket used.
-  trackDevices() {
-    dumpn("trackDevices");
-    const socket = client.connect();
-    let waitForFirst = true;
-    const devices = {};
-
-    socket.s.onopen = function() {
-      dumpn("trackDevices onopen");
-      Services.obs.notifyObservers(null, "adb-track-devices-start");
-      const req = client.createRequest("host:track-devices");
-      socket.send(req);
-    };
-
-    socket.s.onerror = function(event) {
-      dumpn("trackDevices onerror: " + event);
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-    };
-
-    socket.s.onclose = function() {
-      dumpn("trackDevices onclose");
-
-      // Report all devices as disconnected
-      for (const dev in devices) {
-        devices[dev] = false;
-        EventEmitter.emit(ADB, "device-disconnected", dev);
-      }
-
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-
-      // 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.
-      setTimeout(function() { // Give some time to the new adb to start
-        if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
-          ADB.start().then(function() { // try to connect to the new local adb server
-                                         // or, spawn a new one
-            ADB.trackDevices(); // Re-track devices
-          });
-        }
-      }, 2000);
-    };
-
-    socket.s.ondata = function(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 (waitForFirst) {
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          return;
-        }
-      }
-
-      const packet = client.unpackPacket(data, !waitForFirst);
-      waitForFirst = false;
+const { runCommand } = require("./run-command");
 
-      if (packet.data == "") {
-        // All devices got disconnected.
-        for (const dev in devices) {
-          devices[dev] = false;
-          EventEmitter.emit(ADB, "device-disconnected", dev);
-        }
-      } else {
-        // One line per device, each line being $DEVICE\t(offline|device)
-        const lines = packet.data.split("\n");
-        const newDev = {};
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-
-          const [dev, status] = line.split("\t");
-          newDev[dev] = status !== "offline";
-        });
-        // Check which device changed state.
-        for (const dev in newDev) {
-          if (devices[dev] != newDev[dev]) {
-            if (dev in devices || newDev[dev]) {
-              const topic = newDev[dev] ? "device-connected"
-                                        : "device-disconnected";
-              EventEmitter.emit(ADB, topic, dev);
-            }
-            devices[dev] = newDev[dev];
-          }
-        }
-      }
-    };
-  },
-
-  // Sends back an array of device names.
-  listDevices() {
-    dumpn("listDevices");
-
-    return this.runCommand("host:devices").then(
-      function onSuccess(data) {
-        const lines = data.split("\n");
-        const res = [];
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-          const [ device ] = line.split("\t");
-          res.push(device);
-        });
-        return res;
-      }
-    );
-  },
-
-  // sends adb forward localPort devicePort
-  forwardPort(localPort, devicePort) {
-    dumpn("forwardPort " + localPort + " -- " + devicePort);
-    // <host-prefix>:forward:<local>;<remote>
-
-    return this.runCommand("host:forward:" + localPort + ";" + devicePort)
-               .then(function onSuccess(data) {
-                 return data;
-               });
-  },
-
-  // Prepare TCP connection for provided socket path.
-  // The returned value is a port number of localhost for the connection.
-  async prepareTCPConnection(socketPath) {
-    const port = ConnectionManager.getFreeTCPPort();
-    const local = `tcp:${ port }`;
-    const remote = socketPath.startsWith("@")
-                     ? `localabstract:${ socketPath.substring(1) }`
-                     : `localfilesystem:${ socketPath }`;
-    await this.forwardPort(local, remote);
-    return port;
-  },
-
-  // Run a shell command
-  async shell(command) {
-    let state;
-    let stdout = "";
-
-    dumpn("shell " + command);
-
-    return new Promise((resolve, reject) => {
-      const shutdown = function() {
-        dumpn("shell shutdown");
-        socket.close();
-        reject("BAD_RESPONSE");
-      };
+// sends adb forward localPort devicePort
+const forwardPort = function(localPort, devicePort) {
+  dumpn("forwardPort " + localPort + " -- " + devicePort);
+  // <host-prefix>:forward:<local>;<remote>
 
-      const runFSM = function runFSM(data) {
-        dumpn("runFSM " + state);
-        let req;
-        let ignoreResponseCode = false;
-        switch (state) {
-          case "start":
-            state = "send-transport";
-            runFSM();
-            break;
-          case "send-transport":
-            req = client.createRequest("host:transport-any");
-            socket.send(req);
-            state = "wait-transport";
-            break;
-          case "wait-transport":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "send-shell";
-            runFSM();
-            break;
-          case "send-shell":
-            req = client.createRequest("shell:" + command);
-            socket.send(req);
-            state = "rec-shell";
-            break;
-          case "rec-shell":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "decode-shell";
-            if (client.getBuffer(data).byteLength == 4) {
-              break;
-            }
-            ignoreResponseCode = true;
-            // eslint-disable-next-lined no-fallthrough
-          case "decode-shell":
-            const decoder = new TextDecoder();
-            const text = new Uint8Array(client.getBuffer(data),
-                                        ignoreResponseCode ? 4 : 0);
-            stdout += decoder.decode(text);
-            break;
-          default:
-            dumpn("shell Unexpected State: " + state);
-            reject("UNEXPECTED_STATE");
-        }
-      };
-
-      const socket = client.connect();
-      socket.s.onerror = function(event) {
-        dumpn("shell onerror");
-        reject("SOCKET_ERROR");
-      };
-
-      socket.s.onopen = function(event) {
-        dumpn("shell onopen");
-        state = "start";
-        runFSM();
-      };
-
-      socket.s.onclose = function(event) {
-        resolve(stdout);
-        dumpn("shell onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("shell ondata");
-        runFSM(event.data);
-      };
-    });
-  },
-
-  // Asynchronously runs an adb command.
-  // @param command The command as documented in
-  // http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
-  runCommand(command) {
-    dumpn("runCommand " + command);
-    return new Promise((resolve, reject) => {
-      if (!this.ready) {
-        setTimeout(function() {
-          reject("ADB_NOT_READY");
-        });
-        return;
-      }
-
-      const socket = client.connect();
-
-      socket.s.onopen = function() {
-        dumpn("runCommand onopen");
-        const req = client.createRequest(command);
-        socket.send(req);
-      };
-
-      socket.s.onerror = function() {
-        dumpn("runCommand onerror");
-        reject("NETWORK_ERROR");
-      };
-
-      socket.s.onclose = function() {
-        dumpn("runCommand onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("runCommand ondata");
-        const data = event.data;
-
-        const packet = client.unpackPacket(data, false);
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          dumpn("Error: " + packet.data);
-          reject("PROTOCOL_ERROR");
-          return;
-        }
-
-        resolve(packet.data);
-      };
-    });
-  },
+  return runCommand("host:forward:" + localPort + ";" + devicePort)
+             .then(function onSuccess(data) {
+               return data;
+             });
 };
 
-exports.ADB = ADB;
+// Prepare TCP connection for provided socket path.
+// The returned value is a port number of localhost for the connection.
+const prepareTCPConnection = async function(socketPath) {
+  const port = ConnectionManager.getFreeTCPPort();
+  const local = `tcp:${ port }`;
+  const remote = socketPath.startsWith("@")
+                   ? `localabstract:${ socketPath.substring(1) }`
+                   : `localfilesystem:${ socketPath }`;
+  await forwardPort(local, remote);
+  return port;
+};
+exports.prepareTCPConnection = prepareTCPConnection;
copy from devtools/shared/adb/adb.js
copy to devtools/shared/adb/commands/run-command.js
--- a/devtools/shared/adb/adb.js
+++ b/devtools/shared/adb/commands/run-command.js
@@ -1,424 +1,62 @@
 /* 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 { Cc, Ci } = require("chrome");
-const EventEmitter = require("devtools/shared/event-emitter");
-const client = require("./adb-client");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
-const { getFileForBinary } = require("./adb-binary");
 const { setTimeout } = require("resource://gre/modules/Timer.jsm");
-const { Services } = require("resource://gre/modules/Services.jsm");
-const { ConnectionManager } = require("devtools/shared/client/connection-manager");
-loader.lazyRequireGetter(this, "check",
-                         "devtools/shared/adb/adb-running-checker", true);
-
-let ready = false;
-let didRunInitially = false;
+const { ADB } = require("../adb");
+const client = require("../adb-client");
 
 const OKAY = 0x59414b4f;
 
-const ADB = {
-  get didRunInitially() {
-    return didRunInitially;
-  },
-  set didRunInitially(newVal) {
-    didRunInitially = newVal;
-  },
-
-  get ready() {
-    return ready;
-  },
-  set ready(newVal) {
-    ready = newVal;
-  },
-
-  get adbFilePromise() {
-    if (this._adbFilePromise) {
-      return this._adbFilePromise;
+// Asynchronously runs an adb command.
+// @param command The command as documented in
+// http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
+const runCommand = function(command) {
+  dumpn("runCommand " + command);
+  return new Promise((resolve, reject) => {
+    if (!ADB.ready) {
+      setTimeout(function() {
+        reject("ADB_NOT_READY");
+      });
+      return;
     }
-    this._adbFilePromise = getFileForBinary();
-    return this._adbFilePromise;
-  },
-
-  async _runProcess(process, params) {
-    return new Promise((resolve, reject) => {
-      process.runAsync(params, params.length, {
-        observe(subject, topic, data) {
-          switch (topic) {
-            case "process-finished":
-              resolve();
-              break;
-            case "process-failed":
-              reject();
-              break;
-          }
-        },
-      }, false);
-    });
-  },
-
-  // Waits until a predicate returns true or re-tries the predicate calls
-  // |retry| times, we wait for 100ms between each calls.
-  async _waitUntil(predicate, retry = 20) {
-    let count = 0;
-    while (count++ < retry) {
-      if (await predicate()) {
-        return true;
-      }
-      // Wait for 100 milliseconds.
-      await new Promise(resolve => setTimeout(resolve, 100));
-    }
-    // Timed out after trying too many times.
-    return false;
-  },
-
-  // We startup by launching adb in server mode, and setting
-  // the tcp socket preference to |true|
-  async start() {
-    return new Promise(async (resolve, reject) => {
-      const onSuccessfulStart = () => {
-        Services.obs.notifyObservers(null, "adb-ready");
-        this.ready = true;
-        resolve();
-      };
 
-      const isAdbRunning = await check();
-      if (isAdbRunning) {
-        dumpn("Found ADB process running, not restarting");
-        onSuccessfulStart();
-        return;
-      }
-      dumpn("Didn't find ADB process running, restarting");
-
-      this.didRunInitially = true;
-      const process = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Ci.nsIProcess);
-      // FIXME: Bug 1481691 - We should avoid extracting files every time.
-      const adbFile = await this.adbFilePromise;
-      process.init(adbFile);
-      // Hide command prompt window on Windows
-      process.startHidden = true;
-      process.noShell = true;
-      const params = ["start-server"];
-      let isStarted = false;
-      try {
-        await this._runProcess(process, params);
-        isStarted = await this._waitUntil(check);
-      } catch (e) {
-      }
-
-      if (isStarted) {
-        onSuccessfulStart();
-      } else {
-        this.ready = false;
-        reject();
-      }
-    });
-  },
-
-  /**
-   * Stop the ADB server, but only if we started it.  If it was started before
-   * us, we return immediately.
-   */
-  async stop() {
-    if (!this.didRunInitially) {
-      return; // We didn't start the server, nothing to do
-    }
-    await this.kill();
-  },
-
-  /**
-   * Kill the ADB server.
-   */
-  async kill() {
-    try {
-      await this.runCommand("host:kill");
-    } catch (e) {
-      dumpn("Failed to send host:kill command");
-    }
-    dumpn("adb server was terminated by host:kill");
-    this.ready = false;
-    this.didRunInitially = false;
-  },
-
-  // Start tracking devices connecting and disconnecting from the host.
-  // We can't reuse runCommand here because we keep the socket alive.
-  // @return The socket used.
-  trackDevices() {
-    dumpn("trackDevices");
     const socket = client.connect();
-    let waitForFirst = true;
-    const devices = {};
 
     socket.s.onopen = function() {
-      dumpn("trackDevices onopen");
-      Services.obs.notifyObservers(null, "adb-track-devices-start");
-      const req = client.createRequest("host:track-devices");
+      dumpn("runCommand onopen");
+      const req = client.createRequest(command);
       socket.send(req);
     };
 
-    socket.s.onerror = function(event) {
-      dumpn("trackDevices onerror: " + event);
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
+    socket.s.onerror = function() {
+      dumpn("runCommand onerror");
+      reject("NETWORK_ERROR");
     };
 
     socket.s.onclose = function() {
-      dumpn("trackDevices onclose");
-
-      // Report all devices as disconnected
-      for (const dev in devices) {
-        devices[dev] = false;
-        EventEmitter.emit(ADB, "device-disconnected", dev);
-      }
-
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-
-      // 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.
-      setTimeout(function() { // Give some time to the new adb to start
-        if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
-          ADB.start().then(function() { // try to connect to the new local adb server
-                                         // or, spawn a new one
-            ADB.trackDevices(); // Re-track devices
-          });
-        }
-      }, 2000);
+      dumpn("runCommand onclose");
     };
 
     socket.s.ondata = function(event) {
-      dumpn("trackDevices ondata");
+      dumpn("runCommand 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 (waitForFirst) {
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          return;
-        }
-      }
-
-      const packet = client.unpackPacket(data, !waitForFirst);
-      waitForFirst = false;
-
-      if (packet.data == "") {
-        // All devices got disconnected.
-        for (const dev in devices) {
-          devices[dev] = false;
-          EventEmitter.emit(ADB, "device-disconnected", dev);
-        }
-      } else {
-        // One line per device, each line being $DEVICE\t(offline|device)
-        const lines = packet.data.split("\n");
-        const newDev = {};
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-
-          const [dev, status] = line.split("\t");
-          newDev[dev] = status !== "offline";
-        });
-        // Check which device changed state.
-        for (const dev in newDev) {
-          if (devices[dev] != newDev[dev]) {
-            if (dev in devices || newDev[dev]) {
-              const topic = newDev[dev] ? "device-connected"
-                                        : "device-disconnected";
-              EventEmitter.emit(ADB, topic, dev);
-            }
-            devices[dev] = newDev[dev];
-          }
-        }
-      }
-    };
-  },
-
-  // Sends back an array of device names.
-  listDevices() {
-    dumpn("listDevices");
-
-    return this.runCommand("host:devices").then(
-      function onSuccess(data) {
-        const lines = data.split("\n");
-        const res = [];
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-          const [ device ] = line.split("\t");
-          res.push(device);
-        });
-        return res;
-      }
-    );
-  },
-
-  // sends adb forward localPort devicePort
-  forwardPort(localPort, devicePort) {
-    dumpn("forwardPort " + localPort + " -- " + devicePort);
-    // <host-prefix>:forward:<local>;<remote>
-
-    return this.runCommand("host:forward:" + localPort + ";" + devicePort)
-               .then(function onSuccess(data) {
-                 return data;
-               });
-  },
-
-  // Prepare TCP connection for provided socket path.
-  // The returned value is a port number of localhost for the connection.
-  async prepareTCPConnection(socketPath) {
-    const port = ConnectionManager.getFreeTCPPort();
-    const local = `tcp:${ port }`;
-    const remote = socketPath.startsWith("@")
-                     ? `localabstract:${ socketPath.substring(1) }`
-                     : `localfilesystem:${ socketPath }`;
-    await this.forwardPort(local, remote);
-    return port;
-  },
 
-  // Run a shell command
-  async shell(command) {
-    let state;
-    let stdout = "";
-
-    dumpn("shell " + command);
-
-    return new Promise((resolve, reject) => {
-      const shutdown = function() {
-        dumpn("shell shutdown");
+      const packet = client.unpackPacket(data, false);
+      if (!client.checkResponse(data, OKAY)) {
         socket.close();
-        reject("BAD_RESPONSE");
-      };
-
-      const runFSM = function runFSM(data) {
-        dumpn("runFSM " + state);
-        let req;
-        let ignoreResponseCode = false;
-        switch (state) {
-          case "start":
-            state = "send-transport";
-            runFSM();
-            break;
-          case "send-transport":
-            req = client.createRequest("host:transport-any");
-            socket.send(req);
-            state = "wait-transport";
-            break;
-          case "wait-transport":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "send-shell";
-            runFSM();
-            break;
-          case "send-shell":
-            req = client.createRequest("shell:" + command);
-            socket.send(req);
-            state = "rec-shell";
-            break;
-          case "rec-shell":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "decode-shell";
-            if (client.getBuffer(data).byteLength == 4) {
-              break;
-            }
-            ignoreResponseCode = true;
-            // eslint-disable-next-lined no-fallthrough
-          case "decode-shell":
-            const decoder = new TextDecoder();
-            const text = new Uint8Array(client.getBuffer(data),
-                                        ignoreResponseCode ? 4 : 0);
-            stdout += decoder.decode(text);
-            break;
-          default:
-            dumpn("shell Unexpected State: " + state);
-            reject("UNEXPECTED_STATE");
-        }
-      };
-
-      const socket = client.connect();
-      socket.s.onerror = function(event) {
-        dumpn("shell onerror");
-        reject("SOCKET_ERROR");
-      };
-
-      socket.s.onopen = function(event) {
-        dumpn("shell onopen");
-        state = "start";
-        runFSM();
-      };
-
-      socket.s.onclose = function(event) {
-        resolve(stdout);
-        dumpn("shell onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("shell ondata");
-        runFSM(event.data);
-      };
-    });
-  },
-
-  // Asynchronously runs an adb command.
-  // @param command The command as documented in
-  // http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
-  runCommand(command) {
-    dumpn("runCommand " + command);
-    return new Promise((resolve, reject) => {
-      if (!this.ready) {
-        setTimeout(function() {
-          reject("ADB_NOT_READY");
-        });
+        dumpn("Error: " + packet.data);
+        reject("PROTOCOL_ERROR");
         return;
       }
 
-      const socket = client.connect();
-
-      socket.s.onopen = function() {
-        dumpn("runCommand onopen");
-        const req = client.createRequest(command);
-        socket.send(req);
-      };
-
-      socket.s.onerror = function() {
-        dumpn("runCommand onerror");
-        reject("NETWORK_ERROR");
-      };
-
-      socket.s.onclose = function() {
-        dumpn("runCommand onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("runCommand ondata");
-        const data = event.data;
-
-        const packet = client.unpackPacket(data, false);
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          dumpn("Error: " + packet.data);
-          reject("PROTOCOL_ERROR");
-          return;
-        }
-
-        resolve(packet.data);
-      };
-    });
-  },
+      resolve(packet.data);
+    };
+  });
 };
-
-exports.ADB = ADB;
+exports.runCommand = runCommand;
copy from devtools/shared/adb/adb.js
copy to devtools/shared/adb/commands/shell.js
--- a/devtools/shared/adb/adb.js
+++ b/devtools/shared/adb/commands/shell.js
@@ -1,424 +1,101 @@
 /* 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 { Cc, Ci } = require("chrome");
-const EventEmitter = require("devtools/shared/event-emitter");
-const client = require("./adb-client");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
-const { getFileForBinary } = require("./adb-binary");
-const { setTimeout } = require("resource://gre/modules/Timer.jsm");
-const { Services } = require("resource://gre/modules/Services.jsm");
-const { ConnectionManager } = require("devtools/shared/client/connection-manager");
-loader.lazyRequireGetter(this, "check",
-                         "devtools/shared/adb/adb-running-checker", true);
-
-let ready = false;
-let didRunInitially = false;
+const client = require("../adb-client");
 
 const OKAY = 0x59414b4f;
 
-const ADB = {
-  get didRunInitially() {
-    return didRunInitially;
-  },
-  set didRunInitially(newVal) {
-    didRunInitially = newVal;
-  },
-
-  get ready() {
-    return ready;
-  },
-  set ready(newVal) {
-    ready = newVal;
-  },
-
-  get adbFilePromise() {
-    if (this._adbFilePromise) {
-      return this._adbFilePromise;
-    }
-    this._adbFilePromise = getFileForBinary();
-    return this._adbFilePromise;
-  },
+const shell = async function(command) {
+  let state;
+  let stdout = "";
 
-  async _runProcess(process, params) {
-    return new Promise((resolve, reject) => {
-      process.runAsync(params, params.length, {
-        observe(subject, topic, data) {
-          switch (topic) {
-            case "process-finished":
-              resolve();
-              break;
-            case "process-failed":
-              reject();
-              break;
-          }
-        },
-      }, false);
-    });
-  },
-
-  // Waits until a predicate returns true or re-tries the predicate calls
-  // |retry| times, we wait for 100ms between each calls.
-  async _waitUntil(predicate, retry = 20) {
-    let count = 0;
-    while (count++ < retry) {
-      if (await predicate()) {
-        return true;
-      }
-      // Wait for 100 milliseconds.
-      await new Promise(resolve => setTimeout(resolve, 100));
-    }
-    // Timed out after trying too many times.
-    return false;
-  },
-
-  // We startup by launching adb in server mode, and setting
-  // the tcp socket preference to |true|
-  async start() {
-    return new Promise(async (resolve, reject) => {
-      const onSuccessfulStart = () => {
-        Services.obs.notifyObservers(null, "adb-ready");
-        this.ready = true;
-        resolve();
-      };
+  dumpn("shell " + command);
 
-      const isAdbRunning = await check();
-      if (isAdbRunning) {
-        dumpn("Found ADB process running, not restarting");
-        onSuccessfulStart();
-        return;
-      }
-      dumpn("Didn't find ADB process running, restarting");
-
-      this.didRunInitially = true;
-      const process = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Ci.nsIProcess);
-      // FIXME: Bug 1481691 - We should avoid extracting files every time.
-      const adbFile = await this.adbFilePromise;
-      process.init(adbFile);
-      // Hide command prompt window on Windows
-      process.startHidden = true;
-      process.noShell = true;
-      const params = ["start-server"];
-      let isStarted = false;
-      try {
-        await this._runProcess(process, params);
-        isStarted = await this._waitUntil(check);
-      } catch (e) {
-      }
-
-      if (isStarted) {
-        onSuccessfulStart();
-      } else {
-        this.ready = false;
-        reject();
-      }
-    });
-  },
-
-  /**
-   * Stop the ADB server, but only if we started it.  If it was started before
-   * us, we return immediately.
-   */
-  async stop() {
-    if (!this.didRunInitially) {
-      return; // We didn't start the server, nothing to do
-    }
-    await this.kill();
-  },
-
-  /**
-   * Kill the ADB server.
-   */
-  async kill() {
-    try {
-      await this.runCommand("host:kill");
-    } catch (e) {
-      dumpn("Failed to send host:kill command");
-    }
-    dumpn("adb server was terminated by host:kill");
-    this.ready = false;
-    this.didRunInitially = false;
-  },
-
-  // Start tracking devices connecting and disconnecting from the host.
-  // We can't reuse runCommand here because we keep the socket alive.
-  // @return The socket used.
-  trackDevices() {
-    dumpn("trackDevices");
-    const socket = client.connect();
-    let waitForFirst = true;
-    const devices = {};
-
-    socket.s.onopen = function() {
-      dumpn("trackDevices onopen");
-      Services.obs.notifyObservers(null, "adb-track-devices-start");
-      const req = client.createRequest("host:track-devices");
-      socket.send(req);
+  return new Promise((resolve, reject) => {
+    const shutdown = function() {
+      dumpn("shell shutdown");
+      socket.close();
+      reject("BAD_RESPONSE");
     };
 
-    socket.s.onerror = function(event) {
-      dumpn("trackDevices onerror: " + event);
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
+    const runFSM = function runFSM(data) {
+      dumpn("runFSM " + state);
+      let req;
+      let ignoreResponseCode = false;
+      switch (state) {
+        case "start":
+          state = "send-transport";
+          runFSM();
+          break;
+        case "send-transport":
+          req = client.createRequest("host:transport-any");
+          socket.send(req);
+          state = "wait-transport";
+          break;
+        case "wait-transport":
+          if (!client.checkResponse(data, OKAY)) {
+            shutdown();
+            return;
+          }
+          state = "send-shell";
+          runFSM();
+          break;
+        case "send-shell":
+          req = client.createRequest("shell:" + command);
+          socket.send(req);
+          state = "rec-shell";
+          break;
+        case "rec-shell":
+          if (!client.checkResponse(data, OKAY)) {
+            shutdown();
+            return;
+          }
+          state = "decode-shell";
+          if (client.getBuffer(data).byteLength == 4) {
+            break;
+          }
+          ignoreResponseCode = true;
+          // eslint-disable-next-lined no-fallthrough
+        case "decode-shell":
+          const decoder = new TextDecoder();
+          const text = new Uint8Array(client.getBuffer(data),
+                                      ignoreResponseCode ? 4 : 0);
+          stdout += decoder.decode(text);
+          break;
+        default:
+          dumpn("shell Unexpected State: " + state);
+          reject("UNEXPECTED_STATE");
+      }
     };
 
-    socket.s.onclose = function() {
-      dumpn("trackDevices onclose");
-
-      // Report all devices as disconnected
-      for (const dev in devices) {
-        devices[dev] = false;
-        EventEmitter.emit(ADB, "device-disconnected", dev);
-      }
-
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
+    const socket = client.connect();
+    socket.s.onerror = function(event) {
+      dumpn("shell onerror");
+      reject("SOCKET_ERROR");
+    };
 
-      // 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.
-      setTimeout(function() { // Give some time to the new adb to start
-        if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
-          ADB.start().then(function() { // try to connect to the new local adb server
-                                         // or, spawn a new one
-            ADB.trackDevices(); // Re-track devices
-          });
-        }
-      }, 2000);
+    socket.s.onopen = function(event) {
+      dumpn("shell onopen");
+      state = "start";
+      runFSM();
+    };
+
+    socket.s.onclose = function(event) {
+      resolve(stdout);
+      dumpn("shell onclose");
     };
 
     socket.s.ondata = function(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 (waitForFirst) {
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          return;
-        }
-      }
-
-      const packet = client.unpackPacket(data, !waitForFirst);
-      waitForFirst = false;
-
-      if (packet.data == "") {
-        // All devices got disconnected.
-        for (const dev in devices) {
-          devices[dev] = false;
-          EventEmitter.emit(ADB, "device-disconnected", dev);
-        }
-      } else {
-        // One line per device, each line being $DEVICE\t(offline|device)
-        const lines = packet.data.split("\n");
-        const newDev = {};
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-
-          const [dev, status] = line.split("\t");
-          newDev[dev] = status !== "offline";
-        });
-        // Check which device changed state.
-        for (const dev in newDev) {
-          if (devices[dev] != newDev[dev]) {
-            if (dev in devices || newDev[dev]) {
-              const topic = newDev[dev] ? "device-connected"
-                                        : "device-disconnected";
-              EventEmitter.emit(ADB, topic, dev);
-            }
-            devices[dev] = newDev[dev];
-          }
-        }
-      }
+      dumpn("shell ondata");
+      runFSM(event.data);
     };
-  },
-
-  // Sends back an array of device names.
-  listDevices() {
-    dumpn("listDevices");
-
-    return this.runCommand("host:devices").then(
-      function onSuccess(data) {
-        const lines = data.split("\n");
-        const res = [];
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-          const [ device ] = line.split("\t");
-          res.push(device);
-        });
-        return res;
-      }
-    );
-  },
-
-  // sends adb forward localPort devicePort
-  forwardPort(localPort, devicePort) {
-    dumpn("forwardPort " + localPort + " -- " + devicePort);
-    // <host-prefix>:forward:<local>;<remote>
-
-    return this.runCommand("host:forward:" + localPort + ";" + devicePort)
-               .then(function onSuccess(data) {
-                 return data;
-               });
-  },
-
-  // Prepare TCP connection for provided socket path.
-  // The returned value is a port number of localhost for the connection.
-  async prepareTCPConnection(socketPath) {
-    const port = ConnectionManager.getFreeTCPPort();
-    const local = `tcp:${ port }`;
-    const remote = socketPath.startsWith("@")
-                     ? `localabstract:${ socketPath.substring(1) }`
-                     : `localfilesystem:${ socketPath }`;
-    await this.forwardPort(local, remote);
-    return port;
-  },
-
-  // Run a shell command
-  async shell(command) {
-    let state;
-    let stdout = "";
-
-    dumpn("shell " + command);
-
-    return new Promise((resolve, reject) => {
-      const shutdown = function() {
-        dumpn("shell shutdown");
-        socket.close();
-        reject("BAD_RESPONSE");
-      };
-
-      const runFSM = function runFSM(data) {
-        dumpn("runFSM " + state);
-        let req;
-        let ignoreResponseCode = false;
-        switch (state) {
-          case "start":
-            state = "send-transport";
-            runFSM();
-            break;
-          case "send-transport":
-            req = client.createRequest("host:transport-any");
-            socket.send(req);
-            state = "wait-transport";
-            break;
-          case "wait-transport":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "send-shell";
-            runFSM();
-            break;
-          case "send-shell":
-            req = client.createRequest("shell:" + command);
-            socket.send(req);
-            state = "rec-shell";
-            break;
-          case "rec-shell":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "decode-shell";
-            if (client.getBuffer(data).byteLength == 4) {
-              break;
-            }
-            ignoreResponseCode = true;
-            // eslint-disable-next-lined no-fallthrough
-          case "decode-shell":
-            const decoder = new TextDecoder();
-            const text = new Uint8Array(client.getBuffer(data),
-                                        ignoreResponseCode ? 4 : 0);
-            stdout += decoder.decode(text);
-            break;
-          default:
-            dumpn("shell Unexpected State: " + state);
-            reject("UNEXPECTED_STATE");
-        }
-      };
-
-      const socket = client.connect();
-      socket.s.onerror = function(event) {
-        dumpn("shell onerror");
-        reject("SOCKET_ERROR");
-      };
-
-      socket.s.onopen = function(event) {
-        dumpn("shell onopen");
-        state = "start";
-        runFSM();
-      };
-
-      socket.s.onclose = function(event) {
-        resolve(stdout);
-        dumpn("shell onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("shell ondata");
-        runFSM(event.data);
-      };
-    });
-  },
-
-  // Asynchronously runs an adb command.
-  // @param command The command as documented in
-  // http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
-  runCommand(command) {
-    dumpn("runCommand " + command);
-    return new Promise((resolve, reject) => {
-      if (!this.ready) {
-        setTimeout(function() {
-          reject("ADB_NOT_READY");
-        });
-        return;
-      }
-
-      const socket = client.connect();
-
-      socket.s.onopen = function() {
-        dumpn("runCommand onopen");
-        const req = client.createRequest(command);
-        socket.send(req);
-      };
-
-      socket.s.onerror = function() {
-        dumpn("runCommand onerror");
-        reject("NETWORK_ERROR");
-      };
-
-      socket.s.onclose = function() {
-        dumpn("runCommand onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("runCommand ondata");
-        const data = event.data;
-
-        const packet = client.unpackPacket(data, false);
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          dumpn("Error: " + packet.data);
-          reject("PROTOCOL_ERROR");
-          return;
-        }
-
-        resolve(packet.data);
-      };
-    });
-  },
+  });
 };
 
-exports.ADB = ADB;
+exports.shell = shell;
copy from devtools/shared/adb/adb.js
copy to devtools/shared/adb/commands/track-devices.js
--- a/devtools/shared/adb/adb.js
+++ b/devtools/shared/adb/commands/track-devices.js
@@ -1,424 +1,114 @@
 /* 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 { Cc, Ci } = require("chrome");
 const EventEmitter = require("devtools/shared/event-emitter");
-const client = require("./adb-client");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
-const { getFileForBinary } = require("./adb-binary");
 const { setTimeout } = require("resource://gre/modules/Timer.jsm");
 const { Services } = require("resource://gre/modules/Services.jsm");
-const { ConnectionManager } = require("devtools/shared/client/connection-manager");
-loader.lazyRequireGetter(this, "check",
-                         "devtools/shared/adb/adb-running-checker", true);
 
-let ready = false;
-let didRunInitially = false;
+const { ADB } = require("../adb");
+const client = require("../adb-client");
 
 const OKAY = 0x59414b4f;
 
-const ADB = {
-  get didRunInitially() {
-    return didRunInitially;
-  },
-  set didRunInitially(newVal) {
-    didRunInitially = newVal;
-  },
+// Start tracking devices connecting and disconnecting from the host.
+// We can't reuse runCommand here because we keep the socket alive.
+// @return The socket used.
+const trackDevices = function() {
+  dumpn("trackDevices");
+  const socket = client.connect();
+  let waitForFirst = true;
+  const devices = {};
 
-  get ready() {
-    return ready;
-  },
-  set ready(newVal) {
-    ready = newVal;
-  },
+  socket.s.onopen = function() {
+    dumpn("trackDevices onopen");
+    Services.obs.notifyObservers(null, "adb-track-devices-start");
+    const req = client.createRequest("host:track-devices");
+    socket.send(req);
+  };
 
-  get adbFilePromise() {
-    if (this._adbFilePromise) {
-      return this._adbFilePromise;
-    }
-    this._adbFilePromise = getFileForBinary();
-    return this._adbFilePromise;
-  },
+  socket.s.onerror = function(event) {
+    dumpn("trackDevices onerror: " + event);
+    Services.obs.notifyObservers(null, "adb-track-devices-stop");
+  };
+
+  socket.s.onclose = function() {
+    dumpn("trackDevices onclose");
 
-  async _runProcess(process, params) {
-    return new Promise((resolve, reject) => {
-      process.runAsync(params, params.length, {
-        observe(subject, topic, data) {
-          switch (topic) {
-            case "process-finished":
-              resolve();
-              break;
-            case "process-failed":
-              reject();
-              break;
-          }
-        },
-      }, false);
-    });
-  },
+    // Report all devices as disconnected
+    for (const dev in devices) {
+      devices[dev] = false;
+      EventEmitter.emit(ADB, "device-disconnected", dev);
+    }
+
+    Services.obs.notifyObservers(null, "adb-track-devices-stop");
 
-  // Waits until a predicate returns true or re-tries the predicate calls
-  // |retry| times, we wait for 100ms between each calls.
-  async _waitUntil(predicate, retry = 20) {
-    let count = 0;
-    while (count++ < retry) {
-      if (await predicate()) {
-        return true;
+    // 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.
+    setTimeout(function() { // Give some time to the new adb to start
+      if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
+        ADB.start().then(function() { // try to connect to the new local adb server
+                                       // or, spawn a new one
+          trackDevices(); // Re-track devices
+        });
       }
-      // Wait for 100 milliseconds.
-      await new Promise(resolve => setTimeout(resolve, 100));
-    }
-    // Timed out after trying too many times.
-    return false;
-  },
+    }, 2000);
+  };
 
-  // We startup by launching adb in server mode, and setting
-  // the tcp socket preference to |true|
-  async start() {
-    return new Promise(async (resolve, reject) => {
-      const onSuccessfulStart = () => {
-        Services.obs.notifyObservers(null, "adb-ready");
-        this.ready = true;
-        resolve();
-      };
+  socket.s.ondata = function(event) {
+    dumpn("trackDevices ondata");
+    const data = event.data;
+    dumpn("length=" + data.byteLength);
+    const dec = new TextDecoder();
+    dumpn(dec.decode(new Uint8Array(data)).trim());
 
-      const isAdbRunning = await check();
-      if (isAdbRunning) {
-        dumpn("Found ADB process running, not restarting");
-        onSuccessfulStart();
+    // check the OKAY or FAIL on first packet.
+    if (waitForFirst) {
+      if (!client.checkResponse(data, OKAY)) {
+        socket.close();
         return;
       }
-      dumpn("Didn't find ADB process running, restarting");
-
-      this.didRunInitially = true;
-      const process = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Ci.nsIProcess);
-      // FIXME: Bug 1481691 - We should avoid extracting files every time.
-      const adbFile = await this.adbFilePromise;
-      process.init(adbFile);
-      // Hide command prompt window on Windows
-      process.startHidden = true;
-      process.noShell = true;
-      const params = ["start-server"];
-      let isStarted = false;
-      try {
-        await this._runProcess(process, params);
-        isStarted = await this._waitUntil(check);
-      } catch (e) {
-      }
-
-      if (isStarted) {
-        onSuccessfulStart();
-      } else {
-        this.ready = false;
-        reject();
-      }
-    });
-  },
-
-  /**
-   * Stop the ADB server, but only if we started it.  If it was started before
-   * us, we return immediately.
-   */
-  async stop() {
-    if (!this.didRunInitially) {
-      return; // We didn't start the server, nothing to do
     }
-    await this.kill();
-  },
 
-  /**
-   * Kill the ADB server.
-   */
-  async kill() {
-    try {
-      await this.runCommand("host:kill");
-    } catch (e) {
-      dumpn("Failed to send host:kill command");
-    }
-    dumpn("adb server was terminated by host:kill");
-    this.ready = false;
-    this.didRunInitially = false;
-  },
+    const packet = client.unpackPacket(data, !waitForFirst);
+    waitForFirst = false;
 
-  // Start tracking devices connecting and disconnecting from the host.
-  // We can't reuse runCommand here because we keep the socket alive.
-  // @return The socket used.
-  trackDevices() {
-    dumpn("trackDevices");
-    const socket = client.connect();
-    let waitForFirst = true;
-    const devices = {};
-
-    socket.s.onopen = function() {
-      dumpn("trackDevices onopen");
-      Services.obs.notifyObservers(null, "adb-track-devices-start");
-      const req = client.createRequest("host:track-devices");
-      socket.send(req);
-    };
-
-    socket.s.onerror = function(event) {
-      dumpn("trackDevices onerror: " + event);
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-    };
-
-    socket.s.onclose = function() {
-      dumpn("trackDevices onclose");
-
-      // Report all devices as disconnected
+    if (packet.data == "") {
+      // All devices got disconnected.
       for (const dev in devices) {
         devices[dev] = false;
         EventEmitter.emit(ADB, "device-disconnected", dev);
       }
-
-      Services.obs.notifyObservers(null, "adb-track-devices-stop");
-
-      // 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.
-      setTimeout(function() { // Give some time to the new adb to start
-        if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
-          ADB.start().then(function() { // try to connect to the new local adb server
-                                         // or, spawn a new one
-            ADB.trackDevices(); // Re-track devices
-          });
-        }
-      }, 2000);
-    };
-
-    socket.s.ondata = function(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 (waitForFirst) {
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          return;
-        }
-      }
-
-      const packet = client.unpackPacket(data, !waitForFirst);
-      waitForFirst = false;
-
-      if (packet.data == "") {
-        // All devices got disconnected.
-        for (const dev in devices) {
-          devices[dev] = false;
-          EventEmitter.emit(ADB, "device-disconnected", dev);
-        }
-      } else {
-        // One line per device, each line being $DEVICE\t(offline|device)
-        const lines = packet.data.split("\n");
-        const newDev = {};
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-
-          const [dev, status] = line.split("\t");
-          newDev[dev] = status !== "offline";
-        });
-        // Check which device changed state.
-        for (const dev in newDev) {
-          if (devices[dev] != newDev[dev]) {
-            if (dev in devices || newDev[dev]) {
-              const topic = newDev[dev] ? "device-connected"
-                                        : "device-disconnected";
-              EventEmitter.emit(ADB, topic, dev);
-            }
-            devices[dev] = newDev[dev];
-          }
-        }
-      }
-    };
-  },
-
-  // Sends back an array of device names.
-  listDevices() {
-    dumpn("listDevices");
-
-    return this.runCommand("host:devices").then(
-      function onSuccess(data) {
-        const lines = data.split("\n");
-        const res = [];
-        lines.forEach(function(line) {
-          if (line.length == 0) {
-            return;
-          }
-          const [ device ] = line.split("\t");
-          res.push(device);
-        });
-        return res;
-      }
-    );
-  },
-
-  // sends adb forward localPort devicePort
-  forwardPort(localPort, devicePort) {
-    dumpn("forwardPort " + localPort + " -- " + devicePort);
-    // <host-prefix>:forward:<local>;<remote>
-
-    return this.runCommand("host:forward:" + localPort + ";" + devicePort)
-               .then(function onSuccess(data) {
-                 return data;
-               });
-  },
-
-  // Prepare TCP connection for provided socket path.
-  // The returned value is a port number of localhost for the connection.
-  async prepareTCPConnection(socketPath) {
-    const port = ConnectionManager.getFreeTCPPort();
-    const local = `tcp:${ port }`;
-    const remote = socketPath.startsWith("@")
-                     ? `localabstract:${ socketPath.substring(1) }`
-                     : `localfilesystem:${ socketPath }`;
-    await this.forwardPort(local, remote);
-    return port;
-  },
-
-  // Run a shell command
-  async shell(command) {
-    let state;
-    let stdout = "";
-
-    dumpn("shell " + command);
-
-    return new Promise((resolve, reject) => {
-      const shutdown = function() {
-        dumpn("shell shutdown");
-        socket.close();
-        reject("BAD_RESPONSE");
-      };
-
-      const runFSM = function runFSM(data) {
-        dumpn("runFSM " + state);
-        let req;
-        let ignoreResponseCode = false;
-        switch (state) {
-          case "start":
-            state = "send-transport";
-            runFSM();
-            break;
-          case "send-transport":
-            req = client.createRequest("host:transport-any");
-            socket.send(req);
-            state = "wait-transport";
-            break;
-          case "wait-transport":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "send-shell";
-            runFSM();
-            break;
-          case "send-shell":
-            req = client.createRequest("shell:" + command);
-            socket.send(req);
-            state = "rec-shell";
-            break;
-          case "rec-shell":
-            if (!client.checkResponse(data, OKAY)) {
-              shutdown();
-              return;
-            }
-            state = "decode-shell";
-            if (client.getBuffer(data).byteLength == 4) {
-              break;
-            }
-            ignoreResponseCode = true;
-            // eslint-disable-next-lined no-fallthrough
-          case "decode-shell":
-            const decoder = new TextDecoder();
-            const text = new Uint8Array(client.getBuffer(data),
-                                        ignoreResponseCode ? 4 : 0);
-            stdout += decoder.decode(text);
-            break;
-          default:
-            dumpn("shell Unexpected State: " + state);
-            reject("UNEXPECTED_STATE");
-        }
-      };
-
-      const socket = client.connect();
-      socket.s.onerror = function(event) {
-        dumpn("shell onerror");
-        reject("SOCKET_ERROR");
-      };
-
-      socket.s.onopen = function(event) {
-        dumpn("shell onopen");
-        state = "start";
-        runFSM();
-      };
-
-      socket.s.onclose = function(event) {
-        resolve(stdout);
-        dumpn("shell onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("shell ondata");
-        runFSM(event.data);
-      };
-    });
-  },
-
-  // Asynchronously runs an adb command.
-  // @param command The command as documented in
-  // http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
-  runCommand(command) {
-    dumpn("runCommand " + command);
-    return new Promise((resolve, reject) => {
-      if (!this.ready) {
-        setTimeout(function() {
-          reject("ADB_NOT_READY");
-        });
-        return;
-      }
-
-      const socket = client.connect();
-
-      socket.s.onopen = function() {
-        dumpn("runCommand onopen");
-        const req = client.createRequest(command);
-        socket.send(req);
-      };
-
-      socket.s.onerror = function() {
-        dumpn("runCommand onerror");
-        reject("NETWORK_ERROR");
-      };
-
-      socket.s.onclose = function() {
-        dumpn("runCommand onclose");
-      };
-
-      socket.s.ondata = function(event) {
-        dumpn("runCommand ondata");
-        const data = event.data;
-
-        const packet = client.unpackPacket(data, false);
-        if (!client.checkResponse(data, OKAY)) {
-          socket.close();
-          dumpn("Error: " + packet.data);
-          reject("PROTOCOL_ERROR");
+    } else {
+      // One line per device, each line being $DEVICE\t(offline|device)
+      const lines = packet.data.split("\n");
+      const newDev = {};
+      lines.forEach(function(line) {
+        if (line.length == 0) {
           return;
         }
 
-        resolve(packet.data);
-      };
-    });
-  },
+        const [dev, status] = line.split("\t");
+        newDev[dev] = status !== "offline";
+      });
+      // Check which device changed state.
+      for (const dev in newDev) {
+        if (devices[dev] != newDev[dev]) {
+          if (dev in devices || newDev[dev]) {
+            const topic = newDev[dev] ? "device-connected"
+                                      : "device-disconnected";
+            EventEmitter.emit(ADB, topic, dev);
+          }
+          devices[dev] = newDev[dev];
+        }
+      }
+    }
+  };
 };
-
-exports.ADB = ADB;
+exports.trackDevices = trackDevices;
--- a/devtools/shared/adb/moz.build
+++ b/devtools/shared/adb/moz.build
@@ -1,12 +1,16 @@
 # 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/.
 
+DIRS += [
+    'commands',
+]
+
 DevToolsModules(
     'adb-addon.js',
     'adb-binary.js',
     'adb-client.js',
     'adb-device.js',
     'adb-devices-registry.js',
     'adb-running-checker.js',
     'adb-runtime.js',
--- a/devtools/shared/adb/test/test_adb.js
+++ b/devtools/shared/adb/test/test_adb.js
@@ -4,16 +4,17 @@
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const { ExtensionTestUtils } = ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm", {});
 const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
 const { getFileForBinary } = require("devtools/shared/adb/adb-binary");
 const { check } = require("devtools/shared/adb/adb-running-checker");
 const { ADB } = require("devtools/shared/adb/adb");
+const { trackDevices } = require("devtools/shared/adb/commands/index");
 
 const ADB_JSON = {
   "Linux": {
     "x86": [
       "linux/adb",
     ],
     "x86_64": [
       "linux64/adb",
@@ -224,17 +225,17 @@ add_task({
   ok(ADB.ready);
 
   ok(await check(), "adb is now running");
 
   const receivedDeviceId = await new Promise(resolve => {
     EventEmitter.on(ADB, "device-connected", deviceId => {
       resolve(deviceId);
     });
-    ADB.trackDevices();
+    trackDevices();
   });
 
   equal(receivedDeviceId, "1234567890");
 
   await ADB.stop();
   ok(!ADB.ready);
 
   await extension.unload();