Browse Source

TOP patches for removal of mozrunner devices and mozdevice

Ian Neal 3 months ago
parent
commit
3d909431bf

+ 29 - 0
comm-release/patches/TOP-1906540-mozdevice-removal-comm-25320.patch

@@ -0,0 +1,29 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1720365201 -3600
+# Parent  5b0ec655a28cf67272296c7768714c8f06b3a9af
+Bug 1906540 - Remove mozrunner devices and mozdevice from SeaMonkey - mozdevice part in comm. r=frg a=frg
+
+diff --git a/mail/test/resources/installmozmill.py b/mail/test/resources/installmozmill.py
+--- a/mail/test/resources/installmozmill.py
++++ b/mail/test/resources/installmozmill.py
+@@ -87,18 +87,17 @@ def main(args=None):
+                      destination], env=env)
+   if returncode:
+     print('Failure to install virtualenv')
+     sys.exit(returncode)
+   pip = entry_point_path(destination, 'pip')
+ 
+   # Install mozbase packages to the virtualenv
+   mozbase_packages = ['manifestparser', 'mozfile', 'mozinfo', 'mozlog',
+-    'mozprofile', 'mozcrash', 'moznetwork', 'mozprocess', 'mozdevice',
+-    'mozrunner']
++    'mozprofile', 'mozcrash', 'moznetwork', 'mozprocess', 'mozrunner']
+   returncode = call([pip, 'install'] +
+     [os.path.join(mozbase, package) for package in mozbase_packages], env=env)
+   if returncode:
+     print('Failure to install packages')
+     sys.exit(returncode)
+ 
+   # Install mozmill
+   returncode = call([pip, 'install'] + [os.path.abspath(package) for package in packages], env=env)

+ 1 - 0
comm-release/patches/series

@@ -2162,3 +2162,4 @@ TOP-1872623-cancelbookmark-25319.patch
 1861843-2-version-beta-cr-25319.patch
 1902849-version-release-cr-25319.patch
 1902851-1-version-prebeta-cr-25320.patch
+TOP-1906540-mozdevice-removal-comm-25320.patch

+ 9078 - 0
mozilla-release/patches/TOP-1906540-mozdevice-removal-25320.patch

@@ -0,0 +1,9078 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1720364642 -3600
+# Parent  95cbce82a999b85cc352403f8de9db0d6df0a541
+Bug 1906540 - Remove mozrunner devices and mozdevice from SeaMonkey - mozdevice part. r=frg a=frg
+
+diff --git a/config/rules.mk b/config/rules.mk
+--- a/config/rules.mk
++++ b/config/rules.mk
+@@ -63,23 +63,16 @@ CPP_UNIT_TESTS_FILES = $(CPP_UNIT_TESTS)
+ CPP_UNIT_TESTS_DEST = $(DIST)/cppunittests
+ CPP_UNIT_TESTS_TARGET = target
+ INSTALL_TARGETS += CPP_UNIT_TESTS
+ endif
+ 
+ run-cppunittests::
+ 	@$(PYTHON3) $(MOZILLA_DIR)/testing/runcppunittests.py --xre-path=$(DIST)/bin --symbols-path=$(DIST)/crashreporter-symbols $(CPP_UNIT_TESTS)
+ 
+-cppunittests-remote:
+-	$(PYTHON3) -u $(MOZILLA_DIR)/testing/remotecppunittests.py \
+-		--xre-path=$(DEPTH)/dist/bin \
+-		--localLib=$(DEPTH)/dist/$(MOZ_APP_NAME) \
+-		--deviceIP=${TEST_DEVICE} \
+-		$(CPP_UNIT_TESTS) $(EXTRA_TEST_ARGS); \
+-
+ endif # COMPILE_ENVIRONMENT
+ endif # CPP_UNIT_TESTS
+ endif # ENABLE_TESTS
+ 
+ 
+ #
+ # Library rules
+ #
+diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py
+--- a/js/src/jit-test/jit_test.py
++++ b/js/src/jit-test/jit_test.py
+@@ -189,26 +189,16 @@ def main(argv):
+ 
+     if not (os.path.isfile(js_shell) and os.access(js_shell, os.X_OK)):
+         if (platform.system() != 'Windows' or
+             os.path.isfile(js_shell) or not
+             os.path.isfile(js_shell + ".exe") or not
+             os.access(js_shell + ".exe", os.X_OK)):
+             op.error('shell is not executable: ' + js_shell)
+ 
+-    if jittests.stdio_might_be_broken():
+-        # Prefer erring on the side of caution and not using stdio if
+-        # it might be broken on this platform.  The file-redirect
+-        # fallback should work on any platform, so at worst by
+-        # guessing wrong we might have slowed down the tests a bit.
+-        #
+-        # XXX technically we could check for broken stdio, but it
+-        # really seems like overkill.
+-        options.avoid_stdio = True
+-
+     if options.retest:
+         options.read_tests = options.retest
+         options.write_failures = options.retest
+ 
+     test_list = []
+     read_all = True
+ 
+     # No point in adding in noasmjs and wasm-baseline variants if the
+diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py
+--- a/js/src/tests/lib/jittests.py
++++ b/js/src/tests/lib/jittests.py
+@@ -659,20 +659,17 @@ def process_test_results(results, num_te
+     return print_test_summary(num_tests, failures, complete, doing, options)
+ 
+ def run_tests(tests, num_tests, prefix, options, remote=False):
+     slog = None
+     if options.format == 'automation':
+         slog = TestLogger("jittests")
+         slog.suite_start()
+ 
+-    if remote:
+-        ok = run_tests_remote(tests, num_tests, prefix, options, slog)
+-    else:
+-        ok = run_tests_local(tests, num_tests, prefix, options, slog)
++    ok = run_tests_local(tests, num_tests, prefix, options, slog)
+ 
+     if slog:
+         slog.suite_end()
+ 
+     return ok
+ 
+ def run_tests_local(tests, num_tests, prefix, options, slog):
+     # The jstests tasks runner requires the following options. The names are
+@@ -688,27 +685,16 @@ def run_tests_local(tests, num_tests, pr
+     # The test runner wants the prefix as a static on the Test class.
+     JitTest.js_cmd_prefix = prefix
+ 
+     pb = create_progressbar(num_tests, options)
+     gen = run_all_tests(tests, prefix, pb, shim_options)
+     ok = process_test_results(gen, num_tests, pb, options, slog)
+     return ok
+ 
+-def get_remote_results(tests, device, prefix, options):
+-    try:
+-        for i in xrange(0, options.repeat):
+-            for test in tests:
+-                yield run_test_remote(test, device, prefix, options)
+-    except Exception as e:
+-        # After a device error, the device is typically in a
+-        # state where all further tests will fail so there is no point in
+-        # continuing here.
+-        sys.stderr.write("Error running remote tests: {}".format(e.message))
+-
+ def push_libs(options, device):
+     # This saves considerable time in pushing unnecessary libraries
+     # to the device but needs to be updated if the dependencies change.
+     required_libs = ['libnss3.so', 'libmozglue.so', 'libnspr4.so',
+                      'libplc4.so', 'libplds4.so']
+ 
+     for file in os.listdir(options.local_lib):
+         if file in required_libs:
+@@ -716,57 +702,10 @@ def push_libs(options, device):
+             device.push(os.path.join(options.local_lib, file), remote_file)
+ 
+ def push_progs(options, device, progs):
+     for local_file in progs:
+         remote_file = posixpath.join(options.remote_test_root,
+                                      os.path.basename(local_file))
+         device.push(local_file, remote_file)
+ 
+-def run_tests_remote(tests, num_tests, prefix, options, slog):
+-    # Setup device with everything needed to run our tests.
+-    from mozdevice import ADBAndroid
+-    device = ADBAndroid(device=options.device_serial,
+-                        test_root=options.remote_test_root)
+-
+-    # Update the test root to point to our test directory.
+-    jit_tests_dir = posixpath.join(options.remote_test_root, 'jit-tests')
+-    options.remote_test_root = posixpath.join(jit_tests_dir, 'jit-tests')
+-
+-    # Push js shell and libraries.
+-    device.rm(jit_tests_dir, force=True, recursive=True)
+-    device.mkdir(options.remote_test_root, parents=True)
+-    push_libs(options, device)
+-    push_progs(options, device, [prefix[0]])
+-    device.chmod(options.remote_test_root, recursive=True)
+-
+-    JitTest.CacheDir = posixpath.join(options.remote_test_root, '.js-cache')
+-    device.mkdir(JitTest.CacheDir)
+-
+-    device.push(JS_TESTS_DIR, posixpath.join(jit_tests_dir, 'tests'),
+-                timeout=600)
+-
+-    device.push(os.path.dirname(TEST_DIR), options.remote_test_root,
+-                timeout=600)
+-    prefix[0] = os.path.join(options.remote_test_root, 'js')
+-
+-    # Run all tests.
+-    pb = create_progressbar(num_tests, options)
+-    gen = get_remote_results(tests, device, prefix, options)
+-    ok = process_test_results(gen, num_tests, pb, options, slog)
+-    return ok
+-
+-def platform_might_be_android():
+-    try:
+-        # The python package for SL4A provides an |android| module.
+-        # If that module is present, we're likely in SL4A-python on
+-        # device.  False positives and negatives are possible,
+-        # however.
+-        import android  # NOQA: F401
+-        return True
+-    except ImportError:
+-        return False
+-
+-def stdio_might_be_broken():
+-    return platform_might_be_android()
+-
+ if __name__ == '__main__':
+     print('Use ../jit-test/jit_test.py to run these tests.')
+diff --git a/layout/tools/reftest/moz.build b/layout/tools/reftest/moz.build
+--- a/layout/tools/reftest/moz.build
++++ b/layout/tools/reftest/moz.build
+@@ -16,17 +16,16 @@ FINAL_TARGET_FILES += ['bootstrap.js']
+ TEST_HARNESS_FILES.reftest += [
+     '/build/mobile/remoteautomation.py',
+     '/build/pgo/server-locations.txt',
+     '/testing/mochitest/server.js',
+     'mach_test_package_commands.py',
+     'output.py',
+     'reftest-preferences.js',
+     'reftestcommandline.py',
+-    'remotereftest.py',
+     'runreftest.py',
+ ]
+ 
+ TEST_HARNESS_FILES.reftest.chrome += [
+     'chrome/binding.xml',
+     'chrome/userContent.css',
+ ]
+ 
+diff --git a/layout/tools/reftest/remotereftest.py b/layout/tools/reftest/remotereftest.py
+deleted file mode 100644
+--- a/layout/tools/reftest/remotereftest.py
++++ /dev/null
+@@ -1,452 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import, print_function
+-
+-import os
+-import posixpath
+-import psutil
+-import signal
+-import sys
+-import tempfile
+-import time
+-import traceback
+-import urllib2
+-from contextlib import closing
+-
+-from mozdevice import ADBAndroid
+-import mozinfo
+-from automation import Automation
+-from remoteautomation import RemoteAutomation, fennecLogcatFilters
+-
+-from output import OutputHandler
+-from runreftest import RefTest, ReftestResolver
+-import reftestcommandline
+-
+-# We need to know our current directory so that we can serve our test files from it.
+-SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
+-
+-
+-class RemoteReftestResolver(ReftestResolver):
+-    def absManifestPath(self, path):
+-        script_abs_path = os.path.join(SCRIPT_DIRECTORY, path)
+-        if os.path.exists(script_abs_path):
+-            rv = script_abs_path
+-        elif os.path.exists(os.path.abspath(path)):
+-            rv = os.path.abspath(path)
+-        else:
+-            print("Could not find manifest %s" % script_abs_path, file=sys.stderr)
+-            sys.exit(1)
+-        return os.path.normpath(rv)
+-
+-    def manifestURL(self, options, path):
+-        # Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside'
+-        # webroot. It's possible for this url to have a leading "..", but reftest.js will fix that
+-        relPath = os.path.relpath(path, SCRIPT_DIRECTORY)
+-        return "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, relPath)
+-
+-
+-class ReftestServer:
+-    """ Web server used to serve Reftests, for closer fidelity to the real web.
+-        It is virtually identical to the server used in mochitest and will only
+-        be used for running reftests remotely.
+-        Bug 581257 has been filed to refactor this wrapper around httpd.js into
+-        it's own class and use it in both remote and non-remote testing. """
+-
+-    def __init__(self, automation, options, scriptDir):
+-        self.automation = automation
+-        self.utilityPath = options.utilityPath
+-        self.xrePath = options.xrePath
+-        self.profileDir = options.serverProfilePath
+-        self.webServer = options.remoteWebServer
+-        self.httpPort = options.httpPort
+-        self.scriptDir = scriptDir
+-        self.httpdPath = os.path.abspath(options.httpdPath)
+-        if options.remoteWebServer == "10.0.2.2":
+-            # probably running an Android emulator and 10.0.2.2 will
+-            # not be visible from host
+-            shutdownServer = "127.0.0.1"
+-        else:
+-            shutdownServer = self.webServer
+-        self.shutdownURL = "http://%(server)s:%(port)s/server/shutdown" % {
+-                           "server": shutdownServer, "port": self.httpPort}
+-
+-    def start(self):
+-        "Run the Refest server, returning the process ID of the server."
+-
+-        env = self.automation.environment(xrePath=self.xrePath)
+-        env["XPCOM_DEBUG_BREAK"] = "warn"
+-        if self.automation.IS_WIN32:
+-            env["PATH"] = env["PATH"] + ";" + self.xrePath
+-
+-        args = ["-g", self.xrePath,
+-                "-f", os.path.join(self.httpdPath, "httpd.js"),
+-                "-e", "const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = "
+-                      "'%(port)s'; const _SERVER_ADDR ='%(server)s';" % {
+-                      "profile": self.profileDir.replace('\\', '\\\\'), "port": self.httpPort,
+-                      "server": self.webServer},
+-                "-f", os.path.join(self.scriptDir, "server.js")]
+-
+-        xpcshell = os.path.join(self.utilityPath,
+-                                "xpcshell" + self.automation.BIN_SUFFIX)
+-
+-        if not os.access(xpcshell, os.F_OK):
+-            raise Exception('xpcshell not found at %s' % xpcshell)
+-        if self.automation.elf_arm(xpcshell):
+-            raise Exception('xpcshell at %s is an ARM binary; please use '
+-                            'the --utility-path argument to specify the path '
+-                            'to a desktop version.' % xpcshell)
+-
+-        self._process = self.automation.Process([xpcshell] + args, env=env)
+-        pid = self._process.pid
+-        if pid < 0:
+-            print("TEST-UNEXPECTED-FAIL | remotereftests.py | Error starting server.")
+-            return 2
+-        self.automation.log.info("INFO | remotereftests.py | Server pid: %d", pid)
+-
+-    def ensureReady(self, timeout):
+-        assert timeout >= 0
+-
+-        aliveFile = os.path.join(self.profileDir, "server_alive.txt")
+-        i = 0
+-        while i < timeout:
+-            if os.path.exists(aliveFile):
+-                break
+-            time.sleep(1)
+-            i += 1
+-        else:
+-            print("TEST-UNEXPECTED-FAIL | remotereftests.py | "
+-                  "Timed out while waiting for server startup.")
+-            self.stop()
+-            return 1
+-
+-    def stop(self):
+-        if hasattr(self, '_process'):
+-            try:
+-                with closing(urllib2.urlopen(self.shutdownURL)) as c:
+-                    c.read()
+-
+-                rtncode = self._process.poll()
+-                if (rtncode is None):
+-                    self._process.terminate()
+-            except Exception:
+-                self.automation.log.info("Failed to shutdown server at %s" %
+-                                         self.shutdownURL)
+-                traceback.print_exc()
+-                self._process.kill()
+-
+-
+-class RemoteReftest(RefTest):
+-    use_marionette = False
+-    resolver_cls = RemoteReftestResolver
+-
+-    def __init__(self, options, scriptDir):
+-        RefTest.__init__(self, options.suite)
+-        self.run_by_manifest = False
+-        self.scriptDir = scriptDir
+-        self.localLogName = options.localLogName
+-
+-        verbose = False
+-        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+-            verbose = True
+-            print("set verbose!")
+-        self.device = ADBAndroid(adb=options.adb_path,
+-                                 device=options.deviceSerial,
+-                                 test_root=options.remoteTestRoot,
+-                                 verbose=verbose)
+-
+-        if options.remoteTestRoot is None:
+-            options.remoteTestRoot = posixpath.join(self.device.test_root, "reftest")
+-        options.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
+-        options.remoteLogFile = posixpath.join(options.remoteTestRoot, "reftest.log")
+-        options.logFile = options.remoteLogFile
+-        self.remoteProfile = options.remoteProfile
+-        self.remoteTestRoot = options.remoteTestRoot
+-
+-        if not options.ignoreWindowSize:
+-            parts = self.device.get_info(
+-                'screen')['screen'][0].split()
+-            width = int(parts[0].split(':')[1])
+-            height = int(parts[1].split(':')[1])
+-            if (width < 1366 or height < 1050):
+-                self.error("ERROR: Invalid screen resolution %sx%s, "
+-                           "please adjust to 1366x1050 or higher" % (
+-                            width, height))
+-
+-        self._populate_logger(options)
+-        self.outputHandler = OutputHandler(self.log, options.utilityPath, options.symbolsPath)
+-        # RemoteAutomation.py's 'messageLogger' is also used by mochitest. Mimic a mochitest
+-        # MessageLogger object to re-use this code path.
+-        self.outputHandler.write = self.outputHandler.__call__
+-        self.automation = RemoteAutomation(self.device, options.app, self.remoteProfile,
+-                                           options.remoteLogFile, processArgs=None)
+-        self.automation._processArgs['messageLogger'] = self.outputHandler
+-
+-        self.environment = self.automation.environment
+-        if self.automation.IS_DEBUG_BUILD:
+-            self.SERVER_STARTUP_TIMEOUT = 180
+-        else:
+-            self.SERVER_STARTUP_TIMEOUT = 90
+-
+-        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
+-
+-        # Check that Firefox is installed
+-        expected = options.app.split('/')[-1]
+-        if not self.device.is_app_installed(expected):
+-            raise Exception("%s is not installed on this device" % expected)
+-
+-        self.automation.deleteANRs()
+-        self.automation.deleteTombstones()
+-        self.device.clear_logcat()
+-
+-        self.device.rm(self.remoteCache, force=True, recursive=True)
+-
+-        procName = options.app.split('/')[-1]
+-        self.device.pkill(procName)
+-        if self.device.process_exist(procName):
+-            self.log.error("unable to kill %s before starting tests!" % procName)
+-
+-    def findPath(self, paths, filename=None):
+-        for path in paths:
+-            p = path
+-            if filename:
+-                p = os.path.join(p, filename)
+-            if os.path.exists(self.getFullPath(p)):
+-                return path
+-        return None
+-
+-    def startWebServer(self, options):
+-        """ Create the webserver on the host and start it up """
+-        remoteXrePath = options.xrePath
+-        remoteUtilityPath = options.utilityPath
+-        localAutomation = Automation()
+-        localAutomation.IS_WIN32 = False
+-        localAutomation.IS_LINUX = False
+-        localAutomation.IS_MAC = False
+-        localAutomation.UNIXISH = False
+-        hostos = sys.platform
+-        if (hostos == 'mac' or hostos == 'darwin'):
+-            localAutomation.IS_MAC = True
+-        elif (hostos == 'linux' or hostos == 'linux2'):
+-            localAutomation.IS_LINUX = True
+-            localAutomation.UNIXISH = True
+-        elif (hostos == 'win32' or hostos == 'win64'):
+-            localAutomation.BIN_SUFFIX = ".exe"
+-            localAutomation.IS_WIN32 = True
+-
+-        paths = [options.xrePath, localAutomation.DIST_BIN]
+-        options.xrePath = self.findPath(paths)
+-        if options.xrePath is None:
+-            print("ERROR: unable to find xulrunner path for %s, "
+-                  "please specify with --xre-path" % (os.name))
+-            return 1
+-        paths.append("bin")
+-        paths.append(os.path.join("..", "bin"))
+-
+-        xpcshell = "xpcshell"
+-        if (os.name == "nt"):
+-            xpcshell += ".exe"
+-
+-        if (options.utilityPath):
+-            paths.insert(0, options.utilityPath)
+-        options.utilityPath = self.findPath(paths, xpcshell)
+-        if options.utilityPath is None:
+-            print("ERROR: unable to find utility path for %s, "
+-                  "please specify with --utility-path" % (os.name))
+-            return 1
+-
+-        options.serverProfilePath = tempfile.mkdtemp()
+-        self.server = ReftestServer(localAutomation, options, self.scriptDir)
+-        retVal = self.server.start()
+-        if retVal:
+-            return retVal
+-        retVal = self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
+-        if retVal:
+-            return retVal
+-
+-        options.xrePath = remoteXrePath
+-        options.utilityPath = remoteUtilityPath
+-        return 0
+-
+-    def stopWebServer(self, options):
+-        self.server.stop()
+-
+-    def killNamedProc(self, pname, orphans=True):
+-        """ Kill processes matching the given command name """
+-        self.log.info("Checking for %s processes..." % pname)
+-
+-        for proc in psutil.process_iter():
+-            try:
+-                if proc.name() == pname:
+-                    procd = proc.as_dict(attrs=['pid', 'ppid', 'name', 'username'])
+-                    if proc.ppid() == 1 or not orphans:
+-                        self.log.info("killing %s" % procd)
+-                        try:
+-                            os.kill(proc.pid, getattr(signal, "SIGKILL", signal.SIGTERM))
+-                        except Exception as e:
+-                            self.log.info("Failed to kill process %d: %s" % (proc.pid, str(e)))
+-                    else:
+-                        self.log.info("NOT killing %s (not an orphan?)" % procd)
+-            except Exception:
+-                # may not be able to access process info for all processes
+-                continue
+-
+-    def createReftestProfile(self, options, **kwargs):
+-        profile = RefTest.createReftestProfile(self,
+-                                               options,
+-                                               server=options.remoteWebServer,
+-                                               port=options.httpPort,
+-                                               **kwargs)
+-        profileDir = profile.profile
+-        prefs = {}
+-        prefs["app.update.url.android"] = ""
+-        prefs["browser.firstrun.show.localepicker"] = False
+-        prefs["reftest.remote"] = True
+-        prefs["datareporting.policy.dataSubmissionPolicyBypassAcceptance"] = True
+-        # move necko cache to a location that can be cleaned up
+-        prefs["browser.cache.disk.parent_directory"] = self.remoteCache
+-
+-        prefs["layout.css.devPixelsPerPx"] = "1.0"
+-        # Because Fennec is a little wacky (see bug 1156817) we need to load the
+-        # reftest pages at 1.0 zoom, rather than zooming to fit the CSS viewport.
+-        prefs["apz.allow_zooming"] = False
+-
+-        # Set the extra prefs.
+-        profile.set_preferences(prefs)
+-
+-        try:
+-            self.device.push(profileDir, options.remoteProfile)
+-            self.device.chmod(options.remoteProfile, recursive=True)
+-        except Exception:
+-            print("Automation Error: Failed to copy profiledir to device")
+-            raise
+-
+-        return profile
+-
+-    def copyExtraFilesToProfile(self, options, profile):
+-        profileDir = profile.profile
+-        RefTest.copyExtraFilesToProfile(self, options, profile)
+-        if len(os.listdir(profileDir)) > 0:
+-            try:
+-                self.device.push(profileDir, options.remoteProfile)
+-                self.device.chmod(options.remoteProfile, recursive=True)
+-            except Exception:
+-                print("Automation Error: Failed to copy extra files to device")
+-                raise
+-
+-    def printDeviceInfo(self, printLogcat=False):
+-        try:
+-            if printLogcat:
+-                logcat = self.device.get_logcat(filter_out_regexps=fennecLogcatFilters)
+-                print(''.join(logcat))
+-            print("Device info:")
+-            devinfo = self.device.get_info()
+-            for category in devinfo:
+-                if type(devinfo[category]) is list:
+-                    print("  %s:" % category)
+-                    for item in devinfo[category]:
+-                        print("     %s" % item)
+-                else:
+-                    print("  %s: %s" % (category, devinfo[category]))
+-            print("Test root: %s" % self.device.test_root)
+-        except Exception as e:
+-            print("WARNING: Error getting device information: %s" % str(e))
+-
+-    def environment(self, **kwargs):
+-        return self.automation.environment(**kwargs)
+-
+-    def buildBrowserEnv(self, options, profileDir):
+-        browserEnv = RefTest.buildBrowserEnv(self, options, profileDir)
+-        # remove desktop environment not used on device
+-        if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
+-            del browserEnv["XPCOM_MEM_BLOAT_LOG"]
+-        return browserEnv
+-
+-    def runApp(self, options, cmdargs=None, timeout=None, debuggerInfo=None, symbolsPath=None,
+-               valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None, **profileArgs):
+-        if cmdargs is None:
+-            cmdargs = []
+-
+-        if self.use_marionette:
+-            cmdargs.append('-marionette')
+-
+-        binary = options.app
+-        profile = self.createReftestProfile(options, **profileArgs)
+-
+-        # browser environment
+-        env = self.buildBrowserEnv(options, profile.profile)
+-
+-        self.log.info("Running with e10s: {}".format(options.e10s))
+-        status, self.lastTestSeen = self.automation.runApp(None, env,
+-                                                           binary,
+-                                                           profile.profile,
+-                                                           cmdargs,
+-                                                           utilityPath=options.utilityPath,
+-                                                           xrePath=options.xrePath,
+-                                                           debuggerInfo=debuggerInfo,
+-                                                           symbolsPath=symbolsPath,
+-                                                           timeout=timeout)
+-
+-        self.cleanup(profile.profile)
+-        return status
+-
+-    def cleanup(self, profileDir):
+-        self.device.rm(self.remoteTestRoot,  force=True, recursive=True)
+-        self.device.rm(self.remoteProfile, force=True, recursive=True)
+-        self.device.rm(self.remoteCache, force=True, recursive=True)
+-        RefTest.cleanup(self, profileDir)
+-
+-
+-def run_test_harness(parser, options):
+-    reftest = RemoteReftest(options, SCRIPT_DIRECTORY)
+-    parser.validate_remote(options, reftest.automation)
+-    parser.validate(options, reftest)
+-
+-    if mozinfo.info['debug']:
+-        print("changing timeout for remote debug reftests from %s to 600 seconds"
+-              % options.timeout)
+-        options.timeout = 600
+-
+-    # Hack in a symbolic link for jsreftest
+-    os.system("ln -s ../jsreftest " + str(os.path.join(SCRIPT_DIRECTORY, "jsreftest")))
+-
+-    # Despite our efforts to clean up servers started by this script, in practice
+-    # we still see infrequent cases where a process is orphaned and interferes
+-    # with future tests, typically because the old server is keeping the port in use.
+-    # Try to avoid those failures by checking for and killing servers before
+-    # trying to start new ones.
+-    reftest.killNamedProc('ssltunnel')
+-    reftest.killNamedProc('xpcshell')
+-
+-    # Start the webserver
+-    retVal = reftest.startWebServer(options)
+-    if retVal:
+-        return retVal
+-
+-    if options.printDeviceInfo:
+-        reftest.printDeviceInfo()
+-
+-    retVal = 0
+-    try:
+-        if options.verify:
+-            retVal = reftest.verifyTests(options.tests, options)
+-        else:
+-            retVal = reftest.runTests(options.tests, options)
+-    except Exception:
+-        print("Automation Error: Exception caught while running tests")
+-        traceback.print_exc()
+-        retVal = 1
+-
+-    reftest.stopWebServer(options)
+-
+-    if options.printDeviceInfo:
+-        reftest.printDeviceInfo(printLogcat=True)
+-
+-    return retVal
+-
+-
+-if __name__ == "__main__":
+-    parser = reftestcommandline.RemoteArgumentsParser()
+-    options = parser.parse_args()
+-    sys.exit(run_test_harness(parser, options))
+diff --git a/python/mozperftest/setup.py b/python/mozperftest/setup.py
+--- a/python/mozperftest/setup.py
++++ b/python/mozperftest/setup.py
+@@ -4,17 +4,17 @@
+ 
+ from __future__ import absolute_import
+ 
+ from setuptools import setup
+ 
+ PACKAGE_NAME = "mozperftest"
+ PACKAGE_VERSION = "0.1"
+ 
+-deps = ["mozlog >= 6.0", "mozdevice >= 3.0.2", "mozproxy", "mozinfo"]
++deps = ["mozlog >= 6.0", "mozproxy", "mozinfo"]
+ 
+ setup(
+     name=PACKAGE_NAME,
+     version=PACKAGE_VERSION,
+     description="Mozilla's mach perftest command",
+     classifiers=[
+         "Programming Language :: Python :: 3.6",
+     ],
+diff --git a/testing/config/mozbase_requirements.txt b/testing/config/mozbase_requirements.txt
+--- a/testing/config/mozbase_requirements.txt
++++ b/testing/config/mozbase_requirements.txt
+@@ -1,12 +1,11 @@
+ ../mozbase/manifestparser
+ ../mozbase/mozcrash
+ ../mozbase/mozdebug
+-../mozbase/mozdevice
+ ../mozbase/mozfile
+ ../mozbase/mozhttpd
+ ../mozbase/mozinfo
+ ../mozbase/mozinstall
+ ../mozbase/mozleak
+ ../mozbase/mozlog
+ ../mozbase/moznetwork
+ ../mozbase/mozprocess
+diff --git a/testing/marionette/harness/requirements.txt b/testing/marionette/harness/requirements.txt
+--- a/testing/marionette/harness/requirements.txt
++++ b/testing/marionette/harness/requirements.txt
+@@ -1,13 +1,12 @@
+ browsermob-proxy >= 0.8.0
+ manifestparser >= 1.1
+ marionette-driver >= 2.8.0
+ mozcrash >= 1.1.0
+-mozdevice >= 3.0.0
+ mozinfo >= 1.0.0
+ mozlog >= 4.0
+ moznetwork >= 0.27
+ mozprocess >= 1.0.0
+ mozprofile >= 2.2.0
+ mozrunner >= 7.4.0
+ moztest >= 0.8
+ mozversion >= 2.1.0
+diff --git a/testing/mochitest/mach_commands.py b/testing/mochitest/mach_commands.py
+--- a/testing/mochitest/mach_commands.py
++++ b/testing/mochitest/mach_commands.py
+@@ -55,26 +55,17 @@ TESTS_NOT_FOUND = '''
+ The mochitest command could not find any mochitests under the following
+ test path(s):
+ 
+ {}
+ 
+ Please check spelling and make sure there are mochitests living there.
+ '''.lstrip()
+ 
+-ROBOCOP_TESTS_NOT_FOUND = '''
+-The robocop command could not find any tests under the following
+-test path(s):
+-
+-{}
+-
+-Please check spelling and make sure the named tests exist.
+-'''.lstrip()
+-
+-SUPPORTED_APPS = ['firefox', 'android']
++SUPPORTED_APPS = ['firefox']
+ 
+ parser = None
+ 
+ 
+ class MochitestRunner(MozbuildObject):
+ 
+     """Easily run mochitests.
+ 
+@@ -149,60 +140,16 @@ class MochitestRunner(MozbuildObject):
+                 options.keep_open = True
+ 
+         # We need this to enable colorization of output.
+         self.log_manager.enable_unstructured()
+         result = mochitest.run_test_harness(parser, options)
+         self.log_manager.disable_unstructured()
+         return result
+ 
+-    def run_android_test(self, context, tests, suite=None, **kwargs):
+-        host_ret = verify_host_bin()
+-        if host_ret != 0:
+-            return host_ret
+-
+-        import imp
+-        path = os.path.join(self.mochitest_dir, 'runtestsremote.py')
+-        with open(path, 'r') as fh:
+-            imp.load_module('runtestsremote', fh, path,
+-                            ('.py', 'r', imp.PY_SOURCE))
+-        import runtestsremote
+-
+-        options = Namespace(**kwargs)
+-
+-        from manifestparser import TestManifest
+-        if tests and not options.manifestFile:
+-            manifest = TestManifest()
+-            manifest.tests.extend(tests)
+-            options.manifestFile = manifest
+-
+-        return runtestsremote.run_test_harness(parser, options)
+-
+-    def run_robocop_test(self, context, tests, suite=None, **kwargs):
+-        host_ret = verify_host_bin()
+-        if host_ret != 0:
+-            return host_ret
+-
+-        import imp
+-        path = os.path.join(self.mochitest_dir, 'runrobocop.py')
+-        with open(path, 'r') as fh:
+-            imp.load_module('runrobocop', fh, path,
+-                            ('.py', 'r', imp.PY_SOURCE))
+-        import runrobocop
+-
+-        options = Namespace(**kwargs)
+-
+-        from manifestparser import TestManifest
+-        if tests and not options.manifestFile:
+-            manifest = TestManifest()
+-            manifest.tests.extend(tests)
+-            options.manifestFile = manifest
+-
+-        return runrobocop.run_test_harness(parser, options)
+-
+ # parser
+ 
+ 
+ def setup_argument_parser():
+     build_obj = MozbuildObject.from_environment(cwd=here)
+ 
+     build_path = os.path.join(build_obj.topobjdir, 'build')
+     if build_path not in sys.path:
+@@ -219,24 +166,16 @@ def setup_argument_parser():
+             path = os.path.join(here, "runtests.py")
+ 
+         with open(path, 'r') as fh:
+             imp.load_module('mochitest', fh, path,
+                             ('.py', 'r', imp.PY_SOURCE))
+ 
+         from mochitest_options import MochitestArgumentParser
+ 
+-    if conditions.is_android(build_obj):
+-        # On Android, check for a connected device (and offer to start an
+-        # emulator if appropriate) before running tests. This check must
+-        # be done in this admittedly awkward place because
+-        # MochitestArgumentParser initialization fails if no device is found.
+-        from mozrunner.devices.android_device import verify_android_device
+-        verify_android_device(build_obj, install=True, xre=True)
+-
+     global parser
+     parser = MochitestArgumentParser()
+     return parser
+ 
+ 
+ def verify_host_bin():
+     # validate MOZ_HOST_BIN environment variables for Android tests
+     MOZ_HOST_BIN = os.environ.get('MOZ_HOST_BIN')
+@@ -373,22 +312,17 @@ class MachCommands(MachCommandBase):
+                     reason = 'requires {}'.format(' or '.join(apps))
+                 else:
+                     reason = 'excluded by the command line'
+                 msg.append('    mochitest -f {} ({})'.format(name, reason))
+             print(SUPPORTED_TESTS_NOT_FOUND.format(
+                 buildapp, '\n'.join(sorted(msg))))
+             return 1
+ 
+-        if buildapp == 'android':
+-            from mozrunner.devices.android_device import grant_runtime_permissions
+-            grant_runtime_permissions(self)
+-            run_mochitest = mochitest.run_android_test
+-        else:
+-            run_mochitest = mochitest.run_desktop_test
++        run_mochitest = mochitest.run_desktop_test
+ 
+         overall = None
+         for (flavor, subsuite), tests in sorted(suites.items()):
+             fobj = ALL_FLAVORS[flavor]
+ 
+             harness_args = kwargs.copy()
+             harness_args['subsuite'] = subsuite
+             harness_args.update(fobj.get('extra_args', {}))
+@@ -408,59 +342,16 @@ class MachCommands(MachCommandBase):
+ 
+         # Only shutdown the logger if we created it
+         if kwargs['log'].name == 'mach-mochitest':
+             kwargs['log'].shutdown()
+ 
+         return overall
+ 
+ 
+-@CommandProvider
+-class RobocopCommands(MachCommandBase):
+-
+-    @Command('robocop', category='testing',
+-             conditions=[conditions.is_android],
+-             description='Run a Robocop test.',
+-             parser=setup_argument_parser)
+-    @CommandArgument('--serve', default=False, action='store_true',
+-                     help='Run no tests but start the mochi.test web server '
+-                     'and launch Fennec with a test profile.')
+-    def run_robocop(self, serve=False, **kwargs):
+-        if serve:
+-            kwargs['autorun'] = False
+-
+-        if not kwargs.get('robocopIni'):
+-            kwargs['robocopIni'] = os.path.join(self.topobjdir, '_tests', 'testing',
+-                                                'mochitest', 'robocop.ini')
+-
+-        from mozbuild.controller.building import BuildDriver
+-        self._ensure_state_subdir_exists('.')
+-
+-        test_paths = kwargs['test_paths']
+-        kwargs['test_paths'] = []
+-
+-        from moztest.resolve import TestResolver
+-        resolver = self._spawn(TestResolver)
+-        tests = list(resolver.resolve_tests(paths=test_paths, cwd=self._mach_context.cwd,
+-                                            flavor='instrumentation', subsuite='robocop'))
+-        driver = self._spawn(BuildDriver)
+-        driver.install_tests(tests)
+-
+-        if len(tests) < 1:
+-            print(ROBOCOP_TESTS_NOT_FOUND.format('\n'.join(
+-                sorted(list(test_paths)))))
+-            return 1
+-
+-        from mozrunner.devices.android_device import grant_runtime_permissions
+-        grant_runtime_permissions(self)
+-
+-        mochitest = self._spawn(MochitestRunner)
+-        return mochitest.run_robocop_test(self._mach_context, tests, 'robocop', **kwargs)
+-
+-
+ # NOTE python/mach/mach/commands/commandinfo.py references this function
+ #      by name. If this function is renamed or removed, that file should
+ #      be updated accordingly as well.
+ def REMOVED(cls):
+     """Command no longer exists! Use |mach mochitest| instead.
+ 
+     The |mach mochitest| command will automatically detect which flavors and
+     subsuites exist in a given directory. If desired, flavors and subsuites
+diff --git a/testing/mochitest/moz.build b/testing/mochitest/moz.build
+--- a/testing/mochitest/moz.build
++++ b/testing/mochitest/moz.build
+@@ -52,19 +52,17 @@ TEST_HARNESS_FILES.testing.mochitest += 
+     'leaks.py',
+     'mach_test_package_commands.py',
+     'manifest.webapp',
+     'manifestLibrary.js',
+     'mochitest_options.py',
+     'nested_setup.js',
+     'pywebsocket_wrapper.py',
+     'redirect.html',
+-    'runrobocop.py',
+     'runtests.py',
+-    'runtestsremote.py',
+     'server.js',
+     'start_desktop.js',
+ ]
+ 
+ TEST_HARNESS_FILES.testing.mochitest.embed += [
+     'embed/Xm5i5kbIXzc',
+     'embed/Xm5i5kbIXzc^headers^',
+ ]
+diff --git a/testing/mochitest/runrobocop.py b/testing/mochitest/runrobocop.py
+deleted file mode 100644
+--- a/testing/mochitest/runrobocop.py
++++ /dev/null
+@@ -1,572 +0,0 @@
+-# 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/.
+-
+-from __future__ import print_function
+-
+-import json
+-import os
+-import posixpath
+-import sys
+-import tempfile
+-import traceback
+-from collections import defaultdict
+-
+-sys.path.insert(
+-    0, os.path.abspath(
+-        os.path.realpath(
+-            os.path.dirname(__file__))))
+-
+-from automation import Automation
+-from remoteautomation import RemoteAutomation, fennecLogcatFilters
+-from runtests import KeyValueParseError, MochitestDesktop, MessageLogger, parseKeyValue
+-from mochitest_options import MochitestArgumentParser
+-
+-from manifestparser import TestManifest
+-from manifestparser.filters import chunk_by_slice
+-from mozdevice import ADBAndroid
+-import mozfile
+-import mozinfo
+-
+-SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
+-
+-
+-class RobocopTestRunner(MochitestDesktop):
+-    """
+-       A test harness for Robocop. Robocop tests are UI tests for Firefox for Android,
+-       based on the Robotium test framework. This harness leverages some functionality
+-       from mochitest, for convenience.
+-    """
+-    # Some robocop tests run for >60 seconds without generating any output.
+-    NO_OUTPUT_TIMEOUT = 180
+-
+-    def __init__(self, options, message_logger):
+-        """
+-           Simple one-time initialization.
+-        """
+-        MochitestDesktop.__init__(self, options.flavor, vars(options))
+-
+-        verbose = False
+-        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+-            verbose = True
+-        self.device = ADBAndroid(adb=options.adbPath,
+-                                 device=options.deviceSerial,
+-                                 test_root=options.remoteTestRoot,
+-                                 verbose=verbose)
+-
+-        # Check that Firefox is installed
+-        expected = options.app.split('/')[-1]
+-        if not self.device.is_app_installed(expected):
+-            raise Exception("%s is not installed on this device" % expected)
+-
+-        options.logFile = "robocop.log"
+-        if options.remoteTestRoot is None:
+-            options.remoteTestRoot = self.device.test_root
+-        self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
+-        self.remoteProfileCopy = posixpath.join(options.remoteTestRoot, "profile-copy")
+-
+-        self.remoteConfigFile = posixpath.join(options.remoteTestRoot, "robotium.config")
+-        self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs", "robocop.log")
+-
+-        self.options = options
+-
+-        process_args = {'messageLogger': message_logger}
+-        self.auto = RemoteAutomation(self.device, options.remoteappname, self.remoteProfile,
+-                                     self.remoteLogFile, processArgs=process_args)
+-        self.environment = self.auto.environment
+-
+-        self.remoteScreenshots = "/mnt/sdcard/Robotium-Screenshots"
+-        self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
+-
+-        self.localLog = options.logFile
+-        self.localProfile = None
+-        self.certdbNew = True
+-        self.passed = 0
+-        self.failed = 0
+-        self.todo = 0
+-
+-    def startup(self):
+-        """
+-           Second-stage initialization: One-time initialization which may require cleanup.
+-        """
+-        # Despite our efforts to clean up servers started by this script, in practice
+-        # we still see infrequent cases where a process is orphaned and interferes
+-        # with future tests, typically because the old server is keeping the port in use.
+-        # Try to avoid those failures by checking for and killing servers before
+-        # trying to start new ones.
+-        self.killNamedProc('ssltunnel')
+-        self.killNamedProc('xpcshell')
+-        self.auto.deleteANRs()
+-        self.auto.deleteTombstones()
+-        procName = self.options.app.split('/')[-1]
+-        self.device.pkill(procName)
+-        if self.device.process_exist(procName):
+-            self.log.warning("unable to kill %s before running tests!" % procName)
+-        self.device.rm(self.remoteScreenshots, force=True, recursive=True)
+-        self.device.rm(self.remoteMozLog, force=True, recursive=True)
+-        self.device.mkdir(self.remoteMozLog)
+-        logParent = posixpath.dirname(self.remoteLogFile)
+-        self.device.rm(logParent, force=True, recursive=True)
+-        self.device.mkdir(logParent)
+-        # Add Android version (SDK level) to mozinfo so that manifest entries
+-        # can be conditional on android_version.
+-        self.log.info(
+-            "Android sdk version '%s'; will use this to filter manifests" %
+-            str(self.device.version))
+-        mozinfo.info['android_version'] = str(self.device.version)
+-        if self.options.robocopApk:
+-            self.device.install_app(self.options.robocopApk, replace=True)
+-            self.log.debug("Robocop APK %s installed" %
+-                           self.options.robocopApk)
+-        # Display remote diagnostics; if running in mach, keep output terse.
+-        if self.options.log_mach is None:
+-            self.printDeviceInfo()
+-        self.setupLocalPaths()
+-        self.buildProfile()
+-        # ignoreSSLTunnelExts is a workaround for bug 1109310
+-        self.startServers(
+-            self.options,
+-            debuggerInfo=None,
+-            ignoreSSLTunnelExts=True)
+-        self.log.debug("Servers started")
+-
+-    def cleanup(self):
+-        """
+-           Cleanup at end of job run.
+-        """
+-        self.log.debug("Cleaning up...")
+-        self.stopServers()
+-        self.device.pkill(self.options.app.split('/')[-1])
+-        uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
+-        if uploadDir:
+-            self.log.debug("Pulling any remote moz logs and screenshots to %s." %
+-                           uploadDir)
+-            self.device.pull(self.remoteMozLog, uploadDir)
+-            self.device.pull(self.remoteScreenshots, uploadDir)
+-        MochitestDesktop.cleanup(self, self.options)
+-        if self.localProfile:
+-            mozfile.remove(self.localProfile)
+-        self.device.rm(self.remoteProfile, force=True, recursive=True)
+-        self.device.rm(self.remoteProfileCopy, force=True, recursive=True)
+-        self.device.rm(self.remoteScreenshots, force=True, recursive=True)
+-        self.device.rm(self.remoteMozLog, force=True, recursive=True)
+-        self.device.rm(self.remoteConfigFile, force=True)
+-        self.device.rm(self.remoteLogFile, force=True)
+-        self.log.debug("Cleanup complete.")
+-
+-    def findPath(self, paths, filename=None):
+-        for path in paths:
+-            p = path
+-            if filename:
+-                p = os.path.join(p, filename)
+-            if os.path.exists(self.getFullPath(p)):
+-                return path
+-        return None
+-
+-    def makeLocalAutomation(self):
+-        localAutomation = Automation()
+-        localAutomation.IS_WIN32 = False
+-        localAutomation.IS_LINUX = False
+-        localAutomation.IS_MAC = False
+-        localAutomation.UNIXISH = False
+-        hostos = sys.platform
+-        if (hostos == 'mac' or hostos == 'darwin'):
+-            localAutomation.IS_MAC = True
+-        elif (hostos == 'linux' or hostos == 'linux2'):
+-            localAutomation.IS_LINUX = True
+-            localAutomation.UNIXISH = True
+-        elif (hostos == 'win32' or hostos == 'win64'):
+-            localAutomation.BIN_SUFFIX = ".exe"
+-            localAutomation.IS_WIN32 = True
+-        return localAutomation
+-
+-    def setupLocalPaths(self):
+-        """
+-           Setup xrePath and utilityPath and verify xpcshell.
+-
+-           This is similar to switchToLocalPaths in runtestsremote.py.
+-        """
+-        localAutomation = self.makeLocalAutomation()
+-        paths = [
+-            self.options.xrePath,
+-            localAutomation.DIST_BIN
+-        ]
+-        self.options.xrePath = self.findPath(paths)
+-        if self.options.xrePath is None:
+-            self.log.error(
+-                "unable to find xulrunner path for %s, please specify with --xre-path" %
+-                os.name)
+-            sys.exit(1)
+-        self.log.debug("using xre path %s" % self.options.xrePath)
+-        xpcshell = "xpcshell"
+-        if (os.name == "nt"):
+-            xpcshell += ".exe"
+-        if self.options.utilityPath:
+-            paths = [self.options.utilityPath, self.options.xrePath]
+-        else:
+-            paths = [self.options.xrePath]
+-        self.options.utilityPath = self.findPath(paths, xpcshell)
+-        if self.options.utilityPath is None:
+-            self.log.error(
+-                "unable to find utility path for %s, please specify with --utility-path" %
+-                os.name)
+-            sys.exit(1)
+-        self.log.debug("using utility path %s" % self.options.utilityPath)
+-        xpcshell_path = os.path.join(self.options.utilityPath, xpcshell)
+-        if localAutomation.elf_arm(xpcshell_path):
+-            self.log.error('xpcshell at %s is an ARM binary; please use '
+-                           'the --utility-path argument to specify the path '
+-                           'to a desktop version.' % xpcshell_path)
+-            sys.exit(1)
+-        self.log.debug("xpcshell found at %s" % xpcshell_path)
+-
+-    def buildProfile(self):
+-        """
+-           Build a profile locally, keep it locally for use by servers and
+-           push a copy to the remote profile-copy directory.
+-
+-           This is similar to buildProfile in runtestsremote.py.
+-        """
+-        self.options.extraPrefs.append('browser.search.suggest.enabled=true')
+-        self.options.extraPrefs.append('browser.search.suggest.prompted=true')
+-        self.options.extraPrefs.append('layout.css.devPixelsPerPx=1.0')
+-        self.options.extraPrefs.append('browser.chrome.dynamictoolbar=false')
+-        self.options.extraPrefs.append('browser.snippets.enabled=false')
+-        self.options.extraPrefs.append('extensions.autoupdate.enabled=false')
+-
+-        # Override the telemetry init delay for integration testing.
+-        self.options.extraPrefs.append('toolkit.telemetry.initDelay=1')
+-
+-        self.options.extensionsToExclude.extend([
+-            'mochikit@mozilla.org',
+-            'worker-test@mozilla.org.xpi',
+-            'workerbootstrap-test@mozilla.org.xpi',
+-            'indexedDB-test@mozilla.org.xpi',
+-        ])
+-
+-        manifest = MochitestDesktop.buildProfile(self, self.options)
+-        self.localProfile = self.options.profilePath
+-        self.log.debug("Profile created at %s" % self.localProfile)
+-        # some files are not needed for robocop; save time by not pushing
+-        os.remove(os.path.join(self.localProfile, 'userChrome.css'))
+-        try:
+-            self.device.push(self.localProfile, self.remoteProfileCopy)
+-        except Exception:
+-            self.log.error(
+-                "Automation Error: Unable to copy profile to device.")
+-            raise
+-
+-        return manifest
+-
+-    def setupRemoteProfile(self):
+-        """
+-           Remove any remote profile and re-create it.
+-        """
+-        self.log.debug("Updating remote profile at %s" % self.remoteProfile)
+-        self.device.rm(self.remoteProfile, force=True, recursive=True)
+-        self.device.cp(self.remoteProfileCopy, self.remoteProfile, recursive=True)
+-
+-    def parseLocalLog(self):
+-        """
+-           Read and parse the local log file, noting any failures.
+-        """
+-        with open(self.localLog) as currentLog:
+-            data = currentLog.readlines()
+-        os.unlink(self.localLog)
+-        start_found = False
+-        end_found = False
+-        fail_found = False
+-        for line in data:
+-            try:
+-                message = json.loads(line)
+-                if not isinstance(message, dict) or 'action' not in message:
+-                    continue
+-            except ValueError:
+-                continue
+-            if message['action'] == 'test_end':
+-                end_found = True
+-                start_found = False
+-                break
+-            if start_found and not end_found:
+-                if 'status' in message:
+-                    if 'expected' in message:
+-                        self.failed += 1
+-                    elif message['status'] == 'PASS':
+-                        self.passed += 1
+-                    elif message['status'] == 'FAIL':
+-                        self.todo += 1
+-            if message['action'] == 'test_start':
+-                start_found = True
+-            if 'expected' in message:
+-                fail_found = True
+-        result = 0
+-        if fail_found:
+-            result = 1
+-        if not end_found:
+-            self.log.info(
+-                "PROCESS-CRASH | Automation Error: Missing end of test marker (process crashed?)")
+-            result = 1
+-        return result
+-
+-    def logTestSummary(self):
+-        """
+-           Print a summary of all tests run to stdout, for treeherder parsing
+-           (logging via self.log does not work here).
+-        """
+-        print("0 INFO TEST-START | Shutdown")
+-        print("1 INFO Passed: %s" % (self.passed))
+-        print("2 INFO Failed: %s" % (self.failed))
+-        print("3 INFO Todo: %s" % (self.todo))
+-        print("4 INFO SimpleTest FINISHED")
+-        if self.failed > 0:
+-            return 1
+-        return 0
+-
+-    def printDeviceInfo(self, printLogcat=False):
+-        """
+-           Log remote device information and logcat (if requested).
+-
+-           This is similar to printDeviceInfo in runtestsremote.py
+-        """
+-        try:
+-            if printLogcat:
+-                logcat = self.device.get_logcat(
+-                    filterOutRegexps=fennecLogcatFilters)
+-                self.log.info(
+-                    '\n' +
+-                    ''.join(logcat).decode(
+-                        'utf-8',
+-                        'replace'))
+-            self.log.info("Device info:")
+-            devinfo = self.device.get_info()
+-            for category in devinfo:
+-                if type(devinfo[category]) is list:
+-                    self.log.info("  %s:" % category)
+-                    for item in devinfo[category]:
+-                        self.log.info("     %s" % item)
+-                else:
+-                    self.log.info("  %s: %s" % (category, devinfo[category]))
+-            self.log.info("Test root: %s" % self.device.test_root)
+-        except Exception as e:
+-            self.log.warning("Error getting device information: %s" % str(e))
+-
+-    def setupRobotiumConfig(self, browserEnv):
+-        """
+-           Create robotium.config and push it to the device.
+-        """
+-        fHandle = tempfile.NamedTemporaryFile(suffix='.config',
+-                                              prefix='robotium-',
+-                                              dir=os.getcwd(),
+-                                              delete=False)
+-        fHandle.write("profile=%s\n" % self.remoteProfile)
+-        fHandle.write("logfile=%s\n" % self.remoteLogFile)
+-        fHandle.write("host=http://mochi.test:8888/tests\n")
+-        fHandle.write(
+-            "rawhost=http://%s:%s/tests\n" %
+-            (self.options.remoteWebServer, self.options.httpPort))
+-        if browserEnv:
+-            envstr = ""
+-            delim = ""
+-            for key, value in browserEnv.items():
+-                try:
+-                    value.index(',')
+-                    self.log.error("setupRobotiumConfig: browserEnv - Found a ',' "
+-                                   "in our value, unable to process value. key=%s,value=%s" %
+-                                   (key, value))
+-                    self.log.error("browserEnv=%s" % browserEnv)
+-                except ValueError:
+-                    envstr += "%s%s=%s" % (delim, key, value)
+-                    delim = ","
+-            fHandle.write("envvars=%s\n" % envstr)
+-        fHandle.close()
+-        self.device.rm(self.remoteConfigFile, force=True)
+-        self.device.push(fHandle.name, self.remoteConfigFile)
+-        os.unlink(fHandle.name)
+-
+-    def buildBrowserEnv(self):
+-        """
+-           Return an environment dictionary suitable for remote use.
+-
+-           This is similar to buildBrowserEnv in runtestsremote.py.
+-        """
+-        browserEnv = self.environment(
+-            xrePath=None,
+-            debugger=None)
+-        # remove desktop environment not used on device
+-        if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
+-            del browserEnv["XPCOM_MEM_BLOAT_LOG"]
+-        browserEnv["MOZ_LOG_FILE"] = os.path.join(
+-            self.remoteMozLog,
+-            self.mozLogName)
+-
+-        try:
+-            browserEnv.update(
+-                dict(
+-                    parseKeyValue(
+-                        self.options.environment,
+-                        context='--setenv')))
+-        except KeyValueParseError as e:
+-            self.log.error(str(e))
+-            return None
+-
+-        return browserEnv
+-
+-    def runSingleTest(self, test):
+-        """
+-           Run the specified test.
+-        """
+-        self.log.debug("Running test %s" % test['name'])
+-        self.mozLogName = "moz-%s.log" % test['name']
+-        browserEnv = self.buildBrowserEnv()
+-        self.setupRobotiumConfig(browserEnv)
+-        self.setupRemoteProfile()
+-        self.options.app = "am"
+-        timeout = None
+-        if self.options.autorun:
+-            # This launches a test (using "am instrument") and instructs
+-            # Fennec to /quit/ the browser (using Robocop:Quit) and to
+-            # /finish/ all opened activities.
+-            browserArgs = [
+-                "instrument",
+-                "-e", "quit_and_finish", "1",
+-                "-e", "deviceroot", self.device.test_root,
+-                "-e", "class",
+-                "org.mozilla.gecko.tests.%s" % test['name'].split('/')[-1].split('.java')[0],
+-                "org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner"]
+-        else:
+-            # This does not launch a test at all. It launches an activity
+-            # that starts Fennec and then waits indefinitely, since cat
+-            # never returns.
+-            browserArgs = ["start", "-n",
+-                           "org.mozilla.roboexample.test/org.mozilla."
+-                           "gecko.LaunchFennecWithConfigurationActivity", "&&", "cat"]
+-            timeout = sys.maxint  # Forever.
+-
+-            self.log.info("")
+-            self.log.info("Serving mochi.test Robocop root at http://%s:%s/tests/robocop/" %
+-                          (self.options.remoteWebServer, self.options.httpPort))
+-            self.log.info("")
+-        result = -1
+-        log_result = -1
+-        try:
+-            self.device.clear_logcat()
+-            if not timeout:
+-                timeout = self.options.timeout
+-            if not timeout:
+-                timeout = self.NO_OUTPUT_TIMEOUT
+-            result, _ = self.auto.runApp(
+-                None, browserEnv, "am", self.localProfile, browserArgs,
+-                timeout=timeout, symbolsPath=self.options.symbolsPath)
+-            self.log.debug("runApp completes with status %d" % result)
+-            if result != 0:
+-                self.log.error("runApp() exited with code %s" % result)
+-            if self.device.is_file(self.remoteLogFile):
+-                self.device.pull(self.remoteLogFile, self.localLog)
+-                self.device.rm(self.remoteLogFile)
+-            log_result = self.parseLocalLog()
+-            if result != 0 or log_result != 0:
+-                # Display remote diagnostics; if running in mach, keep output
+-                # terse.
+-                if self.options.log_mach is None:
+-                    self.printDeviceInfo(printLogcat=True)
+-        except Exception:
+-            self.log.error(
+-                "Automation Error: Exception caught while running tests")
+-            traceback.print_exc()
+-            result = 1
+-        self.log.debug("Test %s completes with status %d (log status %d)" %
+-                       (test['name'], int(result), int(log_result)))
+-        return result
+-
+-    def runTests(self):
+-        self.startup()
+-        if isinstance(self.options.manifestFile, TestManifest):
+-            mp = self.options.manifestFile
+-        else:
+-            mp = TestManifest(strict=False)
+-            mp.read("robocop.ini")
+-        filters = []
+-        if self.options.totalChunks:
+-            filters.append(
+-                chunk_by_slice(self.options.thisChunk, self.options.totalChunks))
+-        robocop_tests = mp.active_tests(
+-            exists=False, filters=filters, **mozinfo.info)
+-        if not self.options.autorun:
+-            # Force a single loop iteration. The iteration will start Fennec and
+-            # the httpd server, but not actually run a test.
+-            self.options.test_paths = [robocop_tests[0]['name']]
+-        active_tests = []
+-        for test in robocop_tests:
+-            if self.options.test_paths and test['name'] not in self.options.test_paths:
+-                continue
+-            if 'disabled' in test:
+-                self.log.info('TEST-INFO | skipping %s | %s' %
+-                              (test['name'], test['disabled']))
+-                continue
+-            active_tests.append(test)
+-
+-        tests_by_manifest = defaultdict(list)
+-        for test in active_tests:
+-            tests_by_manifest[test['manifest']].append(test['name'])
+-        self.log.suite_start(tests_by_manifest)
+-
+-        worstTestResult = None
+-        for test in active_tests:
+-            result = self.runSingleTest(test)
+-            if worstTestResult is None or worstTestResult == 0:
+-                worstTestResult = result
+-        if worstTestResult is None:
+-            self.log.warning(
+-                "No tests run. Did you pass an invalid TEST_PATH?")
+-            worstTestResult = 1
+-        else:
+-            print("INFO | runtests.py | Test summary: start.")
+-            logResult = self.logTestSummary()
+-            print("INFO | runtests.py | Test summary: end.")
+-            if worstTestResult == 0:
+-                worstTestResult = logResult
+-        return worstTestResult
+-
+-
+-def run_test_harness(parser, options):
+-    parser.validate(options)
+-
+-    if options is None:
+-        raise ValueError(
+-            "Invalid options specified, use --help for a list of valid options")
+-    message_logger = MessageLogger(logger=None)
+-    runResult = -1
+-    robocop = RobocopTestRunner(options, message_logger)
+-
+-    try:
+-        message_logger.logger = robocop.log
+-        message_logger.buffering = False
+-        robocop.message_logger = message_logger
+-        robocop.log.debug("options=%s" % vars(options))
+-        runResult = robocop.runTests()
+-    except KeyboardInterrupt:
+-        robocop.log.info("runrobocop.py | Received keyboard interrupt")
+-        runResult = -1
+-    except Exception:
+-        traceback.print_exc()
+-        robocop.log.error(
+-            "runrobocop.py | Received unexpected exception while running tests")
+-        runResult = 1
+-    finally:
+-        try:
+-            robocop.cleanup()
+-        except Exception:
+-            # ignore device error while cleaning up
+-            traceback.print_exc()
+-        message_logger.finish()
+-    return runResult
+-
+-
+-def main(args=sys.argv[1:]):
+-    parser = MochitestArgumentParser(app='android')
+-    options = parser.parse_args(args)
+-    return run_test_harness(parser, options)
+-
+-
+-if __name__ == "__main__":
+-    sys.exit(main())
+diff --git a/testing/mochitest/runtestsremote.py b/testing/mochitest/runtestsremote.py
+deleted file mode 100644
+--- a/testing/mochitest/runtestsremote.py
++++ /dev/null
+@@ -1,394 +0,0 @@
+-# 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/.
+-
+-import os
+-import posixpath
+-import sys
+-import traceback
+-
+-sys.path.insert(
+-    0, os.path.abspath(
+-        os.path.realpath(
+-            os.path.dirname(__file__))))
+-
+-from automation import Automation
+-from remoteautomation import RemoteAutomation, fennecLogcatFilters
+-from runtests import MochitestDesktop, MessageLogger
+-from mochitest_options import MochitestArgumentParser
+-
+-from mozdevice import ADBAndroid
+-import mozinfo
+-
+-SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
+-
+-
+-class MochiRemote(MochitestDesktop):
+-    localProfile = None
+-    logMessages = []
+-
+-    def __init__(self, options):
+-        MochitestDesktop.__init__(self, options.flavor, vars(options))
+-
+-        verbose = False
+-        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+-            verbose = True
+-        if hasattr(options, 'log'):
+-            delattr(options, 'log')
+-
+-        self.certdbNew = True
+-        self.chromePushed = False
+-        self.mozLogName = "moz.log"
+-
+-        self.device = ADBAndroid(adb=options.adbPath,
+-                                 device=options.deviceSerial,
+-                                 test_root=options.remoteTestRoot,
+-                                 verbose=verbose)
+-
+-        if options.remoteTestRoot is None:
+-            options.remoteTestRoot = self.device.test_root
+-        options.dumpOutputDirectory = options.remoteTestRoot
+-        self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs", "mochitest.log")
+-        logParent = posixpath.dirname(self.remoteLogFile)
+-        self.device.rm(logParent, force=True, recursive=True)
+-        self.device.mkdir(logParent)
+-
+-        self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile/")
+-        self.device.rm(self.remoteProfile, force=True, recursive=True)
+-
+-        self.counts = dict()
+-        self.message_logger = MessageLogger(logger=None)
+-        self.message_logger.logger = self.log
+-        process_args = {'messageLogger': self.message_logger, 'counts': self.counts}
+-        self.automation = RemoteAutomation(self.device, options.remoteappname, self.remoteProfile,
+-                                           self.remoteLogFile, processArgs=process_args)
+-        self.environment = self.automation.environment
+-
+-        # Check that Firefox is installed
+-        expected = options.app.split('/')[-1]
+-        if not self.device.is_app_installed(expected):
+-            raise Exception("%s is not installed on this device" % expected)
+-
+-        self.automation.deleteANRs()
+-        self.automation.deleteTombstones()
+-        self.device.clear_logcat()
+-
+-        self.remoteModulesDir = posixpath.join(options.remoteTestRoot, "modules/")
+-
+-        self.remoteCache = posixpath.join(options.remoteTestRoot, "cache/")
+-        self.device.rm(self.remoteCache, force=True, recursive=True)
+-
+-        # move necko cache to a location that can be cleaned up
+-        options.extraPrefs += ["browser.cache.disk.parent_directory=%s" % self.remoteCache]
+-
+-        self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
+-        self.device.rm(self.remoteMozLog, force=True, recursive=True)
+-        self.device.mkdir(self.remoteMozLog)
+-
+-        self.remoteChromeTestDir = posixpath.join(
+-            options.remoteTestRoot,
+-            "chrome")
+-        self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
+-        self.device.mkdir(self.remoteChromeTestDir)
+-
+-        procName = options.app.split('/')[-1]
+-        self.device.pkill(procName)
+-        if self.device.process_exist(procName):
+-            self.log.warning("unable to kill %s before running tests!" % procName)
+-
+-        # Add Android version (SDK level) to mozinfo so that manifest entries
+-        # can be conditional on android_version.
+-        self.log.info(
+-            "Android sdk version '%s'; will use this to filter manifests" %
+-            str(self.device.version))
+-        mozinfo.info['android_version'] = str(self.device.version)
+-
+-    def cleanup(self, options, final=False):
+-        if final:
+-            self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
+-            self.chromePushed = False
+-            uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
+-            if uploadDir:
+-                self.device.pull(self.remoteMozLog, uploadDir)
+-        self.device.rm(self.remoteLogFile, force=True)
+-        self.device.rm(self.remoteProfile, force=True, recursive=True)
+-        self.device.rm(self.remoteCache, force=True, recursive=True)
+-        MochitestDesktop.cleanup(self, options, final)
+-        self.localProfile = None
+-
+-    def findPath(self, paths, filename=None):
+-        for path in paths:
+-            p = path
+-            if filename:
+-                p = os.path.join(p, filename)
+-            if os.path.exists(self.getFullPath(p)):
+-                return path
+-        return None
+-
+-    def makeLocalAutomation(self):
+-        localAutomation = Automation()
+-        localAutomation.IS_WIN32 = False
+-        localAutomation.IS_LINUX = False
+-        localAutomation.IS_MAC = False
+-        localAutomation.UNIXISH = False
+-        hostos = sys.platform
+-        if (hostos == 'mac' or hostos == 'darwin'):
+-            localAutomation.IS_MAC = True
+-        elif (hostos == 'linux' or hostos == 'linux2'):
+-            localAutomation.IS_LINUX = True
+-            localAutomation.UNIXISH = True
+-        elif (hostos == 'win32' or hostos == 'win64'):
+-            localAutomation.BIN_SUFFIX = ".exe"
+-            localAutomation.IS_WIN32 = True
+-        return localAutomation
+-
+-    # This seems kludgy, but this class uses paths from the remote host in the
+-    # options, except when calling up to the base class, which doesn't
+-    # understand the distinction.  This switches out the remote values for local
+-    # ones that the base class understands.  This is necessary for the web
+-    # server, SSL tunnel and profile building functions.
+-    def switchToLocalPaths(self, options):
+-        """ Set local paths in the options, return a function that will restore remote values """
+-        remoteXrePath = options.xrePath
+-        remoteProfilePath = options.profilePath
+-        remoteUtilityPath = options.utilityPath
+-
+-        localAutomation = self.makeLocalAutomation()
+-        paths = [
+-            options.xrePath,
+-            localAutomation.DIST_BIN,
+-        ]
+-        options.xrePath = self.findPath(paths)
+-        if options.xrePath is None:
+-            self.log.error(
+-                "unable to find xulrunner path for %s, please specify with --xre-path" %
+-                os.name)
+-            sys.exit(1)
+-
+-        xpcshell = "xpcshell"
+-        if (os.name == "nt"):
+-            xpcshell += ".exe"
+-
+-        if options.utilityPath:
+-            paths = [options.utilityPath, options.xrePath]
+-        else:
+-            paths = [options.xrePath]
+-        options.utilityPath = self.findPath(paths, xpcshell)
+-
+-        if options.utilityPath is None:
+-            self.log.error(
+-                "unable to find utility path for %s, please specify with --utility-path" %
+-                os.name)
+-            sys.exit(1)
+-
+-        xpcshell_path = os.path.join(options.utilityPath, xpcshell)
+-        if localAutomation.elf_arm(xpcshell_path):
+-            self.log.error('xpcshell at %s is an ARM binary; please use '
+-                           'the --utility-path argument to specify the path '
+-                           'to a desktop version.' % xpcshell_path)
+-            sys.exit(1)
+-
+-        if self.localProfile:
+-            options.profilePath = self.localProfile
+-        else:
+-            options.profilePath = None
+-
+-        def fixup():
+-            options.xrePath = remoteXrePath
+-            options.utilityPath = remoteUtilityPath
+-            options.profilePath = remoteProfilePath
+-
+-        return fixup
+-
+-    def startServers(self, options, debuggerInfo):
+-        """ Create the servers on the host and start them up """
+-        restoreRemotePaths = self.switchToLocalPaths(options)
+-        # ignoreSSLTunnelExts is a workaround for bug 1109310
+-        MochitestDesktop.startServers(
+-            self,
+-            options,
+-            debuggerInfo,
+-            ignoreSSLTunnelExts=True)
+-        restoreRemotePaths()
+-
+-    def buildProfile(self, options):
+-        restoreRemotePaths = self.switchToLocalPaths(options)
+-        if options.testingModulesDir:
+-            try:
+-                self.device.push(options.testingModulesDir, self.remoteModulesDir)
+-                self.device.chmod(self.remoteModulesDir, recursive=True)
+-            except Exception:
+-                self.log.error(
+-                    "Automation Error: Unable to copy test modules to device.")
+-                raise
+-            savedTestingModulesDir = options.testingModulesDir
+-            options.testingModulesDir = self.remoteModulesDir
+-        else:
+-            savedTestingModulesDir = None
+-        manifest = MochitestDesktop.buildProfile(self, options)
+-        if savedTestingModulesDir:
+-            options.testingModulesDir = savedTestingModulesDir
+-        self.localProfile = options.profilePath
+-
+-        restoreRemotePaths()
+-        options.profilePath = self.remoteProfile
+-        return manifest
+-
+-    def addChromeToProfile(self, options):
+-        manifest = MochitestDesktop.addChromeToProfile(self, options)
+-
+-        # Support Firefox (browser), SeaMonkey (navigator), and Webapp Runtime (webapp).
+-        if options.flavor == 'chrome':
+-            # append overlay to chrome.manifest
+-            chrome = ("overlay chrome://browser/content/browser.xul "
+-                      "chrome://mochikit/content/browser-test-overlay.xul")
+-            path = os.path.join(options.profilePath, 'extensions', 'staged',
+-                                'mochikit@mozilla.org', 'chrome.manifest')
+-            with open(path, "a") as f:
+-                f.write(chrome)
+-        return manifest
+-
+-    def buildURLOptions(self, options, env):
+-        saveLogFile = options.logFile
+-        options.logFile = self.remoteLogFile
+-        options.profilePath = self.localProfile
+-        env["MOZ_HIDE_RESULTS_TABLE"] = "1"
+-        retVal = MochitestDesktop.buildURLOptions(self, options, env)
+-
+-        # we really need testConfig.js (for browser chrome)
+-        try:
+-            self.device.push(options.profilePath, self.remoteProfile)
+-            self.device.chmod(self.remoteProfile, recursive=True)
+-        except Exception:
+-            self.log.error("Automation Error: Unable to copy profile to device.")
+-            raise
+-
+-        options.profilePath = self.remoteProfile
+-        options.logFile = saveLogFile
+-        return retVal
+-
+-    def getChromeTestDir(self, options):
+-        local = super(MochiRemote, self).getChromeTestDir(options)
+-        remote = self.remoteChromeTestDir
+-        if options.flavor == 'chrome' and not self.chromePushed:
+-            self.log.info("pushing %s to %s on device..." % (local, remote))
+-            local = os.path.join(local, "chrome")
+-            self.device.push(local, remote)
+-            self.chromePushed = True
+-        return remote
+-
+-    def getLogFilePath(self, logFile):
+-        return logFile
+-
+-    def printDeviceInfo(self, printLogcat=False):
+-        try:
+-            if printLogcat:
+-                logcat = self.device.get_logcat(
+-                    filterOutRegexps=fennecLogcatFilters)
+-                self.log.info(
+-                    '\n' +
+-                    ''.join(logcat).decode(
+-                        'utf-8',
+-                        'replace'))
+-            self.log.info("Device info:")
+-            devinfo = self.device.get_info()
+-            for category in devinfo:
+-                if type(devinfo[category]) is list:
+-                    self.log.info("  %s:" % category)
+-                    for item in devinfo[category]:
+-                        self.log.info("     %s" % item)
+-                else:
+-                    self.log.info("  %s: %s" % (category, devinfo[category]))
+-            self.log.info("Test root: %s" % self.device.test_root)
+-        except Exception as e:
+-            self.log.warning("Error getting device information: %s" % str(e))
+-
+-    def getGMPPluginPath(self, options):
+-        # TODO: bug 1149374
+-        return None
+-
+-    def buildBrowserEnv(self, options, debugger=False):
+-        browserEnv = MochitestDesktop.buildBrowserEnv(
+-            self,
+-            options,
+-            debugger=debugger)
+-        # remove desktop environment not used on device
+-        if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
+-            del browserEnv["XPCOM_MEM_BLOAT_LOG"]
+-        # override mozLogs to avoid processing in MochitestDesktop base class
+-        self.mozLogs = None
+-        browserEnv["MOZ_LOG_FILE"] = os.path.join(
+-            self.remoteMozLog,
+-            self.mozLogName)
+-        if options.dmd:
+-            browserEnv['DMD'] = '1'
+-        return browserEnv
+-
+-    def runApp(self, *args, **kwargs):
+-        """front-end automation.py's `runApp` functionality until FennecRunner is written"""
+-
+-        # automation.py/remoteautomation `runApp` takes the profile path,
+-        # whereas runtest.py's `runApp` takes a mozprofile object.
+-        if 'profileDir' not in kwargs and 'profile' in kwargs:
+-            kwargs['profileDir'] = kwargs.pop('profile').profile
+-
+-        # remove args not supported by automation.py
+-        kwargs.pop('marionette_args', None)
+-
+-        ret, _ = self.automation.runApp(*args, **kwargs)
+-        self.countpass += self.counts['pass']
+-        self.countfail += self.counts['fail']
+-        self.counttodo += self.counts['todo']
+-
+-        return ret, None
+-
+-
+-def run_test_harness(parser, options):
+-    parser.validate(options)
+-
+-    if options is None:
+-        raise ValueError("Invalid options specified, use --help for a list of valid options")
+-
+-    options.runByManifest = False
+-    # roboextender is used by mochitest-chrome tests like test_java_addons.html,
+-    # but not by any plain mochitests
+-    if options.flavor != 'chrome':
+-        options.extensionsToExclude.append('roboextender@mozilla.org')
+-
+-    mochitest = MochiRemote(options)
+-
+-    if options.log_mach is None:
+-        mochitest.printDeviceInfo()
+-
+-    try:
+-        if options.verify:
+-            retVal = mochitest.verifyTests(options)
+-        else:
+-            retVal = mochitest.runTests(options)
+-    except Exception:
+-        mochitest.log.error("Automation Error: Exception caught while running tests")
+-        traceback.print_exc()
+-        try:
+-            mochitest.cleanup(options)
+-        except Exception:
+-            # device error cleaning up... oh well!
+-            traceback.print_exc()
+-        retVal = 1
+-
+-    if options.log_mach is None:
+-        mochitest.printDeviceInfo(printLogcat=True)
+-
+-    mochitest.message_logger.finish()
+-
+-    return retVal
+-
+-
+-def main(args=sys.argv[1:]):
+-    parser = MochitestArgumentParser(app='android')
+-    options = parser.parse_args(args)
+-
+-    return run_test_harness(parser, options)
+-
+-
+-if __name__ == "__main__":
+-    sys.exit(main())
+diff --git a/testing/mozbase/moz.build b/testing/mozbase/moz.build
+--- a/testing/mozbase/moz.build
++++ b/testing/mozbase/moz.build
+@@ -20,17 +20,16 @@ PYTHON_UNITTEST_MANIFESTS += [
+     'moztest/tests/manifest.ini',
+     'mozversion/tests/manifest.ini',
+ ]
+ 
+ python_modules = [
+     'manifestparser',
+     'mozcrash',
+     'mozdebug',
+-    'mozdevice',
+     'mozfile',
+     'mozhttpd',
+     'mozinfo',
+     'mozinstall',
+     'mozleak',
+     'mozlog',
+     'moznetwork',
+     'mozprocess',
+diff --git a/testing/mozbase/mozdevice/adb_tests/test_device_running_adb_as_root.py b/testing/mozbase/mozdevice/adb_tests/test_device_running_adb_as_root.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/adb_tests/test_device_running_adb_as_root.py
++++ /dev/null
+@@ -1,52 +0,0 @@
+-"""
+- This test is to test devices that adbd does not get started as root.
+- Specifically devices that have ro.secure == 1 and ro.debuggable == 1
+-
+- Running this test case requires various reboots which makes it a
+- very slow test case to run.
+-"""
+-
+-from __future__ import absolute_import, print_function
+-
+-import unittest
+-import sys
+-
+-from mozdevice import DeviceManagerADB
+-
+-
+-class TestFileOperations(unittest.TestCase):
+-
+-    def setUp(self):
+-        dm = DeviceManagerADB()
+-        dm.reboot(wait=True)
+-
+-    def test_run_adb_as_root_parameter(self):
+-        dm = DeviceManagerADB()
+-        self.assertTrue(dm.processInfo("adbd")[2] != "root")
+-        dm = DeviceManagerADB(runAdbAsRoot=True)
+-        self.assertTrue(dm.processInfo("adbd")[2] == "root")
+-
+-    def test_after_reboot_adb_runs_as_root(self):
+-        dm = DeviceManagerADB(runAdbAsRoot=True)
+-        self.assertTrue(dm.processInfo("adbd")[2] == "root")
+-        dm.reboot(wait=True)
+-        self.assertTrue(dm.processInfo("adbd")[2] == "root")
+-
+-    def tearDown(self):
+-        dm = DeviceManagerADB()
+-        dm.reboot()
+-
+-
+-if __name__ == "__main__":
+-    dm = DeviceManagerADB()
+-    if not dm.devices():
+-        print("There are no connected adb devices")
+-        sys.exit(1)
+-    else:
+-        if not (int(dm._runCmd(["shell", "getprop", "ro.secure"]).output[0]) and
+-                int(dm._runCmd(["shell", "getprop", "ro.debuggable"]).output[0])):
+-            print("This test case is meant for devices with devices that start "
+-                  "adbd as non-root and allows for adbd to be restarted as root.")
+-            sys.exit(1)
+-
+-    unittest.main()
+diff --git a/testing/mozbase/mozdevice/adb_tests/test_devicemanagerADB.py b/testing/mozbase/mozdevice/adb_tests/test_devicemanagerADB.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/adb_tests/test_devicemanagerADB.py
++++ /dev/null
+@@ -1,220 +0,0 @@
+-"""
+- Info:
+-   This tests DeviceManagerADB with a real device
+-
+- Requirements:
+-   - You must have a device connected
+-      - It should be listed under 'adb devices'
+-
+- Notes:
+-   - Not all functions have been covered.
+-     In particular, functions from the parent class
+-   - No testing of properties is done
+-   - The test case are very simple and it could be
+-     done with deeper inspection of the return values
+-
+- Author(s):
+-   - Armen Zambrano <armenzg@mozilla.com>
+-
+- Functions that are not being tested:
+- - launchProcess - DEPRECATED
+- - getIP
+- - recordLogcat
+- - saveScreenshot
+- - validateDir
+- - mkDirs
+- - getDeviceRoot
+- - shellCheckOutput
+- - processExist
+-
+- I assume these functions are only useful for Android
+- - getAppRoot()
+- - updateApp()
+- - uninstallApp()
+- - uninstallAppAndReboot()
+-"""
+-from __future__ import absolute_import, print_function
+-
+-import os
+-import re
+-import socket
+-import sys
+-import tempfile
+-import unittest
+-from StringIO import StringIO
+-
+-from mozdevice import DeviceManagerADB, DMError
+-
+-
+-def find_mount_permissions(dm, mount_path):
+-    for mount_point in dm._runCmd(["shell", "mount"]).output:
+-        if mount_point.find(mount_path) > 0:
+-            return re.search('(ro|rw)(?=,)', mount_point).group(0)
+-
+-
+-class DeviceManagerADBTestCase(unittest.TestCase):
+-    tempLocalDir = "tempDir"
+-    tempLocalFile = os.path.join(tempLocalDir, "tempfile.txt")
+-    tempRemoteDir = None
+-    tempRemoteFile = None
+-    tempRemoteSystemFile = None
+-
+-    def setUp(self):
+-        self.assertTrue(find_mount_permissions(self.dm, "/system"), "ro")
+-
+-        self.assertTrue(os.path.exists(self.tempLocalDir))
+-        self.assertTrue(os.path.exists(self.tempLocalFile))
+-
+-        if self.dm.fileExists(self.tempRemoteFile):
+-            self.dm.removeFile(self.tempRemoteFile)
+-        self.assertFalse(self.dm.fileExists(self.tempRemoteFile))
+-
+-        if self.dm.fileExists(self.tempRemoteSystemFile):
+-            self.dm.removeFile(self.tempRemoteSystemFile)
+-
+-        self.assertTrue(self.dm.dirExists(self.tempRemoteDir))
+-
+-    @classmethod
+-    def setUpClass(self):
+-        self.dm = DeviceManagerADB()
+-        if not os.path.exists(self.tempLocalDir):
+-            os.mkdir(self.tempLocalDir)
+-        if not os.path.exists(self.tempLocalFile):
+-            # Create empty file
+-            open(self.tempLocalFile, 'w').close()
+-        self.tempRemoteDir = self.dm.getTempDir()
+-        self.tempRemoteFile = os.path.join(self.tempRemoteDir,
+-                                           os.path.basename(self.tempLocalFile))
+-        self.tempRemoteSystemFile = \
+-            os.path.join("/system", os.path.basename(self.tempLocalFile))
+-
+-    @classmethod
+-    def tearDownClass(self):
+-        os.remove(self.tempLocalFile)
+-        os.rmdir(self.tempLocalDir)
+-        if self.dm.dirExists(self.tempRemoteDir):
+-            # self.tempRemoteFile will get deleted with it
+-            self.dm.removeDir(self.tempRemoteDir)
+-        if self.dm.fileExists(self.tempRemoteSystemFile):
+-            self.dm.removeFile(self.tempRemoteSystemFile)
+-
+-
+-class TestFileOperations(DeviceManagerADBTestCase):
+-
+-    def test_make_and_remove_directory(self):
+-        dir1 = os.path.join(self.tempRemoteDir, "dir1")
+-        self.assertFalse(self.dm.dirExists(dir1))
+-        self.dm.mkDir(dir1)
+-        self.assertTrue(self.dm.dirExists(dir1))
+-        self.dm.removeDir(dir1)
+-        self.assertFalse(self.dm.dirExists(dir1))
+-
+-    def test_push_and_remove_file(self):
+-        self.dm.pushFile(self.tempLocalFile, self.tempRemoteFile)
+-        self.assertTrue(self.dm.fileExists(self.tempRemoteFile))
+-        self.dm.removeFile(self.tempRemoteFile)
+-        self.assertFalse(self.dm.fileExists(self.tempRemoteFile))
+-
+-    def test_push_and_pull_file(self):
+-        self.dm.pushFile(self.tempLocalFile, self.tempRemoteFile)
+-        self.assertTrue(self.dm.fileExists(self.tempRemoteFile))
+-        self.assertFalse(os.path.exists("pulled.txt"))
+-        self.dm.getFile(self.tempRemoteFile, "pulled.txt")
+-        self.assertTrue(os.path.exists("pulled.txt"))
+-        os.remove("pulled.txt")
+-
+-    def test_push_and_pull_directory_and_list_files(self):
+-        self.dm.removeDir(self.tempRemoteDir)
+-        self.assertFalse(self.dm.dirExists(self.tempRemoteDir))
+-        self.dm.pushDir(self.tempLocalDir, self.tempRemoteDir)
+-        self.assertTrue(self.dm.dirExists(self.tempRemoteDir))
+-        response = self.dm.listFiles(self.tempRemoteDir)
+-        # The local dir that was pushed contains the tempLocalFile
+-        self.assertIn(os.path.basename(self.tempLocalFile), response)
+-        # Create a temp dir to pull to
+-        temp_dir = tempfile.mkdtemp()
+-        self.assertTrue(os.path.exists(temp_dir))
+-        self.dm.getDirectory(self.tempRemoteDir, temp_dir)
+-        self.assertTrue(os.path.exists(self.tempLocalFile))
+-
+-    def test_move_and_remove_directories(self):
+-        dir1 = os.path.join(self.tempRemoteDir, "dir1")
+-        dir2 = os.path.join(self.tempRemoteDir, "dir2")
+-
+-        self.assertFalse(self.dm.dirExists(dir1))
+-        self.dm.mkDir(dir1)
+-        self.assertTrue(self.dm.dirExists(dir1))
+-
+-        self.assertFalse(self.dm.dirExists(dir2))
+-        self.dm.moveTree(dir1, dir2)
+-        self.assertTrue(self.dm.dirExists(dir2))
+-
+-        self.dm.removeDir(dir1)
+-        self.dm.removeDir(dir2)
+-        self.assertFalse(self.dm.dirExists(dir1))
+-        self.assertFalse(self.dm.dirExists(dir2))
+-
+-    def test_push_and_remove_system_file(self):
+-        out = StringIO()
+-        self.assertTrue(find_mount_permissions(self.dm, "/system") == "ro")
+-        self.assertFalse(self.dm.fileExists(self.tempRemoteSystemFile))
+-        self.assertRaises(DMError, self.dm.pushFile, self.tempLocalFile, self.tempRemoteSystemFile)
+-        self.dm.shell(['mount', '-w', '-o', 'remount', '/system'], out)
+-        self.assertTrue(find_mount_permissions(self.dm, "/system") == "rw")
+-        self.assertFalse(self.dm.fileExists(self.tempRemoteSystemFile))
+-        self.dm.pushFile(self.tempLocalFile, self.tempRemoteSystemFile)
+-        self.assertTrue(self.dm.fileExists(self.tempRemoteSystemFile))
+-        self.dm.removeFile(self.tempRemoteSystemFile)
+-        self.assertFalse(self.dm.fileExists(self.tempRemoteSystemFile))
+-        self.dm.shell(['mount', '-r', '-o', 'remount', '/system'], out)
+-        out.close()
+-        self.assertTrue(find_mount_permissions(self.dm, "/system") == "ro")
+-
+-
+-class TestOther(DeviceManagerADBTestCase):
+-
+-    def test_get_list_of_processes(self):
+-        self.assertEquals(type(self.dm.getProcessList()), list)
+-
+-    def test_get_current_time(self):
+-        self.assertEquals(type(self.dm.getCurrentTime()), int)
+-
+-    def test_get_info(self):
+-        self.assertEquals(type(self.dm.getInfo()), dict)
+-
+-    def test_list_devices(self):
+-        self.assertEquals(len(list(self.dm.devices())), 1)
+-
+-    def test_shell(self):
+-        out = StringIO()
+-        self.dm.shell(["echo", "$COMPANY", ";", "pwd"], out,
+-                      env={"COMPANY": "Mozilla"}, cwd="/", timeout=4, root=True)
+-        output = str(out.getvalue()).rstrip().splitlines()
+-        out.close()
+-        self.assertEquals(output, ['Mozilla', '/'])
+-
+-    def test_port_forwarding(self):
+-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+-        s.bind(("", 0))
+-        port = s.getsockname()[1]
+-        s.close()
+-        # If successful then no exception is raised
+-        self.dm.forward("tcp:%s" % port, "tcp:2828")
+-
+-    def test_port_forwarding_error(self):
+-        self.assertRaises(DMError, self.dm.forward, "", "")
+-
+-
+-if __name__ == '__main__':
+-    dm = DeviceManagerADB()
+-    if not dm.devices():
+-        print("There are no connected adb devices")
+-        sys.exit(1)
+-
+-    if find_mount_permissions(dm, "/system") == "rw":
+-        print("We've found out that /system is mounted as 'rw'. This is because the command "
+-              "'adb remount' has been run before running this test case. Please reboot the "
+-              "device and try again.")
+-        sys.exit(1)
+-
+-    unittest.main()
+diff --git a/testing/mozbase/mozdevice/mozdevice/__init__.py b/testing/mozbase/mozdevice/mozdevice/__init__.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/__init__.py
++++ /dev/null
+@@ -1,17 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-from .adb import ADBError, ADBRootError, ADBTimeoutError
+-from .adb import ADBProcess, ADBCommand, ADBHost, ADBDevice
+-from .adb_android import ADBAndroid
+-from .adb_b2g import ADBB2G
+-from .devicemanager import DeviceManager, DMError
+-from .devicemanagerADB import DeviceManagerADB
+-from .droid import DroidADB
+-
+-__all__ = ['ADBError', 'ADBRootError', 'ADBTimeoutError', 'ADBProcess', 'ADBCommand', 'ADBHost',
+-           'ADBDevice', 'ADBAndroid', 'ADBB2G', 'DeviceManager', 'DMError',
+-           'DeviceManagerADB', 'DroidADB']
+diff --git a/testing/mozbase/mozdevice/mozdevice/adb.py b/testing/mozbase/mozdevice/mozdevice/adb.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/adb.py
++++ /dev/null
+@@ -1,2264 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-import os
+-import posixpath
+-import re
+-import shutil
+-import subprocess
+-import tempfile
+-import time
+-import traceback
+-
+-from abc import ABCMeta, abstractmethod
+-from distutils import dir_util
+-
+-
+-class ADBProcess(object):
+-    """ADBProcess encapsulates the data related to executing the adb process."""
+-
+-    def __init__(self, args):
+-        #: command argument argument list.
+-        self.args = args
+-        #: Temporary file handle to be used for stdout.
+-        self.stdout_file = tempfile.TemporaryFile()
+-        #: boolean indicating if the command timed out.
+-        self.timedout = None
+-        #: exitcode of the process.
+-        self.exitcode = None
+-        #: subprocess Process object used to execute the command.
+-        self.proc = subprocess.Popen(args,
+-                                     stdout=self.stdout_file,
+-                                     stderr=subprocess.STDOUT)
+-
+-    @property
+-    def stdout(self):
+-        """Return the contents of stdout."""
+-        if not self.stdout_file or self.stdout_file.closed:
+-            content = ""
+-        else:
+-            self.stdout_file.seek(0, os.SEEK_SET)
+-            content = self.stdout_file.read().rstrip()
+-        return content
+-
+-    def __str__(self):
+-        return ('args: %s, exitcode: %s, stdout: %s' % (
+-            ' '.join(self.args), self.exitcode, self.stdout))
+-
+-# ADBError, ADBRootError, and ADBTimeoutError are treated
+-# differently in order that unhandled ADBRootErrors and
+-# ADBTimeoutErrors can be handled distinctly from ADBErrors.
+-
+-
+-class ADBError(Exception):
+-    """ADBError is raised in situations where a command executed on a
+-    device either exited with a non-zero exitcode or when an
+-    unexpected error condition has occurred. Generally, ADBErrors can
+-    be handled and the device can continue to be used.
+-    """
+-    pass
+-
+-
+-class ADBListDevicesError(ADBError):
+-    """ADBListDevicesError is raised when errors are found listing the
+-    devices, typically not any permissions.
+-
+-    The devices information is stocked with the *devices* member.
+-    """
+-
+-    def __init__(self, msg, devices):
+-        ADBError.__init__(self, msg)
+-        self.devices = devices
+-
+-
+-class ADBRootError(Exception):
+-    """ADBRootError is raised when a shell command is to be executed as
+-    root but the device does not support it. This error is fatal since
+-    there is no recovery possible by the script. You must either root
+-    your device or change your scripts to not require running as root.
+-    """
+-    pass
+-
+-
+-class ADBTimeoutError(Exception):
+-    """ADBTimeoutError is raised when either a host command or shell
+-    command takes longer than the specified timeout to execute. The
+-    timeout value is set in the ADBCommand constructor and is 300 seconds by
+-    default. This error is typically fatal since the host is having
+-    problems communicating with the device. You may be able to recover
+-    by rebooting, but this is not guaranteed.
+-
+-    Recovery options are:
+-
+-    * Killing and restarting the adb server via
+-      ::
+-
+-          adb kill-server; adb start-server
+-
+-    * Rebooting the device manually.
+-    * Rebooting the host.
+-    """
+-    pass
+-
+-
+-class ADBCommand(object):
+-    """ADBCommand provides a basic interface to adb commands
+-    which is used to provide the 'command' methods for the
+-    classes ADBHost and ADBDevice.
+-
+-    ADBCommand should only be used as the base class for other
+-    classes and should not be instantiated directly. To enforce this
+-    restriction calling ADBCommand's constructor will raise a
+-    NonImplementedError exception.
+-
+-    ::
+-
+-       from mozdevice import ADBCommand
+-
+-       try:
+-           adbcommand = ADBCommand()
+-       except NotImplementedError:
+-           print "ADBCommand can not be instantiated."
+-    """
+-
+-    def __init__(self,
+-                 adb='adb',
+-                 adb_host=None,
+-                 adb_port=None,
+-                 logger_name='adb',
+-                 timeout=300,
+-                 verbose=False):
+-        """Initializes the ADBCommand object.
+-
+-        :param str adb: path to adb executable. Defaults to 'adb'.
+-        :param adb_host: host of the adb server.
+-        :type adb_host: str or None
+-        :param adb_port: port of the adb server.
+-        :type adb_port: integer or None
+-        :param str logger_name: logging logger name. Defaults to 'adb'.
+-
+-        :raises: * ADBError
+-                 * ADBTimeoutError
+-        """
+-        if self.__class__ == ADBCommand:
+-            raise NotImplementedError
+-
+-        self._logger = self._get_logger(logger_name)
+-        self._verbose = verbose
+-        self._adb_path = adb
+-        self._adb_host = adb_host
+-        self._adb_port = adb_port
+-        self._timeout = timeout
+-        self._polling_interval = 0.1
+-        self._adb_version = ''
+-
+-        self._logger.debug("%s: %s" % (self.__class__.__name__,
+-                                       self.__dict__))
+-
+-        # catch early a missing or non executable adb command
+-        # and get the adb version while we are at it.
+-        try:
+-            output = subprocess.Popen([adb, 'version'],
+-                                      stdout=subprocess.PIPE,
+-                                      stderr=subprocess.PIPE).communicate()
+-            re_version = re.compile(r'Android Debug Bridge version (.*)')
+-            self._adb_version = re_version.match(output[0]).group(1)
+-        except Exception as exc:
+-            raise ADBError('%s: %s is not executable.' % (exc, adb))
+-
+-    def _get_logger(self, logger_name):
+-        logger = None
+-        try:
+-            import mozlog
+-            logger = mozlog.get_default_logger(logger_name)
+-        except ImportError:
+-            pass
+-
+-        if logger is None:
+-            import logging
+-            logger = logging.getLogger(logger_name)
+-        return logger
+-
+-    # Host Command methods
+-
+-    def command(self, cmds, device_serial=None, timeout=None):
+-        """Executes an adb command on the host.
+-
+-        :param list cmds: The command and its arguments to be
+-            executed.
+-        :param device_serial: The device's
+-            serial number if the adb command is to be executed against
+-            a specific device.
+-        :type device_serial: str or None
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per adb call. The
+-            total time spent may exceed this value. If it is not
+-            specified, the value set in the ADBCommand constructor is used.
+-        :type timeout: integer or None
+-        :returns: :class:`mozdevice.ADBProcess`
+-
+-        command() provides a low level interface for executing
+-        commands on the host via adb.
+-
+-        command() executes on the host in such a fashion that stdout
+-        of the adb process is a file handle on the host and
+-        the exit code is available as the exit code of the adb
+-        process.
+-
+-        The caller provides a list containing commands, as well as a
+-        timeout period in seconds.
+-
+-        A subprocess is spawned to execute adb with stdout and stderr
+-        directed to a temporary file. If the process takes longer than
+-        the specified timeout, the process is terminated.
+-
+-        It is the caller's responsibilty to clean up by closing
+-        the stdout temporary file.
+-        """
+-        args = [self._adb_path]
+-        if self._adb_host:
+-            args.extend(['-H', self._adb_host])
+-        if self._adb_port:
+-            args.extend(['-P', str(self._adb_port)])
+-        if device_serial:
+-            args.extend(['-s', device_serial, 'wait-for-device'])
+-        args.extend(cmds)
+-
+-        adb_process = ADBProcess(args)
+-
+-        if timeout is None:
+-            timeout = self._timeout
+-
+-        start_time = time.time()
+-        adb_process.exitcode = adb_process.proc.poll()
+-        while ((time.time() - start_time) <= timeout and
+-               adb_process.exitcode is None):
+-            time.sleep(self._polling_interval)
+-            adb_process.exitcode = adb_process.proc.poll()
+-        if adb_process.exitcode is None:
+-            adb_process.proc.kill()
+-            adb_process.timedout = True
+-            adb_process.exitcode = adb_process.proc.poll()
+-
+-        adb_process.stdout_file.seek(0, os.SEEK_SET)
+-
+-        return adb_process
+-
+-    def command_output(self, cmds, device_serial=None, timeout=None):
+-        """Executes an adb command on the host returning stdout.
+-
+-        :param list cmds: The command and its arguments to be
+-            executed.
+-        :param device_serial: The device's
+-            serial number if the adb command is to be executed against
+-            a specific device.
+-        :type device_serial: str or None
+-        :param timeout: The maximum time in seconds
+-            for any spawned adb process to complete before throwing
+-            an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBCommand constructor is used.
+-        :type timeout: integer or None
+-        :returns: string - content of stdout.
+-
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        adb_process = None
+-        try:
+-            # Need to force the use of the ADBCommand class's command
+-            # since ADBDevice will redefine command and call its
+-            # own version otherwise.
+-            adb_process = ADBCommand.command(self, cmds,
+-                                             device_serial=device_serial,
+-                                             timeout=timeout)
+-            if adb_process.timedout:
+-                raise ADBTimeoutError("%s" % adb_process)
+-            elif adb_process.exitcode:
+-                raise ADBError("%s" % adb_process)
+-            output = adb_process.stdout_file.read().rstrip()
+-            if self._verbose:
+-                self._logger.debug('command_output: %s, '
+-                                   'timeout: %s, '
+-                                   'timedout: %s, '
+-                                   'exitcode: %s, output: %s' %
+-                                   (' '.join(adb_process.args),
+-                                    timeout,
+-                                    adb_process.timedout,
+-                                    adb_process.exitcode,
+-                                    output))
+-
+-            return output
+-        finally:
+-            if adb_process and isinstance(adb_process.stdout_file, file):
+-                adb_process.stdout_file.close()
+-
+-
+-class ADBHost(ADBCommand):
+-    """ADBHost provides a basic interface to adb host commands
+-    which do not target a specific device.
+-
+-    ::
+-
+-       from mozdevice import ADBHost
+-
+-       adbhost = ADBHost()
+-       adbhost.start_server()
+-    """
+-
+-    def __init__(self,
+-                 adb='adb',
+-                 adb_host=None,
+-                 adb_port=None,
+-                 logger_name='adb',
+-                 timeout=300,
+-                 verbose=False):
+-        """Initializes the ADBHost object.
+-
+-        :param str adb: path to adb executable. Defaults to 'adb'.
+-        :param adb_host: host of the adb server.
+-        :type adb_host: str or None
+-        :param adb_port: port of the adb server.
+-        :type adb_port: integer or None
+-        :param str logger_name: logging logger name. Defaults to 'adb'.
+-
+-        :raises: * ADBError
+-                 * ADBTimeoutError
+-        """
+-        ADBCommand.__init__(self, adb=adb, adb_host=adb_host,
+-                            adb_port=adb_port, logger_name=logger_name,
+-                            timeout=timeout, verbose=verbose)
+-
+-    def command(self, cmds, timeout=None):
+-        """Executes an adb command on the host.
+-
+-        :param list cmds: The command and its arguments to be
+-            executed.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per adb call. The
+-            total time spent may exceed this value. If it is not
+-            specified, the value set in the ADBHost constructor is used.
+-        :type timeout: integer or None
+-        :returns: :class:`mozdevice.ADBProcess`
+-
+-        command() provides a low level interface for executing
+-        commands on the host via adb.
+-
+-        command() executes on the host in such a fashion that stdout
+-        of the adb process is a file handle on the host and
+-        the exit code is available as the exit code of the adb
+-        process.
+-
+-        The caller provides a list containing commands, as well as a
+-        timeout period in seconds.
+-
+-        A subprocess is spawned to execute adb with stdout and stderr
+-        directed to a temporary file. If the process takes longer than
+-        the specified timeout, the process is terminated.
+-
+-        It is the caller's responsibilty to clean up by closing
+-        the stdout temporary file.
+-        """
+-        return ADBCommand.command(self, cmds, timeout=timeout)
+-
+-    def command_output(self, cmds, timeout=None):
+-        """Executes an adb command on the host returning stdout.
+-
+-        :param list cmds: The command and its arguments to be
+-            executed.
+-        :param timeout: The maximum time in seconds
+-            for any spawned adb process to complete before throwing
+-            an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBHost constructor is used.
+-        :type timeout: integer or None
+-        :returns: string - content of stdout.
+-
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        return ADBCommand.command_output(self, cmds, timeout=timeout)
+-
+-    def start_server(self, timeout=None):
+-        """Starts the adb server.
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per adb call. The
+-            total time spent may exceed this value. If it is not
+-            specified, the value set in the ADBHost constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-
+-        Attempting to use start_server with any adb_host value other than None
+-        will fail with an ADBError exception.
+-
+-        You will need to start the server on the remote host via the command:
+-
+-        .. code-block:: shell
+-
+-            adb -a fork-server server
+-
+-        If you wish the remote adb server to restart automatically, you can
+-        enclose the command in a loop as in:
+-
+-        .. code-block:: shell
+-
+-            while true; do
+-              adb -a fork-server server
+-            done
+-        """
+-        self.command_output(["start-server"], timeout=timeout)
+-
+-    def kill_server(self, timeout=None):
+-        """Kills the adb server.
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per adb call. The
+-            total time spent may exceed this value. If it is not
+-            specified, the value set in the ADBHost constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        self.command_output(["kill-server"], timeout=timeout)
+-
+-    def devices(self, timeout=None):
+-        """Executes adb devices -l and returns a list of objects describing attached devices.
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per adb call. The
+-            total time spent may exceed this value. If it is not
+-            specified, the value set in the ADBHost constructor is used.
+-        :type timeout: integer or None
+-        :returns: an object contain
+-        :raises: * ADBTimeoutError
+-                 * ADBListDevicesError
+-                 * ADBError
+-
+-        The output of adb devices -l ::
+-
+-            $ adb devices -l
+-            List of devices attached
+-            b313b945               device usb:1-7 product:d2vzw model:SCH_I535 device:d2vzw
+-
+-        is parsed and placed into an object as in
+-
+-        [{'device_serial': 'b313b945', 'state': 'device', 'product': 'd2vzw',
+-          'usb': '1-7', 'device': 'd2vzw', 'model': 'SCH_I535' }]
+-        """
+-        # b313b945               device usb:1-7 product:d2vzw model:SCH_I535 device:d2vzw
+-        # from Android system/core/adb/transport.c statename()
+-        re_device_info = re.compile(
+-            r"([^\s]+)\s+(offline|bootloader|device|host|recovery|sideload|"
+-            "no permissions|unauthorized|unknown)")
+-        devices = []
+-        lines = self.command_output(["devices", "-l"], timeout=timeout).splitlines()
+-        for line in lines:
+-            if line == 'List of devices attached ':
+-                continue
+-            match = re_device_info.match(line)
+-            if match:
+-                device = {
+-                    'device_serial': match.group(1),
+-                    'state': match.group(2)
+-                }
+-                remainder = line[match.end(2):].strip()
+-                if remainder:
+-                    try:
+-                        device.update(dict([j.split(':')
+-                                            for j in remainder.split(' ')]))
+-                    except ValueError:
+-                        self._logger.warning('devices: Unable to parse '
+-                                             'remainder for device %s' % line)
+-                devices.append(device)
+-        for device in devices:
+-            if device['state'] == 'no permissions':
+-                raise ADBListDevicesError(
+-                    "No permissions to detect devices. You should restart the"
+-                    " adb server as root:\n"
+-                    "\n# adb kill-server\n# adb start-server\n"
+-                    "\nor maybe configure your udev rules.",
+-                    devices)
+-        return devices
+-
+-
+-class ADBDevice(ADBCommand):
+-    """ADBDevice is an abstract base class which provides methods which
+-    can be used to interact with the associated Android or B2G based
+-    device. It must be used via one of the concrete implementations in
+-    :class:`ADBAndroid` or :class:`ADBB2G`.
+-    """
+-    __metaclass__ = ABCMeta
+-
+-    def __init__(self,
+-                 device=None,
+-                 adb='adb',
+-                 adb_host=None,
+-                 adb_port=None,
+-                 test_root='',
+-                 logger_name='adb',
+-                 timeout=300,
+-                 verbose=False,
+-                 device_ready_retry_wait=20,
+-                 device_ready_retry_attempts=3):
+-        """Initializes the ADBDevice object.
+-
+-        :param device: When a string is passed, it is interpreted as the
+-            device serial number. This form is not compatible with
+-            devices containing a ":" in the serial; in this case
+-            ValueError will be raised.
+-            When a dictionary is passed it must have one or both of
+-            the keys "device_serial" and "usb". This is compatible
+-            with the dictionaries in the list returned by
+-            ADBHost.devices(). If the value of device_serial is a
+-            valid serial not containing a ":" it will be used to
+-            identify the device, otherwise the value of the usb key,
+-            prefixed with "usb:" is used.
+-            If None is passed and there is exactly one device attached
+-            to the host, that device is used. If there is more than one
+-            device attached, ValueError is raised. If no device is
+-            attached the constructor will block until a device is
+-            attached or the timeout is reached.
+-        :type device: dict, str or None
+-        :param adb_host: host of the adb server to connect to.
+-        :type adb_host: str or None
+-        :param adb_port: port of the adb server to connect to.
+-        :type adb_port: integer or None
+-        :param str logger_name: logging logger name. Defaults to 'adb'.
+-        :param integer device_ready_retry_wait: number of seconds to wait
+-            between attempts to check if the device is ready after a
+-            reboot.
+-        :param integer device_ready_retry_attempts: number of attempts when
+-            checking if a device is ready.
+-
+-        :raises: * ADBError
+-                 * ADBTimeoutError
+-                 * ValueError
+-        """
+-        ADBCommand.__init__(self, adb=adb, adb_host=adb_host,
+-                            adb_port=adb_port, logger_name=logger_name,
+-                            timeout=timeout, verbose=verbose)
+-        self._device_serial = self._get_device_serial(device)
+-        self._initial_test_root = test_root
+-        self._test_root = None
+-        self._device_ready_retry_wait = device_ready_retry_wait
+-        self._device_ready_retry_attempts = device_ready_retry_attempts
+-        self._have_root_shell = False
+-        self._have_su = False
+-        self._have_android_su = False
+-
+-        # Catch exceptions due to the potential for segfaults
+-        # calling su when using an improperly rooted device.
+-
+-        # Note this check to see if adbd is running is performed on
+-        # the device in the state it exists in when the ADBDevice is
+-        # initialized. It may be the case that it has been manipulated
+-        # since its last boot and that its current state does not
+-        # match the state the device will have immediately after a
+-        # reboot. For example, if adb root was called manually prior
+-        # to ADBDevice being initialized, then self._have_root_shell
+-        # will not reflect the state of the device after it has been
+-        # rebooted again. Therefore this check will need to be
+-        # performed again after a reboot.
+-
+-        self._check_adb_root(timeout=timeout)
+-
+-        uid = 'uid=0'
+-        # Do we have a 'Superuser' sh like su?
+-        try:
+-            if self.shell_output("su -c id", timeout=timeout).find(uid) != -1:
+-                self._have_su = True
+-                self._logger.info("su -c supported")
+-        except ADBError:
+-            self._logger.debug("Check for su -c failed")
+-
+-        # Do we have Android's su?
+-        try:
+-            if self.shell_output("su 0 id", timeout=timeout).find(uid) != -1:
+-                self._have_android_su = True
+-                self._logger.info("su 0 supported")
+-        except ADBError:
+-            self._logger.debug("Check for su 0 failed")
+-
+-        self._mkdir_p = None
+-        # Force the use of /system/bin/ls or /system/xbin/ls in case
+-        # there is /sbin/ls which embeds ansi escape codes to colorize
+-        # the output.  Detect if we are using busybox ls. We want each
+-        # entry on a single line and we don't want . or ..
+-        if self.shell_bool("/system/bin/ls /data/local/tmp", timeout=timeout):
+-            self._ls = "/system/bin/ls"
+-        elif self.shell_bool("/system/xbin/ls /data/local/tmp", timeout=timeout):
+-            self._ls = "/system/xbin/ls"
+-        else:
+-            raise ADBError("ADBDevice.__init__: ls not found")
+-        try:
+-            self.shell_output("%s -1A /data/local/tmp" % self._ls, timeout=timeout)
+-            self._ls += " -1A"
+-        except ADBError:
+-            self._ls += " -a"
+-
+-        self._logger.info("%s supported" % self._ls)
+-
+-        # Do we have cp?
+-        self._have_cp = self.shell_bool("type cp", timeout=timeout)
+-        self._logger.info("Native cp support: %s" % self._have_cp)
+-
+-        # Do we have chmod -R?
+-        try:
+-            self._chmod_R = False
+-            re_recurse = re.compile(r'[-]R')
+-            chmod_output = self.shell_output("chmod --help", timeout=timeout)
+-            match = re_recurse.search(chmod_output)
+-            if match:
+-                self._chmod_R = True
+-        except (ADBError, ADBTimeoutError) as e:
+-            self._logger.debug('Check chmod -R: %s' % e)
+-            match = re_recurse.search(e.message)
+-            if match:
+-                self._chmod_R = True
+-        self._logger.info("Native chmod -R support: %s" % self._chmod_R)
+-
+-        self._logger.debug("ADBDevice: %s" % self.__dict__)
+-
+-    def _get_device_serial(self, device):
+-        if device is None:
+-            devices = ADBHost(adb=self._adb_path, adb_host=self._adb_host,
+-                              adb_port=self._adb_port).devices()
+-            if len(devices) > 1:
+-                raise ValueError("ADBDevice called with multiple devices "
+-                                 "attached and no device specified")
+-            elif len(devices) == 0:
+-                # We could error here, but this way we'll wait-for-device before we next
+-                # run a command, which seems more friendly
+-                return
+-            device = devices[0]
+-
+-        def is_valid_serial(serial):
+-            return ":" not in serial or serial.startswith("usb:")
+-
+-        if isinstance(device, (str, unicode)):
+-            # Treat this as a device serial
+-            if not is_valid_serial(device):
+-                raise ValueError("Device serials containing ':' characters are "
+-                                 "invalid. Pass the output from "
+-                                 "ADBHost.devices() for the device instead")
+-            return device
+-
+-        serial = device.get("device_serial")
+-        if serial is not None and is_valid_serial(serial):
+-            return serial
+-        usb = device.get("usb")
+-        if usb is not None:
+-            return "usb:%s" % usb
+-
+-        raise ValueError("Unable to get device serial")
+-
+-    def _check_adb_root(self, timeout=None):
+-        self._have_root_shell = False
+-        uid = 'uid=0'
+-        # Is shell already running as root?
+-        try:
+-            if self.shell_output("id", timeout=timeout).find(uid) != -1:
+-                self._have_root_shell = True
+-                self._logger.info("adbd running as root")
+-        except ADBError:
+-            self._logger.debug("Check for root shell failed")
+-
+-        # Do we need to run adb root to get a root shell?
+-        try:
+-            if (not self._have_root_shell and self.command_output(
+-                    ["root"],
+-                    timeout=timeout).find("cannot run as root") == -1):
+-                self._have_root_shell = True
+-                self._logger.info("adbd restarted as root")
+-        except ADBError:
+-            self._logger.debug("Check for root adbd failed")
+-
+-    @staticmethod
+-    def _escape_command_line(cmd):
+-        """Utility function to return escaped and quoted version of command
+-        line.
+-        """
+-        quoted_cmd = []
+-
+-        for arg in cmd:
+-            arg.replace('&', r'\&')
+-
+-            needs_quoting = False
+-            for char in [' ', '(', ')', '"', '&']:
+-                if arg.find(char) >= 0:
+-                    needs_quoting = True
+-                    break
+-            if needs_quoting:
+-                arg = "'%s'" % arg
+-
+-            quoted_cmd.append(arg)
+-
+-        return " ".join(quoted_cmd)
+-
+-    @staticmethod
+-    def _get_exitcode(file_obj):
+-        """Get the exitcode from the last line of the file_obj for shell
+-        commands.
+-        """
+-        file_obj.seek(0, os.SEEK_END)
+-
+-        line = ''
+-        length = file_obj.tell()
+-        offset = 1
+-        while length - offset >= 0:
+-            file_obj.seek(-offset, os.SEEK_END)
+-            char = file_obj.read(1)
+-            if not char:
+-                break
+-            if char != '\r' and char != '\n':
+-                line = char + line
+-            elif line:
+-                # we have collected everything up to the beginning of the line
+-                break
+-            offset += 1
+-
+-        match = re.match(r'rc=([0-9]+)', line)
+-        if match:
+-            exitcode = int(match.group(1))
+-            file_obj.seek(-1, os.SEEK_CUR)
+-            file_obj.truncate()
+-        else:
+-            exitcode = None
+-
+-        return exitcode
+-
+-    @property
+-    def test_root(self):
+-        """
+-        The test_root property returns the directory on the device where
+-        temporary test files are stored.
+-
+-        The first time test_root it is called it determines and caches a value
+-        for the test root on the device. It determines the appropriate test
+-        root by attempting to create a 'dummy' directory on each of a list of
+-        directories and returning the first successful directory as the
+-        test_root value.
+-
+-        The default list of directories checked by test_root are:
+-
+-        - /storage/sdcard0/tests
+-        - /storage/sdcard1/tests
+-        - /sdcard/tests
+-        - /mnt/sdcard/tests
+-        - /data/local/tests
+-
+-        You may override the default list by providing a test_root argument to
+-        the :class:`ADBDevice` constructor which will then be used when
+-        attempting to create the 'dummy' directory.
+-
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        if self._test_root is not None:
+-            return self._test_root
+-
+-        if self._initial_test_root:
+-            paths = [self._initial_test_root]
+-        else:
+-            paths = ['/storage/sdcard0/tests',
+-                     '/storage/sdcard1/tests',
+-                     '/sdcard/tests',
+-                     '/mnt/sdcard/tests',
+-                     '/data/local/tests']
+-
+-        max_attempts = 3
+-        for attempt in range(1, max_attempts + 1):
+-            for test_root in paths:
+-                self._logger.debug("Setting test root to %s attempt %d of %d" %
+-                                   (test_root, attempt, max_attempts))
+-
+-                if self._try_test_root(test_root):
+-                    self._test_root = test_root
+-                    return self._test_root
+-
+-                self._logger.debug('_setup_test_root: '
+-                                   'Attempt %d of %d failed to set test_root to %s' %
+-                                   (attempt, max_attempts, test_root))
+-
+-            if attempt != max_attempts:
+-                time.sleep(20)
+-
+-        raise ADBError("Unable to set up test root using paths: [%s]"
+-                       % ", ".join(paths))
+-
+-    def _try_test_root(self, test_root):
+-        base_path, sub_path = posixpath.split(test_root)
+-        if not self.is_dir(base_path):
+-            return False
+-
+-        try:
+-            dummy_dir = posixpath.join(test_root, 'dummy')
+-            if self.is_dir(dummy_dir):
+-                self.rm(dummy_dir, recursive=True)
+-            self.mkdir(dummy_dir, parents=True)
+-        except ADBError:
+-            self._logger.debug("%s is not writable" % test_root)
+-            return False
+-
+-        return True
+-
+-    # Host Command methods
+-
+-    def command(self, cmds, timeout=None):
+-        """Executes an adb command on the host against the device.
+-
+-        :param list cmds: The command and its arguments to be
+-            executed.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per adb call. The
+-            total time spent may exceed this value. If it is not
+-            specified, the value set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: :class:`mozdevice.ADBProcess`
+-
+-        command() provides a low level interface for executing
+-        commands for a specific device on the host via adb.
+-
+-        command() executes on the host in such a fashion that stdout
+-        of the adb process are file handles on the host and
+-        the exit code is available as the exit code of the adb
+-        process.
+-
+-        For executing shell commands on the device, use
+-        ADBDevice.shell().  The caller provides a list containing
+-        commands, as well as a timeout period in seconds.
+-
+-        A subprocess is spawned to execute adb for the device with
+-        stdout and stderr directed to a temporary file. If the process
+-        takes longer than the specified timeout, the process is
+-        terminated.
+-
+-        It is the caller's responsibilty to clean up by closing
+-        the stdout temporary file.
+-        """
+-
+-        return ADBCommand.command(self, cmds,
+-                                  device_serial=self._device_serial,
+-                                  timeout=timeout)
+-
+-    def command_output(self, cmds, timeout=None):
+-        """Executes an adb command on the host against the device returning
+-        stdout.
+-
+-        :param list cmds: The command and its arguments to be executed.
+-        :param timeout: The maximum time in seconds
+-            for any spawned adb process to complete before throwing
+-            an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: string - content of stdout.
+-
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        return ADBCommand.command_output(self, cmds,
+-                                         device_serial=self._device_serial,
+-                                         timeout=timeout)
+-
+-    # Port forwarding methods
+-
+-    def _validate_port(self, port, is_local=True):
+-        """Validate a port forwarding specifier. Raises ValueError on failure.
+-
+-        :param str port: The port specifier to validate
+-        :param bool is_local: Flag indicating whether the port represents a local port.
+-        """
+-        prefixes = ["tcp", "localabstract", "localreserved", "localfilesystem", "dev"]
+-
+-        if not is_local:
+-            prefixes += ["jdwp"]
+-
+-        parts = port.split(":", 1)
+-        if len(parts) != 2 or parts[0] not in prefixes:
+-            raise ValueError("Invalid forward specifier %s" % port)
+-
+-    def forward(self, local, remote, allow_rebind=True, timeout=None):
+-        """Forward a local port to a specific port on the device.
+-
+-        Ports are specified in the form:
+-            tcp:<port>
+-            localabstract:<unix domain socket name>
+-            localreserved:<unix domain socket name>
+-            localfilesystem:<unix domain socket name>
+-            dev:<character device name>
+-            jdwp:<process pid> (remote only)
+-
+-        :param str local: Local port to forward
+-        :param str remote: Remote port to which to forward
+-        :param bool allow_rebind: Don't error if the local port is already forwarded
+-        :param timeout: The maximum time in seconds
+-            for any spawned adb process to complete before throwing
+-            an ADBTimeoutError. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ValueError
+-                 * ADBTimeoutError
+-                 * ADBError
+-        """
+-
+-        for port, is_local in [(local, True), (remote, False)]:
+-            self._validate_port(port, is_local=is_local)
+-
+-        cmd = ["forward", local, remote]
+-        if not allow_rebind:
+-            cmd.insert(1, "--no-rebind")
+-        self.command_output(cmd, timeout=timeout)
+-
+-    def list_forwards(self, timeout=None):
+-        """Return a list of tuples specifying active forwards
+-
+-        Return values are of the form (device, local, remote).
+-
+-        :param timeout: The maximum time in seconds
+-            for any spawned adb process to complete before throwing
+-            an ADBTimeoutError. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        forwards = self.command_output(["forward", "--list"], timeout=timeout)
+-        return [tuple(line.split(" ")) for line in forwards.splitlines() if line.strip()]
+-
+-    def remove_forwards(self, local=None, timeout=None):
+-        """Remove existing port forwards.
+-
+-        :param local: local port specifier as for ADBDevice.forward. If local
+-            is not specified removes all forwards.
+-        :type local: str or None
+-        :param timeout: The maximum time in seconds
+-            for any spawned adb process to complete before throwing
+-            an ADBTimeoutError. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ValueError
+-                 * ADBTimeoutError
+-                 * ADBError
+-        """
+-        cmd = ["forward"]
+-        if local is None:
+-            cmd.extend(["--remove-all"])
+-        else:
+-            self._validate_port(local, is_local=True)
+-            cmd.extend(["--remove", local])
+-
+-        self.command_output(cmd, timeout=timeout)
+-
+-    # Device Shell methods
+-
+-    def shell(self, cmd, env=None, cwd=None, timeout=None, root=False):
+-        """Executes a shell command on the device.
+-
+-        :param str cmd: The command to be executed.
+-        :param env: Contains the environment variables and
+-            their values.
+-        :type env: dict or None
+-        :param cwd: The directory from which to execute.
+-        :type cwd: str or None
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per adb call. The
+-            total time spent may exceed this value. If it is not
+-            specified, the value set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :returns: :class:`mozdevice.ADBProcess`
+-        :raises: ADBRootError
+-
+-        shell() provides a low level interface for executing commands
+-        on the device via adb shell.
+-
+-        shell() executes on the host in such as fashion that stdout
+-        contains the stdout and stderr of the host abd process
+-        combined with the stdout and stderr of the shell command
+-        on the device. The exit code of shell() is the exit code of
+-        the adb command if it was non-zero or the extracted exit code
+-        from the output of the shell command executed on the
+-        device.
+-
+-        The caller provides a flag indicating if the command is to be
+-        executed as root, a string for any requested working
+-        directory, a hash defining the environment, a string
+-        containing shell commands, as well as a timeout period in
+-        seconds.
+-
+-        The command line to be executed is created to set the current
+-        directory, set the required environment variables, optionally
+-        execute the command using su and to output the return code of
+-        the command to stdout. The command list is created as a
+-        command sequence separated by && which will terminate the
+-        command sequence on the first command which returns a non-zero
+-        exit code.
+-
+-        A subprocess is spawned to execute adb shell for the device
+-        with stdout and stderr directed to a temporary file. If the
+-        process takes longer than the specified timeout, the process
+-        is terminated. The return code is extracted from the stdout
+-        and is then removed from the file.
+-
+-        It is the caller's responsibilty to clean up by closing
+-        the stdout temporary files.
+-
+-        """
+-        if root and not self._have_root_shell:
+-            # If root was requested and we do not already have a root
+-            # shell, then use the appropriate version of su to invoke
+-            # the shell cmd. Prefer Android's su version since it may
+-            # falsely report support for su -c.
+-            if self._have_android_su:
+-                cmd = "su 0 %s" % cmd
+-            elif self._have_su:
+-                cmd = "su -c \"%s\"" % cmd
+-            else:
+-                raise ADBRootError('Can not run command %s as root!' % cmd)
+-
+-        # prepend cwd and env to command if necessary
+-        if cwd:
+-            cmd = "cd %s && %s" % (cwd, cmd)
+-        if env:
+-            envstr = '&& '.join(map(lambda x: 'export %s=%s' %
+-                                    (x[0], x[1]), env.iteritems()))
+-            cmd = envstr + "&& " + cmd
+-        cmd += "; echo rc=$?"
+-
+-        args = [self._adb_path]
+-        if self._adb_host:
+-            args.extend(['-H', self._adb_host])
+-        if self._adb_port:
+-            args.extend(['-P', str(self._adb_port)])
+-        if self._device_serial:
+-            args.extend(['-s', self._device_serial])
+-        args.extend(["wait-for-device", "shell", cmd])
+-        adb_process = ADBProcess(args)
+-
+-        if timeout is None:
+-            timeout = self._timeout
+-
+-        start_time = time.time()
+-        exitcode = adb_process.proc.poll()
+-        while ((time.time() - start_time) <= timeout) and exitcode is None:
+-            time.sleep(self._polling_interval)
+-            exitcode = adb_process.proc.poll()
+-        if exitcode is None:
+-            adb_process.proc.kill()
+-            adb_process.timedout = True
+-            adb_process.exitcode = adb_process.proc.poll()
+-        elif exitcode == 0:
+-            adb_process.exitcode = self._get_exitcode(adb_process.stdout_file)
+-        else:
+-            adb_process.exitcode = exitcode
+-
+-        adb_process.stdout_file.seek(0, os.SEEK_SET)
+-
+-        return adb_process
+-
+-    def shell_bool(self, cmd, env=None, cwd=None, timeout=None, root=False):
+-        """Executes a shell command on the device returning True on success
+-        and False on failure.
+-
+-        :param str cmd: The command to be executed.
+-        :param env: Contains the environment variables and
+-            their values.
+-        :type env: dict or None
+-        :param cwd: The directory from which to execute.
+-        :type cwd: str or None
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :returns: boolean
+-
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-        """
+-        adb_process = None
+-        try:
+-            adb_process = self.shell(cmd, env=env, cwd=cwd,
+-                                     timeout=timeout, root=root)
+-            if adb_process.timedout:
+-                raise ADBTimeoutError("%s" % adb_process)
+-            return adb_process.exitcode == 0
+-        finally:
+-            if adb_process:
+-                adb_process.stdout_file.close()
+-
+-    def shell_output(self, cmd, env=None, cwd=None, timeout=None, root=False):
+-        """Executes an adb shell on the device returning stdout.
+-
+-        :param str cmd: The command to be executed.
+-        :param env: Contains the environment variables and their values.
+-        :type env: dict or None
+-        :param cwd: The directory from which to execute.
+-        :type cwd: str or None
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per
+-            adb call. The total time spent may exceed this
+-            value. If it is not specified, the value set
+-            in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command
+-            should be executed as root.
+-        :returns: string - content of stdout.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        adb_process = None
+-        try:
+-            adb_process = self.shell(cmd, env=env, cwd=cwd,
+-                                     timeout=timeout, root=root)
+-            if adb_process.timedout:
+-                raise ADBTimeoutError("%s" % adb_process)
+-            elif adb_process.exitcode:
+-                raise ADBError("%s" % adb_process)
+-            output = adb_process.stdout_file.read().rstrip()
+-            if self._verbose:
+-                self._logger.debug('shell_output: %s, '
+-                                   'timeout: %s, '
+-                                   'root: %s, '
+-                                   'timedout: %s, '
+-                                   'exitcode: %s, '
+-                                   'output: %s' %
+-                                   (' '.join(adb_process.args),
+-                                    timeout,
+-                                    root,
+-                                    adb_process.timedout,
+-                                    adb_process.exitcode,
+-                                    output))
+-
+-            return output
+-        finally:
+-            if adb_process and isinstance(adb_process.stdout_file, file):
+-                adb_process.stdout_file.close()
+-
+-    # Informational methods
+-
+-    def _get_logcat_buffer_args(self, buffers):
+-        valid_buffers = set(['radio', 'main', 'events'])
+-        invalid_buffers = set(buffers).difference(valid_buffers)
+-        if invalid_buffers:
+-            raise ADBError('Invalid logcat buffers %s not in %s ' % (
+-                list(invalid_buffers), list(valid_buffers)))
+-        args = []
+-        for b in buffers:
+-            args.extend(['-b', b])
+-        return args
+-
+-    def clear_logcat(self, timeout=None, buffers=[]):
+-        """Clears logcat via adb logcat -c.
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.  This timeout is per
+-            adb call. The total time spent may exceed this
+-            value. If it is not specified, the value set
+-            in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param list buffers: Log buffers to clear. Valid buffers are
+-            "radio", "events", and "main". Defaults to "main".
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        buffers = self._get_logcat_buffer_args(buffers)
+-        cmds = ["logcat", "-c"] + buffers
+-        self.command_output(cmds, timeout=timeout)
+-        self.shell_output("log logcat cleared", timeout=timeout)
+-
+-    def get_logcat(self,
+-                   filter_specs=[
+-                       "dalvikvm:I",
+-                       "ConnectivityService:S",
+-                       "WifiMonitor:S",
+-                       "WifiStateTracker:S",
+-                       "wpa_supplicant:S",
+-                       "NetworkStateTracker:S"],
+-                   format="time",
+-                   filter_out_regexps=[],
+-                   timeout=None,
+-                   buffers=[]):
+-        """Returns the contents of the logcat file as a list of strings.
+-
+-        :param list filter_specs: Optional logcat messages to
+-            be included.
+-        :param str format: Optional logcat format.
+-        :param list filterOutRexps: Optional logcat messages to be
+-            excluded.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param list buffers: Log buffers to retrieve. Valid buffers are
+-            "radio", "events", and "main". Defaults to "main".
+-        :returns: list of lines logcat output.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        buffers = self._get_logcat_buffer_args(buffers)
+-        cmds = ["logcat", "-v", format, "-d"] + buffers + filter_specs
+-        lines = self.command_output(cmds, timeout=timeout).splitlines()
+-
+-        for regex in filter_out_regexps:
+-            lines = [line for line in lines if not re.search(regex, line)]
+-
+-        return lines
+-
+-    def get_prop(self, prop, timeout=None):
+-        """Gets value of a property from the device via adb shell getprop.
+-
+-        :param str prop: The propery name.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: string value of property.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        output = self.shell_output('getprop %s' % prop, timeout=timeout)
+-        return output
+-
+-    def get_state(self, timeout=None):
+-        """Returns the device's state via adb get-state.
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before throwing
+-            an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: string value of adb get-state.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        output = self.command_output(["get-state"], timeout=timeout).strip()
+-        return output
+-
+-    def get_ip_address(self, interfaces=None, timeout=None):
+-        """Returns the device's ip address, or None if it doesn't have one
+-
+-        :param interfaces: Interfaces to allow, or None to allow any
+-            non-loopback interface.
+-        :type interfaces: list or None
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before throwing
+-            an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: string ip address of the device or None if it could not
+-            be found.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        if not interfaces:
+-            interfaces = ["wlan0", "eth0"]
+-            wifi_interface = self.shell_output('getprop wifi.interface', timeout=timeout)
+-            self._logger.debug('get_ip_address: wifi_interface: %s' % wifi_interface)
+-            if wifi_interface and wifi_interface not in interfaces:
+-                interfaces = interfaces.append(wifi_interface)
+-
+-        # ifconfig interface
+-        # can return two different formats:
+-        # eth0: ip 192.168.1.139 mask 255.255.255.0 flags [up broadcast running multicast]
+-        # or
+-        # wlan0     Link encap:Ethernet  HWaddr 00:9A:CD:B8:39:65
+-        # inet addr:192.168.1.38  Bcast:192.168.1.255  Mask:255.255.255.0
+-        # inet6 addr: fe80::29a:cdff:feb8:3965/64 Scope: Link
+-        # UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
+-        # RX packets:180 errors:0 dropped:0 overruns:0 frame:0
+-        # TX packets:218 errors:0 dropped:0 overruns:0 carrier:0
+-        # collisions:0 txqueuelen:1000
+-        # RX bytes:84577 TX bytes:31202
+-
+-        re1_ip = re.compile(r'(\w+): ip ([0-9.]+) mask.*')
+-        # re1_ip will match output of the first format
+-        # with group 1 returning the interface and group 2 returing the ip address.
+-
+-        # re2_interface will match the interface line in the second format
+-        # while re2_ip will match the inet addr line of the second format.
+-        re2_interface = re.compile(r'(\w+)\s+Link')
+-        re2_ip = re.compile(r'\s+inet addr:([0-9.]+)')
+-
+-        matched_interface = None
+-        matched_ip = None
+-        re_bad_addr = re.compile(r'127.0.0.1|0.0.0.0')
+-
+-        self._logger.debug('get_ip_address: ifconfig')
+-        for interface in interfaces:
+-            try:
+-                output = self.shell_output('ifconfig %s' % interface,
+-                                           timeout=timeout)
+-            except ADBError:
+-                output = ''
+-
+-            for line in output.splitlines():
+-                if not matched_interface:
+-                    match = re1_ip.match(line)
+-                    if match:
+-                        matched_interface, matched_ip = match.groups()
+-                    else:
+-                        match = re2_interface.match(line)
+-                        if match:
+-                            matched_interface = match.group(1)
+-                else:
+-                    match = re2_ip.match(line)
+-                    if match:
+-                        matched_ip = match.group(1)
+-
+-                if matched_ip:
+-                    if not re_bad_addr.match(matched_ip):
+-                        self._logger.debug('get_ip_address: found: %s %s' %
+-                                           (matched_interface, matched_ip))
+-                        return matched_ip
+-                    matched_interface = None
+-                    matched_ip = None
+-
+-        self._logger.debug('get_ip_address: netcfg')
+-        # Fall back on netcfg if ifconfig does not work.
+-        # $ adb shell netcfg
+-        # lo       UP   127.0.0.1/8       0x00000049 00:00:00:00:00:00
+-        # dummy0   DOWN   0.0.0.0/0       0x00000082 8e:cd:67:48:b7:c2
+-        # rmnet0   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # rmnet1   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # rmnet2   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # rmnet3   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # rmnet4   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # rmnet5   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # rmnet6   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # rmnet7   DOWN   0.0.0.0/0       0x00000000 00:00:00:00:00:00
+-        # sit0     DOWN   0.0.0.0/0       0x00000080 00:00:00:00:00:00
+-        # vip0     DOWN   0.0.0.0/0       0x00001012 00:01:00:00:00:01
+-        # wlan0    UP   192.168.1.157/24  0x00001043 38:aa:3c:1c:f6:94
+-
+-        re3_netcfg = re.compile(r'(\w+)\s+UP\s+([1-9]\d{0,2}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
+-        try:
+-            output = self.shell_output('netcfg', timeout=timeout)
+-        except ADBError:
+-            output = ''
+-        for line in output.splitlines():
+-            match = re3_netcfg.search(line)
+-            if match:
+-                matched_interface, matched_ip = match.groups()
+-                if matched_interface == "lo" or re_bad_addr.match(matched_ip):
+-                    matched_interface = None
+-                    matched_ip = None
+-                elif matched_ip and matched_interface in interfaces:
+-                    self._logger.debug('get_ip_address: found: %s %s' %
+-                                       (matched_interface, matched_ip))
+-                    return matched_ip
+-        self._logger.debug('get_ip_address: not found')
+-        return matched_ip
+-
+-    # File management methods
+-
+-    def remount(self, timeout=None):
+-        """Remount /system/ in read/write mode
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before throwing
+-            an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError"""
+-
+-        rv = self.command_output(["remount"], timeout=timeout)
+-        if not rv.startswith("remount succeeded"):
+-            raise ADBError("Unable to remount device")
+-
+-    def chmod(self, path, recursive=False, mask="777", timeout=None, root=False):
+-        """Recursively changes the permissions of a directory on the
+-        device.
+-
+-        :param str path: The directory name on the device.
+-        :param bool recursive: Flag specifying if the command should be
+-            executed recursively.
+-        :param str mask: The octal permissions.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before throwing
+-            an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        # Note that on some tests such as webappstartup, an error
+-        # occurs during recursive calls to chmod where a "No such file
+-        # or directory" error will occur for the
+-        # /data/data/org.mozilla.fennec/files/mozilla/*.webapp0/lock
+-        # which is a symbolic link to a socket: lock ->
+-        # 127.0.0.1:+<port>.  On Linux, chmod -R ignores symbolic
+-        # links but it appear Android's version does not. We ignore
+-        # this type of error, but pass on any other errors that are
+-        # detected.
+-        path = posixpath.normpath(path.strip())
+-        self._logger.debug('chmod: path=%s, recursive=%s, mask=%s, root=%s' %
+-                           (path, recursive, mask, root))
+-        if not recursive:
+-            self.shell_output("chmod %s %s" % (mask, path),
+-                              timeout=timeout, root=root)
+-            return
+-
+-        if self._chmod_R:
+-            try:
+-                self.shell_output("chmod -R %s %s" % (mask, path),
+-                                  timeout=timeout, root=root)
+-            except ADBError as e:
+-                if e.message.find('No such file or directory') == -1:
+-                    raise
+-                self._logger.warning('chmod -R %s %s: Ignoring Error: %s' %
+-                                     (mask, path, e.message))
+-            return
+-        # Obtain a list of the directories and files which match path
+-        # and construct a shell script which explictly calls chmod on
+-        # each of them.
+-        entries = self.ls(path, recursive=recursive, timeout=timeout,
+-                          root=root)
+-        tmpf = None
+-        chmodsh = None
+-        try:
+-            tmpf = tempfile.NamedTemporaryFile(delete=False)
+-            for entry in entries:
+-                tmpf.write('chmod %s %s\n' % (mask, entry))
+-            tmpf.close()
+-            chmodsh = '/data/local/tmp/%s' % os.path.basename(tmpf.name)
+-            self.push(tmpf.name, chmodsh)
+-            self.shell_output('chmod 777 %s' % chmodsh, timeout=timeout,
+-                              root=root)
+-            self.shell_output('sh -c %s' % chmodsh, timeout=timeout,
+-                              root=root)
+-        finally:
+-            if tmpf:
+-                os.unlink(tmpf.name)
+-            if chmodsh:
+-                self.rm(chmodsh, timeout=timeout, root=root)
+-
+-    def exists(self, path, timeout=None, root=False):
+-        """Returns True if the path exists on the device.
+-
+-        :param str path: The directory name on the device.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should be
+-            executed as root.
+-        :returns: boolean - True if path exists.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-        """
+-        path = posixpath.normpath(path)
+-        return self.shell_bool('ls -a %s' % path, timeout=timeout, root=root)
+-
+-    def is_dir(self, path, timeout=None, root=False):
+-        """Returns True if path is an existing directory on the device.
+-
+-        :param str path: The path on the device.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :returns: boolean - True if path exists on the device and is a
+-            directory.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-        """
+-        path = posixpath.normpath(path)
+-        return self.shell_bool('ls -a %s/' % path, timeout=timeout, root=root)
+-
+-    def is_file(self, path, timeout=None, root=False):
+-        """Returns True if path is an existing file on the device.
+-
+-        :param str path: The file name on the device.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :returns: boolean - True if path exists on the device and is a
+-            file.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-        """
+-        path = posixpath.normpath(path)
+-        return (
+-            self.exists(path, timeout=timeout, root=root) and
+-            not self.is_dir(path, timeout=timeout, root=root))
+-
+-    def list_files(self, path, timeout=None, root=False):
+-        """Return a list of files/directories contained in a directory
+-        on the device.
+-
+-        :param str path: The directory name on the device.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :returns: list of files/directories contained in the directory.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-        """
+-        path = posixpath.normpath(path.strip())
+-        data = []
+-        if self.is_dir(path, timeout=timeout, root=root):
+-            try:
+-                data = self.shell_output("%s %s" % (self._ls, path),
+-                                         timeout=timeout,
+-                                         root=root).splitlines()
+-                self._logger.debug('list_files: data: %s' % data)
+-            except ADBError:
+-                self._logger.error('Ignoring exception in ADBDevice.list_files\n%s' %
+-                                   traceback.format_exc())
+-        data[:] = [item for item in data if item]
+-        self._logger.debug('list_files: %s' % data)
+-        return data
+-
+-    def ls(self, path, recursive=False, timeout=None, root=False):
+-        """Return a list of matching files/directories on the device.
+-
+-        The ls method emulates the behavior of the ls shell command.
+-        It differs from the list_files method by supporting wild cards
+-        and returning matches even if the path is not a directory and
+-        by allowing a recursive listing.
+-
+-        ls /sdcard always returns /sdcard and not the contents of the
+-        sdcard path. The ls method makes the behavior consistent with
+-        others paths by adjusting /sdcard to /sdcard/. Note this is
+-        also the case of other sdcard related paths such as
+-        /storage/emulated/legacy but no adjustment is made in those
+-        cases.
+-
+-        The ls method works around a Nexus 4 bug which prevents
+-        recursive listing of directories on the sdcard unless the path
+-        ends with "/*" by adjusting sdcard paths ending in "/" to end
+-        with "/*". This adjustment is only made on official Nexus 4
+-        builds with property ro.product.model "Nexus 4". Note that
+-        this will fail to return any "hidden" files or directories
+-        which begin with ".".
+-
+-        :param str path: The directory name on the device.
+-        :param bool recursive: Flag specifying if a recursive listing
+-            is to be returned. If recursive is False, the returned
+-            matches will be relative to the path. If recursive is True,
+-            the returned matches will be absolute paths.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :returns: list of files/directories contained in the directory.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-        """
+-        path = posixpath.normpath(path.strip())
+-        parent = ''
+-        entries = {}
+-
+-        if path == '/sdcard':
+-            path += '/'
+-
+-        # Android 2.3 and later all appear to support ls -R however
+-        # Nexus 4 does not perform a recursive search on the sdcard
+-        # unless the path is a directory with * wild card.
+-        if not recursive:
+-            recursive_flag = ''
+-        else:
+-            recursive_flag = '-R'
+-            if path.startswith('/sdcard') and path.endswith('/'):
+-                model = self.shell_output('getprop ro.product.model',
+-                                          timeout=timeout,
+-                                          root=root)
+-                if model == 'Nexus 4':
+-                    path += '*'
+-        lines = self.shell_output('%s %s %s' % (self._ls, recursive_flag, path),
+-                                  timeout=timeout,
+-                                  root=root).splitlines()
+-        for line in lines:
+-            line = line.strip()
+-            if not line:
+-                parent = ''
+-                continue
+-            if line.endswith(':'):  # This is a directory
+-                parent = line.replace(':', '/')
+-                entry = parent
+-                # Remove earlier entry which is marked as a file.
+-                if parent[:-1] in entries:
+-                    del entries[parent[:-1]]
+-            elif parent:
+-                entry = "%s%s" % (parent, line)
+-            else:
+-                entry = line
+-            entries[entry] = 1
+-        entry_list = entries.keys()
+-        entry_list.sort()
+-        return entry_list
+-
+-    def mkdir(self, path, parents=False, timeout=None, root=False):
+-        """Create a directory on the device.
+-
+-        :param str path: The directory name on the device
+-            to be created.
+-        :param bool parents: Flag indicating if the parent directories are
+-            also to be created. Think mkdir -p path.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        path = posixpath.normpath(path)
+-        if parents:
+-            if self._mkdir_p is None or self._mkdir_p:
+-                # Use shell_bool to catch the possible
+-                # non-zero exitcode if -p is not supported.
+-                if self.shell_bool('mkdir -p %s' % path, timeout=timeout,
+-                                   root=root):
+-                    self._mkdir_p = True
+-                    return
+-            # mkdir -p is not supported. create the parent
+-            # directories individually.
+-            if not self.is_dir(posixpath.dirname(path), root=root):
+-                parts = path.split('/')
+-                name = "/"
+-                for part in parts[:-1]:
+-                    if part != "":
+-                        name = posixpath.join(name, part)
+-                        if not self.is_dir(name, root=root):
+-                            # Use shell_output to allow any non-zero
+-                            # exitcode to raise an ADBError.
+-                            self.shell_output('mkdir %s' % name,
+-                                              timeout=timeout, root=root)
+-
+-        # If parents is True and the directory does exist, we don't
+-        # need to do anything. Otherwise we call mkdir. If the
+-        # directory already exists or if it is a file instead of a
+-        # directory, mkdir will fail and we will raise an ADBError.
+-        if not parents or not self.is_dir(path, root=root):
+-            self.shell_output('mkdir %s' % path, timeout=timeout, root=root)
+-        if not self.is_dir(path, timeout=timeout, root=root):
+-            raise ADBError('mkdir %s Failed' % path)
+-
+-    def push(self, local, remote, timeout=None):
+-        """Pushes a file or directory to the device.
+-
+-        :param str local: The name of the local file or
+-            directory name.
+-        :param str remote: The name of the remote file or
+-            directory name.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        # remove trailing /
+-        local = os.path.normpath(local)
+-        remote = os.path.normpath(remote)
+-        copy_required = False
+-        if os.path.isdir(local):
+-            copy_required = True
+-            temp_parent = tempfile.mkdtemp()
+-            remote_name = os.path.basename(remote)
+-            new_local = os.path.join(temp_parent, remote_name)
+-            dir_util.copy_tree(local, new_local)
+-            local = new_local
+-            # See do_sync_push in
+-            # https://android.googlesource.com/platform/system/core/+/master/adb/file_sync_client.cpp
+-            # Work around change in behavior in adb 1.0.36 where if
+-            # the remote destination directory exists, adb push will
+-            # copy the source directory *into* the destination
+-            # directory otherwise it will copy the source directory
+-            # *onto* the destination directory.
+-            if self._adb_version >= '1.0.36':
+-                remote = '/'.join(remote.rstrip('/').split('/')[:-1])
+-        try:
+-            self.command_output(["push", local, remote], timeout=timeout)
+-        except BaseException:
+-            raise
+-        finally:
+-            if copy_required:
+-                shutil.rmtree(temp_parent)
+-
+-    def pull(self, remote, local, timeout=None):
+-        """Pulls a file or directory from the device.
+-
+-        :param str remote: The path of the remote file or
+-            directory.
+-        :param str local: The path of the local file or
+-            directory name.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        # remove trailing /
+-        local = os.path.normpath(local)
+-        remote = os.path.normpath(remote)
+-        copy_required = False
+-        original_local = local
+-        if self._adb_version >= '1.0.36' and \
+-           os.path.isdir(local) and self.is_dir(remote):
+-            # See do_sync_pull in
+-            # https://android.googlesource.com/platform/system/core/+/master/adb/file_sync_client.cpp
+-            # Work around change in behavior in adb 1.0.36 where if
+-            # the local destination directory exists, adb pull will
+-            # copy the source directory *into* the destination
+-            # directory otherwise it will copy the source directory
+-            # *onto* the destination directory.
+-            #
+-            # If the destination directory does exist, pull to its
+-            # parent directory. If the source and destination leaf
+-            # directory names are different, pull the source directory
+-            # into a temporary directory and then copy the temporary
+-            # directory onto the destination.
+-            local_name = os.path.basename(local)
+-            remote_name = os.path.basename(remote)
+-            if local_name != remote_name:
+-                copy_required = True
+-                temp_parent = tempfile.mkdtemp()
+-                local = os.path.join(temp_parent, remote_name)
+-            else:
+-                local = '/'.join(local.rstrip('/').split('/')[:-1])
+-        try:
+-            self.command_output(["pull", remote, local], timeout=timeout)
+-        except BaseException:
+-            raise
+-        finally:
+-            if copy_required:
+-                dir_util.copy_tree(local, original_local)
+-                shutil.rmtree(temp_parent)
+-
+-    def rm(self, path, recursive=False, force=False, timeout=None, root=False):
+-        """Delete files or directories on the device.
+-
+-        :param str path: The path of the remote file or directory.
+-        :param bool recursive: Flag specifying if the command is
+-            to be applied recursively to the target. Default is False.
+-        :param bool force: Flag which if True will not raise an
+-            error when attempting to delete a non-existent file. Default
+-            is False.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        cmd = "rm"
+-        if recursive:
+-            cmd += " -r"
+-        try:
+-            self.shell_output("%s %s" % (cmd, path), timeout=timeout, root=root)
+-            if self.is_file(path, timeout=timeout, root=root):
+-                raise ADBError('rm("%s") failed to remove file.' % path)
+-        except ADBError as e:
+-            if not force and 'No such file or directory' in e.message:
+-                raise
+-
+-    def rmdir(self, path, timeout=None, root=False):
+-        """Delete empty directory on the device.
+-
+-        :param str path: The directory name on the device.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        self.shell_output("rmdir %s" % path, timeout=timeout, root=root)
+-        if self.is_dir(path, timeout=timeout, root=root):
+-            raise ADBError('rmdir("%s") failed to remove directory.' % path)
+-
+-    # Process management methods
+-
+-    def get_process_list(self, timeout=None):
+-        """Returns list of tuples (pid, name, user) for running
+-        processes on device.
+-
+-        :param timeout: The maximum time
+-            in seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified,
+-            the value set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: list of (pid, name, user) tuples for running processes
+-            on the device.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        adb_process = None
+-        try:
+-            adb_process = self.shell("ps", timeout=timeout)
+-            if adb_process.timedout:
+-                raise ADBTimeoutError("%s" % adb_process)
+-            elif adb_process.exitcode:
+-                raise ADBError("%s" % adb_process)
+-            # first line is the headers
+-            header = adb_process.stdout_file.readline()
+-            pid_i = -1
+-            user_i = -1
+-            els = header.split()
+-            for i in range(len(els)):
+-                item = els[i].lower()
+-                if item == 'user':
+-                    user_i = i
+-                elif item == 'pid':
+-                    pid_i = i
+-            if user_i == -1 or pid_i == -1:
+-                self._logger.error('get_process_list: %s' % header)
+-                raise ADBError('get_process_list: Unknown format: %s: %s' % (
+-                    header, adb_process))
+-            ret = []
+-            line = adb_process.stdout_file.readline()
+-            while line:
+-                els = line.split()
+-                try:
+-                    ret.append([int(els[pid_i]), els[-1], els[user_i]])
+-                except ValueError:
+-                    self._logger.error('get_process_list: %s %s\n%s' % (
+-                        header, line, traceback.format_exc()))
+-                    raise ADBError('get_process_list: %s: %s: %s' % (
+-                        header, line, adb_process))
+-                line = adb_process.stdout_file.readline()
+-            self._logger.debug('get_process_list: %s' % ret)
+-            return ret
+-        finally:
+-            if adb_process and isinstance(adb_process.stdout_file, file):
+-                adb_process.stdout_file.close()
+-
+-    def kill(self, pids, sig=None, attempts=3, wait=5,
+-             timeout=None, root=False):
+-        """Kills processes on the device given a list of process ids.
+-
+-        :param list pids: process ids to be killed.
+-        :param sig: signal to be sent to the process.
+-        :type sig: integer or None
+-        :param integer attempts: number of attempts to try to
+-            kill the processes.
+-        :param integer wait: number of seconds to wait after each attempt.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        pid_list = [str(pid) for pid in pids]
+-        for attempt in range(attempts):
+-            args = ["kill"]
+-            if sig:
+-                args.append("-%d" % sig)
+-            args.extend(pid_list)
+-            try:
+-                self.shell_output(' '.join(args), timeout=timeout, root=root)
+-            except ADBError as e:
+-                if 'No such process' not in e.message:
+-                    raise
+-            pid_set = set(pid_list)
+-            current_pid_set = set([str(proc[0]) for proc in
+-                                   self.get_process_list(timeout=timeout)])
+-            pid_list = list(pid_set.intersection(current_pid_set))
+-            if not pid_list:
+-                break
+-            self._logger.debug("Attempt %d of %d to kill processes %s failed" %
+-                               (attempt + 1, attempts, pid_list))
+-            time.sleep(wait)
+-
+-        if pid_list:
+-            raise ADBError('kill: processes %s not killed' % pid_list)
+-
+-    def pkill(self, appname, sig=None, attempts=3, wait=5,
+-              timeout=None, root=False):
+-        """Kills a processes on the device matching a name.
+-
+-        :param str appname: The app name of the process to
+-            be killed. Note that only the first 75 characters of the
+-            process name are significant.
+-        :param sig: optional signal to be sent to the process.
+-        :type sig: integer or None
+-        :param integer attempts: number of attempts to try to
+-            kill the processes.
+-        :param integer wait: number of seconds to wait after each attempt.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should
+-            be executed as root.
+-
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        procs = self.get_process_list(timeout=timeout)
+-        # limit the comparion to the first 75 characters due to a
+-        # limitation in processname length in android.
+-        pids = [proc[0] for proc in procs if proc[1] == appname[:75]]
+-        if not pids:
+-            return
+-
+-        try:
+-            self.kill(pids, sig, attempts=attempts, wait=wait,
+-                      timeout=timeout, root=root)
+-        except ADBError as e:
+-            if self.process_exist(appname, timeout=timeout):
+-                raise e
+-
+-    def process_exist(self, process_name, timeout=None):
+-        """Returns True if process with name process_name is running on
+-        device.
+-
+-        :param str process_name: The name of the process
+-            to check. Note that only the first 75 characters of the
+-            process name are significant.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: boolean - True if process exists.
+-
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        if not isinstance(process_name, basestring):
+-            raise ADBError("Process name %s is not a string" % process_name)
+-
+-        # Filter out extra spaces.
+-        parts = [x for x in process_name.split(' ') if x != '']
+-        process_name = ' '.join(parts)
+-
+-        # Filter out the quoted env string if it exists
+-        # ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
+-        parts = process_name.split('"')
+-        if len(parts) > 2:
+-            process_name = ' '.join(parts[2:]).strip()
+-
+-        pieces = process_name.split(' ')
+-        parts = pieces[0].split('/')
+-        app = parts[-1]
+-
+-        proc_list = self.get_process_list(timeout=timeout)
+-        if not proc_list:
+-            return False
+-
+-        for proc in proc_list:
+-            proc_name = proc[1].split('/')[-1]
+-            # limit the comparion to the first 75 characters due to a
+-            # limitation in processname length in android.
+-            if proc_name == app[:75]:
+-                return True
+-        return False
+-
+-    def cp(self, source, destination, recursive=False, timeout=None,
+-           root=False):
+-        """Copies a file or directory on the device.
+-
+-        :param source: string containing the path of the source file or
+-            directory.
+-        :param destination: string containing the path of the destination file
+-            or directory.
+-        :param recursive: optional boolean indicating if a recursive copy is to
+-            be performed. Required if the source is a directory. Defaults to
+-            False. Think cp -R source destination.
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        source = posixpath.normpath(source)
+-        destination = posixpath.normpath(destination)
+-        if self._have_cp:
+-            r = '-R' if recursive else ''
+-            self.shell_output('cp %s %s %s' % (r, source, destination),
+-                              timeout=timeout, root=root)
+-            return
+-
+-        # Emulate cp behavior depending on if source and destination
+-        # already exists and whether they are a directory or file.
+-        if not self.exists(source, timeout=timeout, root=root):
+-            raise ADBError("cp: can't stat '%s': No such file or directory" %
+-                           source)
+-
+-        if self.is_file(source, timeout=timeout, root=root):
+-            if self.is_dir(destination, timeout=timeout, root=root):
+-                # Copy the source file into the destination directory
+-                destination = posixpath.join(destination,
+-                                             posixpath.basename(source))
+-            self.shell_output('dd if=%s of=%s' % (source, destination),
+-                              timeout=timeout, root=root)
+-            return
+-
+-        if self.is_file(destination, timeout=timeout, root=root):
+-            raise ADBError('cp: %s: Not a directory' % destination)
+-
+-        if not recursive:
+-            raise ADBError("cp: omitting directory '%s'" % source)
+-
+-        if self.is_dir(destination, timeout=timeout, root=root):
+-            # Copy the source directory into the destination directory.
+-            destination_dir = posixpath.join(destination,
+-                                             posixpath.basename(source))
+-        else:
+-            # Copy the contents of the source directory into the
+-            # destination directory.
+-            destination_dir = destination
+-
+-        try:
+-            # Do not create parent directories since cp does not.
+-            self.mkdir(destination_dir, timeout=timeout, root=root)
+-        except ADBError as e:
+-            if 'File exists' not in e.message:
+-                raise
+-
+-        for i in self.list_files(source, timeout=timeout, root=root):
+-            self.cp(posixpath.join(source, i),
+-                    posixpath.join(destination_dir, i),
+-                    recursive=recursive,
+-                    timeout=timeout, root=root)
+-
+-    def mv(self, source, destination, timeout=None, root=False):
+-        """Moves a file or directory on the device.
+-
+-        :param source: string containing the path of the source file or
+-            directory.
+-        :param destination: string containing the path of the destination file
+-            or directory.
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBRootError
+-                 * ADBError
+-        """
+-        source = posixpath.normpath(source)
+-        destination = posixpath.normpath(destination)
+-        self.shell_output('mv %s %s' % (source, destination), timeout=timeout,
+-                          root=root)
+-
+-    def reboot(self, timeout=None):
+-        """Reboots the device.
+-
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-
+-        reboot() reboots the device, issues an adb wait-for-device in order to
+-        wait for the device to complete rebooting, then calls is_device_ready()
+-        to determine if the device has completed booting.
+-        """
+-        self.command_output(["reboot"], timeout=timeout)
+-        # command_output automatically inserts a 'wait-for-device'
+-        # argument to adb. Issuing an empty command is the same as adb
+-        # -s <device> wait-for-device. We don't send an explicit
+-        # 'wait-for-device' since that would add duplicate
+-        # 'wait-for-device' arguments which is an error in newer
+-        # versions of adb.
+-        self.command_output([], timeout=timeout)
+-        self._check_adb_root(timeout=timeout)
+-        return self.is_device_ready(timeout=timeout)
+-
+-    @abstractmethod
+-    def is_device_ready(self, timeout=None):
+-        """Abstract class that returns True if the device is ready.
+-
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        return
+-
+-    @abstractmethod
+-    def get_battery_percentage(self, timeout=None):
+-        """Abstract class that returns the battery charge as a percentage.
+-
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :returns: battery charge as a percentage.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        return
+-
+-    def get_info(self, directive=None, timeout=None):
+-        """
+-        Returns a dictionary of information strings about the device.
+-
+-        :param directive: information you want to get. Options are:
+-             - `battery` - battery charge as a percentage
+-             - `disk` - total, free, available bytes on disk
+-             - `id` - unique id of the device
+-             - `os` - name of the os
+-             - `process` - list of running processes (same as ps)
+-             - `systime` - system time of the device
+-             - `uptime` - uptime of the device
+-
+-            If `directive` is `None`, will return all available information
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        directives = ['battery', 'disk', 'id', 'os', 'process', 'systime',
+-                      'uptime']
+-
+-        if directive in directives:
+-            directives = [directive]
+-
+-        info = {}
+-        if 'battery' in directives:
+-            info['battery'] = self.get_battery_percentage(timeout=timeout)
+-        if 'disk' in directives:
+-            info['disk'] = self.shell_output('df /data /system /sdcard',
+-                                             timeout=timeout).splitlines()
+-        if 'id' in directives:
+-            info['id'] = self.command_output(['get-serialno'], timeout=timeout)
+-        if 'os' in directives:
+-            info['os'] = self.shell_output('getprop ro.build.display.id',
+-                                           timeout=timeout)
+-        if 'process' in directives:
+-            ps = self.shell_output('ps', timeout=timeout)
+-            info['process'] = ps.splitlines()
+-        if 'systime' in directives:
+-            info['systime'] = self.shell_output('date', timeout=timeout)
+-        if 'uptime' in directives:
+-            uptime = self.shell_output('uptime', timeout=timeout)
+-            if uptime:
+-                m = re.match(r'up time: ((\d+) days, )*(\d{2}):(\d{2}):(\d{2})',
+-                             uptime)
+-                if m:
+-                    uptime = '%d days %d hours %d minutes %d seconds' % tuple(
+-                        [int(g or 0) for g in m.groups()[1:]])
+-                info['uptime'] = uptime
+-        return info
+diff --git a/testing/mozbase/mozdevice/mozdevice/adb_android.py b/testing/mozbase/mozdevice/mozdevice/adb_android.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/adb_android.py
++++ /dev/null
+@@ -1,496 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import, print_function
+-
+-import os
+-import re
+-import time
+-
+-from abc import ABCMeta
+-
+-from . import version_codes
+-
+-from .adb import ADBDevice, ADBError, ADBRootError
+-
+-
+-class ADBAndroid(ADBDevice):
+-    """ADBAndroid implements :class:`ADBDevice` providing Android-specific
+-    functionality.
+-
+-    ::
+-
+-       from mozdevice import ADBAndroid
+-
+-       adbdevice = ADBAndroid()
+-       print(adbdevice.list_files("/mnt/sdcard"))
+-       if adbdevice.process_exist("org.mozilla.fennec"):
+-           print("Fennec is running")
+-    """
+-    __metaclass__ = ABCMeta
+-
+-    def __init__(self,
+-                 device=None,
+-                 adb='adb',
+-                 adb_host=None,
+-                 adb_port=None,
+-                 test_root='',
+-                 logger_name='adb',
+-                 timeout=300,
+-                 verbose=False,
+-                 device_ready_retry_wait=20,
+-                 device_ready_retry_attempts=3):
+-        """Initializes the ADBAndroid object.
+-
+-        :param device: When a string is passed, it is interpreted as the
+-            device serial number. This form is not compatible with
+-            devices containing a ":" in the serial; in this case
+-            ValueError will be raised.
+-            When a dictionary is passed it must have one or both of
+-            the keys "device_serial" and "usb". This is compatible
+-            with the dictionaries in the list returned by
+-            ADBHost.devices(). If the value of device_serial is a
+-            valid serial not containing a ":" it will be used to
+-            identify the device, otherwise the value of the usb key,
+-            prefixed with "usb:" is used.
+-            If None is passed and there is exactly one device attached
+-            to the host, that device is used. If there is more than one
+-            device attached, ValueError is raised. If no device is
+-            attached the constructor will block until a device is
+-            attached or the timeout is reached.
+-        :type device: dict, str or None
+-        :param adb_host: host of the adb server to connect to.
+-        :type adb_host: str or None
+-        :param adb_port: port of the adb server to connect to.
+-        :type adb_port: integer or None
+-        :param str logger_name: logging logger name. Defaults to 'adb'.
+-        :param integer device_ready_retry_wait: number of seconds to wait
+-            between attempts to check if the device is ready after a
+-            reboot.
+-        :param integer device_ready_retry_attempts: number of attempts when
+-            checking if a device is ready.
+-
+-        :raises: * ADBError
+-                 * ADBTimeoutError
+-                 * ValueError
+-        """
+-        ADBDevice.__init__(self, device=device, adb=adb,
+-                           adb_host=adb_host, adb_port=adb_port,
+-                           test_root=test_root,
+-                           logger_name=logger_name, timeout=timeout,
+-                           verbose=verbose,
+-                           device_ready_retry_wait=device_ready_retry_wait,
+-                           device_ready_retry_attempts=device_ready_retry_attempts)
+-        # https://source.android.com/devices/tech/security/selinux/index.html
+-        # setenforce
+-        # usage:  setenforce [ Enforcing | Permissive | 1 | 0 ]
+-        # getenforce returns either Enforcing or Permissive
+-
+-        try:
+-            self.selinux = True
+-            if self.shell_output('getenforce', timeout=timeout) != 'Permissive':
+-                self._logger.info('Setting SELinux Permissive Mode')
+-                self.shell_output("setenforce Permissive", timeout=timeout, root=True)
+-        except (ADBError, ADBRootError) as e:
+-            self._logger.warning('Unable to set SELinux Permissive due to %s.' % e)
+-            self.selinux = False
+-
+-        self.version = int(self.shell_output("getprop ro.build.version.sdk",
+-                                             timeout=timeout))
+-
+-    def reboot(self, timeout=None):
+-        """Reboots the device.
+-
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-
+-        reboot() reboots the device, issues an adb wait-for-device in order to
+-        wait for the device to complete rebooting, then calls is_device_ready()
+-        to determine if the device has completed booting.
+-
+-        If the device supports running adbd as root, adbd will be
+-        restarted running as root. Then, if the device supports
+-        SELinux, setenforce Permissive will be called to change
+-        SELinux to permissive. This must be done after adbd is
+-        restarted in order for the SELinux Permissive setting to
+-        persist.
+-
+-        """
+-        ready = ADBDevice.reboot(self, timeout=timeout)
+-        self._check_adb_root(timeout=timeout)
+-        return ready
+-
+-    # Informational methods
+-
+-    def get_battery_percentage(self, timeout=None):
+-        """Returns the battery charge as a percentage.
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :type timeout: integer or None
+-        :returns: battery charge as a percentage.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        level = None
+-        scale = None
+-        percentage = 0
+-        cmd = "dumpsys battery"
+-        re_parameter = re.compile(r'\s+(\w+):\s+(\d+)')
+-        lines = self.shell_output(cmd, timeout=timeout).splitlines()
+-        for line in lines:
+-            match = re_parameter.match(line)
+-            if match:
+-                parameter = match.group(1)
+-                value = match.group(2)
+-                if parameter == 'level':
+-                    level = float(value)
+-                elif parameter == 'scale':
+-                    scale = float(value)
+-                if parameter is not None and scale is not None:
+-                    percentage = 100.0 * level / scale
+-                    break
+-        return percentage
+-
+-    # System control methods
+-
+-    def is_device_ready(self, timeout=None):
+-        """Checks if a device is ready for testing.
+-
+-        This method uses the android only package manager to check for
+-        readiness.
+-
+-        :param timeout: The maximum time
+-            in seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        # command_output automatically inserts a 'wait-for-device'
+-        # argument to adb. Issuing an empty command is the same as adb
+-        # -s <device> wait-for-device. We don't send an explicit
+-        # 'wait-for-device' since that would add duplicate
+-        # 'wait-for-device' arguments which is an error in newer
+-        # versions of adb.
+-        self.command_output([], timeout=timeout)
+-        pm_error_string = "Error: Could not access the Package Manager"
+-        pm_list_commands = ["packages", "permission-groups", "permissions",
+-                            "instrumentation", "features", "libraries"]
+-        ready_path = os.path.join(self.test_root, "ready")
+-        for attempt in range(self._device_ready_retry_attempts):
+-            failure = 'Unknown failure'
+-            success = True
+-            try:
+-                state = self.get_state(timeout=timeout)
+-                if state != 'device':
+-                    failure = "Device state: %s" % state
+-                    success = False
+-                else:
+-                    if (self.selinux and self.shell_output('getenforce',
+-                                                           timeout=timeout) != 'Permissive'):
+-                        self._logger.info('Setting SELinux Permissive Mode')
+-                        self.shell_output("setenforce Permissive", timeout=timeout, root=True)
+-                    if self.is_dir(ready_path, timeout=timeout):
+-                        self.rmdir(ready_path, timeout=timeout)
+-                    self.mkdir(ready_path, timeout=timeout)
+-                    self.rmdir(ready_path, timeout=timeout)
+-                    # Invoke the pm list commands to see if it is up and
+-                    # running.
+-                    for pm_list_cmd in pm_list_commands:
+-                        data = self.shell_output("pm list %s" % pm_list_cmd,
+-                                                 timeout=timeout)
+-                        if pm_error_string in data:
+-                            failure = data
+-                            success = False
+-                            break
+-            except ADBError as e:
+-                success = False
+-                failure = e.message
+-
+-            if not success:
+-                self._logger.debug('Attempt %s of %s device not ready: %s' % (
+-                    attempt + 1, self._device_ready_retry_attempts,
+-                    failure))
+-                time.sleep(self._device_ready_retry_wait)
+-
+-        return success
+-
+-    def power_on(self, timeout=None):
+-        """Sets the device's power stayon value.
+-
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        try:
+-            self.shell_output('svc power stayon true',
+-                              timeout=timeout,
+-                              root=True)
+-        except ADBError as e:
+-            # Executing this via adb shell errors, but not interactively.
+-            # Any other exitcode is a real error.
+-            if 'exitcode: 137' not in e.message:
+-                raise
+-            self._logger.warning('Unable to set power stayon true: %s' % e)
+-
+-    # Application management methods
+-
+-    def install_app(self, apk_path, timeout=None):
+-        """Installs an app on the device.
+-
+-        :param str apk_path: The apk file name to be installed.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        cmd = ["install"]
+-        if self.version >= version_codes.M:
+-            cmd.append("-g")
+-        cmd.append(apk_path)
+-        data = self.command_output(cmd, timeout=timeout)
+-        if data.find('Success') == -1:
+-            raise ADBError("install failed for %s. Got: %s" %
+-                           (apk_path, data))
+-
+-    def is_app_installed(self, app_name, timeout=None):
+-        """Returns True if an app is installed on the device.
+-
+-        :param str app_name: The name of the app to be checked.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        pm_error_string = 'Error: Could not access the Package Manager'
+-        data = self.shell_output("pm list package %s" % app_name, timeout=timeout)
+-        if pm_error_string in data:
+-            raise ADBError(pm_error_string)
+-        if app_name not in data:
+-            return False
+-        return True
+-
+-    def launch_application(self, app_name, activity_name, intent, url=None,
+-                           extras=None, wait=True, fail_if_running=True,
+-                           timeout=None):
+-        """Launches an Android application
+-
+-        :param str app_name: Name of application (e.g. `com.android.chrome`)
+-        :param str activity_name: Name of activity to launch (e.g. `.Main`)
+-        :param str intent: Intent to launch application with
+-        :param url: URL to open
+-        :type url: str or None
+-        :param extras: Extra arguments for application.
+-        :type extras: dict or None
+-        :param bool wait: If True, wait for application to start before
+-            returning.
+-        :param bool fail_if_running: Raise an exception if instance of
+-            application is already running.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        # If fail_if_running is True, we throw an exception here. Only one
+-        # instance of an application can be running at once on Android,
+-        # starting a new instance may not be what we want depending on what
+-        # we want to do
+-        if fail_if_running and self.process_exist(app_name, timeout=timeout):
+-            raise ADBError("Only one instance of an application may be running "
+-                           "at once")
+-
+-        acmd = ["am", "start"] + \
+-            ["-W" if wait else '', "-n", "%s/%s" % (app_name, activity_name)]
+-
+-        if intent:
+-            acmd.extend(["-a", intent])
+-
+-        if extras:
+-            for (key, val) in extras.iteritems():
+-                if isinstance(val, int):
+-                    extra_type_param = "--ei"
+-                elif isinstance(val, bool):
+-                    extra_type_param = "--ez"
+-                else:
+-                    extra_type_param = "--es"
+-                acmd.extend([extra_type_param, str(key), str(val)])
+-
+-        if url:
+-            acmd.extend(["-d", url])
+-
+-        cmd = self._escape_command_line(acmd)
+-        self.shell_output(cmd, timeout=timeout)
+-
+-    def launch_fennec(self, app_name, intent="android.intent.action.VIEW",
+-                      moz_env=None, extra_args=None, url=None, wait=True,
+-                      fail_if_running=True, timeout=None):
+-        """Convenience method to launch Fennec on Android with various
+-        debugging arguments
+-
+-        :param str app_name: Name of fennec application (e.g.
+-            `org.mozilla.fennec`)
+-        :param str intent: Intent to launch application.
+-        :param moz_env: Mozilla specific environment to pass into
+-            application.
+-        :type moz_env: str or None
+-        :param extra_args: Extra arguments to be parsed by fennec.
+-        :type extra_args: str or None
+-        :param url: URL to open
+-        :type url: str or None
+-        :param bool wait: If True, wait for application to start before
+-            returning.
+-        :param bool fail_if_running: Raise an exception if instance of
+-            application is already running.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        extras = {}
+-
+-        if moz_env:
+-            # moz_env is expected to be a dictionary of environment variables:
+-            # Fennec itself will set them when launched
+-            for (env_count, (env_key, env_val)) in enumerate(moz_env.iteritems()):
+-                extras["env" + str(env_count)] = env_key + "=" + env_val
+-
+-        # Additional command line arguments that fennec will read and use (e.g.
+-        # with a custom profile)
+-        if extra_args:
+-            extras['args'] = " ".join(extra_args)
+-
+-        self.launch_application(app_name, "org.mozilla.gecko.BrowserApp",
+-                                intent, url=url, extras=extras,
+-                                wait=wait, fail_if_running=fail_if_running,
+-                                timeout=timeout)
+-
+-    def stop_application(self, app_name, timeout=None, root=False):
+-        """Stops the specified application
+-
+-        For Android 3.0+, we use the "am force-stop" to do this, which
+-        is reliable and does not require root. For earlier versions of
+-        Android, we simply try to manually kill the processes started
+-        by the app repeatedly until none is around any more. This is
+-        less reliable and does require root.
+-
+-        :param str app_name: Name of application (e.g. `com.android.chrome`)
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :param bool root: Flag specifying if the command should be
+-            executed as root.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        if self.version >= version_codes.HONEYCOMB:
+-            self.shell_output("am force-stop %s" % app_name,
+-                              timeout=timeout, root=root)
+-        else:
+-            num_tries = 0
+-            max_tries = 5
+-            while self.process_exist(app_name, timeout=timeout):
+-                if num_tries > max_tries:
+-                    raise ADBError("Couldn't successfully kill %s after %s "
+-                                   "tries" % (app_name, max_tries))
+-                self.pkill(app_name, timeout=timeout, root=root)
+-                num_tries += 1
+-
+-                # sleep for a short duration to make sure there are no
+-                # additional processes in the process of being launched
+-                # (this is not 100% guaranteed to work since it is inherently
+-                # racey, but it's the best we can do)
+-                time.sleep(1)
+-
+-    def uninstall_app(self, app_name, reboot=False, timeout=None):
+-        """Uninstalls an app on the device.
+-
+-        :param str app_name: The name of the app to be
+-            uninstalled.
+-        :param bool reboot: Flag indicating that the device should
+-            be rebooted after the app is uninstalled. No reboot occurs
+-            if the app is not installed.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        if self.is_app_installed(app_name, timeout=timeout):
+-            data = self.command_output(["uninstall", app_name], timeout=timeout)
+-            if data.find('Success') == -1:
+-                self._logger.debug('uninstall_app failed: %s' % data)
+-                raise ADBError("uninstall failed for %s. Got: %s" % (app_name, data))
+-            if reboot:
+-                self.reboot(timeout=timeout)
+-
+-    def update_app(self, apk_path, timeout=None):
+-        """Updates an app on the device and reboots.
+-
+-        :param str apk_path: The apk file name to be
+-            updated.
+-        :param timeout: The maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :type timeout: integer or None
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        cmd = ["install", "-r"]
+-        if self.version >= version_codes.M:
+-            cmd.append("-g")
+-        cmd.append(apk_path)
+-        output = self.command_output(cmd, timeout=timeout)
+-        self.reboot(timeout=timeout)
+-        return output
+diff --git a/testing/mozbase/mozdevice/mozdevice/adb_b2g.py b/testing/mozbase/mozdevice/mozdevice/adb_b2g.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/adb_b2g.py
++++ /dev/null
+@@ -1,124 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-import traceback
+-
+-import mozfile
+-
+-from .adb import ADBDevice, ADBError
+-
+-
+-class ADBB2G(ADBDevice):
+-    """ADBB2G implements :class:`ADBDevice` providing B2G-specific
+-    functionality.
+-
+-    ::
+-
+-       from mozdevice import ADBB2G
+-
+-       adbdevice = ADBB2G()
+-       print adbdevice.list_files("/mnt/sdcard")
+-       if adbdevice.process_exist("b2g"):
+-           print "B2G is running"
+-    """
+-
+-    def get_battery_percentage(self, timeout=None):
+-        """Returns the battery charge as a percentage.
+-
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :returns: battery charge as a percentage.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        with mozfile.NamedTemporaryFile() as tf:
+-            self.pull('/sys/class/power_supply/battery/capacity', tf.name,
+-                      timeout=timeout)
+-            try:
+-                with open(tf.name) as tf2:
+-                    return tf2.read().splitlines()[0]
+-            except Exception as e:
+-                raise ADBError(traceback.format_exception_only(
+-                    type(e), e)[0].strip())
+-
+-    def get_memory_total(self, timeout=None):
+-        """Returns the total memory available with units.
+-
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADBDevice constructor is used.
+-        :returns: memory total with units.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        meminfo = {}
+-        with mozfile.NamedTemporaryFile() as tf:
+-            self.pull('/proc/meminfo', tf.name, timeout=timeout)
+-            try:
+-                with open(tf.name) as tf2:
+-                    for line in tf2.read().splitlines():
+-                        key, value = line.split(':')
+-                        meminfo[key] = value.strip()
+-            except Exception as e:
+-                raise ADBError(traceback.format_exception_only(
+-                    type(e), e)[0].strip())
+-        return meminfo['MemTotal']
+-
+-    def get_info(self, directive=None, timeout=None):
+-        """
+-        Returns a dictionary of information strings about the device.
+-
+-        :param directive: information you want to get. Options are:
+-             - `battery` - battery charge as a percentage
+-             - `disk` - total, free, available bytes on disk
+-             - `id` - unique id of the device
+-             - `memtotal` - total memory available on the device
+-             - `os` - name of the os
+-             - `process` - list of running processes (same as ps)
+-             - `systime` - system time of the device
+-             - `uptime` - uptime of the device
+-
+-            If `directive` is `None`, will return all available information
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        info = super(ADBB2G, self).get_info(directive=directive,
+-                                            timeout=timeout)
+-
+-        directives = ['memtotal']
+-        if directive in directives:
+-            directives = [directive]
+-
+-        if 'memtotal' in directives:
+-            info['memtotal'] = self.get_memory_total(timeout=timeout)
+-        return info
+-
+-    def is_device_ready(self, timeout=None):
+-        """Returns True if the device is ready.
+-
+-        :param timeout: optional integer specifying the maximum time in
+-            seconds for any spawned adb process to complete before
+-            throwing an ADBTimeoutError.
+-            This timeout is per adb call. The total time spent
+-            may exceed this value. If it is not specified, the value
+-            set in the ADB constructor is used.
+-        :raises: * ADBTimeoutError
+-                 * ADBError
+-        """
+-        return self.shell_bool('ls /sbin', timeout=timeout)
+diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanager.py b/testing/mozbase/mozdevice/mozdevice/devicemanager.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/devicemanager.py
++++ /dev/null
+@@ -1,638 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-import hashlib
+-import mozlog
+-import logging
+-import os
+-import posixpath
+-import re
+-import struct
+-import StringIO
+-import zlib
+-
+-from functools import wraps
+-
+-
+-class DMError(Exception):
+-    "generic devicemanager exception."
+-
+-    def __init__(self, msg='', fatal=False):
+-        self.msg = msg
+-        self.fatal = fatal
+-
+-    def __str__(self):
+-        return self.msg
+-
+-
+-def abstractmethod(method):
+-    line = method.func_code.co_firstlineno
+-    filename = method.func_code.co_filename
+-
+-    @wraps(method)
+-    def not_implemented(*args, **kwargs):
+-        raise NotImplementedError('Abstract method %s at File "%s", line %s '
+-                                  'should be implemented by a concrete class' %
+-                                  (repr(method), filename, line))
+-    return not_implemented
+-
+-
+-class DeviceManager(object):
+-    """
+-    Represents a connection to a device. Once an implementation of this class
+-    is successfully instantiated, you may do things like list/copy files to
+-    the device, launch processes on the device, and install or remove
+-    applications from the device.
+-
+-    Never instantiate this class directly! Instead, instantiate an
+-    implementation of it like DeviceManagerADB. New projects should strongly
+-    consider using adb.py as an alternative.
+-    """
+-
+-    _logcatNeedsRoot = True
+-    default_timeout = 300
+-    short_timeout = 30
+-
+-    def __init__(self, logLevel=None, deviceRoot=None):
+-        try:
+-            self._logger = mozlog.get_default_logger(component="mozdevice")
+-            if not self._logger:  # no global structured logger, fall back to reg logging
+-                self._logger = mozlog.unstructured.getLogger("mozdevice")
+-                if logLevel is not None:
+-                    self._logger.setLevel(logLevel)
+-        except AttributeError:
+-            # Structured logging doesn't work on Python 2.6
+-            self._logger = None
+-        self._logLevel = logLevel
+-        self._remoteIsWin = None
+-        self._isDeviceRootSetup = False
+-        self._deviceRoot = deviceRoot
+-
+-    def _log(self, data):
+-        """
+-        This helper function is called by ProcessHandler to log
+-        the output produced by processes
+-        """
+-        self._logger.debug(data)
+-
+-    @property
+-    def remoteIsWin(self):
+-        if self._remoteIsWin is None:
+-            self._remoteIsWin = self.getInfo("os")["os"][0] == "windows"
+-        return self._remoteIsWin
+-
+-    @property
+-    def logLevel(self):
+-        return self._logLevel
+-
+-    @logLevel.setter
+-    def logLevel_setter(self, newLogLevel):
+-        self._logLevel = newLogLevel
+-        self._logger.setLevel(self._logLevel)
+-
+-    @property
+-    def debug(self):
+-        self._logger.warning("dm.debug is deprecated. Use logLevel.")
+-        levels = {logging.DEBUG: 5, logging.INFO: 3, logging.WARNING: 2,
+-                  logging.ERROR: 1, logging.CRITICAL: 0}
+-        return levels[self.logLevel]
+-
+-    @debug.setter
+-    def debug_setter(self, newDebug):
+-        self._logger.warning("dm.debug is deprecated. Use logLevel.")
+-        newDebug = 5 if newDebug > 5 else newDebug  # truncate >=5 to 5
+-        levels = {5: logging.DEBUG, 3: logging.INFO, 2: logging.WARNING,
+-                  1: logging.ERROR, 0: logging.CRITICAL}
+-        self.logLevel = levels[newDebug]
+-
+-    @abstractmethod
+-    def getInfo(self, directive=None):
+-        """
+-        Returns a dictionary of information strings about the device.
+-
+-        :param directive: information you want to get. Options are:
+-
+-          - `os` - name of the os
+-          - `id` - unique id of the device
+-          - `uptime` - uptime of the device
+-          - `uptimemillis` - uptime of the device in milliseconds
+-            (NOT supported on all implementations)
+-          - `systime` - system time of the device
+-          - `screen` - screen resolution
+-          - `memory` - memory stats
+-          - `memtotal` - total memory available on the device, for example 927208 kB
+-          - `process` - list of running processes (same as ps)
+-          - `disk` - total, free, available bytes on disk
+-          - `power` - power status (charge, battery temp)
+-          - `temperature` - device temperature
+-
+-         If `directive` is `None`, will return all available information
+-        """
+-
+-    @abstractmethod
+-    def getCurrentTime(self):
+-        """
+-        Returns device time in milliseconds since the epoch.
+-        """
+-
+-    def getIP(self, interfaces=['eth0', 'wlan0']):
+-        """
+-        Returns the IP of the device, or None if no connection exists.
+-        """
+-        for interface in interfaces:
+-            match = re.match(r"%s: ip (\S+)" % interface,
+-                             self.shellCheckOutput(['ifconfig', interface],
+-                                                   timeout=self.short_timeout))
+-            if match:
+-                return match.group(1)
+-
+-    def recordLogcat(self):
+-        """
+-        Clears the logcat file making it easier to view specific events.
+-        """
+-        # TODO: spawn this off in a separate thread/process so we can collect all
+-        # the logcat information
+-
+-        # Right now this is just clearing the logcat so we can only see what
+-        # happens after this call.
+-        self.shellCheckOutput(['/system/bin/logcat', '-c'], root=self._logcatNeedsRoot,
+-                              timeout=self.short_timeout)
+-
+-    def getLogcat(self, filterSpecs=["dalvikvm:I", "ConnectivityService:S",
+-                                     "WifiMonitor:S", "WifiStateTracker:S",
+-                                     "wpa_supplicant:S", "NetworkStateTracker:S"],
+-                  format="time",
+-                  filterOutRegexps=[]):
+-        """
+-        Returns the contents of the logcat file as a list of
+-        '\n' terminated strings
+-        """
+-        cmdline = ["/system/bin/logcat", "-v", format, "-d"] + filterSpecs
+-        output = self.shellCheckOutput(cmdline,
+-                                       root=self._logcatNeedsRoot,
+-                                       timeout=self.short_timeout)
+-        lines = output.replace('\r\n', '\n').splitlines(True)
+-
+-        for regex in filterOutRegexps:
+-            lines = [line for line in lines if not re.search(regex, line)]
+-
+-        return lines
+-
+-    def saveScreenshot(self, filename):
+-        """
+-        Takes a screenshot of what's being display on the device. Uses
+-        "screencap" on newer (Android 3.0+) devices (and some older ones with
+-        the functionality backported). This function also works on B2G.
+-
+-        Throws an exception on failure. This will always fail on devices
+-        without the screencap utility.
+-        """
+-        screencap = '/system/bin/screencap'
+-        if not self.fileExists(screencap):
+-            raise DMError("Unable to capture screenshot on device: no screencap utility")
+-
+-        with open(filename, 'w') as pngfile:
+-            # newer versions of screencap can write directly to a png, but some
+-            # older versions can't
+-            tempScreenshotFile = self.deviceRoot + "/ss-dm.tmp"
+-            self.shellCheckOutput(["sh", "-c", "%s > %s" %
+-                                   (screencap, tempScreenshotFile)],
+-                                  root=True)
+-            buf = self.pullFile(tempScreenshotFile)
+-            width = int(struct.unpack("I", buf[0:4])[0])
+-            height = int(struct.unpack("I", buf[4:8])[0])
+-            with open(filename, 'w') as pngfile:
+-                pngfile.write(self._writePNG(buf[12:], width, height))
+-            self.removeFile(tempScreenshotFile)
+-
+-    @abstractmethod
+-    def pushFile(self, localFilename, remoteFilename, retryLimit=1, createDir=True):
+-        """
+-        Copies localname from the host to destname on the device.
+-        """
+-
+-    @abstractmethod
+-    def pushDir(self, localDirname, remoteDirname, retryLimit=1, timeout=None):
+-        """
+-        Push local directory from host to remote directory on the device,
+-        """
+-
+-    @abstractmethod
+-    def pullFile(self, remoteFilename, offset=None, length=None):
+-        """
+-        Returns contents of remoteFile using the "pull" command.
+-
+-        :param remoteFilename: Path to file to pull from remote device.
+-        :param offset: Offset in bytes from which to begin reading (optional)
+-        :param length: Number of bytes to read (optional)
+-        """
+-
+-    @abstractmethod
+-    def getFile(self, remoteFilename, localFilename):
+-        """
+-        Copy file from remote device to local file on host.
+-        """
+-
+-    @abstractmethod
+-    def getDirectory(self, remoteDirname, localDirname, checkDir=True):
+-        """
+-        Copy directory structure from device (remoteDirname) to host (localDirname).
+-        """
+-
+-    @abstractmethod
+-    def validateFile(self, remoteFilename, localFilename):
+-        """
+-        Returns True if a file on the remote device has the same md5 hash as a local one.
+-        """
+-
+-    def validateDir(self, localDirname, remoteDirname):
+-        """
+-        Returns True if remoteDirname on device is same as localDirname on host.
+-        """
+-
+-        self._logger.info("validating directory: %s to %s" % (localDirname, remoteDirname))
+-        for root, dirs, files in os.walk(localDirname):
+-            parts = root.split(localDirname)
+-            for f in files:
+-                remoteRoot = remoteDirname + '/' + parts[1]
+-                remoteRoot = remoteRoot.replace('/', '/')
+-                if (parts[1] == ""):
+-                    remoteRoot = remoteDirname
+-                remoteName = remoteRoot + '/' + f
+-                if (self.validateFile(remoteName, os.path.join(root, f)) is not True):
+-                    return False
+-        return True
+-
+-    @abstractmethod
+-    def mkDir(self, remoteDirname):
+-        """
+-        Creates a single directory on the device file system.
+-        """
+-
+-    def mkDirs(self, filename):
+-        """
+-        Make directory structure on the device.
+-
+-        WARNING: does not create last part of the path. For example, if asked to
+-        create `/mnt/sdcard/foo/bar/baz`, it will only create `/mnt/sdcard/foo/bar`
+-        """
+-        filename = posixpath.normpath(filename)
+-        containing = posixpath.dirname(filename)
+-        if not self.dirExists(containing):
+-            parts = filename.split('/')
+-            name = "/" if not self.remoteIsWin else parts.pop(0)
+-            for part in parts[:-1]:
+-                if part != "":
+-                    name = posixpath.join(name, part)
+-                    self.mkDir(name)  # mkDir will check previous existence
+-
+-    @abstractmethod
+-    def dirExists(self, dirpath):
+-        """
+-        Returns whether dirpath exists and is a directory on the device file system.
+-        """
+-
+-    @abstractmethod
+-    def fileExists(self, filepath):
+-        """
+-        Return whether filepath exists on the device file system,
+-        regardless of file type.
+-        """
+-
+-    @abstractmethod
+-    def listFiles(self, rootdir):
+-        """
+-        Lists files on the device rootdir.
+-
+-        Returns array of filenames, ['file1', 'file2', ...]
+-        """
+-
+-    @abstractmethod
+-    def removeFile(self, filename):
+-        """
+-        Removes filename from the device.
+-        """
+-
+-    @abstractmethod
+-    def removeDir(self, remoteDirname):
+-        """
+-        Does a recursive delete of directory on the device: rm -Rf remoteDirname.
+-        """
+-
+-    @abstractmethod
+-    def moveTree(self, source, destination):
+-        """
+-        Does a move of the file or directory on the device.
+-
+-       :param source: Path to the original file or directory
+-       :param destination: Path to the destination file or directory
+-        """
+-
+-    @abstractmethod
+-    def copyTree(self, source, destination):
+-        """
+-        Does a copy of the file or directory on the device.
+-
+-       :param source: Path to the original file or directory
+-       :param destination: Path to the destination file or directory
+-        """
+-
+-    @abstractmethod
+-    def chmodDir(self, remoteDirname, mask="777"):
+-        """
+-        Recursively changes file permissions in a directory.
+-        """
+-
+-    @property
+-    def deviceRoot(self):
+-        """
+-        The device root on the device filesystem for putting temporary
+-        testing files.
+-        """
+-        # derive deviceroot value if not set
+-        if not self._deviceRoot or not self._isDeviceRootSetup:
+-            self._deviceRoot = self._setupDeviceRoot(self._deviceRoot)
+-            self._isDeviceRootSetup = True
+-
+-        return self._deviceRoot
+-
+-    @abstractmethod
+-    def _setupDeviceRoot(self):
+-        """
+-        Sets up and returns a device root location that can be written to by tests.
+-        """
+-
+-    def getDeviceRoot(self):
+-        """
+-        Get the device root on the device filesystem for putting temporary
+-        testing files.
+-
+-        .. deprecated:: 0.38
+-          Use the :py:attr:`deviceRoot` property instead.
+-        """
+-        return self.deviceRoot
+-
+-    @abstractmethod
+-    def getTempDir(self):
+-        """
+-        Returns a temporary directory we can use on this device, ensuring
+-        also that it exists.
+-        """
+-
+-    @abstractmethod
+-    def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
+-        """
+-        Executes shell command on device and returns exit code.
+-
+-        :param cmd: Commandline list to execute
+-        :param outputfile: File to store output
+-        :param env: Environment to pass to exec command
+-        :param cwd: Directory to execute command from
+-        :param timeout: specified in seconds, defaults to 'default_timeout'
+-        :param root: Specifies whether command requires root privileges
+-        """
+-
+-    def shellCheckOutput(self, cmd, env=None, cwd=None, timeout=None, root=False):
+-        """
+-        Executes shell command on device and returns output as a string. Raises if
+-        the return code is non-zero.
+-
+-        :param cmd: Commandline list to execute
+-        :param env: Environment to pass to exec command
+-        :param cwd: Directory to execute command from
+-        :param timeout: specified in seconds, defaults to 'default_timeout'
+-        :param root: Specifies whether command requires root privileges
+-        :raises: DMError
+-        """
+-        buf = StringIO.StringIO()
+-        retval = self.shell(cmd, buf, env=env, cwd=cwd, timeout=timeout, root=root)
+-        output = str(buf.getvalue()[0:-1]).rstrip()
+-        buf.close()
+-        if retval != 0:
+-            raise DMError(
+-                "Non-zero return code for command: %s "
+-                "(output: '%s', retval: '%s')" % (cmd, output, retval))
+-        return output
+-
+-    @abstractmethod
+-    def getProcessList(self):
+-        """
+-        Returns array of tuples representing running processes on the device.
+-
+-        Format of tuples is (processId, processName, userId)
+-        """
+-
+-    def processInfo(self, processName):
+-        """
+-        Returns information on the process with processName.
+-        Information on process is in tuple format: (pid, process path, user)
+-        If a process with the specified name does not exist this function will return None.
+-        """
+-        if not isinstance(processName, basestring):
+-            raise TypeError("Process name %s is not a string" % processName)
+-
+-        processInfo = None
+-
+-        # filter out extra spaces
+-        parts = filter(lambda x: x != '', processName.split(' '))
+-        processName = ' '.join(parts)
+-
+-        # filter out the quoted env string if it exists
+-        # ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
+-        parts = processName.split('"')
+-        if (len(parts) > 2):
+-            processName = ' '.join(parts[2:]).strip()
+-
+-        pieces = processName.split(' ')
+-        parts = pieces[0].split('/')
+-        app = parts[-1]
+-
+-        procList = self.getProcessList()
+-        if (procList == []):
+-            return None
+-
+-        for proc in procList:
+-            procName = proc[1].split('/')[-1]
+-            if (procName == app):
+-                processInfo = proc
+-                break
+-        return processInfo
+-
+-    def processExist(self, processName):
+-        """
+-        Returns True if process with name processName is running on device.
+-        """
+-        processInfo = self.processInfo(processName)
+-        if processInfo:
+-            return processInfo[0]
+-
+-    @abstractmethod
+-    def killProcess(self, processName, sig=None):
+-        """
+-        Kills the process named processName. If sig is not None, process is
+-        killed with the specified signal.
+-
+-        :param processName: path or name of the process to kill
+-        :param sig: signal to pass into the kill command (optional)
+-        """
+-
+-    @abstractmethod
+-    def reboot(self, wait=False, ipAddr=None):
+-        """
+-        Reboots the device.
+-
+-        :param wait: block on device to come back up before returning
+-        :param ipAddr: deprecated; do not use
+-        """
+-
+-    @abstractmethod
+-    def installApp(self, appBundlePath, destPath=None):
+-        """
+-        Installs an application onto the device.
+-
+-        :param appBundlePath: path to the application bundle on the device
+-        :param destPath: destination directory of where application should be
+-                         installed to (optional)
+-        """
+-
+-    @abstractmethod
+-    def uninstallApp(self, appName, installPath=None):
+-        """
+-        Uninstalls the named application from device and DOES NOT cause a reboot.
+-
+-        :param appName: the name of the application (e.g org.mozilla.fennec)
+-        :param installPath: the path to where the application was installed (optional)
+-        """
+-
+-    @abstractmethod
+-    def uninstallAppAndReboot(self, appName, installPath=None):
+-        """
+-        Uninstalls the named application from device and causes a reboot.
+-
+-        :param appName: the name of the application (e.g org.mozilla.fennec)
+-        :param installPath: the path to where the application was installed (optional)
+-        """
+-
+-    @abstractmethod
+-    def updateApp(self, appBundlePath, processName=None, destPath=None,
+-                  wait=False, ipAddr=None):
+-        """
+-        Updates the application on the device and reboots.
+-
+-        :param appBundlePath: path to the application bundle on the device
+-        :param processName: used to end the process if the applicaiton is
+-                            currently running (optional)
+-        :param destPath: Destination directory to where the application should
+-                         be installed (optional)
+-        :param wait: block on device to come back up before returning
+-        :param ipAddr: deprecated; do not use
+-        """
+-
+-    @staticmethod
+-    def _writePNG(buf, width, height):
+-        """
+-        Method for writing a PNG from a buffer, used by getScreenshot on older devices,
+-        """
+-        # Based on: http://code.activestate.com/recipes/577443-write-a-png-image-in-native-python/
+-        width_byte_4 = width * 4
+-        raw_data = b"".join(b'\x00' + buf[span:span + width_byte_4]
+-                            for span in range(0, (height - 1) * width * 4, width_byte_4))
+-
+-        def png_pack(png_tag, data):
+-            chunk_head = png_tag + data
+-            return struct.pack("!I", len(data)) \
+-                + chunk_head \
+-                + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
+-        return b"".join([
+-            b'\x89PNG\r\n\x1a\n',
+-            png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
+-            png_pack(b'IDAT', zlib.compress(raw_data, 9)),
+-            png_pack(b'IEND', b'')])
+-
+-    @abstractmethod
+-    def _getRemoteHash(self, filename):
+-        """
+-        Return the md5 sum of a file on the device.
+-        """
+-
+-    @staticmethod
+-    def _getLocalHash(filename):
+-        """
+-        Return the MD5 sum of a file on the host.
+-        """
+-        f = open(filename, 'rb')
+-        if f is None:
+-            return None
+-
+-        try:
+-            mdsum = hashlib.md5()
+-        except Exception:
+-            return None
+-
+-        while 1:
+-            data = f.read(1024)
+-            if not data:
+-                break
+-            mdsum.update(data)
+-
+-        f.close()
+-        hexval = mdsum.hexdigest()
+-        return hexval
+-
+-    @staticmethod
+-    def _escapedCommandLine(cmd):
+-        """
+-        Utility function to return escaped and quoted version of command line.
+-        """
+-        quotedCmd = []
+-
+-        for arg in cmd:
+-            arg.replace('&', '\&')
+-
+-            needsQuoting = False
+-            for char in [' ', '(', ')', '"', '&']:
+-                if arg.find(char) >= 0:
+-                    needsQuoting = True
+-                    break
+-            if needsQuoting:
+-                arg = '\'%s\'' % arg
+-
+-            quotedCmd.append(arg)
+-
+-        return " ".join(quotedCmd)
+-
+-
+-def _pop_last_line(file_obj):
+-    """
+-    Utility function to get the last line from a file. Function also removes
+-    it from the file. Intended to strip off the return code from a shell
+-    command.
+-    """
+-    bytes_from_end = 1
+-    file_obj.seek(0, 2)
+-    length = file_obj.tell() + 1
+-    while bytes_from_end < length:
+-        file_obj.seek((-1) * bytes_from_end, 2)
+-        data = file_obj.read()
+-
+-        if bytes_from_end == length - 1 and len(data) == 0:  # no data, return None
+-            return None
+-
+-        if data[0] == '\n' or bytes_from_end == length - 1:
+-            # found the last line, which should have the return value
+-            if data[0] == '\n':
+-                data = data[1:]
+-
+-            # truncate off the return code line
+-            file_obj.truncate(length - bytes_from_end)
+-            file_obj.seek(0, 2)
+-            file_obj.write('\0')
+-
+-            return data
+-
+-        bytes_from_end += 1
+-
+-    return None
+diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
++++ /dev/null
+@@ -1,886 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-import logging
+-import re
+-import os
+-import tempfile
+-import time
+-import traceback
+-
+-from distutils import dir_util
+-
+-from .devicemanager import DeviceManager, DMError
+-from mozprocess import ProcessHandler
+-import mozfile
+-from . import version_codes
+-
+-
+-class DeviceManagerADB(DeviceManager):
+-    """
+-    Implementation of DeviceManager interface that uses the Android "adb"
+-    utility to communicate with the device. Normally used to communicate
+-    with a device that is directly connected with the host machine over a USB
+-    port.
+-    """
+-
+-    _haveRootShell = None
+-    _haveSu = None
+-    _suModifier = None
+-    _lsModifier = None
+-    _useZip = False
+-    _logcatNeedsRoot = False
+-    _pollingInterval = 0.01
+-    _packageName = None
+-    _tempDir = None
+-    _adb_version = None
+-    _sdk_version = None
+-    connected = False
+-
+-    def __init__(self, host=None, port=5555, retryLimit=5, packageName='fennec',
+-                 adbPath=None, deviceSerial=None, deviceRoot=None,
+-                 logLevel=logging.ERROR, autoconnect=True, runAdbAsRoot=False,
+-                 serverHost=None, serverPort=None, **kwargs):
+-        DeviceManager.__init__(self, logLevel=logLevel,
+-                               deviceRoot=deviceRoot)
+-        self.host = host
+-        self.port = port
+-        self.retryLimit = retryLimit
+-
+-        self._serverHost = serverHost
+-        self._serverPort = serverPort
+-
+-        # the path to adb, or 'adb' to assume that it's on the PATH
+-        self._adbPath = adbPath or 'adb'
+-
+-        # The serial number of the device to use with adb, used in cases
+-        # where multiple devices are being managed by the same adb instance.
+-        self._deviceSerial = deviceSerial
+-
+-        # Some devices do no start adb as root, if allowed you can use
+-        # this to reboot adbd on the device as root automatically
+-        self._runAdbAsRoot = runAdbAsRoot
+-
+-        if packageName == 'fennec':
+-            if os.getenv('USER'):
+-                self._packageName = 'org.mozilla.fennec_' + os.getenv('USER')
+-            else:
+-                self._packageName = 'org.mozilla.fennec_'
+-        elif packageName:
+-            self._packageName = packageName
+-
+-        # verify that we can run the adb command. can't continue otherwise
+-        self._verifyADB()
+-
+-        if autoconnect:
+-            self.connect()
+-
+-    def connect(self):
+-        if not self.connected:
+-            # try to connect to the device over tcp/ip if we have a hostname
+-            if self.host:
+-                self._connectRemoteADB()
+-
+-            # verify that we can connect to the device. can't continue
+-            self._verifyDevice()
+-
+-            # Note SDK version
+-            try:
+-                proc = self._runCmd(["shell", "getprop", "ro.build.version.sdk"],
+-                                    timeout=self.short_timeout)
+-                self._sdk_version = int(proc.output[0])
+-            except (OSError, ValueError):
+-                self._sdk_version = 0
+-            self._logger.info("Detected Android sdk %d" % self._sdk_version)
+-
+-            # Some commands require root to work properly, even with ADB (e.g.
+-            # grabbing APKs out of /data). For these cases, we check whether
+-            # we're running as root. If that isn't true, check for the
+-            # existence of an su binary
+-            self._checkForRoot()
+-
+-            # can we use zip to speed up some file operations? (currently not
+-            # required)
+-            try:
+-                self._verifyZip()
+-            except DMError:
+-                pass
+-
+-    def __del__(self):
+-        if self.host:
+-            self._disconnectRemoteADB()
+-
+-    def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
+-        # FIXME: this function buffers all output of the command into memory,
+-        # always. :(
+-
+-        # If requested to run as root, check that we can actually do that
+-        if root:
+-            if self._haveRootShell is None and self._haveSu is None:
+-                self._checkForRoot()
+-            if not self._haveRootShell and not self._haveSu:
+-                raise DMError(
+-                    "Shell command '%s' requested to run as root but root "
+-                    "is not available on this device. Root your device or "
+-                    "refactor the test/harness to not require root." %
+-                    self._escapedCommandLine(cmd))
+-
+-        # Getting the return code is more complex than you'd think because adb
+-        # doesn't actually return the return code from a process, so we have to
+-        # capture the output to get it
+-        if root and self._haveSu:
+-            cmdline = "su %s \"%s\"" % (self._suModifier,
+-                                        self._escapedCommandLine(cmd))
+-        else:
+-            cmdline = self._escapedCommandLine(cmd)
+-        cmdline += "; echo $?"
+-
+-        # prepend cwd and env to command if necessary
+-        if cwd:
+-            cmdline = "cd %s; %s" % (cwd, cmdline)
+-        if env:
+-            envstr = '; '.join(map(lambda x: 'export %s=%s' % (x[0], x[1]), env.iteritems()))
+-            cmdline = envstr + "; " + cmdline
+-
+-        # all output should be in stdout
+-        args = [self._adbPath]
+-        if self._serverHost is not None:
+-            args.extend(['-H', self._serverHost])
+-        if self._serverPort is not None:
+-            args.extend(['-P', str(self._serverPort)])
+-        if self._deviceSerial:
+-            args.extend(['-s', self._deviceSerial])
+-        args.extend(["shell", cmdline])
+-
+-        def _timeout():
+-            self._logger.error("Timeout exceeded for shell call '%s'" % ' '.join(args))
+-
+-        self._logger.debug("shell - command: %s" % ' '.join(args))
+-        proc = ProcessHandler(args, processOutputLine=self._log, onTimeout=_timeout)
+-
+-        if not timeout:
+-            # We are asserting that all commands will complete in this time unless
+-            # otherwise specified
+-            timeout = self.default_timeout
+-
+-        timeout = int(timeout)
+-        proc.run(timeout)
+-        proc.wait()
+-        output = proc.output
+-
+-        if output:
+-            lastline = output[-1]
+-            if lastline:
+-                m = re.search('([0-9]+)', lastline)
+-                if m:
+-                    return_code = m.group(1)
+-                    for line in output:
+-                        outputfile.write(line + '\n')
+-                    outputfile.seek(-2, 2)
+-                    outputfile.truncate()  # truncate off the return code
+-                    return int(return_code)
+-
+-        return None
+-
+-    def forward(self, local, remote):
+-        """
+-        Forward socket connections.
+-
+-        Forward specs are one of:
+-          tcp:<port>
+-          localabstract:<unix domain socket name>
+-          localreserved:<unix domain socket name>
+-          localfilesystem:<unix domain socket name>
+-          dev:<character device name>
+-          jdwp:<process pid> (remote only)
+-        """
+-        if not self._checkCmd(['forward', local, remote], timeout=self.short_timeout) == 0:
+-            raise DMError("Failed to forward socket connection.")
+-
+-    def remove_forward(self, local=None):
+-        """
+-        Turn off forwarding of socket connection.
+-        """
+-        cmd = ['forward']
+-        if local is None:
+-            cmd.extend(['--remove-all'])
+-        else:
+-            cmd.extend(['--remove', local])
+-        if not self._checkCmd(cmd, timeout=self.short_timeout) == 0:
+-            raise DMError("Failed to remove connection forwarding.")
+-
+-    def remount(self):
+-        "Remounts the /system partition on the device read-write."
+-        return self._checkCmd(['remount'], timeout=self.short_timeout)
+-
+-    def devices(self):
+-        "Return a list of connected devices as (serial, status) tuples."
+-        proc = self._runCmd(['devices'])
+-        proc.output.pop(0)  # ignore first line of output
+-        devices = []
+-        for line in proc.output:
+-            result = re.match('(.*?)\t(.*)', line)
+-            if result:
+-                devices.append((result.group(1), result.group(2)))
+-        return devices
+-
+-    def _connectRemoteADB(self):
+-        self._checkCmd(["connect", self.host + ":" + str(self.port)])
+-
+-    def _disconnectRemoteADB(self):
+-        self._checkCmd(["disconnect", self.host + ":" + str(self.port)])
+-
+-    def pushFile(self, localname, destname, retryLimit=None, createDir=True):
+-        # you might expect us to put the file *in* the directory in this case,
+-        # but that would be inconsistent with historical behavior.
+-        retryLimit = retryLimit or self.retryLimit
+-        if self.dirExists(destname):
+-            raise DMError("Attempted to push a file (%s) to a directory (%s)!" %
+-                          (localname, destname))
+-        if not os.access(localname, os.F_OK):
+-            raise DMError("File not found: %s" % localname)
+-
+-        proc = self._runCmd(["push", os.path.realpath(localname), destname],
+-                            retryLimit=retryLimit)
+-        if proc.returncode != 0:
+-            raise DMError("Error pushing file %s -> %s; output: %s" %
+-                          (localname, destname, proc.output))
+-
+-    def mkDir(self, name):
+-        result = self._runCmd(["shell", "mkdir", name], timeout=self.short_timeout).output
+-        if len(result) and 'read-only file system' in result[0].lower():
+-            raise DMError("Error creating directory: read only file system")
+-
+-    def pushDir(self, localDir, remoteDir, retryLimit=None, timeout=None):
+-        # adb "push" accepts a directory as an argument, but if the directory
+-        # contains symbolic links, the links are pushed, rather than the linked
+-        # files; we either zip/unzip or re-copy the directory into a temporary
+-        # one to get around this limitation
+-        retryLimit = retryLimit or self.retryLimit
+-        if self._useZip:
+-            self.removeDir(remoteDir)
+-            self.mkDirs(remoteDir + "/x")
+-            try:
+-                localZip = tempfile.mktemp() + ".zip"
+-                remoteZip = remoteDir + "/adbdmtmp.zip"
+-                proc = ProcessHandler(["zip", "-r", localZip, '.'], cwd=localDir,
+-                                      processOutputLine=self._log)
+-                proc.run()
+-                proc.wait()
+-                self.pushFile(localZip, remoteZip, retryLimit=retryLimit, createDir=False)
+-                mozfile.remove(localZip)
+-                data = self._runCmd(["shell", "unzip", "-o", remoteZip,
+-                                     "-d", remoteDir]).output[0]
+-                self._checkCmd(["shell", "rm", remoteZip],
+-                               retryLimit=retryLimit, timeout=self.short_timeout)
+-                if re.search("unzip: exiting", data) or re.search("Operation not permitted", data):
+-                    raise Exception("unzip failed, or permissions error")
+-            except Exception:
+-                self._logger.warning(traceback.format_exc())
+-                self._logger.warning("zip/unzip failure: falling back to normal push")
+-                self._useZip = False
+-                self.pushDir(localDir, remoteDir, retryLimit=retryLimit, timeout=timeout)
+-        else:
+-            localDir = os.path.normpath(localDir)
+-            remoteDir = os.path.normpath(remoteDir)
+-            tempParent = tempfile.mkdtemp()
+-            remoteName = os.path.basename(remoteDir)
+-            newLocal = os.path.join(tempParent, remoteName)
+-            dir_util.copy_tree(localDir, newLocal)
+-            # See do_sync_push in
+-            # https://android.googlesource.com/platform/system/core/+/master/adb/file_sync_client.cpp
+-            # Work around change in behavior in adb 1.0.36 where if
+-            # the remote destination directory exists, adb push will
+-            # copy the source directory *into* the destination
+-            # directory otherwise it will copy the source directory
+-            # *onto* the destination directory.
+-            if self._adb_version >= '1.0.36':
+-                remoteDir = '/'.join(remoteDir.rstrip('/').split('/')[:-1])
+-            try:
+-                if self._checkCmd(["push", newLocal, remoteDir],
+-                                  retryLimit=retryLimit, timeout=timeout):
+-                    raise DMError("failed to push %s (copy of %s) to %s" %
+-                                  (newLocal, localDir, remoteDir))
+-            except BaseException:
+-                raise
+-            finally:
+-                mozfile.remove(tempParent)
+-
+-    def dirExists(self, remotePath):
+-        self._detectLsModifier()
+-        data = self._runCmd(["shell", "ls", self._lsModifier, remotePath + '/'],
+-                            timeout=self.short_timeout).output
+-
+-        if len(data) == 1:
+-            res = data[0]
+-            if "Not a directory" in res or "No such file or directory" in res:
+-                return False
+-        return True
+-
+-    def fileExists(self, filepath):
+-        self._detectLsModifier()
+-        data = self._runCmd(["shell", "ls", self._lsModifier, filepath],
+-                            timeout=self.short_timeout).output
+-        if len(data) == 1:
+-            foundpath = data[0].decode('utf-8').rstrip()
+-            if foundpath == filepath:
+-                return True
+-        return False
+-
+-    def removeFile(self, filename):
+-        if self.fileExists(filename):
+-            self._checkCmd(["shell", "rm", filename], timeout=self.short_timeout)
+-
+-    def removeDir(self, remoteDir):
+-        if self.dirExists(remoteDir):
+-            self._checkCmd(["shell", "rm", "-r", remoteDir], timeout=self.short_timeout)
+-        else:
+-            self.removeFile(remoteDir.strip())
+-
+-    def moveTree(self, source, destination):
+-        self._checkCmd(["shell", "mv", source, destination], timeout=self.short_timeout)
+-
+-    def copyTree(self, source, destination):
+-        self._checkCmd(["shell", "dd", "if=%s" % source, "of=%s" % destination])
+-
+-    def listFiles(self, rootdir):
+-        self._detectLsModifier()
+-        data = self._runCmd(["shell", "ls", self._lsModifier, rootdir],
+-                            timeout=self.short_timeout).output
+-        data[:] = [item.rstrip('\r\n') for item in data]
+-        if (len(data) == 1):
+-            if (data[0] == rootdir):
+-                return []
+-            if (data[0].find("No such file or directory") != -1):
+-                return []
+-            if (data[0].find("Not a directory") != -1):
+-                return []
+-            if (data[0].find("Permission denied") != -1):
+-                return []
+-            if (data[0].find("opendir failed") != -1):
+-                return []
+-            if (data[0].find("Device or resource busy") != -1):
+-                return []
+-        return data
+-
+-    def getProcessList(self):
+-        ret = []
+-        p = self._runCmd(["shell", "ps"], timeout=self.short_timeout)
+-        if not p or not p.output or len(p.output) < 1:
+-            return ret
+-        # first line is the headers
+-        p.output.pop(0)
+-        for proc in p.output:
+-            els = proc.split()
+-            # We need to figure out if this is "user pid name" or
+-            # "pid user vsz stat command"
+-            if els[1].isdigit():
+-                ret.append(list([int(els[1]), els[len(els) - 1], els[0]]))
+-            else:
+-                ret.append(list([int(els[0]), els[len(els) - 1], els[1]]))
+-        return ret
+-
+-    def fireProcess(self, appname, failIfRunning=False):
+-        """
+-        Starts a process
+-
+-        returns: pid
+-
+-        DEPRECATED: Use shell() or launchApplication() for new code
+-        """
+-        # strip out env vars
+-        parts = appname.split('"')
+-        if (len(parts) > 2):
+-            parts = parts[2:]
+-        return self.launchProcess(parts, failIfRunning)
+-
+-    def launchProcess(self, cmd, outputFile="process.txt", cwd='', env='', failIfRunning=False):
+-        """
+-        Launches a process, redirecting output to standard out
+-
+-        WARNING: Does not work how you expect on Android! The application's
+-        own output will be flushed elsewhere.
+-
+-        DEPRECATED: Use shell() or launchApplication() for new code
+-        """
+-        if cmd[0] == "am":
+-            self._checkCmd(["shell"] + cmd)
+-            return outputFile
+-
+-        acmd = ["-W"]
+-        cmd = ' '.join(cmd).strip()
+-        i = cmd.find(" ")
+-        re_url = re.compile('^[http|file|chrome|about].*')
+-        last = cmd.rfind(" ")
+-        uri = ""
+-        args = ""
+-        if re_url.match(cmd[last:].strip()):
+-            args = cmd[i:last].strip()
+-            uri = cmd[last:].strip()
+-        else:
+-            args = cmd[i:].strip()
+-        acmd.append("-n")
+-        acmd.append(cmd[0:i] + "/org.mozilla.gecko.BrowserApp")
+-        if args != "":
+-            acmd.append("--es")
+-            acmd.append("args")
+-            acmd.append(args)
+-        if env != '' and env is not None:
+-            envCnt = 0
+-            # env is expected to be a dict of environment variables
+-            for envkey, envval in env.iteritems():
+-                acmd.append("--es")
+-                acmd.append("env" + str(envCnt))
+-                acmd.append(envkey + "=" + envval)
+-                envCnt += 1
+-        if uri != "":
+-            acmd.append("-d")
+-            acmd.append(uri)
+-
+-        acmd = ["shell", ' '.join(map(lambda x: '"' + x + '"', ["am", "start"] + acmd))]
+-        self._logger.info(acmd)
+-        self._checkCmd(acmd)
+-        return outputFile
+-
+-    def killProcess(self, appname, sig=None):
+-        try:
+-            self.shellCheckOutput(["am", "force-stop", appname], timeout=self.short_timeout)
+-        except Exception:
+-            # no problem - will kill it instead
+-            self._logger.info("killProcess failed force-stop of %s" % appname)
+-
+-        shell_args = ["shell"]
+-        if self._sdk_version >= version_codes.N:
+-            # Bug 1334613 - force use of root
+-            if self._haveRootShell is None and self._haveSu is None:
+-                self._checkForRoot()
+-            if not self._haveRootShell and not self._haveSu:
+-                raise DMError(
+-                    "killProcess '%s' requested to run as root but root "
+-                    "is not available on this device. Root your device or "
+-                    "refactor the test/harness to not require root." %
+-                    appname)
+-            if not self._haveRootShell:
+-                shell_args.extend(["su", self._suModifier])
+-
+-        procs = self.getProcessList()
+-        for (pid, name, user) in procs:
+-            if name == appname:
+-                args = list(shell_args)
+-                args.append("kill")
+-                if sig:
+-                    args.append("-%d" % sig)
+-                args.append(str(pid))
+-                p = self._runCmd(args, timeout=self.short_timeout)
+-                if p.returncode != 0 and len(p.output) > 0 and \
+-                   'No such process' not in p.output[0]:
+-                    raise DMError("Error killing process "
+-                                  "'%s': %s" % (appname, p.output))
+-
+-    def _runPull(self, remoteFile, localFile):
+-        """
+-        Pulls remoteFile from device to host
+-        """
+-        try:
+-            self._runCmd(["pull",  remoteFile, localFile])
+-        except (OSError, ValueError):
+-            raise DMError("Error pulling remote file '%s' to '%s'" % (remoteFile, localFile))
+-
+-    def pullFile(self, remoteFile, offset=None, length=None):
+-        # TODO: add debug flags and allow for printing stdout
+-        with mozfile.NamedTemporaryFile() as tf:
+-            self._runPull(remoteFile, tf.name)
+-            # we need to reopen the file to get the written contents
+-            with open(tf.name) as tf2:
+-                # ADB pull does not support offset and length, but we can
+-                # instead read only the requested portion of the local file
+-                if offset is not None and length is not None:
+-                    tf2.seek(offset)
+-                    return tf2.read(length)
+-                elif offset is not None:
+-                    tf2.seek(offset)
+-                    return tf2.read()
+-                else:
+-                    return tf2.read()
+-
+-    def getFile(self, remoteFile, localFile):
+-        self._runPull(remoteFile, localFile)
+-
+-    def getDirectory(self, remoteDir, localDir, checkDir=True):
+-        localDir = os.path.normpath(localDir)
+-        remoteDir = os.path.normpath(remoteDir)
+-        copyRequired = False
+-        originalLocal = localDir
+-        if self._adb_version >= '1.0.36' and \
+-           os.path.isdir(localDir) and self.dirExists(remoteDir):
+-            # See do_sync_pull in
+-            # https://android.googlesource.com/platform/system/core/+/master/adb/file_sync_client.cpp
+-            # Work around change in behavior in adb 1.0.36 where if
+-            # the local destination directory exists, adb pull will
+-            # copy the source directory *into* the destination
+-            # directory otherwise it will copy the source directory
+-            # *onto* the destination directory.
+-            #
+-            # If the destination directory does exist, pull to its
+-            # parent directory. If the source and destination leaf
+-            # directory names are different, pull the source directory
+-            # into a temporary directory and then copy the temporary
+-            # directory onto the destination.
+-            localName = os.path.basename(localDir)
+-            remoteName = os.path.basename(remoteDir)
+-            if localName != remoteName:
+-                copyRequired = True
+-                tempParent = tempfile.mkdtemp()
+-                localDir = os.path.join(tempParent, remoteName)
+-            else:
+-                localDir = '/'.join(localDir.rstrip('/').split('/')[:-1])
+-        self._runCmd(["pull", remoteDir, localDir]).wait()
+-        if copyRequired:
+-            dir_util.copy_tree(localDir, originalLocal)
+-            mozfile.remove(tempParent)
+-
+-    def validateFile(self, remoteFile, localFile):
+-        md5Remote = self._getRemoteHash(remoteFile)
+-        md5Local = self._getLocalHash(localFile)
+-        if md5Remote is None or md5Local is None:
+-            return None
+-        return md5Remote == md5Local
+-
+-    def _getRemoteHash(self, remoteFile):
+-        """
+-        Return the md5 sum of a file on the device
+-        """
+-        with tempfile.NamedTemporaryFile() as f:
+-            self._runPull(remoteFile, f.name)
+-
+-            return self._getLocalHash(f.name)
+-
+-    def _setupDeviceRoot(self, deviceRoot):
+-        # user-specified device root, create it and return it
+-        if deviceRoot:
+-            self.mkDir(deviceRoot)
+-            return deviceRoot
+-
+-        # we must determine the device root ourselves
+-        paths = [('/storage/sdcard0', 'tests'),
+-                 ('/storage/sdcard1', 'tests'),
+-                 ('/storage/sdcard', 'tests'),
+-                 ('/mnt/sdcard', 'tests'),
+-                 ('/sdcard', 'tests'),
+-                 ('/data/local', 'tests')]
+-        for (basePath, subPath) in paths:
+-            if self.dirExists(basePath):
+-                root = os.path.join(basePath, subPath)
+-                try:
+-                    self.mkDir(root)
+-                    return root
+-                except Exception:
+-                    pass
+-
+-        raise DMError("Unable to set up device root using paths: [%s]"
+-                      % ", ".join(["'%s'" % os.path.join(b, s) for b, s in paths]))
+-
+-    def getTempDir(self):
+-        # Cache result to speed up operations depending
+-        # on the temporary directory.
+-        if not self._tempDir:
+-            self._tempDir = "%s/tmp" % self.deviceRoot
+-            self.mkDir(self._tempDir)
+-
+-        return self._tempDir
+-
+-    def reboot(self, wait=False, **kwargs):
+-        self._checkCmd(["reboot"])
+-        if wait:
+-            self._checkCmd(["wait-for-device"])
+-            if self._runAdbAsRoot:
+-                self._adb_root()
+-            self._checkCmd(["shell", "ls", "/sbin"], timeout=self.short_timeout)
+-
+-    def updateApp(self, appBundlePath, **kwargs):
+-        return self._runCmd(["install", "-r", appBundlePath]).output
+-
+-    def getCurrentTime(self):
+-        timestr = str(self._runCmd(["shell", "date", "+%s"], timeout=self.short_timeout).output[0])
+-        if (not timestr or not timestr.isdigit()):
+-            raise DMError("Unable to get current time using date (got: '%s')" % timestr)
+-        return int(timestr) * 1000
+-
+-    def getInfo(self, directive=None):
+-        directive = directive or "all"
+-        ret = {}
+-        if directive == "id" or directive == "all":
+-            ret["id"] = self._runCmd(["get-serialno"], timeout=self.short_timeout).output[0]
+-        if directive == "os" or directive == "all":
+-            ret["os"] = self.shellCheckOutput(
+-                ["getprop", "ro.build.display.id"], timeout=self.short_timeout)
+-        if directive == "uptime" or directive == "all":
+-            uptime = self.shellCheckOutput(["uptime"], timeout=self.short_timeout)
+-            if not uptime:
+-                raise DMError("error getting uptime")
+-            m = re.match("up time: ((\d+) days, )*(\d{2}):(\d{2}):(\d{2})", uptime)
+-            if m:
+-                uptime = "%d days %d hours %d minutes %d seconds" % tuple(
+-                    [int(g or 0) for g in m.groups()[1:]])
+-            ret["uptime"] = uptime
+-        if directive == "process" or directive == "all":
+-            data = self.shellCheckOutput(["ps"], timeout=self.short_timeout)
+-            ret["process"] = data.split('\n')
+-        if directive == "systime" or directive == "all":
+-            ret["systime"] = self.shellCheckOutput(["date"], timeout=self.short_timeout)
+-        if directive == "memtotal" or directive == "all":
+-            meminfo = {}
+-            for line in self.pullFile("/proc/meminfo").splitlines():
+-                key, value = line.split(":")
+-                meminfo[key] = value.strip()
+-            ret["memtotal"] = meminfo["MemTotal"]
+-        if directive == "disk" or directive == "all":
+-            data = self.shellCheckOutput(
+-                ["df", "/data", "/system", "/sdcard"], timeout=self.short_timeout)
+-            ret["disk"] = data.split('\n')
+-        self._logger.debug("getInfo: %s" % ret)
+-        return ret
+-
+-    def uninstallApp(self, appName, installPath=None):
+-        status = self._runCmd(["uninstall", appName]).output[0].strip()
+-        if status != 'Success':
+-            raise DMError("uninstall failed for %s. Got: %s" % (appName, status))
+-
+-    def uninstallAppAndReboot(self, appName, installPath=None):
+-        self.uninstallApp(appName)
+-        self.reboot()
+-
+-    def _runCmd(self, args, timeout=None, retryLimit=None):
+-        """
+-        Runs a command using adb
+-        If timeout is specified, the process is killed after <timeout> seconds.
+-
+-        returns: instance of ProcessHandler
+-        """
+-        retryLimit = retryLimit or self.retryLimit
+-        finalArgs = [self._adbPath]
+-        if self._serverHost is not None:
+-            finalArgs.extend(['-H', self._serverHost])
+-        if self._serverPort is not None:
+-            finalArgs.extend(['-P', str(self._serverPort)])
+-        if self._deviceSerial:
+-            finalArgs.extend(['-s', self._deviceSerial])
+-        finalArgs.extend(args)
+-        self._logger.debug("_runCmd - command: %s" % ' '.join(finalArgs))
+-        if not timeout:
+-            timeout = self.default_timeout
+-
+-        def _timeout():
+-            self._logger.error("Timeout exceeded for _runCmd call '%s'" % ' '.join(finalArgs))
+-
+-        retries = 0
+-        while retries < retryLimit:
+-            proc = ProcessHandler(finalArgs, storeOutput=True,
+-                                  processOutputLine=self._log, onTimeout=_timeout)
+-            proc.run(timeout=timeout)
+-            proc.returncode = proc.wait()
+-            if proc.returncode is None:
+-                proc.kill()
+-                retries += 1
+-            else:
+-                return proc
+-
+-    # timeout is specified in seconds, and if no timeout is given,
+-    # we will run until we hit the default_timeout specified in the __init__
+-    def _checkCmd(self, args, timeout=None, retryLimit=None):
+-        """
+-        Runs a command using adb and waits for the command to finish.
+-        If timeout is specified, the process is killed after <timeout> seconds.
+-
+-        returns: returncode from process
+-        """
+-        retryLimit = retryLimit or self.retryLimit
+-        finalArgs = [self._adbPath]
+-        if self._serverHost is not None:
+-            finalArgs.extend(['-H', self._serverHost])
+-        if self._serverPort is not None:
+-            finalArgs.extend(['-P', str(self._serverPort)])
+-        if self._deviceSerial:
+-            finalArgs.extend(['-s', self._deviceSerial])
+-        finalArgs.extend(args)
+-        self._logger.debug("_checkCmd - command: %s" % ' '.join(finalArgs))
+-        if not timeout:
+-            # We are asserting that all commands will complete in this
+-            # time unless otherwise specified
+-            timeout = self.default_timeout
+-
+-        def _timeout():
+-            self._logger.error("Timeout exceeded for _checkCmd call '%s'" % ' '.join(finalArgs))
+-
+-        timeout = int(timeout)
+-        retries = 0
+-        while retries < retryLimit:
+-            proc = ProcessHandler(finalArgs, processOutputLine=self._log, onTimeout=_timeout)
+-            proc.run(timeout=timeout)
+-            ret_code = proc.wait()
+-            if ret_code is None:
+-                proc.kill()
+-                retries += 1
+-            else:
+-                return ret_code
+-
+-        raise DMError("Timeout exceeded for _checkCmd call after %d retries." % retries)
+-
+-    def chmodDir(self, remoteDir, mask="777"):
+-        if (self.dirExists(remoteDir)):
+-            if '/sdcard' in remoteDir:
+-                self._logger.debug("chmod %s -- skipped (/sdcard)" % remoteDir)
+-            else:
+-                files = self.listFiles(remoteDir.strip())
+-                for f in files:
+-                    remoteEntry = remoteDir.strip() + "/" + f.strip()
+-                    if (self.dirExists(remoteEntry)):
+-                        self.chmodDir(remoteEntry)
+-                    else:
+-                        self._checkCmd(["shell", "chmod", mask, remoteEntry],
+-                                       timeout=self.short_timeout)
+-                        self._logger.info("chmod %s" % remoteEntry)
+-                self._checkCmd(["shell", "chmod", mask, remoteDir], timeout=self.short_timeout)
+-                self._logger.debug("chmod %s" % remoteDir)
+-        else:
+-            self._checkCmd(["shell", "chmod", mask, remoteDir.strip()], timeout=self.short_timeout)
+-            self._logger.debug("chmod %s" % remoteDir.strip())
+-
+-    def _verifyADB(self):
+-        """
+-        Check to see if adb itself can be executed.
+-        """
+-        if self._adbPath != 'adb':
+-            if not os.access(self._adbPath, os.X_OK):
+-                raise DMError("invalid adb path, or adb not executable: %s" % self._adbPath)
+-
+-        try:
+-            re_version = re.compile(r'Android Debug Bridge version (.*)')
+-            proc = self._runCmd(["version"], timeout=self.short_timeout)
+-            self._adb_version = re_version.match(proc.output[0]).group(1)
+-            self._logger.info("Detected adb %s" % self._adb_version)
+-        except os.error as err:
+-            raise DMError(
+-                "unable to execute ADB (%s): ensure Android SDK is installed "
+-                "and adb is in your $PATH" % err)
+-
+-    def _verifyDevice(self):
+-        # If there is a device serial number, see if adb is connected to it
+-        if self._deviceSerial:
+-            deviceStatus = None
+-            for line in self._runCmd(["devices"]).output:
+-                m = re.match('(.+)?\s+(.+)$', line)
+-                if m:
+-                    if self._deviceSerial == m.group(1):
+-                        deviceStatus = m.group(2)
+-            if deviceStatus is None:
+-                raise DMError("device not found: %s" % self._deviceSerial)
+-            elif deviceStatus != "device":
+-                raise DMError("bad status for device %s: %s" % (self._deviceSerial, deviceStatus))
+-
+-        # Check to see if we can connect to device and run a simple command
+-        if not self._checkCmd(["shell", "echo"], timeout=self.short_timeout) == 0:
+-            raise DMError("unable to connect to device")
+-
+-    def _checkForRoot(self):
+-        self._haveRootShell = False
+-        self._haveSu = False
+-        # If requested to attempt to run adbd as root, do so before
+-        # checking whether adbs is running as root.
+-        if self._runAdbAsRoot:
+-            self._adb_root()
+-
+-        # Check whether we _are_ root by default (some development boards work
+-        # this way, this is also the result of some relatively rare rooting
+-        # techniques)
+-        proc = self._runCmd(["shell", "id"], timeout=self.short_timeout)
+-        if proc.output and 'uid=0(root)' in proc.output[0]:
+-            self._haveRootShell = True
+-            # if this returns true, we don't care about su
+-            return
+-
+-        # if root shell is not available, check if 'su' can be used to gain
+-        # root
+-        def su_id(su_modifier, timeout):
+-            proc = self._runCmd(["shell", "su", su_modifier, "id"],
+-                                timeout=timeout)
+-
+-            # wait for response for maximum of 15 seconds, in case su
+-            # prompts for a password or triggers the Android SuperUser
+-            # prompt
+-            start_time = time.time()
+-            retcode = None
+-            while (time.time() - start_time) <= 15 and retcode is None:
+-                retcode = proc.poll()
+-            if retcode is None:  # still not terminated, kill
+-                proc.kill()
+-
+-            if proc.output and 'uid=0(root)' in proc.output[0]:
+-                return True
+-            return False
+-
+-        if su_id('0', self.short_timeout):
+-            self._haveSu = True
+-            self._suModifier = '0'
+-        elif su_id('-c', self.short_timeout):
+-            self._haveSu = True
+-            self._suModifier = '-c'
+-
+-    def _isUnzipAvailable(self):
+-        data = self._runCmd(["shell", "unzip"]).output
+-        for line in data:
+-            if (re.search('Usage', line)):
+-                return True
+-        return False
+-
+-    def _isLocalZipAvailable(self):
+-        def _noOutput(line):
+-            # suppress output from zip ProcessHandler
+-            pass
+-        try:
+-            proc = ProcessHandler(["zip", "-?"], storeOutput=False, processOutputLine=_noOutput)
+-            proc.run()
+-            proc.wait()
+-        except Exception:
+-            return False
+-        return True
+-
+-    def _verifyZip(self):
+-        # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
+-        # can use these to push just one file per directory -- a significant
+-        # optimization for large directories.
+-        self._useZip = False
+-        if (self._isUnzipAvailable() and self._isLocalZipAvailable()):
+-            self._logger.info("will use zip to push directories")
+-            self._useZip = True
+-        else:
+-            raise DMError("zip not available")
+-
+-    def _adb_root(self):
+-        """ Some devices require us to reboot adbd as root.
+-            This function takes care of it.
+-        """
+-        if self.processInfo("adbd")[2] != "root":
+-            self._checkCmd(["root"])
+-            self._checkCmd(["wait-for-device"])
+-            if self.processInfo("adbd")[2] != "root":
+-                raise DMError("We tried rebooting adbd as root, however, it failed.")
+-
+-    def _detectLsModifier(self):
+-        if self._lsModifier is None:
+-            # Check if busybox -1A is required in order to get one
+-            # file per line.
+-            output = self._runCmd(["shell", "ls", "-1A", "/"],
+-                                  timeout=self.short_timeout).output
+-            output = ' '.join(output)
+-            if 'error: device not found' in output:
+-                raise DMError(output)
+-            if "Unknown option '-1'. Aborting." in output:
+-                self._lsModifier = "-a"
+-            elif "No such file or directory" in output:
+-                self._lsModifier = "-a"
+-            else:
+-                self._lsModifier = "-1A"
+diff --git a/testing/mozbase/mozdevice/mozdevice/dmcli.py b/testing/mozbase/mozdevice/mozdevice/dmcli.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/dmcli.py
++++ /dev/null
+@@ -1,352 +0,0 @@
+-# 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/.
+-
+-"""
+-Command-line client to control a device
+-"""
+-from __future__ import absolute_import, print_function
+-
+-import errno
+-import logging
+-import os
+-import posixpath
+-import StringIO
+-import sys
+-import mozdevice
+-import mozlog
+-import argparse
+-
+-
+-class DMCli(object):
+-
+-    def __init__(self):
+-        self.commands = {'deviceroot': {'function': self.deviceroot,
+-                                        'help': 'get device root directory for storing temporary '
+-                                        'files'},
+-                         'install': {'function': self.install,
+-                                     'args': [{'name': 'file'}],
+-                                     'help': 'push this package file to the device'
+-                                     ' and install it'},
+-                         'uninstall': {'function': self.uninstall,
+-                                       'args': [{'name': 'packagename'}],
+-                                       'help': 'uninstall the named app from the device'},
+-                         'killapp': {'function': self.kill,
+-                                     'args': [{'name': 'process_name', 'nargs': '*'}],
+-                                     'help': 'kills any processes with name(s) on device'},
+-                         'launchapp': {'function': self.launchapp,
+-                                       'args': [{'name': 'appname'},
+-                                                {'name': 'activity_name'},
+-                                                {'name': '--intent',
+-                                                 'action': 'store',
+-                                                 'default': 'android.intent.action.VIEW'},
+-                                                {'name': '--url',
+-                                                 'action': 'store'},
+-                                                {'name': '--no-fail-if-running',
+-                                                 'action': 'store_true',
+-                                                 'help': 'Don\'t fail if application is'
+-                                                 ' already running'}
+-                                                ],
+-                                       'help': 'launches application on device'},
+-                         'listapps': {'function': self.listapps,
+-                                      'help': 'list applications on device'},
+-                         'push': {'function': self.push,
+-                                  'args': [{'name': 'local_file'},
+-                                           {'name': 'remote_file'}
+-                                           ],
+-                                  'help': 'copy file/dir to device'},
+-                         'pull': {'function': self.pull,
+-                                  'args': [{'name': 'local_file'},
+-                                           {'name': 'remote_file', 'nargs': '?'}],
+-                                  'help': 'copy file/dir from device'},
+-                         'shell': {'function': self.shell,
+-                                   'args': [{'name': 'command', 'nargs': argparse.REMAINDER},
+-                                            {'name': '--root', 'action': 'store_true',
+-                                             'help': 'Run command as root'}],
+-                                   'help': 'run shell command on device'},
+-                         'info': {'function': self.getinfo,
+-                                  'args': [{'name': 'directive', 'nargs': '?'}],
+-                                  'help': 'get information on specified '
+-                                  'aspect of the device (if no argument '
+-                                  'given, print all available information)'
+-                                  },
+-                         'ps': {'function': self.processlist,
+-                                'help': 'get information on running processes on device'
+-                                },
+-                         'logcat': {'function': self.logcat,
+-                                    'help': 'get logcat from device'
+-                                    },
+-                         'ls': {'function': self.listfiles,
+-                                'args': [{'name': 'remote_dir'}],
+-                                'help': 'list files on device'
+-                                },
+-                         'rm': {'function': self.removefile,
+-                                'args': [{'name': 'remote_file'}],
+-                                'help': 'remove file from device'
+-                                },
+-                         'isdir': {'function': self.isdir,
+-                                   'args': [{'name': 'remote_dir'}],
+-                                   'help': 'print if remote file is a directory'
+-                                   },
+-                         'mkdir': {'function': self.mkdir,
+-                                   'args': [{'name': 'remote_dir'}],
+-                                   'help': 'makes a directory on device'
+-                                   },
+-                         'rmdir': {'function': self.rmdir,
+-                                   'args': [{'name': 'remote_dir'}],
+-                                   'help': 'recursively remove directory from device'
+-                                   },
+-                         'screencap': {'function': self.screencap,
+-                                       'args': [{'name': 'png_file'}],
+-                                       'help': 'capture screenshot of device in action'
+-                                       },
+-                         'clearlogcat': {'function': self.clearlogcat,
+-                                         'help': 'clear the logcat'
+-                                         },
+-                         'reboot': {'function': self.reboot,
+-                                    'help': 'reboot the device',
+-                                    'args': [{'name': '--wait',
+-                                              'action': 'store_true',
+-                                              'help': 'Wait for device to come back up'
+-                                              ' before exiting'}]
+-
+-                                    },
+-                         'isfile': {'function': self.isfile,
+-                                    'args': [{'name': 'remote_file'}],
+-                                    'help': 'check whether a file exists on the device'
+-                                    },
+-                         'launchfennec': {'function': self.launchfennec,
+-                                          'args': [{'name': 'appname'},
+-                                                   {'name': '--intent', 'action': 'store',
+-                                                    'default': 'android.intent.action.VIEW'},
+-                                                   {'name': '--url', 'action': 'store'},
+-                                                   {'name': '--extra-args', 'action': 'store'},
+-                                                   {'name': '--mozenv', 'action': 'store',
+-                                                    'help': 'Gecko environment variables to set'
+-                                                    ' in "KEY1=VAL1 KEY2=VAL2" format'},
+-                                                   {'name': '--no-fail-if-running',
+-                                                    'action': 'store_true',
+-                                                    'help': 'Don\'t fail if application is '
+-                                                    'already running'}
+-                                                   ],
+-                                          'help': 'launch fennec'
+-                                          },
+-                         'getip': {'function': self.getip,
+-                                   'args': [{'name': 'interface', 'nargs': '*'}],
+-                                   'help': 'get the ip address of the device'
+-                                   }
+-                         }
+-
+-        self.parser = argparse.ArgumentParser()
+-        self.add_options(self.parser)
+-        self.add_commands(self.parser)
+-        mozlog.commandline.add_logging_group(self.parser)
+-
+-    def run(self, args=sys.argv[1:]):
+-        args = self.parser.parse_args()
+-
+-        mozlog.commandline.setup_logging(
+-            'mozdevice', args, {'mach': sys.stdout})
+-
+-        self.dm = self.getDevice(hwid=args.hwid,
+-                                 host=args.host, port=args.port,
+-                                 verbose=args.verbose)
+-
+-        ret = args.func(args)
+-        if ret is None:
+-            ret = 0
+-
+-        sys.exit(ret)
+-
+-    def add_options(self, parser):
+-        parser.add_argument("-v", "--verbose", action="store_true",
+-                            help="Verbose output from DeviceManager",
+-                            default=bool(os.environ.get('VERBOSE')))
+-        parser.add_argument("--host", action="store",
+-                            help="Device hostname (only if using TCP/IP, "
+-                            "defaults to TEST_DEVICE environment "
+-                            "variable if present)",
+-                            default=os.environ.get('TEST_DEVICE'))
+-        parser.add_argument("-p", "--port", action="store",
+-                            type=int,
+-                            help="Custom device port (if using "
+-                            "adb-over-tcp)", default=None)
+-        parser.add_argument("-d", "--hwid", action="store",
+-                            help="HWID", default=None)
+-        parser.add_argument("--package-name", action="store",
+-                            help="Packagename (if using DeviceManagerADB)",
+-                            default=None)
+-
+-    def add_commands(self, parser):
+-        subparsers = parser.add_subparsers(title="Commands", metavar="<command>")
+-        for (commandname, commandprops) in sorted(self.commands.iteritems()):
+-            subparser = subparsers.add_parser(commandname, help=commandprops['help'])
+-            if commandprops.get('args'):
+-                for arg in commandprops['args']:
+-                    # this is more elegant but doesn't work in python 2.6
+-                    # (which we still use on tbpl @ mozilla where we install
+-                    # this package)
+-                    # kwargs = { k: v for k,v in arg.items() if k is not 'name' }
+-                    kwargs = {}
+-                    for (k, v) in arg.items():
+-                        if k is not 'name':
+-                            kwargs[k] = v
+-                    subparser.add_argument(arg['name'], **kwargs)
+-            subparser.set_defaults(func=commandprops['function'])
+-
+-    def getDevice(self, hwid=None, host=None, port=None,
+-                  packagename=None, verbose=False):
+-        '''
+-        Returns a device with the specified parameters
+-        '''
+-        logLevel = logging.ERROR
+-        if verbose:
+-            logLevel = logging.DEBUG
+-
+-        if host and not port:
+-            port = 5555
+-        return mozdevice.DroidADB(packageName=packagename,
+-                                  host=host, port=port,
+-                                  logLevel=logLevel)
+-
+-    def deviceroot(self, args):
+-        print(self.dm.deviceRoot)
+-
+-    def push(self, args):
+-        (src, dest) = (args.local_file, args.remote_file)
+-        if os.path.isdir(src):
+-            self.dm.pushDir(src, dest)
+-        else:
+-            dest_is_dir = dest[-1] == '/' or self.dm.dirExists(dest)
+-            dest = posixpath.normpath(dest)
+-            if dest_is_dir:
+-                dest = posixpath.join(dest, os.path.basename(src))
+-            self.dm.pushFile(src, dest)
+-
+-    def pull(self, args):
+-        (src, dest) = (args.local_file, args.remote_file)
+-        if not self.dm.fileExists(src):
+-            print('No such file or directory')
+-            return
+-        if not dest:
+-            dest = posixpath.basename(src)
+-        if self.dm.dirExists(src):
+-            self.dm.getDirectory(src, dest)
+-        else:
+-            self.dm.getFile(src, dest)
+-
+-    def install(self, args):
+-        basename = os.path.basename(args.file)
+-        app_path_on_device = posixpath.join(self.dm.deviceRoot,
+-                                            basename)
+-        self.dm.pushFile(args.file, app_path_on_device)
+-        self.dm.installApp(app_path_on_device)
+-
+-    def uninstall(self, args):
+-        self.dm.uninstallApp(args.packagename)
+-
+-    def launchapp(self, args):
+-        self.dm.launchApplication(args.appname, args.activity_name,
+-                                  args.intent, url=args.url,
+-                                  failIfRunning=(not args.no_fail_if_running))
+-
+-    def listapps(self, args):
+-        for app in self.dm.getInstalledApps():
+-            print(app)
+-
+-    def stopapp(self, args):
+-        self.dm.stopApplication(args.appname)
+-
+-    def kill(self, args):
+-        for name in args.process_name:
+-            self.dm.killProcess(name)
+-
+-    def shell(self, args):
+-        buf = StringIO.StringIO()
+-        self.dm.shell(args.command, buf, root=args.root)
+-        print(str(buf.getvalue()[0:-1]).rstrip())
+-
+-    def getinfo(self, args):
+-        info = self.dm.getInfo(directive=args.directive)
+-        for (infokey, infoitem) in sorted(info.iteritems()):
+-            if infokey == "process":
+-                pass  # skip process list: get that through ps
+-            elif args.directive is None:
+-                print("%s: %s" % (infokey.upper(), infoitem))
+-            else:
+-                print(infoitem)
+-
+-    def logcat(self, args):
+-        print(''.join(self.dm.getLogcat()))
+-
+-    def clearlogcat(self, args):
+-        self.dm.recordLogcat()
+-
+-    def reboot(self, args):
+-        self.dm.reboot(wait=args.wait)
+-
+-    def processlist(self, args):
+-        pslist = self.dm.getProcessList()
+-        for ps in pslist:
+-            print(" ".join(str(i) for i in ps))
+-
+-    def listfiles(self, args):
+-        filelist = self.dm.listFiles(args.remote_dir)
+-        for file in filelist:
+-            print(file)
+-
+-    def removefile(self, args):
+-        self.dm.removeFile(args.remote_file)
+-
+-    def isdir(self, args):
+-        if self.dm.dirExists(args.remote_dir):
+-            print("TRUE")
+-            return
+-
+-        print("FALSE")
+-        return errno.ENOTDIR
+-
+-    def mkdir(self, args):
+-        self.dm.mkDir(args.remote_dir)
+-
+-    def rmdir(self, args):
+-        self.dm.removeDir(args.remote_dir)
+-
+-    def screencap(self, args):
+-        self.dm.saveScreenshot(args.png_file)
+-
+-    def isfile(self, args):
+-        if self.dm.fileExists(args.remote_file):
+-            print("TRUE")
+-            return
+-        print("FALSE")
+-        return errno.ENOENT
+-
+-    def launchfennec(self, args):
+-        mozEnv = None
+-        if args.mozenv:
+-            mozEnv = {}
+-            keyvals = args.mozenv.split()
+-            for keyval in keyvals:
+-                (key, _, val) = keyval.partition("=")
+-                mozEnv[key] = val
+-        self.dm.launchFennec(args.appname, intent=args.intent,
+-                             mozEnv=mozEnv,
+-                             extraArgs=args.extra_args, url=args.url,
+-                             failIfRunning=(not args.no_fail_if_running))
+-
+-    def getip(self, args):
+-        if args.interface:
+-            print(self.dm.getIP(args.interface))
+-        else:
+-            print(self.dm.getIP())
+-
+-
+-def cli(args=sys.argv[1:]):
+-    # process the command line
+-    cli = DMCli()
+-    cli.run(args)
+-
+-
+-if __name__ == '__main__':
+-    cli()
+diff --git a/testing/mozbase/mozdevice/mozdevice/droid.py b/testing/mozbase/mozdevice/mozdevice/droid.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/droid.py
++++ /dev/null
+@@ -1,198 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-import StringIO
+-import re
+-import time
+-
+-from mozdevice import version_codes
+-
+-from .devicemanagerADB import DeviceManagerADB
+-from .devicemanager import DMError
+-
+-
+-class DroidMixin(object):
+-    """Mixin to extend DeviceManager with Android-specific functionality"""
+-
+-    _stopApplicationNeedsRoot = True
+-
+-    def _getExtraAmStartArgs(self):
+-        return []
+-
+-    def launchApplication(self, appName, activityName, intent, url=None,
+-                          extras=None, wait=True, failIfRunning=True):
+-        """
+-        Launches an Android application
+-
+-        :param appName: Name of application (e.g. `com.android.chrome`)
+-        :param activityName: Name of activity to launch (e.g. `.Main`)
+-        :param intent: Intent to launch application with
+-        :param url: URL to open
+-        :param extras: Dictionary of extra arguments to launch application with
+-        :param wait: If True, wait for application to start before returning
+-        :param failIfRunning: Raise an exception if instance of application is already running
+-        """
+-
+-        # If failIfRunning is True, we throw an exception here. Only one
+-        # instance of an application can be running at once on Android,
+-        # starting a new instance may not be what we want depending on what
+-        # we want to do
+-        if failIfRunning and self.processExist(appName):
+-            raise DMError("Only one instance of an application may be running "
+-                          "at once")
+-
+-        acmd = ["am", "start"] + self._getExtraAmStartArgs() + \
+-            ["-W" if wait else '', "-n", "%s/%s" % (appName, activityName)]
+-
+-        if intent:
+-            acmd.extend(["-a", intent])
+-
+-        if extras:
+-            for (key, val) in extras.iteritems():
+-                if type(val) is int:
+-                    extraTypeParam = "--ei"
+-                elif type(val) is bool:
+-                    extraTypeParam = "--ez"
+-                else:
+-                    extraTypeParam = "--es"
+-                acmd.extend([extraTypeParam, str(key), str(val)])
+-
+-        if url:
+-            acmd.extend(["-d", url])
+-
+-        # shell output not that interesting and debugging logs should already
+-        # show what's going on here... so just create an empty memory buffer
+-        # and ignore (except on error)
+-        shellOutput = StringIO.StringIO()
+-        if self.shell(acmd, shellOutput) == 0:
+-            return
+-
+-        shellOutput.seek(0)
+-        raise DMError("Unable to launch application (shell output: '%s')" % shellOutput.read())
+-
+-    def launchFennec(self, appName, intent="android.intent.action.VIEW",
+-                     mozEnv=None, extraArgs=None, url=None, wait=True,
+-                     failIfRunning=True):
+-        """
+-        Convenience method to launch Fennec on Android with various debugging
+-        arguments
+-
+-        :param appName: Name of fennec application (e.g. `org.mozilla.fennec`)
+-        :param intent: Intent to launch application with
+-        :param mozEnv: Mozilla specific environment to pass into application
+-        :param extraArgs: Extra arguments to be parsed by fennec
+-        :param url: URL to open
+-        :param wait: If True, wait for application to start before returning
+-        :param failIfRunning: Raise an exception if instance of application is already running
+-        """
+-        extras = {}
+-
+-        if mozEnv:
+-            # mozEnv is expected to be a dictionary of environment variables: Fennec
+-            # itself will set them when launched
+-            for (envCnt, (envkey, envval)) in enumerate(mozEnv.iteritems()):
+-                extras["env" + str(envCnt)] = envkey + "=" + envval
+-
+-        # Additional command line arguments that fennec will read and use (e.g.
+-        # with a custom profile)
+-        if extraArgs:
+-            extras['args'] = " ".join(extraArgs)
+-
+-        self.launchApplication(appName, "org.mozilla.gecko.BrowserApp", intent, url=url,
+-                               extras=extras,
+-                               wait=wait, failIfRunning=failIfRunning)
+-
+-    def getInstalledApps(self):
+-        """
+-        Lists applications installed on this Android device
+-
+-        Returns a list of application names in the form [ 'org.mozilla.fennec', ... ]
+-        """
+-        output = self.shellCheckOutput(["pm", "list", "packages", "-f"])
+-        apps = []
+-        for line in output.splitlines():
+-            # lines are of form: package:/system/app/qik-tmo.apk=com.qiktmobile.android
+-            apps.append(line.split('=')[1])
+-
+-        return apps
+-
+-    def stopApplication(self, appName):
+-        """
+-        Stops the specified application
+-
+-        For Android 3.0+, we use the "am force-stop" to do this, which is
+-        reliable and does not require root. For earlier versions of Android,
+-        we simply try to manually kill the processes started by the app
+-        repeatedly until none is around any more. This is less reliable and
+-        does require root.
+-
+-        :param appName: Name of application (e.g. `com.android.chrome`)
+-        """
+-        version = self.shellCheckOutput(["getprop", "ro.build.version.sdk"])
+-        if int(version) >= version_codes.HONEYCOMB:
+-            self.shellCheckOutput(["am", "force-stop", appName],
+-                                  root=self._stopApplicationNeedsRoot)
+-        else:
+-            num_tries = 0
+-            max_tries = 5
+-            while self.processExist(appName):
+-                if num_tries > max_tries:
+-                    raise DMError("Couldn't successfully kill %s after %s "
+-                                  "tries" % (appName, max_tries))
+-                self.killProcess(appName)
+-                num_tries += 1
+-
+-                # sleep for a short duration to make sure there are no
+-                # additional processes in the process of being launched
+-                # (this is not 100% guaranteed to work since it is inherently
+-                # racey, but it's the best we can do)
+-                time.sleep(1)
+-
+-
+-class DroidADB(DeviceManagerADB, DroidMixin):
+-
+-    _stopApplicationNeedsRoot = False
+-
+-    def getTopActivity(self):
+-        package = None
+-        data = None
+-        try:
+-            # Increased timeout to 60 seconds after intermittent timeouts at 30.
+-            data = self.shellCheckOutput(
+-                ["dumpsys", "window", "windows"], timeout=60)
+-        except Exception:
+-            # dumpsys seems to intermittently fail (seen on 4.3 emulator), producing
+-            # no output.
+-            return ""
+-        # "dumpsys window windows" produces many lines of input. The top/foreground
+-        # activity is indicated by something like:
+-        #   mFocusedApp=AppWindowToken{483e6db0 token=HistoryRecord{484dcad8 com.mozilla.something/.something}} # noqa
+-        # or, on other devices:
+-        #   FocusedApplication: name='AppWindowToken{41a65340 token=ActivityRecord{418fbd68 org.mozilla.fennec_mozdev/org.mozilla.gecko.BrowserApp}}', dispatchingTimeout=5000.000ms # noqa
+-        # Extract this line, ending in the forward slash:
+-        m = re.search('mFocusedApp(.+)/', data)
+-        if not m:
+-            m = re.search('FocusedApplication(.+)/', data)
+-        if m:
+-            line = m.group(0)
+-            # Extract package name: string of non-whitespace ending in forward slash
+-            m = re.search('(\S+)/$', line)
+-            if m:
+-                package = m.group(1)
+-        if not package:
+-            # On some Android 4.4 devices, when the home screen is displayed,
+-            # dumpsys reports "mFocusedApp=null". Guard against this case and
+-            # others where the focused app can not be determined by returning
+-            # an empty string.
+-            package = ""
+-        return package
+-
+-    def getAppRoot(self, packageName):
+-        """
+-        Returns the root directory for the specified android application
+-        """
+-        # relying on convention
+-        return '/data/data/%s' % packageName
+diff --git a/testing/mozbase/mozdevice/mozdevice/version_codes.py b/testing/mozbase/mozdevice/mozdevice/version_codes.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/mozdevice/version_codes.py
++++ /dev/null
+@@ -1,62 +0,0 @@
+-# 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/.
+-
+-"""
+-VERSION CODES of the android releases.
+-
+-See http://developer.android.com/reference/android/os/Build.VERSION_CODES.html.
+-"""
+-from __future__ import absolute_import
+-
+-# Magic version number for a current development build, which has
+-# not yet turned into an official release.
+-CUR_DEVELOPMENT = 10000
+-
+-# October 2008: The original, first, version of Android
+-BASE = 1
+-# February 2009: First Android update, officially called 1.1
+-BASE_1_1 = 2
+-# May 2009: Android 1.5
+-CUPCAKE = 3
+-# September 2009: Android 1.6
+-DONUT = 4
+-# November 2009: Android 2.0
+-ECLAIR = 5
+-# December 2009: Android 2.0.1
+-ECLAIR_0_1 = 6
+-# January 2010: Android 2.1
+-ECLAIR_MR1 = 7
+-# June 2010: Android 2.2
+-FROYO = 8
+-# November 2010: Android 2.3
+-GINGERBREAD = 9
+-# February 2011: Android 2.3.3
+-GINGERBREAD_MR1 = 10
+-# February 2011: Android 3.0
+-HONEYCOMB = 11
+-# May 2011: Android 3.1
+-HONEYCOMB_MR1 = 12
+-# June 2011: Android 3.2
+-HONEYCOMB_MR2 = 13
+-# October 2011: Android 4.0
+-ICE_CREAM_SANDWICH = 14
+-# December 2011: Android 4.0.3
+-ICE_CREAM_SANDWICH_MR1 = 15
+-# June 2012: Android 4.1
+-JELLY_BEAN = 16
+-# November 2012: Android 4.2
+-JELLY_BEAN_MR1 = 17
+-# July 2013: Android 4.3
+-JELLY_BEAN_MR2 = 18
+-# October 2013: Android 4.4
+-KITKAT = 19
+-# Android 4.4W
+-KITKAT_WATCH = 20
+-# Lollilop
+-LOLLIPOP = 21
+-LOLLIPOP_MR1 = 22
+-# M
+-M = 23
+-# N
+-N = 24
+diff --git a/testing/mozbase/mozdevice/setup.py b/testing/mozbase/mozdevice/setup.py
+deleted file mode 100644
+--- a/testing/mozbase/mozdevice/setup.py
++++ /dev/null
+@@ -1,40 +0,0 @@
+-
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-from setuptools import setup
+-
+-PACKAGE_NAME = 'mozdevice'
+-PACKAGE_VERSION = '0.51'
+-
+-deps = ['mozfile >= 1.0',
+-        'mozlog >= 3.0',
+-        'moznetwork >= 0.24',
+-        'mozprocess >= 0.19',
+-        ]
+-
+-setup(name=PACKAGE_NAME,
+-      version=PACKAGE_VERSION,
+-      description="Mozilla-authored device management",
+-      long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
+-      classifiers=['Programming Language :: Python :: 2.7',
+-                   'Programming Language :: Python :: 2 :: Only'],
+-      # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+-      keywords='',
+-      author='Mozilla Automation and Testing Team',
+-      author_email='tools@lists.mozilla.org',
+-      url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
+-      license='MPL',
+-      packages=['mozdevice'],
+-      include_package_data=True,
+-      zip_safe=False,
+-      install_requires=deps,
+-      entry_points="""
+-      # -*- Entry points: -*-
+-      [console_scripts]
+-      dm = mozdevice.dmcli:cli
+-      """,
+-      )
+diff --git a/testing/mozbase/mozrunner/setup.py b/testing/mozbase/mozrunner/setup.py
+--- a/testing/mozbase/mozrunner/setup.py
++++ b/testing/mozbase/mozrunner/setup.py
+@@ -7,17 +7,16 @@ from __future__ import absolute_import
+ from setuptools import setup, find_packages
+ 
+ PACKAGE_NAME = 'mozrunner'
+ PACKAGE_VERSION = '7.4.0'
+ 
+ desc = """Reliable start/stop/configuration of Mozilla Applications (Firefox, Thunderbird, etc.)"""
+ 
+ deps = [
+-    'mozdevice>=1.1.6',
+     'mozfile>=1.2',
+     'mozinfo>=0.7,<2',
+     'mozlog~=4.1.1',
+     'mozprocess>=0.23,<2',
+     'mozprofile~=2.1',
+     'six>=1.10.0,<2',
+ ]
+ 
+diff --git a/testing/mozbase/packages.txt b/testing/mozbase/packages.txt
+--- a/testing/mozbase/packages.txt
++++ b/testing/mozbase/packages.txt
+@@ -1,12 +1,11 @@
+ manifestparser.pth:testing/mozbase/manifestparser
+ mozcrash.pth:testing/mozbase/mozcrash
+ mozdebug.pth:testing/mozbase/mozdebug
+-mozdevice.pth:testing/mozbase/mozdevice
+ mozfile.pth:testing/mozbase/mozfile
+ mozhttpd.pth:testing/mozbase/mozhttpd
+ mozinfo.pth:testing/mozbase/mozinfo
+ mozinstall.pth:testing/mozbase/mozinstall
+ mozleak.pth:testing/mozbase/mozleak
+ mozlog.pth:testing/mozbase/mozlog
+ moznetwork.pth:testing/mozbase/moznetwork
+ mozprocess.pth:testing/mozbase/mozprocess
+diff --git a/testing/mozharness/configs/android/androidarm_4_3.py b/testing/mozharness/configs/android/androidarm_4_3.py
+deleted file mode 100644
+--- a/testing/mozharness/configs/android/androidarm_4_3.py
++++ /dev/null
+@@ -1,372 +0,0 @@
+-import os
+-
+-config = {
+-    "buildbot_json_path": "buildprops.json",
+-    "hostutils_manifest_path": "testing/config/tooltool-manifests/linux64/hostutils.manifest",
+-    "robocop_package_name": "org.mozilla.roboexample.test",
+-    "marionette_address": "localhost:2828",
+-    "marionette_test_manifest": "unit-tests.ini",
+-    "download_tooltool": True,
+-    "tooltool_servers": ['http://relengapi/tooltool/'],
+-    "tooltool_manifest_path": "testing/config/tooltool-manifests/androidarm_4_3/releng.manifest",
+-    "tooltool_cache": "/builds/worker/tooltool_cache",
+-    "avds_dir": "/builds/worker/workspace/build/.android",
+-    "emulator_manifest": """
+-        [
+-        {
+-        "size": 140097024,
+-        "digest": "51781032335c09103e8509b1a558bf22a7119392cf1ea301c49c01bdf21ff0ceb37d260bc1c322cd9b903252429fb01830fc27e4632be30cd345c95bf4b1a39b",
+-        "algorithm": "sha512",
+-        "filename": "android-sdk_r24.0.2-linux.tgz",
+-        "unpack": "True"
+-        }
+-        ] """,
+-    "tools_manifest": """
+-        [
+-        {
+-        "size": 193383673,
+-        "digest": "6609e8b95db59c6a3ad60fc3dcfc358b2c8ec8b4dda4c2780eb439e1c5dcc5d550f2e47ce56ba14309363070078d09b5287e372f6e95686110ff8a2ef1838221",
+-        "algorithm": "sha512",
+-        "filename": "android-sdk18_0.r18moz1.orig.tar.gz",
+-        "unpack": "True"
+-        }
+-        ] """,
+-    "emulator_process_name": "emulator64-arm",
+-    "emulator_extra_args": "-show-kernel -debug init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket",
+-    "exes": {
+-        'adb': '%(abs_work_dir)s/android-sdk18/platform-tools/adb',
+-    },
+-    "env": {
+-        "DISPLAY": ":0.0",
+-        "PATH": "%(PATH)s:%(abs_work_dir)s/android-sdk-linux/tools:%(abs_work_dir)s/android-sdk18/platform-tools",
+-        "MINIDUMP_SAVEPATH": "%(abs_work_dir)s/../minidumps"
+-    },
+-    "default_actions": [
+-        'clobber',
+-        'read-buildbot-config',
+-        'setup-avds',
+-        'start-emulator',
+-        'download-and-extract',
+-        'create-virtualenv',
+-        'verify-emulator',
+-        'install',
+-        'run-tests',
+-    ],
+-    "emulator": {
+-        "name": "test-1",
+-        "device_id": "emulator-5554",
+-        "http_port": "8854",  # starting http port to use for the mochitest server
+-        "ssl_port": "4454",  # starting ssl port to use for the server
+-        "emulator_port": 5554,
+-    },
+-    "suite_definitions": {
+-        "mochitest": {
+-            "run_filename": "runtestsremote.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--extra-profile-file=fonts",
+-                "--extra-profile-file=hyphenation",
+-                "--screenshot-on-fail",
+-                "--total-chunks=20",
+-            ],
+-        },
+-        "mochitest-gl": {
+-            "run_filename": "runtestsremote.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--screenshot-on-fail",
+-                "--total-chunks=10",
+-                "--subsuite=webgl",
+-            ],
+-        },
+-        "mochitest-chrome": {
+-            "run_filename": "runtestsremote.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--extra-profile-file=fonts",
+-                "--extra-profile-file=hyphenation",
+-                "--screenshot-on-fail",
+-                "--flavor=chrome",
+-            ],
+-        },
+-        "mochitest-plain-gpu": {
+-            "run_filename": "runtestsremote.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--screenshot-on-fail",
+-                "--subsuite=gpu",
+-            ],
+-        },
+-        "mochitest-plain-clipboard": {
+-            "run_filename": "runtestsremote.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--screenshot-on-fail",
+-                "--subsuite=clipboard",
+-            ],
+-        },
+-        "mochitest-media": {
+-            "run_filename": "runtestsremote.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--screenshot-on-fail",
+-                "--chunk-by-runtime",
+-                "--total-chunks=2",
+-                "--subsuite=media",
+-            ],
+-        },
+-        "robocop": {
+-            "run_filename": "runrobocop.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--total-chunks=4",
+-                "--robocop-apk=../../robocop.apk",
+-                "--robocop-ini=robocop.ini",
+-            ],
+-        },
+-        "reftest": {
+-            "run_filename": "remotereftest.py",
+-            "testsdir": "reftest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--ignore-window-size",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--httpd-path", "%(modules_dir)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--total-chunks=16",
+-                "--extra-profile-file=fonts",
+-                "--extra-profile-file=hyphenation",
+-                "--suite=reftest",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-            ],
+-            "tests": ["tests/layout/reftests/reftest.list",],
+-        },
+-        "reftest-debug": {
+-            "run_filename": "remotereftest.py",
+-            "testsdir": "reftest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--ignore-window-size",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--httpd-path", "%(modules_dir)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--total-chunks=48",
+-                "--extra-profile-file=fonts",
+-                "--extra-profile-file=hyphenation",
+-                "tests/layout/reftests/reftest.list",
+-            ],
+-        },
+-        "crashtest": {
+-            "run_filename": "remotereftest.py",
+-            "testsdir": "reftest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--ignore-window-size",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--httpd-path",
+-                "%(modules_dir)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--total-chunks=4",
+-                "--suite=crashtest",
+-            ],
+-            "tests": ["tests/testing/crashtest/crashtests.list",],
+-        },
+-        "crashtest-debug": {
+-            "run_filename": "remotereftest.py",
+-            "testsdir": "reftest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--ignore-window-size",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--httpd-path",
+-                "%(modules_dir)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--total-chunks=10",
+-                "tests/testing/crashtest/crashtests.list",
+-            ],
+-        },
+-        "jsreftest": {
+-            "run_filename": "remotereftest.py",
+-            "testsdir": "reftest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--ignore-window-size",
+-                "--remote-webserver=%(remote_webserver)s", "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s", "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s", "--httpd-path", "%(modules_dir)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--total-chunks=10",
+-                "--extra-profile-file=jsreftest/tests/user.js",
+-                "--suite=jstestbrowser",
+-            ],
+-            "tests": ["../jsreftest/tests/jstests.list",],
+-        },
+-        "jsreftest-debug": {
+-            "run_filename": "remotereftest.py",
+-            "testsdir": "reftest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--ignore-window-size",
+-                "--remote-webserver=%(remote_webserver)s", "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s", "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s", "--httpd-path", "%(modules_dir)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "../jsreftest/tests/jstests.list",
+-                "--total-chunks=35",
+-                "--extra-profile-file=jsreftest/tests/user.js",
+-            ],
+-        },
+-        "xpcshell": {
+-            "run_filename": "remotexpcshelltests.py",
+-            "testsdir": "xpcshell",
+-            "install": False,
+-            "options": [
+-                "--xre-path=%(xre_path)s",
+-                "--testing-modules-dir=%(modules_dir)s",
+-                "--apk=%(installer_path)s",
+-                "--no-logfiles",
+-                "--symbols-path=%(symbols_path)s",
+-                "--manifest=tests/xpcshell.ini",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--total-chunks=3",
+-            ],
+-        },
+-        "cppunittest": {
+-            "run_filename": "remotecppunittests.py",
+-            "testsdir": "cppunittest",
+-            "install": False,
+-            "options": [
+-                "--symbols-path=%(symbols_path)s",
+-                "--xre-path=%(xre_path)s",
+-                "--localBinDir=../bin",
+-                "--apk=%(installer_path)s",
+-                ".",
+-            ],
+-        },
+-        "marionette": {
+-            "run_filename": os.path.join("harness", "marionette_harness", "runtests.py"),
+-            "testsdir": "marionette",
+-            "options": [
+-                "--emulator",
+-                "--app=fennec",
+-                "--package=%(app)s",
+-                "--address=%(address)s",
+-                "%(test_manifest)s",
+-                "--disable-e10s",
+-                "--gecko-log=%(gecko_log)s",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--startup-timeout=300",
+-            ],
+-        },
+-        "geckoview": {
+-            "run_filename": "rungeckoview.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--utility-path=%(utility_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-            ],
+-        },
+-
+-    },  # end suite_definitions
+-    "download_minidump_stackwalk": True,
+-    "default_blob_upload_servers": [
+-        "https://blobupload.elasticbeanstalk.com",
+-    ],
+-}
+diff --git a/testing/mozharness/configs/android/androidx86.py b/testing/mozharness/configs/android/androidx86.py
+deleted file mode 100644
+--- a/testing/mozharness/configs/android/androidx86.py
++++ /dev/null
+@@ -1,91 +0,0 @@
+-import os
+-
+-config = {
+-    "buildbot_json_path": "buildprops.json",
+-    "hostutils_manifest_path": "testing/config/tooltool-manifests/linux64/hostutils.manifest",
+-    "tooltool_manifest_path": "testing/config/tooltool-manifests/androidx86/releng.manifest",
+-    "tooltool_cache": "/builds/worker/tooltool_cache",
+-    "download_tooltool": True,
+-    "tooltool_servers": ['http://relengapi/tooltool/'],
+-    "avds_dir": "/builds/worker/workspace/build/.android",
+-    "emulator_manifest": """
+-        [
+-        {
+-        "size": 193383673,
+-        "digest": "6609e8b95db59c6a3ad60fc3dcfc358b2c8ec8b4dda4c2780eb439e1c5dcc5d550f2e47ce56ba14309363070078d09b5287e372f6e95686110ff8a2ef1838221",
+-        "algorithm": "sha512",
+-        "filename": "android-sdk18_0.r18moz1.orig.tar.gz",
+-        "unpack": "True"
+-        }
+-        ] """,
+-    "emulator_process_name": "emulator64-x86",
+-    "emulator_extra_args": "-show-kernel -debug init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket -qemu -m 1024",
+-    "exes": {
+-        'adb': '%(abs_work_dir)s/android-sdk18/platform-tools/adb',
+-    },
+-    "env": {
+-        "DISPLAY": ":0.0",
+-        "PATH": "%(PATH)s:%(abs_work_dir)s/android-sdk18/tools:%(abs_work_dir)s/android-sdk18/platform-tools",
+-        "MINIDUMP_SAVEPATH": "%(abs_work_dir)s/../minidumps"
+-    },
+-    "default_actions": [
+-        'clobber',
+-        'read-buildbot-config',
+-        'setup-avds',
+-        'start-emulator',
+-        'download-and-extract',
+-        'create-virtualenv',
+-        'verify-emulator',
+-        'install',
+-        'run-tests',
+-    ],
+-    "emulator": {
+-        "name": "test-1",
+-        "device_id": "emulator-5554",
+-        "http_port": "8854",  # starting http port to use for the mochitest server
+-        "ssl_port": "4454",  # starting ssl port to use for the server
+-        "emulator_port": 5554,
+-    },
+-    "suite_definitions": {
+-        "xpcshell": {
+-            "run_filename": "remotexpcshelltests.py",
+-            "testsdir": "xpcshell",
+-            "install": False,
+-            "options": [
+-                "--xre-path=%(xre_path)s",
+-                "--testing-modules-dir=%(modules_dir)s",
+-                "--apk=%(installer_path)s",
+-                "--no-logfiles",
+-                "--symbols-path=%(symbols_path)s",
+-                "--manifest=tests/xpcshell.ini",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-            ],
+-        },
+-        "mochitest-chrome": {
+-            "run_filename": "runtestsremote.py",
+-            "testsdir": "mochitest",
+-            "options": [
+-                "--app=%(app)s",
+-                "--remote-webserver=%(remote_webserver)s",
+-                "--xre-path=%(xre_path)s",
+-                "--utility-path=%(utility_path)s",
+-                "--http-port=%(http_port)s",
+-                "--ssl-port=%(ssl_port)s",
+-                "--certificate-path=%(certs_path)s",
+-                "--symbols-path=%(symbols_path)s",
+-                "--quiet",
+-                "--log-raw=%(raw_log_file)s",
+-                "--log-errorsummary=%(error_summary_file)s",
+-                "--extra-profile-file=fonts",
+-                "--extra-profile-file=hyphenation",
+-                "--screenshot-on-fail",
+-                "--flavor=chrome",
+-            ],
+-        },
+-    },  # end suite_definitions
+-    "download_minidump_stackwalk": True,
+-    "default_blob_upload_servers": [
+-        "https://blobupload.elasticbeanstalk.com",
+-    ],
+-}
+diff --git a/testing/mozharness/mozharness/mozilla/mozbase.py b/testing/mozharness/mozharness/mozilla/mozbase.py
+--- a/testing/mozharness/mozharness/mozilla/mozbase.py
++++ b/testing/mozharness/mozharness/mozilla/mozbase.py
+@@ -27,13 +27,13 @@ class MozbaseMixin(object):
+         # in-tree requirements files propagate to all active trees.
+         mozbase_dir = os.path.join('tests', 'mozbase')
+         self.register_virtualenv_module(
+             'manifestparser',
+             url=os.path.join(mozbase_dir, 'manifestdestiny')
+         )
+ 
+         for m in ('mozfile', 'mozlog', 'mozinfo', 'moznetwork', 'mozhttpd',
+-                  'mozcrash', 'mozinstall', 'mozdevice', 'mozprofile',
+-                  'mozprocess', 'mozrunner'):
++                  'mozcrash', 'mozinstall', 'mozprofile', 'mozprocess',
++                  'mozrunner'):
+             self.register_virtualenv_module(
+                 m, url=os.path.join(mozbase_dir, m)
+             )
+diff --git a/testing/mozharness/scripts/marionette.py b/testing/mozharness/scripts/marionette.py
+--- a/testing/mozharness/scripts/marionette.py
++++ b/testing/mozharness/scripts/marionette.py
+@@ -206,18 +206,18 @@ class MarionetteTest(TestingMixin, Mercu
+             # XXX Bug 879765: Dependent modules need to be listed before parent
+             # modules, otherwise they will get installed from the pypi server.
+             # XXX Bug 908356: This block can be removed as soon as the
+             # in-tree requirements files propagate to all active trees.
+             mozbase_dir = os.path.join('tests', 'mozbase')
+             self.register_virtualenv_module(
+                 'manifestparser', os.path.join(mozbase_dir, 'manifestdestiny'))
+             for m in ('mozfile', 'mozlog', 'mozinfo', 'moznetwork', 'mozhttpd',
+-                      'mozcrash', 'mozinstall', 'mozdevice', 'mozprofile',
+-                      'mozprocess', 'mozrunner'):
++                      'mozcrash', 'mozinstall', 'mozprofile', 'mozprocess',
++                      'mozrunner'):
+                 self.register_virtualenv_module(
+                     m, os.path.join(mozbase_dir, m))
+ 
+             self.register_virtualenv_module(
+                 'marionette', os.path.join('tests', 'marionette'))
+ 
+     def _get_test_suite(self, is_emulator):
+         """
+diff --git a/testing/mozharness/test/pip-freeze.example.txt b/testing/mozharness/test/pip-freeze.example.txt
+--- a/testing/mozharness/test/pip-freeze.example.txt
++++ b/testing/mozharness/test/pip-freeze.example.txt
+@@ -2,17 +2,16 @@ MakeItSo==0.2.6
+ PyYAML==3.10
+ Tempita==0.5.1
+ WebOb==1.2b3
+ -e hg+http://k0s.org/mozilla/hg/configuration@35416ad140982c11eba0a2d6b96d683f53429e94#egg=configuration-dev
+ coverage==3.5.1
+ -e hg+http://k0s.org/mozilla/hg/jetperf@4645ae34d2c41a353dcdbd856b486b6d3faabb99#egg=jetperf-dev
+ logilab-astng==0.23.1
+ logilab-common==0.57.1
+-mozdevice==0.2
+ -e hg+https://hg.mozilla.org/build/mozharness@df6b7f1e14d8c472125ef7a77b8a3b40c96ae181#egg=mozharness-jetperf
+ mozhttpd==0.3
+ mozinfo==0.3.3
+ nose==1.1.2
+ pyflakes==0.5.0
+ pylint==0.25.1
+ -e hg+https://hg.mozilla.org/build/talos@ee5c0b090d808e81a8fc5ba5f96b012797b3e785#egg=talos-dev
+ virtualenv==1.7.1.2
+diff --git a/testing/mozharness/test/test_base_python.py b/testing/mozharness/test/test_base_python.py
+--- a/testing/mozharness/test/test_base_python.py
++++ b/testing/mozharness/test/test_base_python.py
+@@ -16,17 +16,16 @@ class TestVirtualenvMixin(unittest.TestC
+         # from the file
+         expected = {'MakeItSo': '0.2.6',
+                     'PyYAML': '3.10',
+                     'Tempita': '0.5.1',
+                     'WebOb': '1.2b3',
+                     'coverage': '3.5.1',
+                     'logilab-astng': '0.23.1',
+                     'logilab-common': '0.57.1',
+-                    'mozdevice': '0.2',
+                     'mozhttpd': '0.3',
+                     'mozinfo': '0.3.3',
+                     'nose': '1.1.2',
+                     'pyflakes': '0.5.0',
+                     'pylint': '0.25.1',
+                     'virtualenv': '1.7.1.2',
+                     'wsgiref': '0.1.2'}
+ 
+diff --git a/testing/remotecppunittests.py b/testing/remotecppunittests.py
+deleted file mode 100644
+--- a/testing/remotecppunittests.py
++++ /dev/null
+@@ -1,295 +0,0 @@
+-#!/usr/bin/env python
+-#
+-# 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/.
+-
+-from __future__ import absolute_import, print_function
+-import os
+-import sys
+-import subprocess
+-from zipfile import ZipFile
+-import runcppunittests as cppunittests
+-import mozcrash
+-import mozfile
+-import mozinfo
+-import mozlog
+-import StringIO
+-import posixpath
+-from mozdevice import devicemanagerADB
+-
+-try:
+-    from mozbuild.base import MozbuildObject
+-    build_obj = MozbuildObject.from_environment()
+-except ImportError:
+-    build_obj = None
+-
+-
+-class RemoteCPPUnitTests(cppunittests.CPPUnitTests):
+-
+-    def __init__(self, devmgr, options, progs):
+-        cppunittests.CPPUnitTests.__init__(self)
+-        self.options = options
+-        self.device = devmgr
+-        self.remote_test_root = self.device.deviceRoot + "/cppunittests"
+-        self.remote_bin_dir = posixpath.join(self.remote_test_root, "b")
+-        self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp")
+-        self.remote_home_dir = posixpath.join(self.remote_test_root, "h")
+-        if options.setup:
+-            self.setup_bin(progs)
+-
+-    def setup_bin(self, progs):
+-        if not self.device.dirExists(self.remote_test_root):
+-            self.device.mkDir(self.remote_test_root)
+-        if self.device.dirExists(self.remote_tmp_dir):
+-            self.device.removeDir(self.remote_tmp_dir)
+-        self.device.mkDir(self.remote_tmp_dir)
+-        if self.device.dirExists(self.remote_bin_dir):
+-            self.device.removeDir(self.remote_bin_dir)
+-        self.device.mkDir(self.remote_bin_dir)
+-        if self.device.dirExists(self.remote_home_dir):
+-            self.device.removeDir(self.remote_home_dir)
+-        self.device.mkDir(self.remote_home_dir)
+-        self.push_libs()
+-        self.push_progs(progs)
+-        self.device.chmodDir(self.remote_bin_dir)
+-
+-    def push_libs(self):
+-        if self.options.local_apk:
+-            with mozfile.TemporaryDirectory() as tmpdir:
+-                apk_contents = ZipFile(self.options.local_apk)
+-
+-                for info in apk_contents.infolist():
+-                    if info.filename.endswith(".so"):
+-                        print("Pushing %s.." % info.filename, file=sys.stderr)
+-                        remote_file = posixpath.join(
+-                            self.remote_bin_dir, os.path.basename(info.filename))
+-                        apk_contents.extract(info, tmpdir)
+-                        local_file = os.path.join(tmpdir, info.filename)
+-                        with open(local_file) as f:
+-                            # Decompress xz-compressed file.
+-                            if f.read(5)[1:] == '7zXZ':
+-                                cmd = [
+-                                    'xz', '-df', '--suffix', '.so', local_file]
+-                                subprocess.check_output(cmd)
+-                                # xz strips the ".so" file suffix.
+-                                os.rename(local_file[:-3], local_file)
+-                        self.device.pushFile(local_file, remote_file)
+-
+-        elif self.options.local_lib:
+-            for path in os.listdir(self.options.local_lib):
+-                if path.endswith(".so"):
+-                    print("Pushing {}..".format(path), file=sys.stderr)
+-                    remote_file = posixpath.join(self.remote_bin_dir, path)
+-                    local_file = os.path.join(self.options.local_lib, path)
+-                    self.device.pushFile(local_file, remote_file)
+-            # Additional libraries may be found in a sub-directory such as
+-            # "lib/armeabi-v7a"
+-            for subdir in ["assets", "lib"]:
+-                local_arm_lib = os.path.join(self.options.local_lib, subdir)
+-                if os.path.isdir(local_arm_lib):
+-                    for root, dirs, paths in os.walk(local_arm_lib):
+-                        for path in paths:
+-                            if path.endswith(".so"):
+-                                print("Pushing {}..".format(path), file=sys.stderr)
+-                                remote_file = posixpath.join(
+-                                    self.remote_bin_dir, path)
+-                                local_file = os.path.join(root, path)
+-                                self.device.pushFile(local_file, remote_file)
+-
+-    def push_progs(self, progs):
+-        for local_file in progs:
+-            remote_file = posixpath.join(
+-                self.remote_bin_dir, os.path.basename(local_file))
+-            self.device.pushFile(local_file, remote_file)
+-
+-    def build_environment(self):
+-        env = self.build_core_environment()
+-        env['LD_LIBRARY_PATH'] = self.remote_bin_dir
+-        env["TMPDIR"] = self.remote_tmp_dir
+-        env["HOME"] = self.remote_home_dir
+-        env["MOZ_XRE_DIR"] = self.remote_bin_dir
+-        if self.options.add_env:
+-            for envdef in self.options.add_env:
+-                envdef_parts = envdef.split("=", 1)
+-                if len(envdef_parts) == 2:
+-                    env[envdef_parts[0]] = envdef_parts[1]
+-                elif len(envdef_parts) == 1:
+-                    env[envdef_parts[0]] = ""
+-                else:
+-                    self.log.warning(
+-                        "invalid --addEnv option skipped: %s" % envdef)
+-
+-        return env
+-
+-    def run_one_test(self, prog, env, symbols_path=None, interactive=False,
+-                     timeout_factor=1):
+-        """
+-        Run a single C++ unit test program remotely.
+-
+-        Arguments:
+-        * prog: The path to the test program to run.
+-        * env: The environment to use for running the program.
+-        * symbols_path: A path to a directory containing Breakpad-formatted
+-                        symbol files for producing stack traces on crash.
+-        * timeout_factor: An optional test-specific timeout multiplier.
+-
+-        Return True if the program exits with a zero status, False otherwise.
+-        """
+-        basename = os.path.basename(prog)
+-        remote_bin = posixpath.join(self.remote_bin_dir, basename)
+-        self.log.test_start(basename)
+-        buf = StringIO.StringIO()
+-        test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * \
+-            timeout_factor
+-        returncode = self.device.shell(
+-            [remote_bin], buf, env=env, cwd=self.remote_home_dir,
+-            timeout=test_timeout)
+-        self.log.process_output(basename, "\n%s" % buf.getvalue(),
+-                                command=[remote_bin])
+-        with mozfile.TemporaryDirectory() as tempdir:
+-            self.device.getDirectory(self.remote_home_dir, tempdir)
+-            if mozcrash.check_for_crashes(tempdir, symbols_path,
+-                                          test_name=basename):
+-                self.log.test_end(basename, status='CRASH', expected='PASS')
+-                return False
+-        result = returncode == 0
+-        if not result:
+-            self.log.test_end(basename, status='FAIL', expected='PASS',
+-                              message=("test failed with return code %s" %
+-                                       returncode))
+-        else:
+-            self.log.test_end(basename, status='PASS', expected='PASS')
+-        return result
+-
+-
+-class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions):
+-
+-    def __init__(self):
+-        cppunittests.CPPUnittestOptions.__init__(self)
+-        defaults = {}
+-
+-        self.add_option("--deviceIP", action="store",
+-                        type="string", dest="device_ip",
+-                        help="ip address of remote device to test")
+-        defaults["device_ip"] = None
+-
+-        self.add_option("--devicePort", action="store",
+-                        type="string", dest="device_port",
+-                        help="port of remote device to test")
+-        defaults["device_port"] = 20701
+-
+-        self.add_option("--noSetup", action="store_false",
+-                        dest="setup",
+-                        help="do not copy any files to device (to be used only if "
+-                        "device is already setup)")
+-        defaults["setup"] = True
+-
+-        self.add_option("--localLib", action="store",
+-                        type="string", dest="local_lib",
+-                        help="location of libraries to push -- preferably stripped")
+-        defaults["local_lib"] = None
+-
+-        self.add_option("--apk", action="store",
+-                        type="string", dest="local_apk",
+-                        help="local path to Fennec APK")
+-        defaults["local_apk"] = None
+-
+-        self.add_option("--localBinDir", action="store",
+-                        type="string", dest="local_bin",
+-                        help="local path to bin directory")
+-        defaults[
+-            "local_bin"] = build_obj.bindir if build_obj is not None else None
+-
+-        self.add_option("--remoteTestRoot", action="store",
+-                        type="string", dest="remote_test_root",
+-                        help="remote directory to use as test root (eg. /data/local/tests)")
+-        # /data/local/tests is used because it is usually not possible to set +x permissions
+-        # on binaries on /mnt/sdcard
+-        defaults["remote_test_root"] = "/data/local/tests"
+-
+-        self.add_option("--with-b2g-emulator", action="store",
+-                        type="string", dest="with_b2g_emulator",
+-                        help="Start B2G Emulator (specify path to b2g home)")
+-        self.add_option("--emulator", default="arm", choices=["x86", "arm"],
+-                        help="Architecture of emulator to use: x86 or arm")
+-        self.add_option("--addEnv", action="append",
+-                        type="string", dest="add_env",
+-                        help="additional remote environment variable definitions "
+-                        "(eg. --addEnv \"somevar=something\")")
+-        defaults["add_env"] = None
+-
+-        self.set_defaults(**defaults)
+-
+-
+-def run_test_harness(options, args):
+-    if options.with_b2g_emulator:
+-        from mozrunner import B2GEmulatorRunner
+-        runner = B2GEmulatorRunner(
+-            arch=options.emulator, b2g_home=options.with_b2g_emulator)
+-        runner.start()
+-        # because we just started the emulator, we need more than the
+-        # default number of retries here.
+-        retryLimit = 50
+-    else:
+-        retryLimit = 5
+-    try:
+-        dm_args = {'deviceRoot': options.remote_test_root}
+-        dm_args['retryLimit'] = retryLimit
+-        if options.device_ip:
+-            dm_args['host'] = options.device_ip
+-            dm_args['port'] = options.device_port
+-        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+-            dm_args['logLevel'] = logging.DEBUG # noqa python 2 / 3
+-        dm = devicemanagerADB.DeviceManagerADB(**dm_args)
+-    except BaseException:
+-        if options.with_b2g_emulator:
+-            runner.cleanup()
+-            runner.wait()
+-        raise
+-
+-    options.xre_path = os.path.abspath(options.xre_path)
+-    cppunittests.update_mozinfo()
+-    progs = cppunittests.extract_unittests_from_args(args,
+-                                                     mozinfo.info,
+-                                                     options.manifest_path)
+-    tester = RemoteCPPUnitTests(dm, options, [item[0] for item in progs])
+-    try:
+-        result = tester.run_tests(
+-            progs, options.xre_path, options.symbols_path)
+-    finally:
+-        if options.with_b2g_emulator:
+-            runner.cleanup()
+-            runner.wait()
+-    return result
+-
+-
+-def main():
+-    parser = RemoteCPPUnittestOptions()
+-    mozlog.commandline.add_logging_group(parser)
+-    options, args = parser.parse_args()
+-    if not args:
+-        print("""Usage: %s <test binary> [<test binary>...]""" % sys.argv[0], file=sys.stderr)
+-        sys.exit(1)
+-    if options.local_lib is not None and not os.path.isdir(options.local_lib):
+-        print("""Error: --localLib directory %s not found""" % options.local_lib, file=sys.stderr)
+-        sys.exit(1)
+-    if options.local_apk is not None and not os.path.isfile(options.local_apk):
+-        print("""Error: --apk file %s not found""" % options.local_apk, file=sys.stderr)
+-        sys.exit(1)
+-    if not options.xre_path:
+-        print("""Error: --xre-path is required""", file=sys.stderr)
+-        sys.exit(1)
+-
+-    log = mozlog.commandline.setup_logging("remotecppunittests", options,
+-                                           {"tbpl": sys.stdout})
+-    try:
+-        result = run_test_harness(options, args)
+-    except Exception as e:
+-        log.error(str(e))
+-        result = False
+-    sys.exit(0 if result else 1)
+-
+-
+-if __name__ == '__main__':
+-    main()
+diff --git a/testing/testsuite-targets.mk b/testing/testsuite-targets.mk
+--- a/testing/testsuite-targets.mk
++++ b/testing/testsuite-targets.mk
+@@ -26,76 +26,44 @@ CHECK_TEST_ERROR = $(call check_test_err
+ CHECK_TEST_ERROR_RERUN = $(call check_test_error_internal,'To rerun your failures please run "make $@-rerun-failures"')
+ endif
+ 
+ # Usage: |make [EXTRA_TEST_ARGS=...] *test|.
+ RUN_REFTEST = rm -f ./$@.log && $(PYTHON3) _tests/reftest/runreftest.py \
+   --extra-profile-file=$(DIST)/plugins \
+   $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) $(1) | tee ./$@.log
+ 
+-REMOTE_REFTEST = rm -f ./$@.log && $(PYTHON3) _tests/reftest/remotereftest.py \
+-  --ignore-window-size \
+-  --app=$(TEST_PACKAGE_NAME) --deviceIP=${TEST_DEVICE} --xre-path=${MOZ_HOST_BIN} \
+-  --httpd-path=_tests/modules --suite reftest \
+-  --extra-profile-file=$(topsrcdir)/mobile/android/fonts \
+-  $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) $(1) | tee ./$@.log
+-
+ ifeq ($(OS_ARCH),WINNT) #{
+ # GPU-rendered shadow layers are unsupported here
+ OOP_CONTENT = --setpref=layers.async-pan-zoom.enabled=true --setpref=browser.tabs.remote.autostart=true --setpref=layers.acceleration.disabled=true
+ GPU_RENDERING =
+ else
+ OOP_CONTENT = --setpref=layers.async-pan-zoom.enabled=true --setpref=browser.tabs.remote.autostart=true
+ GPU_RENDERING = --setpref=layers.acceleration.force-enabled=true
+ endif #}
+ 
+ reftest: TEST_PATH?=layout/reftests/reftest.list
+ reftest:
+ 	$(call RUN_REFTEST,'$(topsrcdir)/$(TEST_PATH)')
+ 	$(CHECK_TEST_ERROR)
+ 
+-reftest-remote: TEST_PATH?=layout/reftests/reftest.list
+-reftest-remote:
+-	@if [ '${MOZ_HOST_BIN}' = '' ]; then \
+-        echo 'environment variable MOZ_HOST_BIN must be set to a directory containing host xpcshell'; \
+-    elif [ ! -d ${MOZ_HOST_BIN} ]; then \
+-        echo 'MOZ_HOST_BIN does not specify a directory'; \
+-    elif [ ! -f ${MOZ_HOST_BIN}/xpcshell ]; then \
+-        echo 'xpcshell not found in MOZ_HOST_BIN'; \
+-    else \
+-        ln -s $(abspath $(topsrcdir)) _tests/reftest/tests; \
+-        $(call REMOTE_REFTEST,'tests/$(TEST_PATH)'); \
+-        $(CHECK_TEST_ERROR); \
+-    fi
+-
+ crashtest: TEST_PATH?=testing/crashtest/crashtests.list
+ crashtest:
+ 	$(call RUN_REFTEST,'$(topsrcdir)/$(TEST_PATH)')
+ 	$(CHECK_TEST_ERROR)
+ 
+ jstestbrowser: TESTS_PATH?=test-stage/jsreftest/tests/
+ jstestbrowser:
+ 	$(MAKE) -C $(DEPTH)/config
+ 	$(MAKE) stage-jstests
+ 	$(call RUN_REFTEST,'$(DIST)/$(TESTS_PATH)/jstests.list' --extra-profile-file=$(DIST)/test-stage/jsreftest/tests/user.js)
+ 	$(CHECK_TEST_ERROR)
+ 
+ GARBAGE += $(addsuffix .log,$(MOCHITESTS) reftest crashtest jstestbrowser)
+ 
+-REMOTE_CPPUNITTESTS = \
+-	$(PYTHON3) -u $(topsrcdir)/testing/remotecppunittests.py \
+-	  --xre-path=$(DEPTH)/dist/bin \
+-	  --localLib=$(DEPTH)/dist/fennec \
+-	  --deviceIP=${TEST_DEVICE} \
+-	  $(TEST_PATH) $(EXTRA_TEST_ARGS)
+-
+-# Usage: |make [TEST_PATH=...] [EXTRA_TEST_ARGS=...] cppunittests-remote|.
+-cppunittests-remote:
+-	$(call REMOTE_CPPUNITTESTS);
+-
+ jetpack-tests:
+ 	cd $(topsrcdir)/addon-sdk/source && $(PYTHON) bin/cfx -b $(abspath $(browser_path)) --parseable testpkgs
+ 
+ # Package up the tests and test harnesses
+ include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
+ 
+ PKG_STAGE = $(DIST)/test-stage
+ 
+diff --git a/testing/tools/mach_test_package_bootstrap.py b/testing/tools/mach_test_package_bootstrap.py
+--- a/testing/tools/mach_test_package_bootstrap.py
++++ b/testing/tools/mach_test_package_bootstrap.py
+@@ -13,17 +13,16 @@ import types
+ 
+ SEARCH_PATHS = [
+     'marionette/client',
+     'marionette/harness',
+     'mochitest',
+     'mozbase/manifestparser',
+     'mozbase/mozcrash',
+     'mozbase/mozdebug',
+-    'mozbase/mozdevice',
+     'mozbase/mozfile',
+     'mozbase/mozhttpd',
+     'mozbase/mozinfo',
+     'mozbase/mozinstall',
+     'mozbase/mozleak',
+     'mozbase/mozlog',
+     'mozbase/moznetwork',
+     'mozbase/mozprocess',
+diff --git a/testing/xpcshell/mach_commands.py b/testing/xpcshell/mach_commands.py
+--- a/testing/xpcshell/mach_commands.py
++++ b/testing/xpcshell/mach_commands.py
+@@ -135,94 +135,18 @@ class XPCShellRunner(MozbuildObject):
+         self.log_manager.disable_unstructured()
+ 
+         if not result and not xpcshell.sequential:
+             print("Tests were run in parallel. Try running with --sequential "
+                   "to make sure the failures were not caused by this.")
+         return int(not result)
+ 
+ 
+-class AndroidXPCShellRunner(MozbuildObject):
+-    """Get specified DeviceManager"""
+-    def get_devicemanager(self, ip, port, remote_test_root):
+-        import mozdevice
+-        dm = None
+-        if ip:
+-            dm = mozdevice.DroidADB(ip, port, packageName=None, deviceRoot=remote_test_root)
+-        else:
+-            dm = mozdevice.DroidADB(packageName=None, deviceRoot=remote_test_root)
+-        return dm
+-
+-    """Run Android xpcshell tests."""
+-    def run_test(self, **kwargs):
+-        # TODO Bug 794506 remove once mach integrates with virtualenv.
+-        build_path = os.path.join(self.topobjdir, 'build')
+-        if build_path not in sys.path:
+-            sys.path.append(build_path)
+-
+-        import remotexpcshelltests
+-
+-        dm = self.get_devicemanager(kwargs["deviceIP"], kwargs["devicePort"],
+-                                    kwargs["remoteTestRoot"])
+-
+-        log = kwargs.pop("log")
+-        self.log_manager.enable_unstructured()
+-
+-        if kwargs["xpcshell"] is None:
+-            kwargs["xpcshell"] = "xpcshell"
+-
+-        if not kwargs["objdir"]:
+-            kwargs["objdir"] = self.topobjdir
+-
+-        if not kwargs["localLib"]:
+-            kwargs["localLib"] = os.path.join(self.topobjdir, 'dist/fennec')
+-
+-        if not kwargs["localBin"]:
+-            kwargs["localBin"] = os.path.join(self.topobjdir, 'dist/bin')
+-
+-        if not kwargs["testingModulesDir"]:
+-            kwargs["testingModulesDir"] = os.path.join(self.topobjdir, '_tests/modules')
+-
+-        if not kwargs["mozInfo"]:
+-            kwargs["mozInfo"] = os.path.join(self.topobjdir, 'mozinfo.json')
+-
+-        if not kwargs["manifest"]:
+-            kwargs["manifest"] = os.path.join(self.topobjdir, '_tests/xpcshell/xpcshell.ini')
+-
+-        if not kwargs["symbolsPath"]:
+-            kwargs["symbolsPath"] = os.path.join(self.distdir, 'crashreporter-symbols')
+-
+-        if not kwargs["localAPK"]:
+-            for file_name in os.listdir(os.path.join(kwargs["objdir"], "dist")):
+-                if file_name.endswith(".apk") and file_name.startswith("fennec"):
+-                    kwargs["localAPK"] = os.path.join(kwargs["objdir"], "dist", file_name)
+-                    print ("using APK: %s" % kwargs["localAPK"])
+-                    break
+-            else:
+-                raise Exception("APK not found in objdir. You must specify an APK.")
+-
+-        if not kwargs["sequential"]:
+-            kwargs["sequential"] = True
+-
+-        xpcshell = remotexpcshelltests.XPCShellRemote(dm, kwargs, log)
+-
+-        result = xpcshell.runTests(kwargs, testClass=remotexpcshelltests.RemoteXPCShellTestThread,
+-                                   mobileArgs=xpcshell.mobileArgs)
+-
+-        self.log_manager.disable_unstructured()
+-
+-        return int(not result)
+-
+-
+ def get_parser():
+-    build_obj = MozbuildObject.from_environment(cwd=here)
+-    if conditions.is_android(build_obj):
+-        return parser_remote()
+-    else:
+-        return parser_desktop()
++    return parser_desktop()
+ 
+ 
+ @CommandProvider
+ class MachCommands(MachCommandBase):
+     @Command('xpcshell-test', category='testing',
+              description='Run XPCOM Shell tests (API direct unit testing)',
+              conditions=[lambda *args: True],
+              parser=get_parser)
+@@ -247,21 +171,16 @@ class MachCommands(MachCommandBase):
+             params['log'] = structured.commandline.setup_logging("XPCShellTests",
+                                                                  params,
+                                                                  {"mach": sys.stdout},
+                                                                  {"verbose": True})
+ 
+         if not params['threadCount']:
+             params['threadCount'] = int((cpu_count() * 3) / 2)
+ 
+-        if conditions.is_android(self):
+-            from mozrunner.devices.android_device import verify_android_device
+-            verify_android_device(self)
+-            xpcshell = self._spawn(AndroidXPCShellRunner)
+-        else:
+-            xpcshell = self._spawn(XPCShellRunner)
++        xpcshell = self._spawn(XPCShellRunner)
+         xpcshell.cwd = self._mach_context.cwd
+ 
+         try:
+             return xpcshell.run_test(**params)
+         except InvalidTestPathError as e:
+             print(e.message)
+             return 1
+diff --git a/testing/xpcshell/remotexpcshelltests.py b/testing/xpcshell/remotexpcshelltests.py
+deleted file mode 100644
+--- a/testing/xpcshell/remotexpcshelltests.py
++++ /dev/null
+@@ -1,618 +0,0 @@
+-#!/usr/bin/env python
+-#
+-# 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/.
+-
+-from __future__ import absolute_import, print_function
+-
+-import logging
+-import posixpath
+-import os
+-import sys
+-import subprocess
+-import runxpcshelltests as xpcshell
+-import tempfile
+-import time
+-from argparse import Namespace
+-from zipfile import ZipFile
+-from mozlog import commandline
+-import shutil
+-import mozdevice
+-import mozfile
+-import mozinfo
+-
+-from xpcshellcommandline import parser_remote
+-
+-here = os.path.dirname(os.path.abspath(__file__))
+-
+-
+-def remoteJoin(path1, path2):
+-    return posixpath.join(path1, path2)
+-
+-
+-class RemoteXPCShellTestThread(xpcshell.XPCShellTestThread):
+-    def __init__(self, *args, **kwargs):
+-        xpcshell.XPCShellTestThread.__init__(self, *args, **kwargs)
+-
+-        self.shellReturnCode = None
+-        # embed the mobile params from the harness into the TestThread
+-        mobileArgs = kwargs.get('mobileArgs')
+-        for key in mobileArgs:
+-            setattr(self, key, mobileArgs[key])
+-
+-    def buildCmdTestFile(self, name):
+-        remoteDir = self.remoteForLocal(os.path.dirname(name))
+-        if remoteDir == self.remoteHere:
+-            remoteName = os.path.basename(name)
+-        else:
+-            remoteName = remoteJoin(remoteDir, os.path.basename(name))
+-        return ['-e', 'const _TEST_FILE = ["%s"];' %
+-                remoteName.replace('\\', '/')]
+-
+-    def remoteForLocal(self, local):
+-        for mapping in self.pathMapping:
+-            if (os.path.abspath(mapping.local) == os.path.abspath(local)):
+-                return mapping.remote
+-        return local
+-
+-    def setupTempDir(self):
+-        # make sure the temp dir exists
+-        self.clearRemoteDir(self.remoteTmpDir)
+-        # env var is set in buildEnvironment
+-        return self.remoteTmpDir
+-
+-    def setupProfileDir(self):
+-        self.clearRemoteDir(self.profileDir)
+-        if self.interactive or self.singleFile:
+-            self.log.info("profile dir is %s" % self.profileDir)
+-        return self.profileDir
+-
+-    def setupMozinfoJS(self):
+-        local = tempfile.mktemp()
+-        mozinfo.output_to_file(local)
+-        mozInfoJSPath = remoteJoin(self.profileDir, "mozinfo.json")
+-        self.device.pushFile(local, mozInfoJSPath)
+-        os.remove(local)
+-        return mozInfoJSPath
+-
+-    def logCommand(self, name, completeCmd, testdir):
+-        self.log.info("%s | full command: %r" % (name, completeCmd))
+-        self.log.info("%s | current directory: %r" % (name, self.remoteHere))
+-        self.log.info("%s | environment: %s" % (name, self.env))
+-
+-    def getHeadFiles(self, test):
+-        """Override parent method to find files on remote device.
+-
+-        Obtains lists of head- files.  Returns a list of head files.
+-        """
+-        def sanitize_list(s, kind):
+-            for f in s.strip().split(' '):
+-                f = f.strip()
+-                if len(f) < 1:
+-                    continue
+-
+-                path = remoteJoin(self.remoteHere, f)
+-
+-                # skip check for file existence: the convenience of discovering
+-                # a missing file does not justify the time cost of the round trip
+-                # to the device
+-                yield path
+-
+-        self.remoteHere = self.remoteForLocal(test['here'])
+-
+-        headlist = test.get('head', '')
+-        return list(sanitize_list(headlist, 'head'))
+-
+-    def buildXpcsCmd(self):
+-        # change base class' paths to remote paths and use base class to build command
+-        self.xpcshell = remoteJoin(self.remoteBinDir, "xpcw")
+-        self.headJSPath = remoteJoin(self.remoteScriptsDir, 'head.js')
+-        self.httpdJSPath = remoteJoin(self.remoteComponentsDir, 'httpd.js')
+-        self.httpdManifest = remoteJoin(self.remoteComponentsDir, 'httpd.manifest')
+-        self.testingModulesDir = self.remoteModulesDir
+-        self.testharnessdir = self.remoteScriptsDir
+-        xpcshell.XPCShellTestThread.buildXpcsCmd(self)
+-        # remove "-g <dir> -a <dir>" and add "--greomni <apk>"
+-        del(self.xpcsCmd[1:5])
+-        if self.options['localAPK']:
+-            self.xpcsCmd.insert(3, '--greomni')
+-            self.xpcsCmd.insert(4, self.remoteAPK)
+-
+-        if self.remoteDebugger:
+-            # for example, "/data/local/gdbserver" "localhost:12345"
+-            self.xpcsCmd = [
+-              self.remoteDebugger,
+-              self.remoteDebuggerArgs,
+-              self.xpcsCmd]
+-
+-    def killTimeout(self, proc):
+-        self.kill(proc)
+-
+-    def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
+-        self.timedout = False
+-        cmd.insert(1, self.remoteHere)
+-        outputFile = "xpcshelloutput"
+-        with open(outputFile, 'w+') as f:
+-            try:
+-                self.shellReturnCode = self.device.shell(cmd, f, timeout=timeout+10)
+-            except mozdevice.DMError as e:
+-                if self.timedout:
+-                    # If the test timed out, there is a good chance the device
+-                    # manager also timed out and raised DMError.
+-                    # Ignore the DMError to simplify the error report.
+-                    self.shellReturnCode = None
+-                    pass
+-                else:
+-                    raise e
+-        # The device manager may have timed out waiting for xpcshell.
+-        # Guard against an accumulation of hung processes by killing
+-        # them here. Note also that IPC tests may spawn new instances
+-        # of xpcshell.
+-        self.device.killProcess("xpcshell")
+-        return outputFile
+-
+-    def checkForCrashes(self,
+-                        dump_directory,
+-                        symbols_path,
+-                        test_name=None):
+-        if not self.device.dirExists(self.remoteMinidumpDir):
+-            # The minidumps directory is automatically created when Fennec
+-            # (first) starts, so its lack of presence is a hint that
+-            # something went wrong.
+-            print("Automation Error: No crash directory (%s) found on remote device" %
+-                  self.remoteMinidumpDir)
+-            # Whilst no crash was found, the run should still display as a failure
+-            return True
+-        with mozfile.TemporaryDirectory() as dumpDir:
+-            self.device.getDirectory(self.remoteMinidumpDir, dumpDir)
+-            crashed = xpcshell.XPCShellTestThread.checkForCrashes(
+-                  self, dumpDir, symbols_path, test_name)
+-            self.clearRemoteDir(self.remoteMinidumpDir)
+-        return crashed
+-
+-    def communicate(self, proc):
+-        f = open(proc, "r")
+-        contents = f.read()
+-        f.close()
+-        os.remove(proc)
+-        return contents, ""
+-
+-    def poll(self, proc):
+-        if self.device.processExist("xpcshell") is None:
+-            return self.getReturnCode(proc)
+-        # Process is still running
+-        return None
+-
+-    def kill(self, proc):
+-        return self.device.killProcess("xpcshell", True)
+-
+-    def getReturnCode(self, proc):
+-        if self.shellReturnCode is not None:
+-            return self.shellReturnCode
+-        else:
+-            return -1
+-
+-    def removeDir(self, dirname):
+-        self.device.removeDir(dirname)
+-
+-    def clearRemoteDir(self, remoteDir):
+-        out = ""
+-        try:
+-            out = self.device.shellCheckOutput([self.remoteClearDirScript, remoteDir])
+-        except mozdevice.DMError:
+-            self.log.info("unable to delete %s: '%s'" % (remoteDir, str(out)))
+-            self.log.info("retrying after 10 seconds...")
+-            time.sleep(10)
+-            try:
+-                out = self.device.shellCheckOutput([self.remoteClearDirScript, remoteDir])
+-            except mozdevice.DMError:
+-                self.log.error("failed to delete %s: '%s'" % (remoteDir, str(out)))
+-
+-    # TODO: consider creating a separate log dir.  We don't have the test file structure,
+-    # so we use filename.log.  Would rather see ./logs/filename.log
+-    def createLogFile(self, test, stdout):
+-        try:
+-            f = None
+-            filename = test.replace('\\', '/').split('/')[-1] + ".log"
+-            f = open(filename, "w")
+-            f.write(stdout)
+-
+-        finally:
+-            if f is not None:
+-                f.close()
+-
+-
+-# A specialization of XPCShellTests that runs tests on an Android device
+-# via devicemanager.
+-class XPCShellRemote(xpcshell.XPCShellTests, object):
+-
+-    def __init__(self, devmgr, options, log):
+-        xpcshell.XPCShellTests.__init__(self, log)
+-
+-        # Add Android version (SDK level) to mozinfo so that manifest entries
+-        # can be conditional on android_version.
+-        mozinfo.info['android_version'] = self.device.version
+-
+-        self.localLib = options['localLib']
+-        self.localBin = options['localBin']
+-        self.options = options
+-        self.device = devmgr
+-        self.pathMapping = []
+-        self.remoteTestRoot = "%s/xpc" % self.device.deviceRoot
+-        # remoteBinDir contains xpcshell and its wrapper script, both of which must
+-        # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
+-        # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
+-        # /data/local, always.
+-        self.remoteBinDir = "/data/local/xpcb"
+-        # Terse directory names are used here ("c" for the components directory)
+-        # to minimize the length of the command line used to execute
+-        # xpcshell on the remote device. adb has a limit to the number
+-        # of characters used in a shell command, and the xpcshell command
+-        # line can be quite complex.
+-        self.remoteTmpDir = remoteJoin(self.remoteTestRoot, "tmp")
+-        self.remoteScriptsDir = self.remoteTestRoot
+-        self.remoteComponentsDir = remoteJoin(self.remoteTestRoot, "c")
+-        self.remoteModulesDir = remoteJoin(self.remoteTestRoot, "m")
+-        self.remoteMinidumpDir = remoteJoin(self.remoteTestRoot, "minidumps")
+-        self.remoteClearDirScript = remoteJoin(self.remoteBinDir, "cleardir")
+-        self.profileDir = remoteJoin(self.remoteTestRoot, "p")
+-        self.remoteDebugger = options['debugger']
+-        self.remoteDebuggerArgs = options['debuggerArgs']
+-        self.testingModulesDir = options['testingModulesDir']
+-
+-        self.env = {}
+-
+-        if options['objdir']:
+-            self.xpcDir = os.path.join(options['objdir'], "_tests/xpcshell")
+-        elif os.path.isdir(os.path.join(here, 'tests')):
+-            self.xpcDir = os.path.join(here, 'tests')
+-        else:
+-            print("Couldn't find local xpcshell test directory", file=sys.stderr)
+-            sys.exit(1)
+-
+-        if options['localAPK']:
+-            self.localAPKContents = ZipFile(options['localAPK'])
+-        if options['setup']:
+-            self.setupTestDir()
+-            self.setupUtilities()
+-            self.setupModules()
+-        self.setupMinidumpDir()
+-        self.remoteAPK = None
+-        if options['localAPK']:
+-            self.remoteAPK = remoteJoin(self.remoteBinDir, os.path.basename(options['localAPK']))
+-            self.setAppRoot()
+-
+-        # data that needs to be passed to the RemoteXPCShellTestThread
+-        self.mobileArgs = {
+-            'device': self.device,
+-            'remoteBinDir': self.remoteBinDir,
+-            'remoteScriptsDir': self.remoteScriptsDir,
+-            'remoteComponentsDir': self.remoteComponentsDir,
+-            'remoteModulesDir': self.remoteModulesDir,
+-            'options': self.options,
+-            'remoteDebugger': self.remoteDebugger,
+-            'pathMapping': self.pathMapping,
+-            'profileDir': self.profileDir,
+-            'remoteTmpDir': self.remoteTmpDir,
+-            'remoteMinidumpDir': self.remoteMinidumpDir,
+-            'remoteClearDirScript': self.remoteClearDirScript,
+-        }
+-        if self.remoteAPK:
+-            self.mobileArgs['remoteAPK'] = self.remoteAPK
+-
+-    def setLD_LIBRARY_PATH(self):
+-        self.env["LD_LIBRARY_PATH"] = self.remoteBinDir
+-
+-    def pushWrapper(self):
+-        # Rather than executing xpcshell directly, this wrapper script is
+-        # used. By setting environment variables and the cwd in the script,
+-        # the length of the per-test command line is shortened. This is
+-        # often important when using ADB, as there is a limit to the length
+-        # of the ADB command line.
+-        localWrapper = tempfile.mktemp()
+-        f = open(localWrapper, "w")
+-        f.write("#!/system/bin/sh\n")
+-        for envkey, envval in self.env.iteritems():
+-            f.write("export %s=%s\n" % (envkey, envval))
+-        f.writelines([
+-            "cd $1\n",
+-            "echo xpcw: cd $1\n",
+-            "shift\n",
+-            "echo xpcw: xpcshell \"$@\"\n",
+-            "%s/xpcshell \"$@\"\n" % self.remoteBinDir])
+-        f.close()
+-        remoteWrapper = remoteJoin(self.remoteBinDir, "xpcw")
+-        self.device.pushFile(localWrapper, remoteWrapper)
+-        os.remove(localWrapper)
+-
+-        # Removing and re-creating a directory is a common operation which
+-        # can be implemented more efficiently with a shell script.
+-        localWrapper = tempfile.mktemp()
+-        f = open(localWrapper, "w")
+-        # The directory may not exist initially, so rm may fail. 'rm -f' is not
+-        # supported on some Androids. Similarly, 'test' and 'if [ -d ]' are not
+-        # universally available, so we just ignore errors from rm.
+-        f.writelines([
+-            "#!/system/bin/sh\n",
+-            "rm -r \"$1\"\n",
+-            "mkdir \"$1\"\n"])
+-        f.close()
+-        self.device.pushFile(localWrapper, self.remoteClearDirScript)
+-        os.remove(localWrapper)
+-
+-        self.device.chmodDir(self.remoteBinDir)
+-
+-    def buildEnvironment(self):
+-        self.buildCoreEnvironment()
+-        self.setLD_LIBRARY_PATH()
+-        if self.options['localAPK'] and self.appRoot:
+-            self.env["GRE_HOME"] = self.appRoot
+-        self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir
+-        self.env["TMPDIR"] = self.remoteTmpDir
+-        self.env["HOME"] = self.profileDir
+-        self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
+-        self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir
+-        if self.options['setup']:
+-            self.pushWrapper()
+-
+-    def setAppRoot(self):
+-        # Determine the application root directory associated with the package
+-        # name used by the Fennec APK.
+-        self.appRoot = None
+-        packageName = None
+-        if self.options['localAPK']:
+-            try:
+-                packageName = self.localAPKContents.read("package-name.txt")
+-                if packageName:
+-                    self.appRoot = self.device.getAppRoot(packageName.strip())
+-            except Exception as detail:
+-                print("unable to determine app root: " + str(detail))
+-                pass
+-        return None
+-
+-    def setupUtilities(self):
+-        if (not self.device.dirExists(self.remoteBinDir)):
+-            # device.mkDir may fail here where shellCheckOutput may succeed -- see bug 817235
+-            try:
+-                self.device.shellCheckOutput(["mkdir", self.remoteBinDir])
+-            except mozdevice.DMError:
+-                # Might get a permission error; try again as root, if available
+-                self.device.shellCheckOutput(["mkdir", self.remoteBinDir], root=True)
+-                self.device.shellCheckOutput(["chmod", "777", self.remoteBinDir], root=True)
+-
+-        remotePrefDir = remoteJoin(self.remoteBinDir, "defaults/pref")
+-        if (self.device.dirExists(self.remoteTmpDir)):
+-            self.device.removeDir(self.remoteTmpDir)
+-        self.device.mkDir(self.remoteTmpDir)
+-        if (not self.device.dirExists(remotePrefDir)):
+-            self.device.mkDirs(remoteJoin(remotePrefDir, "extra"))
+-        if (not self.device.dirExists(self.remoteScriptsDir)):
+-            self.device.mkDir(self.remoteScriptsDir)
+-        if (not self.device.dirExists(self.remoteComponentsDir)):
+-            self.device.mkDir(self.remoteComponentsDir)
+-
+-        local = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'head.js')
+-        remoteFile = remoteJoin(self.remoteScriptsDir, "head.js")
+-        self.device.pushFile(local, remoteFile)
+-
+-        # The xpcshell binary is required for all tests. Additional binaries
+-        # are required for some tests. This list should be similar to
+-        # TEST_HARNESS_BINS in testing/mochitest/Makefile.in.
+-        binaries = ["xpcshell",
+-                    "ssltunnel",
+-                    "certutil",
+-                    "pk12util",
+-                    "BadCertServer",
+-                    "OCSPStaplingServer",
+-                    "GenerateOCSPResponse",
+-                    "SymantecSanctionsServer"]
+-        for fname in binaries:
+-            local = os.path.join(self.localBin, fname)
+-            if os.path.isfile(local):
+-                print("Pushing %s.." % fname, file=sys.stderr)
+-                remoteFile = remoteJoin(self.remoteBinDir, fname)
+-                self.device.pushFile(local, remoteFile)
+-            else:
+-                print("*** Expected binary %s not found in %s!" %
+-                      (fname, self.localBin), file=sys.stderr)
+-
+-        local = os.path.join(self.localBin, "components/httpd.js")
+-        remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.js")
+-        self.device.pushFile(local, remoteFile)
+-
+-        local = os.path.join(self.localBin, "components/httpd.manifest")
+-        remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.manifest")
+-        self.device.pushFile(local, remoteFile)
+-
+-        local = os.path.join(self.localBin, "components/test_necko.xpt")
+-        remoteFile = remoteJoin(self.remoteComponentsDir, "test_necko.xpt")
+-        self.device.pushFile(local, remoteFile)
+-
+-        if self.options['localAPK']:
+-            remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(self.options['localAPK']))
+-            self.device.pushFile(self.options['localAPK'], remoteFile)
+-
+-        self.pushLibs()
+-
+-    def pushLibs(self):
+-        elfhack = os.path.join(self.localBin, 'elfhack')
+-        if not os.path.exists(elfhack):
+-            elfhack = None
+-        pushed_libs_count = 0
+-        if self.options['localAPK']:
+-            try:
+-                dir = tempfile.mkdtemp()
+-                for info in self.localAPKContents.infolist():
+-                    if info.filename.endswith(".so"):
+-                        print("Pushing %s.." % info.filename, file=sys.stderr)
+-                        remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(info.filename))
+-                        self.localAPKContents.extract(info, dir)
+-                        localFile = os.path.join(dir, info.filename)
+-                        with open(localFile) as f:
+-                            # Decompress xz-compressed file.
+-                            if f.read(5)[1:] == '7zXZ':
+-                                cmd = ['xz', '-df', '--suffix', '.so', localFile]
+-                                subprocess.check_output(cmd)
+-                                # xz strips the ".so" file suffix.
+-                                os.rename(localFile[:-3], localFile)
+-                                # elfhack -r should provide better crash reports
+-                                if elfhack:
+-                                    cmd = [elfhack, '-r', localFile]
+-                                    subprocess.check_output(cmd)
+-                        self.device.pushFile(localFile, remoteFile)
+-                        pushed_libs_count += 1
+-            finally:
+-                shutil.rmtree(dir)
+-            return pushed_libs_count
+-
+-        for file in os.listdir(self.localLib):
+-            if (file.endswith(".so")):
+-                print("Pushing %s.." % file, file=sys.stderr)
+-                if 'libxul' in file:
+-                    print("This is a big file, it could take a while.", file=sys.stderr)
+-                localFile = os.path.join(self.localLib, file)
+-                remoteFile = remoteJoin(self.remoteBinDir, file)
+-                self.device.pushFile(localFile, remoteFile)
+-                pushed_libs_count += 1
+-
+-        # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a"
+-        localArmLib = os.path.join(self.localLib, "lib")
+-        if os.path.exists(localArmLib):
+-            for root, dirs, files in os.walk(localArmLib):
+-                for file in files:
+-                    if (file.endswith(".so")):
+-                        print("Pushing %s.." % file, file=sys.stderr)
+-                        localFile = os.path.join(root, file)
+-                        remoteFile = remoteJoin(self.remoteBinDir, file)
+-                        self.device.pushFile(localFile, remoteFile)
+-                        pushed_libs_count += 1
+-
+-        return pushed_libs_count
+-
+-    def setupModules(self):
+-        if self.testingModulesDir:
+-            self.device.pushDir(self.testingModulesDir, self.remoteModulesDir)
+-
+-    def setupTestDir(self):
+-        print('pushing %s' % self.xpcDir)
+-        try:
+-            # The tests directory can be quite large: 5000 files and growing!
+-            # Sometimes - like on a low-end aws instance running an emulator - the push
+-            # may exceed the default 5 minute timeout, so we increase it here to 10 minutes.
+-            self.device.pushDir(self.xpcDir, self.remoteScriptsDir, timeout=600, retryLimit=10)
+-        except TypeError:
+-            # Foopies have an older mozdevice ver without retryLimit
+-            self.device.pushDir(self.xpcDir, self.remoteScriptsDir)
+-
+-    def setupMinidumpDir(self):
+-        if self.device.dirExists(self.remoteMinidumpDir):
+-            self.device.removeDir(self.remoteMinidumpDir)
+-        self.device.mkDir(self.remoteMinidumpDir)
+-
+-    def buildTestList(self, test_tags=None, test_paths=None, verify=False):
+-        xpcshell.XPCShellTests.buildTestList(
+-            self, test_tags=test_tags, test_paths=test_paths, verify=verify)
+-        uniqueTestPaths = set([])
+-        for test in self.alltests:
+-            uniqueTestPaths.add(test['here'])
+-        for testdir in uniqueTestPaths:
+-            abbrevTestDir = os.path.relpath(testdir, self.xpcDir)
+-            remoteScriptDir = remoteJoin(self.remoteScriptsDir, abbrevTestDir)
+-            self.pathMapping.append(PathMapping(testdir, remoteScriptDir))
+-
+-
+-def verifyRemoteOptions(parser, options):
+-    if isinstance(options, Namespace):
+-        options = vars(options)
+-
+-    if options['localLib'] is None:
+-        if options['localAPK'] and options['objdir']:
+-            for path in ['dist/fennec', 'fennec/lib']:
+-                options['localLib'] = os.path.join(options['objdir'], path)
+-                if os.path.isdir(options['localLib']):
+-                    break
+-            else:
+-                parser.error("Couldn't find local library dir, specify --local-lib-dir")
+-        elif options['objdir']:
+-            options['localLib'] = os.path.join(options['objdir'], 'dist/bin')
+-        elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
+-            # assume tests are being run from a tests.zip
+-            options['localLib'] = os.path.abspath(os.path.join(here, '..', 'bin'))
+-        else:
+-            parser.error("Couldn't find local library dir, specify --local-lib-dir")
+-
+-    if options['localBin'] is None:
+-        if options['objdir']:
+-            for path in ['dist/bin', 'bin']:
+-                options['localBin'] = os.path.join(options['objdir'], path)
+-                if os.path.isdir(options['localBin']):
+-                    break
+-            else:
+-                parser.error("Couldn't find local binary dir, specify --local-bin-dir")
+-        elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
+-            # assume tests are being run from a tests.zip
+-            options['localBin'] = os.path.abspath(os.path.join(here, '..', 'bin'))
+-        else:
+-            parser.error("Couldn't find local binary dir, specify --local-bin-dir")
+-    return options
+-
+-
+-class PathMapping:
+-
+-    def __init__(self, localDir, remoteDir):
+-        self.local = localDir
+-        self.remote = remoteDir
+-
+-
+-def main():
+-    if sys.version_info < (2, 7):
+-        print("Error: You must use python version 2.7 or newer but less than 3.0", file=sys.stderr)
+-        sys.exit(1)
+-
+-    parser = parser_remote()
+-    options = parser.parse_args()
+-    if not options.localAPK:
+-        for file in os.listdir(os.path.join(options.objdir, "dist")):
+-            if (file.endswith(".apk") and file.startswith("fennec")):
+-                options.localAPK = os.path.join(options.objdir, "dist")
+-                options.localAPK = os.path.join(options.localAPK, file)
+-                print("using APK: " + options.localAPK, file=sys.stderr)
+-                break
+-        else:
+-            print("Error: please specify an APK", file=sys.stderr)
+-            sys.exit(1)
+-
+-    options = verifyRemoteOptions(parser, options)
+-    log = commandline.setup_logging("Remote XPCShell",
+-                                    options,
+-                                    {"tbpl": sys.stdout})
+-
+-    dm_args = {'deviceRoot': options['remoteTestRoot']}
+-    if options['deviceIP']:
+-        dm_args['host'] = options['deviceIP']
+-        dm_args['port'] = options['devicePort']
+-    if options['log_tbpl_level'] == 'debug' or options['log_mach_level'] == 'debug':
+-        dm_args['logLevel'] = logging.DEBUG
+-    dm = mozdevice.DroidADB(**dm_args)
+-
+-    if options['interactive'] and not options['testPath']:
+-        print("Error: You must specify a test filename in interactive mode!", file=sys.stderr)
+-        sys.exit(1)
+-
+-    if options['xpcshell'] is None:
+-        options['xpcshell'] = "xpcshell"
+-
+-    xpcsh = XPCShellRemote(dm, options, log)
+-
+-    # we don't run concurrent tests on mobile
+-    options['sequential'] = True
+-
+-    if not xpcsh.runTests(options,
+-                          testClass=RemoteXPCShellTestThread,
+-                          mobileArgs=xpcsh.mobileArgs):
+-        sys.exit(1)
+-
+-
+-if __name__ == '__main__':
+-    main()

+ 2781 - 0
mozilla-release/patches/TOP-1906540-mozrunner-devices-removal-25320.patch

@@ -0,0 +1,2781 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1720364499 -3600
+# Parent  6dbc703f582441ac9908b5136433b9add4b157a1
+Bug 1906540 - Remove mozrunner devices and mozdevice from SeaMonkey - mozrunner part. r=frg a=frg
+
+diff --git a/testing/mozbase/docs/mozrunner.rst b/testing/mozbase/docs/mozrunner.rst
+--- a/testing/mozbase/docs/mozrunner.rst
++++ b/testing/mozbase/docs/mozrunner.rst
+@@ -102,43 +102,16 @@ gecko process.
+ 
+     waiting = 0
+     while runner.wait(timeout=1) is None:
+         waiting += 1
+         print("Been waiting for %d seconds so far.." % waiting)
+     assert waiting <= 100
+ 
+ 
+-Using a device runner
+----------------------
+-
+-The previous examples used a GeckoRuntimeRunner. If you want to control a
+-gecko process on a remote device, you need to use a DeviceRunner. The api is
+-nearly identical except you don't pass in a binary, instead you create a device
+-object. For example to run Firefox for Android on the emulator, you might do:
+-
+-.. code-block:: python
+-
+-    from mozrunner import FennecEmulatorRunner
+-
+-    avd_home = 'path/to/avd'
+-    runner = FennecEmulatorRunner(app='org.mozilla.fennec', avd_home=avd_home)
+-    runner.start()
+-    runner.wait()
+-
+-Device runners have a `device` object. Remember that the gecko process runs on
+-the device. In the case of the emulator, it is possible to start the
+-device independently of the gecko process.
+-
+-.. code-block:: python
+-
+-    runner.device.start() # launches the emulator
+-    runner.start()        # stops the gecko process (if started), installs the profile, (re)starts the gecko process
+-
+-
+ Runner API Documentation
+ ------------------------
+ 
+ Application Runners
+ ~~~~~~~~~~~~~~~~~~~
+ .. automodule:: mozrunner.runners
+    :members:
+ 
+@@ -153,31 +126,8 @@ GeckoRuntimeRunner
+    :show-inheritance:
+    :members:
+ 
+ BlinkRuntimeRunner
+ ~~~~~~~~~~~~~~~~~~
+ .. autoclass:: mozrunner.base.BlinkRuntimeRunner
+    :show-inheritance:
+    :members:
+-
+-DeviceRunner
+-~~~~~~~~~~~~
+-.. autoclass:: mozrunner.base.DeviceRunner
+-   :show-inheritance:
+-   :members:
+-
+-Device API Documentation
+-------------------------
+-
+-Generally using the device classes directly shouldn't be required, but in some
+-cases it may be desirable.
+-
+-Device
+-~~~~~~
+-.. autoclass:: mozrunner.devices.Device
+-   :members:
+-
+-EmulatorAVD
+-~~~~~~~~~~~
+-.. autoclass:: mozrunner.devices.EmulatorAVD
+-   :show-inheritance:
+-   :members:
+diff --git a/testing/mozbase/mozrunner/mozrunner/__init__.py b/testing/mozbase/mozrunner/mozrunner/__init__.py
+--- a/testing/mozbase/mozrunner/mozrunner/__init__.py
++++ b/testing/mozbase/mozrunner/mozrunner/__init__.py
+@@ -5,10 +5,9 @@
+ 
+ from __future__ import absolute_import
+ 
+ from .cli import *
+ from .errors import *
+ from .runners import *
+ 
+ import mozrunner.base
+-import mozrunner.devices
+ import mozrunner.utils
+diff --git a/testing/mozbase/mozrunner/mozrunner/application.py b/testing/mozbase/mozrunner/mozrunner/application.py
+--- a/testing/mozbase/mozrunner/mozrunner/application.py
++++ b/testing/mozbase/mozrunner/mozrunner/application.py
+@@ -4,130 +4,42 @@
+ 
+ from __future__ import absolute_import
+ 
+ from abc import ABCMeta, abstractmethod
+ from distutils.spawn import find_executable
+ import os
+ import posixpath
+ 
+-from mozdevice import ADBAndroid
+ from mozprofile import (
+     Profile,
+     ChromeProfile,
+     FirefoxProfile,
+     ThunderbirdProfile
+ )
+ 
+ here = os.path.abspath(os.path.dirname(__file__))
+ 
+ 
+ def get_app_context(appname):
+     context_map = {
+         'chrome': ChromeContext,
+         'default': DefaultContext,
+-        'fennec': FennecContext,
+         'firefox': FirefoxContext,
+         'thunderbird': ThunderbirdContext,
+     }
+     if appname not in context_map:
+         raise KeyError("Application '%s' not supported!" % appname)
+     return context_map[appname]
+ 
+ 
+ class DefaultContext(object):
+     profile_class = Profile
+ 
+ 
+-class RemoteContext(object):
+-    __metaclass__ = ABCMeta
+-    device = None
+-    _remote_profile = None
+-    _adb = None
+-    profile_class = Profile
+-    _bindir = None
+-    remote_test_root = ''
+-    remote_process = None
+-
+-    @property
+-    def bindir(self):
+-        if self._bindir is None:
+-            paths = [find_executable('emulator')]
+-            paths = [p for p in paths if p is not None if os.path.isfile(p)]
+-            if not paths:
+-                self._bindir = ''
+-            else:
+-                self._bindir = os.path.dirname(paths[0])
+-        return self._bindir
+-
+-    @property
+-    def adb(self):
+-        if not self._adb:
+-            paths = [os.environ.get('ADB'),
+-                     os.environ.get('ADB_PATH'),
+-                     self.which('adb')]
+-            paths = [p for p in paths if p is not None if os.path.isfile(p)]
+-            if not paths:
+-                raise OSError(
+-                    'Could not find the adb binary, make sure it is on your'
+-                    'path or set the $ADB_PATH environment variable.')
+-            self._adb = paths[0]
+-        return self._adb
+-
+-    @property
+-    def remote_profile(self):
+-        if not self._remote_profile:
+-            self._remote_profile = posixpath.join(self.remote_test_root,
+-                                                  'profile')
+-        return self._remote_profile
+-
+-    def which(self, binary):
+-        paths = os.environ.get('PATH', {}).split(os.pathsep)
+-        if self.bindir is not None and os.path.abspath(self.bindir) not in paths:
+-            paths.insert(0, os.path.abspath(self.bindir))
+-            os.environ['PATH'] = os.pathsep.join(paths)
+-
+-        return find_executable(binary)
+-
+-    @abstractmethod
+-    def stop_application(self):
+-        """ Run (device manager) command to stop application. """
+-        pass
+-
+-
+-class FennecContext(RemoteContext):
+-    _remote_profiles_ini = None
+-    _remote_test_root = None
+-
+-    def __init__(self, app=None, adb_path=None, avd_home=None, device_serial=None):
+-        self._adb = adb_path
+-        self.avd_home = avd_home
+-        self.remote_process = app
+-        self.device_serial = device_serial
+-        self.device = ADBAndroid(adb=self.adb, device=device_serial)
+-
+-    def stop_application(self):
+-        self.device.stop_application(self.remote_process)
+-
+-    @property
+-    def remote_test_root(self):
+-        if not self._remote_test_root:
+-            self._remote_test_root = self.device.test_root
+-        return self._remote_test_root
+-
+-    @property
+-    def remote_profiles_ini(self):
+-        if not self._remote_profiles_ini:
+-            self._remote_profiles_ini = posixpath.join(
+-                '/data', 'data', self.remote_process,
+-                'files', 'mozilla', 'profiles.ini'
+-            )
+-        return self._remote_profiles_ini
+-
+-
+ class FirefoxContext(object):
+     profile_class = FirefoxProfile
+ 
+ 
+ class ThunderbirdContext(object):
+     profile_class = ThunderbirdProfile
+ 
+ 
+diff --git a/testing/mozbase/mozrunner/mozrunner/base/__init__.py b/testing/mozbase/mozrunner/mozrunner/base/__init__.py
+--- a/testing/mozbase/mozrunner/mozrunner/base/__init__.py
++++ b/testing/mozbase/mozrunner/mozrunner/base/__init__.py
+@@ -1,6 +1,5 @@
+ # flake8: noqa
+ from __future__ import absolute_import
+ 
+ from .runner import BaseRunner
+-from .device import DeviceRunner, FennecRunner
+ from .browser import GeckoRuntimeRunner, BlinkRuntimeRunner
+diff --git a/testing/mozbase/mozrunner/mozrunner/base/device.py b/testing/mozbase/mozrunner/mozrunner/base/device.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/base/device.py
++++ /dev/null
+@@ -1,190 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import, print_function
+-
+-import codecs
+-import datetime
+-import re
+-import signal
+-import six
+-import sys
+-import tempfile
+-import time
+-
+-import mozfile
+-
+-from .runner import BaseRunner
+-from ..devices import BaseEmulator
+-
+-
+-class DeviceRunner(BaseRunner):
+-    """
+-    The base runner class used for running gecko on
+-    remote devices (or emulators), such as B2G.
+-    """
+-    env = {'MOZ_CRASHREPORTER': '1',
+-           'MOZ_CRASHREPORTER_NO_REPORT': '1',
+-           'MOZ_CRASHREPORTER_SHUTDOWN': '1',
+-           'MOZ_HIDE_RESULTS_TABLE': '1',
+-           'MOZ_LOG': 'signaling:3,mtransport:4,DataChannel:4,jsep:4,MediaPipelineFactory:4',
+-           'R_LOG_LEVEL': '6',
+-           'R_LOG_DESTINATION': 'stderr',
+-           'R_LOG_VERBOSE': '1', }
+-
+-    def __init__(self, device_class, device_args=None, **kwargs):
+-        process_log = tempfile.NamedTemporaryFile(suffix='pidlog')
+-        # the env will be passed to the device, it is not a *real* env
+-        self._device_env = dict(DeviceRunner.env)
+-        self._device_env['MOZ_PROCESS_LOG'] = process_log.name
+-        # be sure we do not pass env to the parent class ctor
+-        env = kwargs.pop('env', None)
+-        if env:
+-            self._device_env.update(env)
+-
+-        if six.PY2:
+-            stdout = codecs.getwriter('utf-8')(sys.stdout)
+-        else:
+-            stdout = codecs.getwriter('utf-8')(sys.stdout.buffer)
+-        process_args = {'stream': stdout,
+-                        'processOutputLine': self.on_output,
+-                        'onFinish': self.on_finish,
+-                        'onTimeout': self.on_timeout}
+-        process_args.update(kwargs.get('process_args') or {})
+-
+-        kwargs['process_args'] = process_args
+-        BaseRunner.__init__(self, **kwargs)
+-
+-        device_args = device_args or {}
+-        self.device = device_class(**device_args)
+-
+-    @property
+-    def command(self):
+-        cmd = [self.app_ctx.adb]
+-        if self.app_ctx.dm._deviceSerial:
+-            cmd.extend(['-s', self.app_ctx.dm._deviceSerial])
+-        cmd.append('shell')
+-        for k, v in self._device_env.iteritems():
+-            cmd.append('%s=%s' % (k, v))
+-        cmd.append(self.app_ctx.remote_binary)
+-        return cmd
+-
+-    def start(self, *args, **kwargs):
+-        if isinstance(self.device, BaseEmulator) and not self.device.connected:
+-            self.device.start()
+-        self.device.connect()
+-        self.device.setup_profile(self.profile)
+-
+-        # TODO: this doesn't work well when the device is running but dropped
+-        # wifi for some reason. It would be good to probe the state of the device
+-        # to see if we have the homescreen running, or something, before waiting here
+-        self.device.wait_for_net()
+-
+-        if not self.device.wait_for_net():
+-            raise Exception("Network did not come up when starting device")
+-
+-        pid = BaseRunner.start(self, *args, **kwargs)
+-
+-        timeout = 10  # seconds
+-        starttime = datetime.datetime.now()
+-        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
+-            if self.is_running():
+-                break
+-            time.sleep(1)
+-        else:
+-            print("timed out waiting for '%s' process to start" % self.app_ctx.remote_process)
+-
+-        if not self.device.wait_for_net():
+-            raise Exception("Failed to get a network connection")
+-        return pid
+-
+-    def stop(self, sig=None):
+-        def _wait_for_shutdown(pid, timeout=10):
+-            start_time = datetime.datetime.now()
+-            end_time = datetime.timedelta(seconds=timeout)
+-            while datetime.datetime.now() - start_time < end_time:
+-                if self.is_running() != pid:
+-                    return True
+-                time.sleep(1)
+-            return False
+-
+-        remote_pid = self.is_running()
+-        if remote_pid:
+-            self.app_ctx.dm.killProcess(
+-                self.app_ctx.remote_process, sig=sig)
+-            if not _wait_for_shutdown(remote_pid) and sig is not None:
+-                print("timed out waiting for '%s' process to exit, trying "
+-                      "without signal {}".format(
+-                          self.app_ctx.remote_process, sig))
+-
+-            # need to call adb stop otherwise the system will attempt to
+-            # restart the process
+-            remote_pid = self.is_running() or remote_pid
+-            self.app_ctx.stop_application()
+-            if not _wait_for_shutdown(remote_pid):
+-                print("timed out waiting for '%s' process to exit".format(
+-                    self.app_ctx.remote_process))
+-
+-    def is_running(self):
+-        return self.app_ctx.dm.processExist(self.app_ctx.remote_process)
+-
+-    def on_output(self, line):
+-        match = re.findall(r"TEST-START \| ([^\s]*)", line)
+-        if match:
+-            self.last_test = match[-1]
+-
+-    def on_timeout(self):
+-        self.stop(sig=signal.SIGABRT)
+-        msg = "DeviceRunner TEST-UNEXPECTED-FAIL | %s | application timed out after %s seconds"
+-        if self.timeout:
+-            timeout = self.timeout
+-        else:
+-            timeout = self.output_timeout
+-            msg = "%s with no output" % msg
+-
+-        print(msg % (self.last_test, timeout))
+-        self.check_for_crashes()
+-
+-    def on_finish(self):
+-        self.check_for_crashes()
+-
+-    def check_for_crashes(self, dump_save_path=None, test_name=None, **kwargs):
+-        test_name = test_name or self.last_test
+-        dump_dir = self.device.pull_minidumps()
+-        crashed = BaseRunner.check_for_crashes(
+-            self,
+-            dump_directory=dump_dir,
+-            dump_save_path=dump_save_path,
+-            test_name=test_name,
+-            **kwargs)
+-        mozfile.remove(dump_dir)
+-        return crashed
+-
+-    def cleanup(self, *args, **kwargs):
+-        BaseRunner.cleanup(self, *args, **kwargs)
+-        self.device.cleanup()
+-
+-
+-class FennecRunner(DeviceRunner):
+-
+-    def __init__(self, cmdargs=None, **kwargs):
+-        super(FennecRunner, self).__init__(**kwargs)
+-        self.cmdargs = cmdargs or []
+-
+-    @property
+-    def command(self):
+-        cmd = [self.app_ctx.adb]
+-        if self.app_ctx.dm._deviceSerial:
+-            cmd.extend(["-s", self.app_ctx.dm._deviceSerial])
+-        cmd.append("shell")
+-        app = "%s/org.mozilla.gecko.BrowserApp" % self.app_ctx.remote_process
+-        am_subcommand = ["am", "start", "-a", "android.activity.MAIN", "-n", app]
+-        app_params = ["-no-remote", "-profile", self.app_ctx.remote_profile]
+-        app_params.extend(self.cmdargs)
+-        am_subcommand.extend(["--es", "args", "'%s'" % " ".join(app_params)])
+-        # Append env variables in the form |--es env0 MOZ_CRASHREPORTER=1|
+-        for (count, (k, v)) in enumerate(self._device_env.iteritems()):
+-            am_subcommand.extend(["--es", "env%d" % count, "%s=%s" % (k, v)])
+-        cmd.append("%s" % " ".join(am_subcommand))
+-        return cmd
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/__init__.py b/testing/mozbase/mozrunner/mozrunner/devices/__init__.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/__init__.py
++++ /dev/null
+@@ -1,15 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-from .emulator import BaseEmulator, Emulator, EmulatorAVD
+-from .base import Device
+-
+-from mozrunner.devices import emulator_battery
+-from mozrunner.devices import emulator_geo
+-from mozrunner.devices import emulator_screen
+-
+-__all__ = ['BaseEmulator', 'Emulator', 'EmulatorAVD', 'Device',
+-           'emulator_battery', 'emulator_geo', 'emulator_screen']
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/android_device.py b/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
++++ /dev/null
+@@ -1,810 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import, print_function
+-
+-import fileinput
+-import glob
+-import os
+-import platform
+-import psutil
+-import shutil
+-import signal
+-import sys
+-import telnetlib
+-import time
+-import urlparse
+-import urllib2
+-from distutils.spawn import find_executable
+-
+-from mozdevice import DeviceManagerADB, DMError
+-from mozprocess import ProcessHandler
+-
+-EMULATOR_HOME_DIR = os.path.join(os.path.expanduser('~'), '.mozbuild', 'android-device')
+-
+-EMULATOR_AUTH_FILE = os.path.join(os.path.expanduser('~'), '.emulator_console_auth_token')
+-
+-TOOLTOOL_URL = 'https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py'
+-
+-TRY_URL = 'https://hg.mozilla.org/try/raw-file/default'
+-
+-MANIFEST_PATH = 'testing/config/tooltool-manifests'
+-
+-verbose_logging = False
+-
+-
+-class AvdInfo(object):
+-    """
+-       Simple class to contain an AVD description.
+-    """
+-
+-    def __init__(self, description, name, tooltool_manifest, extra_args,
+-                 x86):
+-        self.description = description
+-        self.name = name
+-        self.tooltool_manifest = tooltool_manifest
+-        self.extra_args = extra_args
+-        self.x86 = x86
+-
+-
+-"""
+-   A dictionary to map an AVD type to a description of that type of AVD.
+-
+-   There is one entry for each type of AVD used in Mozilla automated tests
+-   and the parameters for each reflect those used in mozharness.
+-"""
+-AVD_DICT = {
+-    '4.3': AvdInfo('Android 4.3',
+-                   'mozemulator-4.3',
+-                   'testing/config/tooltool-manifests/androidarm_4_3/mach-emulator.manifest',
+-                   ['-show-kernel', '-debug',
+-                    'init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket'],
+-                   False),
+-    '6.0': AvdInfo('Android 6.0',
+-                   'mozemulator-6.0',
+-                   'testing/config/tooltool-manifests/androidarm_6_0/mach-emulator.manifest',
+-                   ['-show-kernel', '-debug',
+-                    'init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket'],
+-                   False),
+-    '7.0': AvdInfo('Android 7.0',
+-                   'mozemulator-7.0',
+-                   'testing/config/tooltool-manifests/androidarm_7_0/mach-emulator.manifest',
+-                   ['-debug',
+-                    'init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket',
+-                    '-ranchu',
+-                    '-qemu', '-m', '2048'],
+-                   False),
+-    'x86': AvdInfo('Android 4.2 x86',
+-                   'mozemulator-x86',
+-                   'testing/config/tooltool-manifests/androidx86/mach-emulator.manifest',
+-                   ['-debug',
+-                    'init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket',
+-                    '-qemu', '-m', '1024', '-enable-kvm'],
+-                   True),
+-    'x86-6.0': AvdInfo('Android 6.0 x86',
+-                       'mozemulator-x86-6.0',
+-                       'testing/config/tooltool-manifests/androidx86_6_0/mach-emulator.manifest',
+-                       ['-debug',
+-                        'init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket',
+-                        '-ranchu',
+-                        '-qemu', '-m', '2048'],
+-                       True)
+-}
+-
+-
+-def verify_android_device(build_obj, install=False, xre=False, debugger=False, verbose=False):
+-    """
+-       Determine if any Android device is connected via adb.
+-       If no device is found, prompt to start an emulator.
+-       If a device is found or an emulator started and 'install' is
+-       specified, also check whether Firefox is installed on the
+-       device; if not, prompt to install Firefox.
+-       If 'xre' is specified, also check with MOZ_HOST_BIN is set
+-       to a valid xre/host-utils directory; if not, prompt to set
+-       one up.
+-       If 'debugger' is specified, also check that JimDB is installed;
+-       if JimDB is not found, prompt to set up JimDB.
+-       Returns True if the emulator was started or another device was
+-       already connected.
+-    """
+-    device_verified = False
+-    emulator = AndroidEmulator('*', substs=build_obj.substs, verbose=verbose)
+-    devices = emulator.dm.devices()
+-    if (len(devices) > 0) and ('device' in [d[1] for d in devices]):
+-        device_verified = True
+-    elif emulator.is_available():
+-        response = raw_input(
+-            "No Android devices connected. Start an emulator? (Y/n) ").strip()
+-        if response.lower().startswith('y') or response == '':
+-            if not emulator.check_avd():
+-                _log_info("Fetching AVD. This may take a while...")
+-                emulator.update_avd()
+-            _log_info("Starting emulator running %s..." %
+-                      emulator.get_avd_description())
+-            emulator.start()
+-            emulator.wait_for_start()
+-            device_verified = True
+-
+-    if device_verified and install:
+-        # Determine if Firefox is installed on the device; if not,
+-        # prompt to install. This feature allows a test command to
+-        # launch an emulator, install Firefox, and proceed with testing
+-        # in one operation. It is also a basic safeguard against other
+-        # cases where testing is requested but Firefox installation has
+-        # been forgotten.
+-        # If Firefox is installed, there is no way to determine whether
+-        # the current build is installed, and certainly no way to
+-        # determine if the installed build is the desired build.
+-        # Installing every time is problematic because:
+-        #  - it prevents testing against other builds (downloaded apk)
+-        #  - installation may take a couple of minutes.
+-        installed = emulator.dm.shellCheckOutput(['pm', 'list',
+-                                                  'packages', 'org.mozilla.'])
+-        if 'fennec' not in installed and 'firefox' not in installed:
+-            response = raw_input(
+-                "It looks like Firefox is not installed on this device.\n"
+-                "Install Firefox? (Y/n) ").strip()
+-            if response.lower().startswith('y') or response == '':
+-                _log_info("Installing Firefox. This may take a while...")
+-                build_obj._run_make(directory=".", target='install',
+-                                    ensure_exit_code=False)
+-
+-    if device_verified and xre:
+-        # Check whether MOZ_HOST_BIN has been set to a valid xre; if not,
+-        # prompt to install one.
+-        xre_path = os.environ.get('MOZ_HOST_BIN')
+-        err = None
+-        if not xre_path:
+-            err = "environment variable MOZ_HOST_BIN is not set to a directory" \
+-                  "containing host xpcshell"
+-        elif not os.path.isdir(xre_path):
+-            err = '$MOZ_HOST_BIN does not specify a directory'
+-        elif not os.path.isfile(os.path.join(xre_path, 'xpcshell')):
+-            err = '$MOZ_HOST_BIN/xpcshell does not exist'
+-        if err:
+-            xre_path = glob.glob(os.path.join(EMULATOR_HOME_DIR, 'host-utils*'))
+-            for path in xre_path:
+-                if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'xpcshell')):
+-                    os.environ['MOZ_HOST_BIN'] = path
+-                    err = None
+-                    break
+-        if err:
+-            _log_info("Host utilities not found: %s" % err)
+-            response = raw_input(
+-                "Download and setup your host utilities? (Y/n) ").strip()
+-            if response.lower().startswith('y') or response == '':
+-                _log_info("Installing host utilities. This may take a while...")
+-                host_platform = _get_host_platform()
+-                if host_platform:
+-                    path = os.path.join(MANIFEST_PATH, host_platform, 'hostutils.manifest')
+-                    _get_tooltool_manifest(build_obj.substs, path, EMULATOR_HOME_DIR,
+-                                           'releng.manifest')
+-                    _tooltool_fetch()
+-                    xre_path = glob.glob(os.path.join(EMULATOR_HOME_DIR, 'host-utils*'))
+-                    for path in xre_path:
+-                        if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'xpcshell')):
+-                            os.environ['MOZ_HOST_BIN'] = path
+-                            err = None
+-                            break
+-                    if err:
+-                        _log_warning("Unable to install host utilities.")
+-                else:
+-                    _log_warning(
+-                        "Unable to install host utilities -- your platform is not supported!")
+-
+-    if debugger:
+-        # Optionally set up JimDB. See https://wiki.mozilla.org/Mobile/Fennec/Android/GDB.
+-        build_platform = _get_device_platform(build_obj.substs)
+-        jimdb_path = os.path.join(EMULATOR_HOME_DIR, 'jimdb-%s' % build_platform)
+-        jimdb_utils_path = os.path.join(jimdb_path, 'utils')
+-        gdb_path = os.path.join(jimdb_path, 'bin', 'gdb')
+-        err = None
+-        if not os.path.isdir(jimdb_path):
+-            err = '%s does not exist' % jimdb_path
+-        elif not os.path.isfile(gdb_path):
+-            err = '%s not found' % gdb_path
+-        if err:
+-            _log_info("JimDB (%s) not found: %s" % (build_platform, err))
+-            response = raw_input(
+-                "Download and setup JimDB (%s)? (Y/n) " % build_platform).strip()
+-            if response.lower().startswith('y') or response == '':
+-                host_platform = _get_host_platform()
+-                if host_platform:
+-                    _log_info(
+-                        "Installing JimDB (%s/%s). This may take a while..." % (host_platform,
+-                                                                                build_platform))
+-                    path = os.path.join(MANIFEST_PATH, host_platform,
+-                                        'jimdb-%s.manifest' % build_platform)
+-                    _get_tooltool_manifest(build_obj.substs, path,
+-                                           EMULATOR_HOME_DIR, 'releng.manifest')
+-                    _tooltool_fetch()
+-                    if os.path.isfile(gdb_path):
+-                        # Get JimDB utilities from git repository
+-                        proc = ProcessHandler(['git', 'pull'], cwd=jimdb_utils_path)
+-                        proc.run()
+-                        git_pull_complete = False
+-                        try:
+-                            proc.wait()
+-                            if proc.proc.returncode == 0:
+-                                git_pull_complete = True
+-                        except Exception:
+-                            if proc.poll() is None:
+-                                proc.kill(signal.SIGTERM)
+-                        if not git_pull_complete:
+-                            _log_warning("Unable to update JimDB utils from git -- "
+-                                         "some JimDB features may be unavailable.")
+-                    else:
+-                        _log_warning("Unable to install JimDB -- unable to fetch from tooltool.")
+-                else:
+-                    _log_warning("Unable to install JimDB -- your platform is not supported!")
+-        if os.path.isfile(gdb_path):
+-            # sync gdbinit.local with build settings
+-            _update_gdbinit(build_obj.substs, os.path.join(jimdb_utils_path, "gdbinit.local"))
+-            # ensure JimDB is in system path, so that mozdebug can find it
+-            bin_path = os.path.join(jimdb_path, 'bin')
+-            os.environ['PATH'] = "%s:%s" % (bin_path, os.environ['PATH'])
+-
+-    return device_verified
+-
+-
+-def run_firefox_for_android(build_obj, params):
+-    """
+-       Launch Firefox for Android on the connected device.
+-       Optional 'params' allow parameters to be passed to Firefox.
+-    """
+-    adb_path = _find_sdk_exe(build_obj.substs, 'adb', False)
+-    if not adb_path:
+-        adb_path = 'adb'
+-    dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1)
+-    try:
+-        #
+-        # Construct an adb command similar to:
+-        #
+-        # $ adb shell am start -a android.activity.MAIN \
+-        #   -n org.mozilla.fennec_$USER \
+-        #   -d <url param> \
+-        #   --es args "<params>"
+-        #
+-        app = "%s/org.mozilla.gecko.BrowserApp" % build_obj.substs['ANDROID_PACKAGE_NAME']
+-        cmd = ['am', 'start', '-a', 'android.activity.MAIN', '-n', app]
+-        if params:
+-            for p in params:
+-                if urlparse.urlparse(p).scheme != "":
+-                    cmd.extend(['-d', p])
+-                    params.remove(p)
+-                    break
+-        if params:
+-            cmd.extend(['--es', 'args', '"%s"' % ' '.join(params)])
+-        _log_debug(cmd)
+-        output = dm.shellCheckOutput(cmd, timeout=10)
+-        _log_info(output)
+-    except DMError:
+-        _log_warning("unable to launch Firefox for Android")
+-        return 1
+-    return 0
+-
+-
+-def grant_runtime_permissions(build_obj):
+-    """
+-    Grant required runtime permissions to the specified app
+-    (typically org.mozilla.fennec_$USER).
+-    """
+-    app = build_obj.substs['ANDROID_PACKAGE_NAME']
+-    adb_path = _find_sdk_exe(build_obj.substs, 'adb', False)
+-    if not adb_path:
+-        adb_path = 'adb'
+-    dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1)
+-    dm.default_timeout = 10
+-    try:
+-        sdk_level = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
+-        if sdk_level and int(sdk_level) >= 23:
+-            _log_info("Granting important runtime permissions to %s" % app)
+-            dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.WRITE_EXTERNAL_STORAGE'])
+-            dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.READ_EXTERNAL_STORAGE'])
+-            dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.ACCESS_FINE_LOCATION'])
+-            dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.CAMERA'])
+-    except DMError:
+-        _log_warning("Unable to grant runtime permissions to %s" % app)
+-
+-
+-class AndroidEmulator(object):
+-
+-    """
+-        Support running the Android emulator with an AVD from Mozilla
+-        test automation.
+-
+-        Example usage:
+-            emulator = AndroidEmulator()
+-            if not emulator.is_running() and emulator.is_available():
+-                if not emulator.check_avd():
+-                    warn("this may take a while...")
+-                    emulator.update_avd()
+-                emulator.start()
+-                emulator.wait_for_start()
+-                emulator.wait()
+-    """
+-
+-    def __init__(self, avd_type='4.3', verbose=False, substs=None, device_serial=None):
+-        global verbose_logging
+-        self.emulator_log = None
+-        self.emulator_path = 'emulator'
+-        verbose_logging = verbose
+-        self.substs = substs
+-        self.avd_type = self._get_avd_type(avd_type)
+-        self.avd_info = AVD_DICT[self.avd_type]
+-        self.gpu = True
+-        self.restarted = False
+-        adb_path = _find_sdk_exe(substs, 'adb', False)
+-        if not adb_path:
+-            adb_path = 'adb'
+-        self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1,
+-                                   deviceSerial=device_serial)
+-        self.dm.default_timeout = 10
+-        _log_debug("Running on %s" % platform.platform())
+-        _log_debug("Emulator created with type %s" % self.avd_type)
+-
+-    def __del__(self):
+-        if self.emulator_log:
+-            self.emulator_log.close()
+-
+-    def is_running(self):
+-        """
+-           Returns True if the Android emulator is running.
+-        """
+-        for proc in psutil.process_iter():
+-            name = proc.name()
+-            # On some platforms, "emulator" may start an emulator with
+-            # process name "emulator64-arm" or similar.
+-            if name and name.startswith('emulator'):
+-                return True
+-        return False
+-
+-    def is_available(self):
+-        """
+-           Returns True if an emulator executable is found.
+-        """
+-        found = False
+-        emulator_path = _find_sdk_exe(self.substs, 'emulator', True)
+-        if emulator_path:
+-            self.emulator_path = emulator_path
+-            found = True
+-        return found
+-
+-    def check_avd(self, force=False):
+-        """
+-           Determine if the AVD is already installed locally.
+-           (This is usually used to determine if update_avd() is likely
+-           to require a download; it is a convenient way of determining
+-           whether a 'this may take a while' warning is warranted.)
+-
+-           Returns True if the AVD is installed.
+-        """
+-        avd = os.path.join(
+-            EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd')
+-        if force and os.path.exists(avd):
+-            shutil.rmtree(avd)
+-        if os.path.exists(avd):
+-            _log_debug("AVD found at %s" % avd)
+-            return True
+-        return False
+-
+-    def update_avd(self, force=False):
+-        """
+-           If required, update the AVD via tooltool.
+-
+-           If the AVD directory is not found, or "force" is requested,
+-           download the tooltool manifest associated with the AVD and then
+-           invoke tooltool.py on the manifest. tooltool.py will download the
+-           required archive (unless already present in the local tooltool
+-           cache) and install the AVD.
+-        """
+-        avd = os.path.join(
+-            EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd')
+-        ini_file = os.path.join(
+-            EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.ini')
+-        if force and os.path.exists(avd):
+-            shutil.rmtree(avd)
+-        if not os.path.exists(avd):
+-            if os.path.exists(ini_file):
+-                os.remove(ini_file)
+-            path = self.avd_info.tooltool_manifest
+-            _get_tooltool_manifest(self.substs, path, EMULATOR_HOME_DIR, 'releng.manifest')
+-            _tooltool_fetch()
+-            self._update_avd_paths()
+-
+-    def start(self):
+-        """
+-           Launch the emulator.
+-        """
+-        if os.path.exists(EMULATOR_AUTH_FILE):
+-            os.remove(EMULATOR_AUTH_FILE)
+-            _log_debug("deleted %s" % EMULATOR_AUTH_FILE)
+-        # create an empty auth file to disable emulator authentication
+-        auth_file = open(EMULATOR_AUTH_FILE, 'w')
+-        auth_file.close()
+-
+-        def outputHandler(line):
+-            self.emulator_log.write("<%s>\n" % line)
+-            if "Invalid value for -gpu" in line or "Invalid GPU mode" in line:
+-                self.gpu = False
+-        env = os.environ
+-        env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd")
+-        command = [self.emulator_path, "-avd",
+-                   self.avd_info.name, "-port", "5554"]
+-        if self.gpu:
+-            command += ['-gpu', 'swiftshader']
+-        if self.avd_info.extra_args:
+-            # -enable-kvm option is not valid on OSX
+-            if _get_host_platform() == 'macosx64' and '-enable-kvm' in self.avd_info.extra_args:
+-                self.avd_info.extra_args.remove('-enable-kvm')
+-            command += self.avd_info.extra_args
+-        log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log')
+-        self.emulator_log = open(log_path, 'w')
+-        _log_debug("Starting the emulator with this command: %s" %
+-                   ' '.join(command))
+-        _log_debug("Emulator output will be written to '%s'" %
+-                   log_path)
+-        self.proc = ProcessHandler(
+-            command, storeOutput=False, processOutputLine=outputHandler,
+-            env=env)
+-        self.proc.run()
+-        _log_debug("Emulator started with pid %d" %
+-                   int(self.proc.proc.pid))
+-
+-    def wait_for_start(self):
+-        """
+-           Verify that the emulator is running, the emulator device is visible
+-           to adb, and Android has booted.
+-        """
+-        if not self.proc:
+-            _log_warning("Emulator not started!")
+-            return False
+-        if self.check_completed():
+-            return False
+-        _log_debug("Waiting for device status...")
+-        while(('emulator-5554', 'device') not in self.dm.devices()):
+-            time.sleep(10)
+-            if self.check_completed():
+-                return False
+-        _log_debug("Device status verified.")
+-
+-        _log_debug("Checking that Android has booted...")
+-        complete = False
+-        while(not complete):
+-            output = ''
+-            try:
+-                output = self.dm.shellCheckOutput(
+-                    ['getprop', 'sys.boot_completed'], timeout=5)
+-            except DMError:
+-                # adb not yet responding...keep trying
+-                pass
+-            if output.strip() == '1':
+-                complete = True
+-            else:
+-                time.sleep(10)
+-                if self.check_completed():
+-                    return False
+-        _log_debug("Android boot status verified.")
+-
+-        if not self._verify_emulator():
+-            return False
+-        if self.avd_info.x86:
+-            _log_info("Running the x86 emulator; be sure to install an x86 APK!")
+-        else:
+-            _log_info("Running the arm emulator; be sure to install an arm APK!")
+-        return True
+-
+-    def check_completed(self):
+-        if self.proc.proc.poll() is not None:
+-            if not self.gpu and not self.restarted:
+-                _log_warning("Emulator failed to start. Your emulator may be out of date.")
+-                _log_warning("Trying to restart the emulator without -gpu argument.")
+-                self.restarted = True
+-                self.start()
+-                return False
+-            _log_warning("Emulator has already completed!")
+-            log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log')
+-            _log_warning("See log at %s and/or use --verbose for more information." % log_path)
+-            return True
+-        return False
+-
+-    def wait(self):
+-        """
+-           Wait for the emulator to close. If interrupted, close the emulator.
+-        """
+-        try:
+-            self.proc.wait()
+-        except Exception:
+-            if self.proc.poll() is None:
+-                self.cleanup()
+-        return self.proc.poll()
+-
+-    def cleanup(self):
+-        """
+-           Close the emulator.
+-        """
+-        self.proc.kill(signal.SIGTERM)
+-
+-    def get_avd_description(self):
+-        """
+-           Return the human-friendly description of this AVD.
+-        """
+-        return self.avd_info.description
+-
+-    def _update_avd_paths(self):
+-        avd_path = os.path.join(EMULATOR_HOME_DIR, "avd")
+-        ini_file = os.path.join(avd_path, "test-1.ini")
+-        ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini")
+-        os.rename(ini_file, ini_file_new)
+-        avd_dir = os.path.join(avd_path, "test-1.avd")
+-        avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd")
+-        os.rename(avd_dir, avd_dir_new)
+-        self._replace_ini_contents(ini_file_new)
+-
+-    def _replace_ini_contents(self, path):
+-        with open(path, "r") as f:
+-            lines = f.readlines()
+-        with open(path, "w") as f:
+-            for line in lines:
+-                if line.startswith('path='):
+-                    avd_path = os.path.join(EMULATOR_HOME_DIR, "avd")
+-                    f.write('path=%s/%s.avd\n' %
+-                            (avd_path, self.avd_info.name))
+-                elif line.startswith('path.rel='):
+-                    f.write('path.rel=avd/%s.avd\n' % self.avd_info.name)
+-                else:
+-                    f.write(line)
+-
+-    def _telnet_cmd(self, telnet, command):
+-        _log_debug(">>> " + command)
+-        telnet.write('%s\n' % command)
+-        result = telnet.read_until('OK', 10)
+-        _log_debug("<<< " + result)
+-        return result
+-
+-    def _verify_emulator(self):
+-        telnet_ok = False
+-        tn = None
+-        while(not telnet_ok):
+-            try:
+-                tn = telnetlib.Telnet('localhost', 5554, 10)
+-                if tn is not None:
+-                    tn.read_until('OK', 10)
+-                    self._telnet_cmd(tn, 'avd status')
+-                    self._telnet_cmd(tn, 'redir list')
+-                    self._telnet_cmd(tn, 'network status')
+-                    tn.write('quit\n')
+-                    tn.read_all()
+-                    telnet_ok = True
+-                else:
+-                    _log_warning("Unable to connect to port 5554")
+-            except Exception:
+-                _log_warning("Trying again after unexpected exception")
+-            finally:
+-                if tn is not None:
+-                    tn.close()
+-            if not telnet_ok:
+-                time.sleep(10)
+-                if self.proc.proc.poll() is not None:
+-                    _log_warning("Emulator has already completed!")
+-                    return False
+-        return telnet_ok
+-
+-    def _get_avd_type(self, requested):
+-        if requested in AVD_DICT.keys():
+-            return requested
+-        if self.substs:
+-            if not self.substs['TARGET_CPU'].startswith('arm'):
+-                return 'x86'
+-        return '4.3'
+-
+-
+-def _find_sdk_exe(substs, exe, tools):
+-    if tools:
+-        subdir = 'tools'
+-    else:
+-        subdir = 'platform-tools'
+-
+-    found = False
+-    if not found and substs:
+-        # It's best to use the tool specified by the build, rather
+-        # than something we find on the PATH or crawl for.
+-        try:
+-            exe_path = substs[exe.upper()]
+-            if os.path.exists(exe_path):
+-                found = True
+-            else:
+-                _log_debug(
+-                    "Unable to find executable at %s" % exe_path)
+-        except KeyError:
+-            _log_debug("%s not set" % exe.upper())
+-
+-    # Append '.exe' to the name on Windows if it's not present,
+-    # so that the executable can be found.
+-    if (os.name == 'nt' and not exe.lower().endswith('.exe')):
+-        exe += '.exe'
+-
+-    if not found:
+-        # Can exe be found in the Android SDK?
+-        try:
+-            android_sdk_root = os.environ['ANDROID_SDK_ROOT']
+-            exe_path = os.path.join(
+-                android_sdk_root, subdir, exe)
+-            if os.path.exists(exe_path):
+-                found = True
+-            else:
+-                _log_debug(
+-                    "Unable to find executable at %s" % exe_path)
+-        except KeyError:
+-            _log_debug("ANDROID_SDK_ROOT not set")
+-
+-    if not found:
+-        # Can exe be found in the default bootstrap location?
+-        mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH',
+-                                       os.path.expanduser(os.path.join('~', '.mozbuild')))
+-        exe_path = os.path.join(
+-            mozbuild_path, 'android-sdk-linux', subdir, exe)
+-        if os.path.exists(exe_path):
+-            found = True
+-        else:
+-            _log_debug(
+-                "Unable to find executable at %s" % exe_path)
+-
+-    if not found:
+-        # Is exe on PATH?
+-        exe_path = find_executable(exe)
+-        if exe_path:
+-            found = True
+-        else:
+-            _log_debug("Unable to find executable on PATH")
+-
+-    if found:
+-        _log_debug("%s found at %s" % (exe, exe_path))
+-        try:
+-            creation_time = os.path.getctime(exe_path)
+-            _log_debug("  ...with creation time %s" % time.ctime(creation_time))
+-        except Exception:
+-            _log_warning("Could not get creation time for %s" % exe_path)
+-
+-        prop_path = os.path.join(os.path.dirname(exe_path), "source.properties")
+-        if os.path.exists(prop_path):
+-            with open(prop_path, 'r') as f:
+-                for line in f.readlines():
+-                    if line.startswith("Pkg.Revision"):
+-                        line = line.strip()
+-                        _log_debug("  ...with SDK version in %s: %s" % (prop_path, line))
+-                        break
+-    else:
+-        exe_path = None
+-    return exe_path
+-
+-
+-def _log_debug(text):
+-    if verbose_logging:
+-        print("DEBUG: %s" % text)
+-
+-
+-def _log_warning(text):
+-    print("WARNING: %s" % text)
+-
+-
+-def _log_info(text):
+-    print("%s" % text)
+-
+-
+-def _download_file(url, filename, path):
+-    _log_debug("Download %s to %s/%s..." % (url, path, filename))
+-    f = urllib2.urlopen(url)
+-    if not os.path.isdir(path):
+-        try:
+-            os.makedirs(path)
+-        except Exception as e:
+-            _log_warning(str(e))
+-            return False
+-    local_file = open(os.path.join(path, filename), 'wb')
+-    local_file.write(f.read())
+-    local_file.close()
+-    _log_debug("Downloaded %s to %s/%s" % (url, path, filename))
+-    return True
+-
+-
+-def _get_tooltool_manifest(substs, src_path, dst_path, filename):
+-    if not os.path.isdir(dst_path):
+-        try:
+-            os.makedirs(dst_path)
+-        except Exception as e:
+-            _log_warning(str(e))
+-    copied = False
+-    if substs and 'top_srcdir' in substs:
+-        src = os.path.join(substs['top_srcdir'], src_path)
+-        if os.path.exists(src):
+-            dst = os.path.join(dst_path, filename)
+-            shutil.copy(src, dst)
+-            copied = True
+-            _log_debug("Copied tooltool manifest %s to %s" % (src, dst))
+-    if not copied:
+-        url = os.path.join(TRY_URL, src_path)
+-        _download_file(url, filename, dst_path)
+-
+-
+-def _tooltool_fetch():
+-    def outputHandler(line):
+-        _log_debug(line)
+-    _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR)
+-    command = [sys.executable, 'tooltool.py',
+-               'fetch', '-o', '-m', 'releng.manifest']
+-    proc = ProcessHandler(
+-        command, processOutputLine=outputHandler, storeOutput=False,
+-        cwd=EMULATOR_HOME_DIR)
+-    proc.run()
+-    try:
+-        proc.wait()
+-    except Exception:
+-        if proc.poll() is None:
+-            proc.kill(signal.SIGTERM)
+-
+-
+-def _get_host_platform():
+-    plat = None
+-    if 'darwin' in str(sys.platform).lower():
+-        plat = 'macosx64'
+-    elif 'linux' in str(sys.platform).lower():
+-        if '64' in platform.architecture()[0]:
+-            plat = 'linux64'
+-        else:
+-            plat = 'linux32'
+-    return plat
+-
+-
+-def _get_device_platform(substs):
+-    # PIE executables are required when SDK level >= 21 - important for gdbserver
+-    adb_path = _find_sdk_exe(substs, 'adb', False)
+-    if not adb_path:
+-        adb_path = 'adb'
+-    dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1)
+-    sdk_level = None
+-    try:
+-        cmd = ['getprop', 'ro.build.version.sdk']
+-        _log_debug(cmd)
+-        output = dm.shellCheckOutput(cmd, timeout=10)
+-        if output:
+-            sdk_level = int(output)
+-    except Exception:
+-        _log_warning("unable to determine Android sdk level")
+-    pie = ''
+-    if sdk_level and sdk_level >= 21:
+-        pie = '-pie'
+-    if substs['TARGET_CPU'].startswith('arm'):
+-        return 'arm%s' % pie
+-    return 'x86%s' % pie
+-
+-
+-def _update_gdbinit(substs, path):
+-    if os.path.exists(path):
+-        obj_replaced = False
+-        src_replaced = False
+-        # update existing objdir/srcroot in place
+-        for line in fileinput.input(path, inplace=True):
+-            if "feninit.default.objdir" in line and substs and 'MOZ_BUILD_ROOT' in substs:
+-                print("python feninit.default.objdir = '%s'" % substs['MOZ_BUILD_ROOT'])
+-                obj_replaced = True
+-            elif "feninit.default.srcroot" in line and substs and 'top_srcdir' in substs:
+-                print("python feninit.default.srcroot = '%s'" % substs['top_srcdir'])
+-                src_replaced = True
+-            else:
+-                print(line.strip())
+-        # append objdir/srcroot if not updated
+-        if (not obj_replaced) and substs and 'MOZ_BUILD_ROOT' in substs:
+-            with open(path, "a") as f:
+-                f.write("\npython feninit.default.objdir = '%s'\n" % substs['MOZ_BUILD_ROOT'])
+-        if (not src_replaced) and substs and 'top_srcdir' in substs:
+-            with open(path, "a") as f:
+-                f.write("python feninit.default.srcroot = '%s'\n" % substs['top_srcdir'])
+-    else:
+-        # write objdir/srcroot to new gdbinit file
+-        with open(path, "w") as f:
+-            if substs and 'MOZ_BUILD_ROOT' in substs:
+-                f.write("python feninit.default.objdir = '%s'\n" % substs['MOZ_BUILD_ROOT'])
+-            if substs and 'top_srcdir' in substs:
+-                f.write("python feninit.default.srcroot = '%s'\n" % substs['top_srcdir'])
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/autophone.py b/testing/mozbase/mozrunner/mozrunner/devices/autophone.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/autophone.py
++++ /dev/null
+@@ -1,653 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import, print_function
+-
+-import glob
+-import json
+-import logging
+-import os
+-import shutil
+-import signal
+-import socket
+-import sys
+-import threading
+-import time
+-import which
+-import BaseHTTPServer
+-import SimpleHTTPServer
+-
+-from mozbuild.virtualenv import VirtualenvManager
+-from mozdevice import DeviceManagerADB
+-from mozprocess import ProcessHandler
+-
+-
+-class AutophoneRunner(object):
+-    """
+-       Supporting the mach 'autophone' command: configure, run autophone.
+-    """
+-    config = {'base-dir': None,
+-              'requirements-installed': False,
+-              'devices-configured': False,
+-              'test-manifest': None}
+-    CONFIG_FILE = os.path.join(os.path.expanduser('~'), '.mozbuild', 'autophone.json')
+-
+-    def __init__(self, build_obj, verbose):
+-        self.build_obj = build_obj
+-        self.verbose = verbose
+-        self.autophone_options = []
+-        self.httpd = None
+-        self.webserver_required = False
+-
+-    def reset_to_clean(self):
+-        """
+-           If confirmed, remove the autophone directory and configuration.
+-        """
+-        dir = self.config['base-dir']
+-        if dir and os.path.exists(dir) and os.path.exists(self.CONFIG_FILE):
+-            self.build_obj.log(logging.WARN, "autophone", {},
+-                               "*** This will delete %s and reset your "
+-                               "'mach autophone' configuration! ***" % dir)
+-            response = raw_input(
+-                "Proceed with deletion? (y/N) ").strip()
+-            if response.lower().startswith('y'):
+-                os.remove(self.CONFIG_FILE)
+-                shutil.rmtree(dir)
+-        else:
+-            self.build_obj.log(logging.INFO, "autophone", {},
+-                               "Already clean -- nothing to do!")
+-
+-    def save_config(self):
+-        """
+-           Persist self.config to a file.
+-        """
+-        try:
+-            with open(self.CONFIG_FILE, 'w') as f:
+-                json.dump(self.config, f)
+-            if self.verbose:
+-                print("saved configuration: %s" % self.config)
+-        except Exception:
+-            self.build_obj.log(logging.ERROR, "autophone", {},
+-                               "unable to save 'mach autophone' "
+-                               "configuration to %s" % self.CONFIG_FILE)
+-            if self.verbose:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   str(sys.exc_info()[0]))
+-
+-    def load_config(self):
+-        """
+-           Import the configuration info saved by save_config().
+-        """
+-        if os.path.exists(self.CONFIG_FILE):
+-            try:
+-                with open(self.CONFIG_FILE, 'r') as f:
+-                    self.config = json.load(f)
+-                if self.verbose:
+-                    print("loaded configuration: %s" % self.config)
+-            except Exception:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   "unable to load 'mach autophone' "
+-                                   "configuration from %s" % self.CONFIG_FILE)
+-                if self.verbose:
+-                    self.build_obj.log(logging.ERROR, "autophone", {},
+-                                       str(sys.exc_info()[0]))
+-
+-    def setup_directory(self):
+-        """
+-           Find the autophone source code location, or download if necessary.
+-        """
+-        keep_going = True
+-        dir = self.config['base-dir']
+-        if not dir:
+-            dir = os.path.join(os.path.expanduser('~'), 'mach-autophone')
+-        if os.path.exists(os.path.join(dir, '.git')):
+-            response = raw_input(
+-                "Run autophone from existing directory, %s (Y/n) " % dir).strip()
+-            if 'n' not in response.lower():
+-                self.build_obj.log(logging.INFO, "autophone", {},
+-                                   "Configuring and running autophone at %s" % dir)
+-                return keep_going
+-        self.build_obj.log(logging.INFO, "autophone", {},
+-                           "Unable to find an existing autophone directory. "
+-                           "Let's setup a new one...")
+-        response = raw_input(
+-            "Enter location of new autophone directory: [%s] " % dir).strip()
+-        if response != '':
+-            dir = response
+-        self.config['base-dir'] = dir
+-        if not os.path.exists(os.path.join(dir, '.git')):
+-            self.build_obj.log(logging.INFO, "autophone", {},
+-                               "Cloning autophone repository to '%s'..." % dir)
+-            self.config['requirements-installed'] = False
+-            self.config['devices-configured'] = False
+-            self.run_process(['git', 'clone', 'https://github.com/mozilla/autophone', dir])
+-            self.run_process(['git', 'submodule', 'update', '--init', '--remote'], cwd=dir)
+-        if not os.path.exists(os.path.join(dir, '.git')):
+-            # git not installed? File permission problem? github not available?
+-            self.build_obj.log(logging.ERROR, "autophone", {},
+-                               "Unable to clone autophone directory.")
+-            if not self.verbose:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   "Try re-running this command with --verbose to get more info.")
+-            keep_going = False
+-        return keep_going
+-
+-    def install_requirements(self):
+-        """
+-           Install required python modules in a virtualenv rooted at <autophone>/_virtualenv.
+-        """
+-        keep_going = True
+-        dir = self.config['base-dir']
+-        vdir = os.path.join(dir, '_virtualenv')
+-        self.auto_virtualenv_manager = VirtualenvManager(self.build_obj.topsrcdir,
+-                                                         self.build_obj.topobjdir,
+-                                                         vdir, sys.stdout,
+-                                                         os.path.join(self.build_obj.topsrcdir,
+-                                                                      'build',
+-                                                                      'virtualenv_packages.txt'))
+-        if not self.config['requirements-installed'] or not os.path.exists(vdir):
+-            self.build_obj.log(logging.INFO, "autophone", {},
+-                               "Installing required modules in a virtualenv...")
+-            self.auto_virtualenv_manager.build()
+-            self.auto_virtualenv_manager._run_pip(['install', '-r',
+-                                                   os.path.join(dir, 'requirements.txt')])
+-            self.config['requirements-installed'] = True
+-        return keep_going
+-
+-    def configure_devices(self):
+-        """
+-           Ensure devices.ini is set up.
+-        """
+-        keep_going = True
+-        device_ini = os.path.join(self.config['base-dir'], 'devices.ini')
+-        if os.path.exists(device_ini):
+-            response = raw_input(
+-                "Use existing device configuration at %s? (Y/n) " % device_ini).strip()
+-            if 'n' not in response.lower():
+-                self.build_obj.log(logging.INFO, "autophone", {},
+-                                   "Using device configuration at %s" % device_ini)
+-                return keep_going
+-        keep_going = False
+-        self.build_obj.log(logging.INFO, "autophone", {},
+-                           "You must configure at least one Android device "
+-                           "before running autophone.")
+-        response = raw_input(
+-            "Configure devices now? (Y/n) ").strip()
+-        if response.lower().startswith('y') or response == '':
+-            response = raw_input(
+-                "Connect your rooted Android test device(s) with usb and press Enter ")
+-            adb_path = 'adb'
+-            try:
+-                if os.path.exists(self.build_obj.substs["ADB"]):
+-                    adb_path = self.build_obj.substs["ADB"]
+-            except Exception:
+-                if self.verbose:
+-                    self.build_obj.log(logging.ERROR, "autophone", {},
+-                                       str(sys.exc_info()[0]))
+-                # No build environment?
+-                try:
+-                    adb_path = which.which('adb')
+-                except which.WhichError:
+-                    adb_path = raw_input(
+-                        "adb not found. Enter path to adb: ").strip()
+-            if self.verbose:
+-                print("Using adb at %s" % adb_path)
+-            dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1)
+-            device_index = 1
+-            try:
+-                with open(os.path.join(self.config['base-dir'], 'devices.ini'), 'w') as f:
+-                    for device in dm.devices():
+-                        serial = device[0]
+-                        if self.verify_device(adb_path, serial):
+-                            f.write("[device-%d]\nserialno=%s\n" % (device_index, serial))
+-                            device_index += 1
+-                            self.build_obj.log(logging.INFO, "autophone", {},
+-                                               "Added '%s' to device configuration." % serial)
+-                            keep_going = True
+-                        else:
+-                            self.build_obj.log(logging.WARNING, "autophone", {},
+-                                               "Device '%s' is not rooted - skipping" % serial)
+-            except Exception:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   "Failed to get list of connected Android devices.")
+-                if self.verbose:
+-                    self.build_obj.log(logging.ERROR, "autophone", {},
+-                                       str(sys.exc_info()[0]))
+-                keep_going = False
+-            if device_index <= 1:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   "No devices configured! (Can you see your rooted test device(s)"
+-                                   " in 'adb devices'?")
+-                keep_going = False
+-            if keep_going:
+-                self.config['devices-configured'] = True
+-        return keep_going
+-
+-    def configure_tests(self):
+-        """
+-           Determine the required autophone --test-path option.
+-        """
+-        dir = self.config['base-dir']
+-        self.build_obj.log(logging.INFO, "autophone", {},
+-                           "Autophone must be started with a 'test manifest' "
+-                           "describing the type(s) of test(s) to run.")
+-        test_options = []
+-        for ini in glob.glob(os.path.join(dir, 'tests', '*.ini')):
+-            with open(ini, 'r') as f:
+-                content = f.readlines()
+-                for line in content:
+-                    if line.startswith('# @mach@ '):
+-                        webserver = False
+-                        if '@webserver@' in line:
+-                            webserver = True
+-                            line = line.replace('@webserver@', '')
+-                        test_options.append((line[9:].strip(), ini, webserver))
+-                        break
+-        if len(test_options) >= 1:
+-            test_options.sort()
+-            self.build_obj.log(logging.INFO, "autophone", {},
+-                               "These test manifests are available:")
+-            index = 1
+-            for option in test_options:
+-                print("%d. %s" % (index, option[0]))
+-                index += 1
+-            highest = index - 1
+-            path = None
+-            while not path:
+-                path = None
+-                self.webserver_required = False
+-                response = raw_input(
+-                    "Select test manifest (1-%d, or path to test manifest) " % highest).strip()
+-                if os.path.isfile(response):
+-                    path = response
+-                    self.config['test-manifest'] = path
+-                    # Assume a webserver is required; if it isn't, user can provide a dummy url.
+-                    self.webserver_required = True
+-                else:
+-                    try:
+-                        choice = int(response)
+-                        if choice >= 1 and choice <= highest:
+-                            path = test_options[choice - 1][1]
+-                            if test_options[choice - 1][2]:
+-                                self.webserver_required = True
+-                        else:
+-                            self.build_obj.log(logging.ERROR, "autophone", {},
+-                                               "'%s' invalid: Enter a number between "
+-                                               "1 and %d!" % (response, highest))
+-                    except ValueError:
+-                        self.build_obj.log(logging.ERROR, "autophone", {},
+-                                           "'%s' unrecognized: Enter a number between "
+-                                           "1 and %d!" % (response, highest))
+-            self.autophone_options.extend(['--test-path', path])
+-        else:
+-            # Provide a simple backup for the unusual case where test manifests
+-            # cannot be found.
+-            response = ""
+-            default = self.config['test-manifest'] or ""
+-            while not os.path.isfile(response):
+-                response = raw_input(
+-                    "Enter path to a test manifest: [%s] " % default).strip()
+-                if response == "":
+-                    response = default
+-            self.autophone_options.extend(['--test-path', response])
+-            self.config['test-manifest'] = response
+-            # Assume a webserver is required; if it isn't, user can provide a dummy url.
+-            self.webserver_required = True
+-
+-        return True
+-
+-    def write_unittest_defaults(self, defaults_path, xre_path):
+-        """
+-           Write unittest-defaults.ini.
+-        """
+-        try:
+-            # This should be similar to unittest-defaults.ini.example
+-            with open(defaults_path, 'w') as f:
+-                f.write("""\
+-# Created by 'mach autophone'
+-[runtests]
+-xre_path = %s
+-utility_path = %s
+-console_level = DEBUG
+-log_level = DEBUG
+-time_out = 300""" % (xre_path, xre_path))
+-            if self.verbose:
+-                print("Created %s with host utilities path %s" % (defaults_path, xre_path))
+-        except Exception:
+-            self.build_obj.log(logging.ERROR, "autophone", {},
+-                               "Unable to create %s" % defaults_path)
+-            if self.verbose:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   str(sys.exc_info()[0]))
+-
+-    def configure_unittests(self):
+-        """
+-           Ensure unittest-defaults.ini is set up.
+-        """
+-        defaults_path = os.path.join(self.config['base-dir'], 'configs', 'unittest-defaults.ini')
+-        if os.path.isfile(defaults_path):
+-            response = raw_input(
+-                "Use existing unit test configuration at %s? (Y/n) " % defaults_path).strip()
+-            if 'n' in response.lower():
+-                os.remove(defaults_path)
+-        if not os.path.isfile(defaults_path):
+-            xre_path = os.environ.get('MOZ_HOST_BIN')
+-            if not xre_path or not os.path.isdir(xre_path):
+-                emulator_path = os.path.join(os.path.expanduser('~'), '.mozbuild',
+-                                             'android-device')
+-                xre_paths = glob.glob(os.path.join(emulator_path, 'host-utils*'))
+-                for xre_path in xre_paths:
+-                    if os.path.isdir(xre_path):
+-                        break
+-            if not xre_path or not os.path.isdir(xre_path) or \
+-               not os.path.isfile(os.path.join(xre_path, 'xpcshell')):
+-                self.build_obj.log(logging.INFO, "autophone", {},
+-                                   "Some tests require access to 'host utilities' "
+-                                   "such as xpcshell.")
+-                xre_path = raw_input(
+-                    "Enter path to host utilities directory: ").strip()
+-                if not xre_path or not os.path.isdir(xre_path) or \
+-                   not os.path.isfile(os.path.join(xre_path, 'xpcshell')):
+-                    self.build_obj.log(
+-                        logging.ERROR, "autophone", {},
+-                        "Unable to configure unit tests - no path to host utilities.")
+-                    return False
+-            self.write_unittest_defaults(defaults_path, xre_path)
+-        if os.path.isfile(defaults_path):
+-            self.build_obj.log(logging.INFO, "autophone", {},
+-                               "Using unit test configuration at %s" % defaults_path)
+-        return True
+-
+-    def configure_ip(self):
+-        """
+-           Determine what IP should be used for the autophone --ipaddr option.
+-        """
+-        # Take a guess at the IP to suggest.  This won't always get the "right" IP,
+-        # but will save some typing, sometimes.
+-        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+-        s.connect(('8.8.8.8', 0))
+-        ip = s.getsockname()[0]
+-        response = raw_input(
+-            "IP address of interface to use for phone callbacks [%s] " % ip).strip()
+-        if response == "":
+-            response = ip
+-        self.autophone_options.extend(['--ipaddr', response])
+-        self.ipaddr = response
+-        return True
+-
+-    def configure_webserver(self):
+-        """
+-           Determine the autophone --webserver-url option.
+-        """
+-        if self.webserver_required:
+-            self.build_obj.log(logging.INFO, "autophone", {},
+-                               "Some of your selected tests require a webserver.")
+-            response = raw_input("Start a webserver now? [Y/n] ").strip()
+-            parts = []
+-            while len(parts) != 2:
+-                response2 = raw_input(
+-                    "Webserver address? [%s:8100] " % self.ipaddr).strip()
+-                if response2 == "":
+-                    parts = [self.ipaddr, "8100"]
+-                else:
+-                    parts = response2.split(":")
+-                if len(parts) == 2:
+-                    addr = parts[0]
+-                    try:
+-                        port = int(parts[1])
+-                        if port <= 0:
+-                            self.build_obj.log(
+-                                logging.ERROR, "autophone", {},
+-                                "Port must be > 0. "
+-                                "Enter webserver address in the format <ip>:<port>")
+-                            parts = []
+-                    except ValueError:
+-                        self.build_obj.log(
+-                            logging.ERROR, "autophone", {},
+-                            "Port must be a number. "
+-                            "Enter webserver address in the format <ip>:<port>")
+-                        parts = []
+-                else:
+-                    self.build_obj.log(
+-                        logging.ERROR, "autophone", {},
+-                        "Enter webserver address in the format <ip>:<port>")
+-            if not ('n' in response.lower()):
+-                self.launch_webserver(addr, port)
+-            self.autophone_options.extend(['--webserver-url',
+-                                           'http://%s:%d' % (addr, port)])
+-        return True
+-
+-    def configure_other(self):
+-        """
+-           Advanced users may set up additional options in autophone.ini.
+-           Find and handle that case silently.
+-        """
+-        path = os.path.join(self.config['base-dir'], 'autophone.ini')
+-        if os.path.isfile(path):
+-            self.autophone_options.extend(['--config', path])
+-        return True
+-
+-    def configure(self):
+-        """
+-           Ensure all configuration files are set up and determine autophone options.
+-        """
+-        return self.configure_devices() and \
+-            self.configure_unittests() and \
+-            self.configure_tests() and \
+-            self.configure_ip() and \
+-            self.configure_webserver() and \
+-            self.configure_other()
+-
+-    def verify_device(self, adb_path, device):
+-        """
+-           Check that the specified device is available and rooted.
+-        """
+-        try:
+-            dm = DeviceManagerADB(adbPath=adb_path, retryLimit=1, deviceSerial=device)
+-            if dm._haveSu or dm._haveRootShell:
+-                return True
+-        except Exception:
+-            self.build_obj.log(
+-                logging.WARN, "autophone", {},
+-                "Unable to verify root on device.")
+-            if self.verbose:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   str(sys.exc_info()[0]))
+-        return False
+-
+-    def launch_autophone(self):
+-        """
+-           Launch autophone in its own thread and wait for autophone startup.
+-        """
+-        self.build_obj.log(logging.INFO, "autophone", {},
+-                           "Launching autophone...")
+-        self.thread = threading.Thread(target=self.run_autophone)
+-        self.thread.start()
+-        # Wait for startup, so that autophone startup messages do not get mixed
+-        # in with our interactive command prompts.
+-        dir = self.config['base-dir']
+-        started = False
+-        for seconds in [5, 5, 3, 3, 1, 1, 1, 1]:
+-            time.sleep(seconds)
+-            if self.run_process(['./ap.sh', 'autophone-status'], cwd=dir, dump=False):
+-                started = True
+-                break
+-        time.sleep(1)
+-        if not started:
+-            self.build_obj.log(logging.WARN, "autophone", {},
+-                               "Autophone is taking longer than expected to start.")
+-
+-    def run_autophone(self):
+-        dir = self.config['base-dir']
+-        cmd = [self.auto_virtualenv_manager.python_path, 'autophone.py']
+-        cmd.extend(self.autophone_options)
+-        self.run_process(cmd, cwd=dir, dump=True)
+-
+-    def command_prompts(self):
+-        """
+-           Interactive command prompts: Provide access to ap.sh and trigger_runs.py.
+-        """
+-        dir = self.config['base-dir']
+-        if self.thread.isAlive():
+-            self.build_obj.log(
+-                logging.INFO, "autophone", {},
+-                "Use 'trigger' to select builds to test using the current test manifest.")
+-            self.build_obj.log(
+-                logging.INFO, "autophone", {},
+-                "Type 'trigger', 'help', 'quit', or an autophone command.")
+-        quitting = False
+-        while self.thread.isAlive() and not quitting:
+-            response = raw_input(
+-                "autophone command? ").strip().lower()
+-            if response == "help":
+-                self.run_process(['./ap.sh', 'autophone-help'], cwd=dir, dump=True)
+-                print("""\
+-
+-Additional commands available in this interactive shell:
+-
+-trigger
+-   Initiate autophone test runs. You will be prompted for a set of builds
+-   to run tests against. (To run a different type of test, quit, run this
+-   mach command again, and select a new test manifest.)
+-
+-quit
+-   Shutdown autophone and exit this shell (short-cut to 'autophone-shutdown')
+-
+-                      """)
+-                continue
+-            if response == "trigger":
+-                self.trigger_prompts()
+-                continue
+-            if response == "quit":
+-                self.build_obj.log(logging.INFO, "autophone", {},
+-                                   "Quitting...")
+-                response = "autophone-shutdown"
+-            if response == "autophone-shutdown":
+-                quitting = True
+-            self.run_process(['./ap.sh', response], cwd=dir, dump=True)
+-        if self.httpd:
+-            self.httpd.shutdown()
+-        self.thread.join()
+-
+-    def trigger_prompts(self):
+-        """
+-           Sub-prompts for the "trigger" command.
+-        """
+-        dir = self.config['base-dir']
+-        self.build_obj.log(
+-            logging.INFO, "autophone", {},
+-            "Tests will be run against a build or collection of builds, selected by:")
+-        print("""\
+-1. The latest build
+-2. Build URL
+-3. Build ID
+-4. Date/date-time range\
+-              """)
+-        highest = 4
+-        choice = 0
+-        while (choice < 1 or choice > highest):
+-            response = raw_input(
+-                "Build selection type? (1-%d) " % highest).strip()
+-            try:
+-                choice = int(response)
+-            except ValueError:
+-                self.build_obj.log(logging.ERROR, "autophone", {},
+-                                   "Enter a number between 1 and %d" % highest)
+-                choice = 0
+-        if choice == 1:
+-            options = ["latest"]
+-        elif choice == 2:
+-            url = raw_input(
+-                "Enter url of build to test; may be an http or file schema ").strip()
+-            options = ["--build-url=%s" % url]
+-        elif choice == 3:
+-            response = raw_input(
+-                "Enter Build ID, eg 20120403063158 ").strip()
+-            options = [response]
+-        elif choice == 4:
+-            start = raw_input(
+-                "Enter start build date or date-time, "
+-                "e.g. 2012-04-03 or 2012-04-03T06:31:58 ").strip()
+-            end = raw_input(
+-                "Enter end build date or date-time, "
+-                "e.g. 2012-04-03 or 2012-04-03T06:31:58 ").strip()
+-            options = [start, end]
+-        self.build_obj.log(
+-            logging.INFO, "autophone", {},
+-            "You may optionally specify a repository name like 'mozilla-inbound' or 'try'.")
+-        self.build_obj.log(
+-            logging.INFO, "autophone", {},
+-            "If not specified, 'mozilla-central' is assumed.")
+-        repo = raw_input(
+-            "Enter repository name: ").strip()
+-        if len(repo) > 0:
+-            options.extend(["--repo=%s" % repo])
+-        if repo == "mozilla-central" or repo == "mozilla-aurora" or len(repo) < 1:
+-            self.build_obj.log(
+-                logging.INFO, "autophone", {},
+-                "You may optionally specify the build location, like 'nightly' or 'tinderbox'.")
+-            location = raw_input(
+-                "Enter build location: ").strip()
+-            if len(location) > 0:
+-                options.extend(["--build-location=%s" % location])
+-        else:
+-            options.extend(["--build-location=tinderbox"])
+-        cmd = [self.auto_virtualenv_manager.python_path, "trigger_runs.py"]
+-        cmd.extend(options)
+-        self.build_obj.log(
+-            logging.INFO, "autophone", {},
+-            "Triggering...Tests will run once builds have been downloaded.")
+-        self.build_obj.log(logging.INFO, "autophone", {},
+-                           "Use 'autophone-status' to check progress.")
+-        self.run_process(cmd, cwd=dir, dump=True)
+-
+-    def launch_webserver(self, addr, port):
+-        """
+-           Launch the webserver (in a separate thread).
+-        """
+-        self.build_obj.log(logging.INFO, "autophone", {},
+-                           "Launching webserver...")
+-        self.webserver_addr = addr
+-        self.webserver_port = port
+-        self.threadweb = threading.Thread(target=self.run_webserver)
+-        self.threadweb.start()
+-
+-    def run_webserver(self):
+-        class AutoHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+-            # A simple request handler with logging suppressed.
+-
+-            def log_message(self, format, *args):
+-                pass
+-
+-        os.chdir(self.config['base-dir'])
+-        address = (self.webserver_addr, self.webserver_port)
+-        self.httpd = BaseHTTPServer.HTTPServer(address, AutoHTTPRequestHandler)
+-        try:
+-            self.httpd.serve_forever()
+-        except KeyboardInterrupt:
+-            print("Web server interrupted.")
+-
+-    def run_process(self, cmd, cwd=None, dump=False):
+-        def _processOutput(line):
+-            if self.verbose or dump:
+-                print(line)
+-
+-        if self.verbose:
+-            self.build_obj.log(logging.INFO, "autophone", {},
+-                               "Running '%s' in '%s'" % (cmd, cwd))
+-        proc = ProcessHandler(cmd, cwd=cwd, processOutputLine=_processOutput,
+-                              processStderrLine=_processOutput)
+-        proc.run()
+-        proc_complete = False
+-        try:
+-            proc.wait()
+-            if proc.proc.returncode == 0:
+-                proc_complete = True
+-        except Exception:
+-            if proc.poll() is None:
+-                proc.kill(signal.SIGTERM)
+-        if not proc_complete:
+-            if not self.verbose:
+-                print(proc.output)
+-        return proc_complete
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/base.py b/testing/mozbase/mozrunner/mozrunner/devices/base.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/base.py
++++ /dev/null
+@@ -1,292 +0,0 @@
+-from __future__ import absolute_import, print_function
+-
+-
+-from ConfigParser import (
+-    ConfigParser,
+-    RawConfigParser
+-)
+-import datetime
+-import os
+-import posixpath
+-import re
+-import shutil
+-import subprocess
+-import tempfile
+-import time
+-
+-from mozdevice import DMError
+-from mozprocess import ProcessHandler
+-
+-
+-class Device(object):
+-    connected = False
+-    logcat_proc = None
+-
+-    def __init__(self, app_ctx, logdir=None, serial=None, restore=True):
+-        self.app_ctx = app_ctx
+-        self.dm = self.app_ctx.dm
+-        self.restore = restore
+-        self.serial = serial
+-        self.logdir = os.path.abspath(os.path.expanduser(logdir))
+-        self.added_files = set()
+-        self.backup_files = set()
+-
+-    @property
+-    def remote_profiles(self):
+-        """
+-        A list of remote profiles on the device.
+-        """
+-        remote_ini = self.app_ctx.remote_profiles_ini
+-        if not self.dm.fileExists(remote_ini):
+-            raise IOError("Remote file '%s' not found" % remote_ini)
+-
+-        local_ini = tempfile.NamedTemporaryFile()
+-        self.dm.getFile(remote_ini, local_ini.name)
+-        cfg = ConfigParser()
+-        cfg.read(local_ini.name)
+-
+-        profiles = []
+-        for section in cfg.sections():
+-            if cfg.has_option(section, 'Path'):
+-                if cfg.has_option(section, 'IsRelative') and cfg.getint(section, 'IsRelative'):
+-                    profiles.append(posixpath.join(posixpath.dirname(remote_ini),
+-                                                   cfg.get(section, 'Path')))
+-                else:
+-                    profiles.append(cfg.get(section, 'Path'))
+-        return profiles
+-
+-    def pull_minidumps(self):
+-        """
+-        Saves any minidumps found in the remote profile on the local filesystem.
+-
+-        :returns: Path to directory containing the dumps.
+-        """
+-        remote_dump_dir = posixpath.join(self.app_ctx.remote_profile, 'minidumps')
+-        local_dump_dir = tempfile.mkdtemp()
+-        self.dm.getDirectory(remote_dump_dir, local_dump_dir)
+-        if os.listdir(local_dump_dir):
+-            for f in self.dm.listFiles(remote_dump_dir):
+-                self.dm.removeFile(posixpath.join(remote_dump_dir, f))
+-        return local_dump_dir
+-
+-    def setup_profile(self, profile):
+-        """
+-        Copy profile to the device and update the remote profiles.ini
+-        to point to the new profile.
+-
+-        :param profile: mozprofile object to copy over.
+-        """
+-        self.dm.remount()
+-
+-        if self.dm.dirExists(self.app_ctx.remote_profile):
+-            self.dm.shellCheckOutput(['rm', '-r', self.app_ctx.remote_profile])
+-
+-        self.dm.pushDir(profile.profile, self.app_ctx.remote_profile)
+-
+-        timeout = 5  # seconds
+-        starttime = datetime.datetime.now()
+-        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
+-            if self.dm.fileExists(self.app_ctx.remote_profiles_ini):
+-                break
+-            time.sleep(1)
+-        else:
+-            print("timed out waiting for profiles.ini")
+-
+-        local_profiles_ini = tempfile.NamedTemporaryFile()
+-        self.dm.getFile(self.app_ctx.remote_profiles_ini, local_profiles_ini.name)
+-
+-        config = ProfileConfigParser()
+-        config.read(local_profiles_ini.name)
+-        for section in config.sections():
+-            if 'Profile' in section:
+-                config.set(section, 'IsRelative', 0)
+-                config.set(section, 'Path', self.app_ctx.remote_profile)
+-
+-        new_profiles_ini = tempfile.NamedTemporaryFile()
+-        config.write(open(new_profiles_ini.name, 'w'))
+-
+-        self.backup_file(self.app_ctx.remote_profiles_ini)
+-        self.dm.pushFile(new_profiles_ini.name, self.app_ctx.remote_profiles_ini)
+-
+-        # Ideally all applications would read the profile the same way, but in practice
+-        # this isn't true. Perform application specific profile-related setup if necessary.
+-        if hasattr(self.app_ctx, 'setup_profile'):
+-            for remote_path in self.app_ctx.remote_backup_files:
+-                self.backup_file(remote_path)
+-            self.app_ctx.setup_profile(profile)
+-
+-    def _get_online_devices(self):
+-        return [d[0] for d in self.dm.devices()
+-                if d[1] != 'offline'
+-                if not d[0].startswith('emulator')]
+-
+-    def connect(self):
+-        """
+-        Connects to a running device. If no serial was specified in the
+-        constructor, defaults to the first entry in `adb devices`.
+-        """
+-        if self.connected:
+-            return
+-
+-        if self.serial:
+-            serial = self.serial
+-        else:
+-            online_devices = self._get_online_devices()
+-            if not online_devices:
+-                raise IOError("No devices connected. Ensure the device is on and "
+-                              "remote debugging via adb is enabled in the settings.")
+-            serial = online_devices[0]
+-
+-        self.dm._deviceSerial = serial
+-        self.dm.connect()
+-        self.connected = True
+-
+-        if self.logdir:
+-            # save logcat
+-            logcat_log = os.path.join(self.logdir, '%s.log' % serial)
+-            if os.path.isfile(logcat_log):
+-                self._rotate_log(logcat_log)
+-            self.logcat_proc = self.start_logcat(serial, logfile=logcat_log)
+-
+-    def start_logcat(self, serial, logfile=None, stream=None, filterspec=None):
+-        logcat_args = [self.app_ctx.adb, '-s', '%s' % serial,
+-                       'logcat', '-v', 'time', '-b', 'main', '-b', 'radio']
+-        # only log filterspec
+-        if filterspec:
+-            logcat_args.extend(['-s', filterspec])
+-        process_args = {}
+-        if logfile:
+-            process_args['logfile'] = logfile
+-        elif stream:
+-            process_args['stream'] = stream
+-        proc = ProcessHandler(logcat_args, **process_args)
+-        proc.run()
+-        return proc
+-
+-    def reboot(self):
+-        """
+-        Reboots the device via adb.
+-        """
+-        self.dm.reboot(wait=True)
+-
+-    def install_busybox(self, busybox):
+-        """
+-        Installs busybox on the device.
+-
+-        :param busybox: Path to busybox binary to install.
+-        """
+-        self.dm.remount()
+-        print('pushing %s' % self.app_ctx.remote_busybox)
+-        self.dm.pushFile(busybox, self.app_ctx.remote_busybox, retryLimit=10)
+-        # TODO for some reason using dm.shellCheckOutput doesn't work,
+-        #      while calling adb shell directly does.
+-        args = [self.app_ctx.adb, '-s', self.dm._deviceSerial,
+-                'shell', 'cd /system/bin; chmod 555 busybox;'
+-                'for x in `./busybox --list`; do ln -s ./busybox $x; done']
+-        adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+-        adb.wait()
+-        self.dm._verifyZip()
+-
+-    def wait_for_net(self):
+-        active = False
+-        time_out = 0
+-        while not active and time_out < 40:
+-            proc = subprocess.Popen([self.app_ctx.adb, 'shell', '/system/bin/netcfg'],
+-                                    stdout=subprocess.PIPE)
+-            proc.stdout.readline()  # ignore first line
+-            line = proc.stdout.readline()
+-            while line != "":
+-                if (re.search(r'UP\s+[1-9]\d{0,2}\.\d{1,3}\.\d{1,3}\.\d{1,3}', line)):
+-                    active = True
+-                    break
+-                line = proc.stdout.readline()
+-            time_out += 1
+-            time.sleep(1)
+-        return active
+-
+-    def backup_file(self, remote_path):
+-        if not self.restore:
+-            return
+-
+-        if self.dm.fileExists(remote_path) or self.dm.dirExists(remote_path):
+-            self.dm.copyTree(remote_path, '%s.orig' % remote_path)
+-            self.backup_files.add(remote_path)
+-        else:
+-            self.added_files.add(remote_path)
+-
+-    def cleanup(self):
+-        """
+-        Cleanup the device.
+-        """
+-        if not self.restore:
+-            return
+-
+-        try:
+-            self.dm._verifyDevice()
+-        except DMError:
+-            return
+-
+-        self.dm.remount()
+-        # Restore the original profile
+-        for added_file in self.added_files:
+-            self.dm.removeFile(added_file)
+-
+-        for backup_file in self.backup_files:
+-            if self.dm.fileExists('%s.orig' % backup_file) or \
+-               self.dm.dirExists('%s.orig' % backup_file):
+-                self.dm.moveTree('%s.orig' % backup_file, backup_file)
+-
+-        # Perform application specific profile cleanup if necessary
+-        if hasattr(self.app_ctx, 'cleanup_profile'):
+-            self.app_ctx.cleanup_profile()
+-
+-        # Remove the test profile
+-        self.dm.removeDir(self.app_ctx.remote_profile)
+-
+-    def _rotate_log(self, srclog, index=1):
+-        """
+-        Rotate a logfile, by recursively rotating logs further in the sequence,
+-        deleting the last file if necessary.
+-        """
+-        basename = os.path.basename(srclog)
+-        basename = basename[:-len('.log')]
+-        if index > 1:
+-            basename = basename[:-len('.1')]
+-        basename = '%s.%d.log' % (basename, index)
+-
+-        destlog = os.path.join(self.logdir, basename)
+-        if os.path.isfile(destlog):
+-            if index == 3:
+-                os.remove(destlog)
+-            else:
+-                self._rotate_log(destlog, index + 1)
+-        shutil.move(srclog, destlog)
+-
+-
+-class ProfileConfigParser(RawConfigParser):
+-    """
+-    Class to create profiles.ini config files
+-
+-    Subclass of RawConfigParser that outputs .ini files in the exact
+-    format expected for profiles.ini, which is slightly different
+-    than the default format.
+-    """
+-
+-    def optionxform(self, optionstr):
+-        return optionstr
+-
+-    def write(self, fp):
+-        if self._defaults:
+-            fp.write("[%s]\n" % ConfigParser.DEFAULTSECT)
+-            for (key, value) in self._defaults.items():
+-                fp.write("%s=%s\n" % (key, str(value).replace('\n', '\n\t')))
+-            fp.write("\n")
+-        for section in self._sections:
+-            fp.write("[%s]\n" % section)
+-            for (key, value) in self._sections[section].items():
+-                if key == "__name__":
+-                    continue
+-                if (value is not None) or (self._optcre == self.OPTCRE):
+-                    key = "=".join((key, str(value).replace('\n', '\n\t')))
+-                fp.write("%s\n" % (key))
+-            fp.write("\n")
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/emulator.py b/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
++++ /dev/null
+@@ -1,291 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-from telnetlib import Telnet
+-import datetime
+-import os
+-import shutil
+-import subprocess
+-import tempfile
+-import time
+-
+-from mozprocess import ProcessHandler
+-
+-from .base import Device
+-from .emulator_battery import EmulatorBattery
+-from .emulator_geo import EmulatorGeo
+-from .emulator_screen import EmulatorScreen
+-from ..errors import TimeoutException
+-
+-
+-class ArchContext(object):
+-
+-    def __init__(self, arch, context, binary=None, avd=None, extra_args=None):
+-        homedir = getattr(context, 'homedir', '')
+-        kernel = os.path.join(homedir, 'prebuilts', 'qemu-kernel', '%s', '%s')
+-        sysdir = os.path.join(homedir, 'out', 'target', 'product', '%s')
+-        self.extra_args = []
+-        self.binary = os.path.join(context.bindir or '', 'emulator')
+-        if arch == 'x86':
+-            self.binary = os.path.join(context.bindir or '', 'emulator-x86')
+-            self.kernel = kernel % ('x86', 'kernel-qemu')
+-            self.sysdir = sysdir % 'generic_x86'
+-        elif avd:
+-            self.avd = avd
+-            self.extra_args = [
+-                '-show-kernel', '-debug',
+-                'init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket'
+-            ]
+-        else:
+-            self.kernel = kernel % ('arm', 'kernel-qemu-armv7')
+-            self.sysdir = sysdir % 'generic'
+-            self.extra_args = ['-cpu', 'cortex-a8']
+-
+-        if binary:
+-            self.binary = binary
+-
+-        if extra_args:
+-            self.extra_args.extend(extra_args)
+-
+-
+-class SDCard(object):
+-
+-    def __init__(self, emulator, size):
+-        self.emulator = emulator
+-        self.path = self.create_sdcard(size)
+-
+-    def create_sdcard(self, sdcard_size):
+-        """
+-        Creates an sdcard partition in the emulator.
+-
+-        :param sdcard_size: Size of partition to create, e.g '10MB'.
+-        """
+-        mksdcard = self.emulator.app_ctx.which('mksdcard')
+-        path = tempfile.mktemp(prefix='sdcard', dir=self.emulator.tmpdir)
+-        sdargs = [mksdcard, '-l', 'mySdCard', sdcard_size, path]
+-        sd = subprocess.Popen(sdargs, stdout=subprocess.PIPE,
+-                              stderr=subprocess.STDOUT)
+-        retcode = sd.wait()
+-        if retcode:
+-            raise Exception('unable to create sdcard: exit code %d: %s'
+-                            % (retcode, sd.stdout.read()))
+-        return path
+-
+-
+-class BaseEmulator(Device):
+-    port = None
+-    proc = None
+-    telnet = None
+-
+-    def __init__(self, app_ctx, **kwargs):
+-        self.arch = ArchContext(kwargs.pop('arch', 'arm'), app_ctx,
+-                                binary=kwargs.pop('binary', None),
+-                                avd=kwargs.pop('avd', None))
+-        super(BaseEmulator, self).__init__(app_ctx, **kwargs)
+-        self.tmpdir = tempfile.mkdtemp()
+-        # These rely on telnet
+-        self.battery = EmulatorBattery(self)
+-        self.geo = EmulatorGeo(self)
+-        self.screen = EmulatorScreen(self)
+-
+-    @property
+-    def args(self):
+-        """
+-        Arguments to pass into the emulator binary.
+-        """
+-        return [self.arch.binary]
+-
+-    def start(self):
+-        """
+-        Starts a new emulator.
+-        """
+-        if self.proc:
+-            return
+-
+-        original_devices = set(self._get_online_devices())
+-
+-        # QEMU relies on atexit() to remove temporary files, which does not
+-        # work since mozprocess uses SIGKILL to kill the emulator process.
+-        # Use a customized temporary directory so we can clean it up.
+-        os.environ['ANDROID_TMP'] = self.tmpdir
+-
+-        qemu_log = None
+-        qemu_proc_args = {}
+-        if self.logdir:
+-            # save output from qemu to logfile
+-            qemu_log = os.path.join(self.logdir, 'qemu.log')
+-            if os.path.isfile(qemu_log):
+-                self._rotate_log(qemu_log)
+-            qemu_proc_args['logfile'] = qemu_log
+-        else:
+-            qemu_proc_args['processOutputLine'] = lambda line: None
+-        self.proc = ProcessHandler(self.args, **qemu_proc_args)
+-        self.proc.run()
+-
+-        devices = set(self._get_online_devices())
+-        now = datetime.datetime.now()
+-        while (devices - original_devices) == set([]):
+-            time.sleep(1)
+-            # Sometimes it takes more than 60s to launch emulator, so we
+-            # increase timeout value to 180s. Please see bug 1143380.
+-            if datetime.datetime.now() - now > datetime.timedelta(
+-                    seconds=180):
+-                raise TimeoutException(
+-                    'timed out waiting for emulator to start')
+-            devices = set(self._get_online_devices())
+-        devices = devices - original_devices
+-        self.serial = devices.pop()
+-        self.connect()
+-
+-    def _get_online_devices(self):
+-        return [d[0] for d in self.dm.devices() if d[1] != 'offline' if
+-                d[0].startswith('emulator')]
+-
+-    def connect(self):
+-        """
+-        Connects to a running device. If no serial was specified in the
+-        constructor, defaults to the first entry in `adb devices`.
+-        """
+-        if self.connected:
+-            return
+-
+-        super(BaseEmulator, self).connect()
+-        serial = self.serial or self.dm._deviceSerial
+-        self.port = int(serial[serial.rindex('-') + 1:])
+-
+-    def cleanup(self):
+-        """
+-        Cleans up and kills the emulator, if it was started by mozrunner.
+-        """
+-        super(BaseEmulator, self).cleanup()
+-        if self.proc:
+-            self.proc.kill()
+-            self.proc = None
+-            self.connected = False
+-
+-        # Remove temporary files
+-        if os.path.isdir(self.tmpdir):
+-            shutil.rmtree(self.tmpdir)
+-
+-    def _get_telnet_response(self, command=None):
+-        output = []
+-        assert self.telnet
+-        if command is not None:
+-            self.telnet.write('%s\n' % command)
+-        while True:
+-            line = self.telnet.read_until('\n')
+-            output.append(line.rstrip())
+-            if line.startswith('OK'):
+-                return output
+-            elif line.startswith('KO:'):
+-                raise Exception('bad telnet response: %s' % line)
+-
+-    def _run_telnet(self, command):
+-        if not self.telnet:
+-            self.telnet = Telnet('localhost', self.port)
+-            self._get_telnet_response()
+-        return self._get_telnet_response(command)
+-
+-    def __del__(self):
+-        if self.telnet:
+-            self.telnet.write('exit\n')
+-            self.telnet.read_all()
+-
+-
+-class Emulator(BaseEmulator):
+-
+-    def __init__(self, app_ctx, arch, resolution=None, sdcard=None, userdata=None,
+-                 no_window=None, binary=None, **kwargs):
+-        super(Emulator, self).__init__(app_ctx, arch=arch, binary=binary, **kwargs)
+-
+-        # emulator args
+-        self.resolution = resolution or '320x480'
+-        self._sdcard_size = sdcard
+-        self._sdcard = None
+-        self.userdata = tempfile.NamedTemporaryFile(prefix='userdata-qemu', dir=self.tmpdir)
+-        self.initdata = userdata if userdata else os.path.join(self.arch.sysdir, 'userdata.img')
+-        self.no_window = no_window
+-
+-    @property
+-    def sdcard(self):
+-        if self._sdcard_size and not self._sdcard:
+-            self._sdcard = SDCard(self, self._sdcard_size).path
+-        else:
+-            return self._sdcard
+-
+-    @property
+-    def args(self):
+-        """
+-        Arguments to pass into the emulator binary.
+-        """
+-        qemu_args = super(Emulator, self).args
+-        qemu_args.extend([
+-            '-kernel', self.arch.kernel,
+-            '-sysdir', self.arch.sysdir,
+-            '-data', self.userdata.name,
+-            '-initdata', self.initdata,
+-            '-wipe-data'])
+-        if self.no_window:
+-            qemu_args.append('-no-window')
+-        if self.sdcard:
+-            qemu_args.extend(['-sdcard', self.sdcard])
+-        qemu_args.extend(['-memory', '512',
+-                          '-partition-size', '512',
+-                          '-verbose',
+-                          '-skin', self.resolution,
+-                          '-gpu', 'on',
+-                          '-qemu'] + self.arch.extra_args)
+-        return qemu_args
+-
+-    def connect(self):
+-        """
+-        Connects to a running device. If no serial was specified in the
+-        constructor, defaults to the first entry in `adb devices`.
+-        """
+-        if self.connected:
+-            return
+-
+-        super(Emulator, self).connect()
+-        self.geo.set_default_location()
+-        self.screen.initialize()
+-
+-        # setup DNS fix for networking
+-        self.app_ctx.dm.shellCheckOutput(['setprop', 'net.dns1', '10.0.2.3'])
+-
+-    def cleanup(self):
+-        """
+-        Cleans up and kills the emulator, if it was started by mozrunner.
+-        """
+-        super(Emulator, self).cleanup()
+-        # Remove temporary files
+-        self.userdata.close()
+-
+-
+-class EmulatorAVD(BaseEmulator):
+-
+-    def __init__(self, app_ctx, binary, avd, port=5554, **kwargs):
+-        super(EmulatorAVD, self).__init__(app_ctx, binary=binary, avd=avd, **kwargs)
+-        self.port = port
+-
+-    @property
+-    def args(self):
+-        """
+-        Arguments to pass into the emulator binary.
+-        """
+-        qemu_args = super(EmulatorAVD, self).args
+-        qemu_args.extend(['-avd', self.arch.avd,
+-                          '-port', str(self.port)])
+-        qemu_args.extend(self.arch.extra_args)
+-        return qemu_args
+-
+-    def start(self):
+-        if self.proc:
+-            return
+-
+-        env = os.environ
+-        env['ANDROID_AVD_HOME'] = self.app_ctx.avd_home
+-
+-        super(EmulatorAVD, self).start()
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/emulator_battery.py b/testing/mozbase/mozrunner/mozrunner/devices/emulator_battery.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/emulator_battery.py
++++ /dev/null
+@@ -1,55 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-
+-class EmulatorBattery(object):
+-
+-    def __init__(self, emulator):
+-        self.emulator = emulator
+-
+-    def get_state(self):
+-        status = {}
+-        state = {}
+-
+-        response = self.emulator._run_telnet('power display')
+-        for line in response:
+-            if ':' in line:
+-                field, value = line.split(':')
+-                value = value.strip()
+-                if value == 'true':
+-                    value = True
+-                elif value == 'false':
+-                    value = False
+-                elif field == 'capacity':
+-                    value = float(value)
+-                status[field] = value
+-
+-        state['level'] = status.get('capacity', 0.0) / 100
+-        if status.get('AC') == 'online':
+-            state['charging'] = True
+-        else:
+-            state['charging'] = False
+-
+-        return state
+-
+-    def get_charging(self):
+-        return self.get_state()['charging']
+-
+-    def get_level(self):
+-        return self.get_state()['level']
+-
+-    def set_level(self, level):
+-        self.emulator._run_telnet('power capacity %d' % (level * 100))
+-
+-    def set_charging(self, charging):
+-        if charging:
+-            cmd = 'power ac on'
+-        else:
+-            cmd = 'power ac off'
+-        self.emulator._run_telnet(cmd)
+-
+-    charging = property(get_charging, set_charging)
+-    level = property(get_level, set_level)
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/emulator_geo.py b/testing/mozbase/mozrunner/mozrunner/devices/emulator_geo.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/emulator_geo.py
++++ /dev/null
+@@ -1,19 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-
+-class EmulatorGeo(object):
+-
+-    def __init__(self, emulator):
+-        self.emulator = emulator
+-
+-    def set_default_location(self):
+-        self.lon = -122.08769
+-        self.lat = 37.41857
+-        self.set_location(self.lon, self.lat)
+-
+-    def set_location(self, lon, lat):
+-        self.emulator._run_telnet('geo fix %0.5f %0.5f' % (self.lon, self.lat))
+diff --git a/testing/mozbase/mozrunner/mozrunner/devices/emulator_screen.py b/testing/mozbase/mozrunner/mozrunner/devices/emulator_screen.py
+deleted file mode 100644
+--- a/testing/mozbase/mozrunner/mozrunner/devices/emulator_screen.py
++++ /dev/null
+@@ -1,91 +0,0 @@
+-# 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/.
+-
+-from __future__ import absolute_import
+-
+-
+-class EmulatorScreen(object):
+-    """Class for screen related emulator commands."""
+-
+-    SO_PORTRAIT_PRIMARY = 'portrait-primary'
+-    SO_PORTRAIT_SECONDARY = 'portrait-secondary'
+-    SO_LANDSCAPE_PRIMARY = 'landscape-primary'
+-    SO_LANDSCAPE_SECONDARY = 'landscape-secondary'
+-
+-    def __init__(self, emulator):
+-        self.emulator = emulator
+-
+-    def initialize(self):
+-        self.orientation = self.SO_PORTRAIT_PRIMARY
+-
+-    def _get_raw_orientation(self):
+-        """Get the raw value of the current device orientation."""
+-        response = self.emulator._run_telnet('sensor get orientation')
+-
+-        return response[0].split('=')[1].strip()
+-
+-    def _set_raw_orientation(self, data):
+-        """Set the raw value of the specified device orientation."""
+-        self.emulator._run_telnet('sensor set orientation %s' % data)
+-
+-    def get_orientation(self):
+-        """Get the current device orientation.
+-
+-        Returns;
+-            orientation -- Orientation of the device. One of:
+-                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
+-                            SO_PORTRIAT_SECONDARY - system buttons at the top
+-                            SO_LANDSCAPE_PRIMARY - system buttons at the right
+-                            SO_LANDSCAPE_SECONDARY - system buttons at the left
+-
+-        """
+-        data = self._get_raw_orientation()
+-
+-        if data == '0:-90:0':
+-            orientation = self.SO_PORTRAIT_PRIMARY
+-        elif data == '0:90:0':
+-            orientation = self.SO_PORTRAIT_SECONDARY
+-        elif data == '0:0:90':
+-            orientation = self.SO_LANDSCAPE_PRIMARY
+-        elif data == '0:0:-90':
+-            orientation = self.SO_LANDSCAPE_SECONDARY
+-        else:
+-            raise ValueError('Unknown orientation sensor value: %s.' % data)
+-
+-        return orientation
+-
+-    def set_orientation(self, orientation):
+-        """Set the specified device orientation.
+-
+-        Args
+-            orientation -- Orientation of the device. One of:
+-                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
+-                            SO_PORTRIAT_SECONDARY - system buttons at the top
+-                            SO_LANDSCAPE_PRIMARY - system buttons at the right
+-                            SO_LANDSCAPE_SECONDARY - system buttons at the left
+-        """
+-        orientation = SCREEN_ORIENTATIONS[orientation]
+-
+-        if orientation == self.SO_PORTRAIT_PRIMARY:
+-            data = '0:-90:0'
+-        elif orientation == self.SO_PORTRAIT_SECONDARY:
+-            data = '0:90:0'
+-        elif orientation == self.SO_LANDSCAPE_PRIMARY:
+-            data = '0:0:90'
+-        elif orientation == self.SO_LANDSCAPE_SECONDARY:
+-            data = '0:0:-90'
+-        else:
+-            raise ValueError('Invalid orientation: %s' % orientation)
+-
+-        self._set_raw_orientation(data)
+-
+-    orientation = property(get_orientation, set_orientation)
+-
+-
+-SCREEN_ORIENTATIONS = {"portrait": EmulatorScreen.SO_PORTRAIT_PRIMARY,
+-                       "landscape": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
+-                       "portrait-primary": EmulatorScreen.SO_PORTRAIT_PRIMARY,
+-                       "landscape-primary": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
+-                       "portrait-secondary": EmulatorScreen.SO_PORTRAIT_SECONDARY,
+-                       "landscape-secondary": EmulatorScreen.SO_LANDSCAPE_SECONDARY}
+diff --git a/testing/mozbase/mozrunner/mozrunner/runners.py b/testing/mozbase/mozrunner/mozrunner/runners.py
+--- a/testing/mozbase/mozrunner/mozrunner/runners.py
++++ b/testing/mozbase/mozrunner/mozrunner/runners.py
+@@ -6,18 +6,17 @@
+ """
+ This module contains a set of shortcut methods that create runners for commonly
+ used Mozilla applications, such as Firefox, Firefox for Android or Thunderbird.
+ """
+ 
+ from __future__ import absolute_import
+ 
+ from .application import get_app_context
+-from .base import GeckoRuntimeRunner, FennecRunner, BlinkRuntimeRunner
+-from .devices import EmulatorAVD
++from .base import GeckoRuntimeRunner, BlinkRuntimeRunner
+ 
+ 
+ def Runner(*args, **kwargs):
+     """
+     Create a generic GeckoRuntime runner.
+ 
+     :param binary: Path to binary.
+     :param cmdargs: Arguments to pass into binary.
+@@ -80,53 +79,14 @@ def ChromeRunner(*args, **kwargs):
+ 
+     :param binary: Path to Chrome binary.
+     :param cmdargs: Arguments to pass into the binary.
+     """
+     kwargs['app_ctx'] = get_app_context('chrome')()
+     return BlinkRuntimeRunner(*args, **kwargs)
+ 
+ 
+-def FennecEmulatorRunner(avd='mozemulator-4.3',
+-                         adb_path=None,
+-                         avd_home=None,
+-                         logdir=None,
+-                         serial=None,
+-                         binary=None,
+-                         app='org.mozilla.fennec',
+-                         **kwargs):
+-    """
+-    Create a Fennec emulator runner. This can either start a new emulator
+-    (which will use an avd), or connect to  an already-running emulator.
+-
+-    :param avd: name of an AVD available in your environment.
+-        Typically obtained via tooltool: either 'mozemulator-4.3' or 'mozemulator-x86'.
+-        Defaults to 'mozemulator-4.3'
+-    :param avd_home: Path to avd parent directory
+-    :param logdir: Path to save logfiles such as logcat and qemu output.
+-    :param serial: Serial of emulator to connect to as seen in `adb devices`.
+-        Defaults to the first entry in `adb devices`.
+-    :param binary: Path to emulator binary.
+-        Defaults to None, which causes the device_class to guess based on PATH.
+-    :param app: Name of Fennec app (often org.mozilla.fennec_$USER)
+-        Defaults to 'org.mozilla.fennec'
+-    :param cmdargs: Arguments to pass into binary.
+-    :returns: A DeviceRunner for Android emulators.
+-    """
+-    kwargs['app_ctx'] = get_app_context('fennec')(app, adb_path=adb_path,
+-                                                  avd_home=avd_home,
+-                                                  device_serial=serial)
+-    device_args = {'app_ctx': kwargs['app_ctx'],
+-                   'avd': avd,
+-                   'binary': binary,
+-                   'logdir': logdir}
+-    return FennecRunner(device_class=EmulatorAVD,
+-                        device_args=device_args,
+-                        **kwargs)
+-
+-
+ runners = {
+     'chrome': ChromeRunner,
+     'default': Runner,
+     'firefox': FirefoxRunner,
+-    'fennec': FennecEmulatorRunner,
+     'thunderbird': ThunderbirdRunner,
+ }

+ 2 - 0
mozilla-release/patches/series

@@ -7099,3 +7099,5 @@ TOP-1905160-NSS3903-11513.patch
 1902849-version-release-mr-25319.patch
 1902851-1-version-prebeta-mr-25320.patch
 1902935-seamonkey-credits-25320.patch
+TOP-1906540-mozrunner-devices-removal-25320.patch
+TOP-1906540-mozdevice-removal-25320.patch