Browse Source

update patch(es) for bug 1906540

Ian Neal 3 months ago
parent
commit
f9af910ce8

+ 3688 - 23
mozilla-release/patches/TOP-1906540-mozdevice-removal-25320.patch → mozilla-release/patches/TOP-1906540-mozdevice-removal-mozilla-25320.patch

@@ -1,8 +1,8 @@
 # HG changeset patch
 # HG changeset patch
 # User Ian Neal <iann_cvs@blueyonder.co.uk>
 # 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
+# Date 1720395449 -3600
+# Parent  6dbc703f582441ac9908b5136433b9add4b157a1
+Bug 1906540 - Remove mozrunner devices and mozdevice from SeaMonkey. r=frg a=frg
 
 
 diff --git a/config/rules.mk b/config/rules.mk
 diff --git a/config/rules.mk b/config/rules.mk
 --- a/config/rules.mk
 --- a/config/rules.mk
@@ -64,7 +64,60 @@ diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py
 diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py
 diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py
 --- a/js/src/tests/lib/jittests.py
 --- a/js/src/tests/lib/jittests.py
 +++ b/js/src/tests/lib/jittests.py
 +++ b/js/src/tests/lib/jittests.py
-@@ -659,20 +659,17 @@ def process_test_results(results, num_te
+@@ -365,52 +365,16 @@ def find_tests(substring=None):
+             if filename in ('shell.js', 'browser.js'):
+                 continue
+             test = os.path.join(dirpath, filename)
+             if substring is None \
+                or substring in os.path.relpath(test, TEST_DIR):
+                 ans.append(test)
+     return ans
+ 
+-def run_test_remote(test, device, prefix, options):
+-    from mozdevice import ADBDevice, ADBProcessError
+-
+-    if options.test_reflect_stringify:
+-        raise ValueError("can't run Reflect.stringify tests remotely")
+-    cmd = test.command(prefix,
+-                       posixpath.join(options.remote_test_root, 'lib/'),
+-                       posixpath.join(options.remote_test_root, 'modules/'),
+-                       posixpath.join(options.remote_test_root, 'tests'))
+-    if options.show_cmd:
+-        print(subprocess.list2cmdline(cmd))
+-
+-    env = {}
+-    if test.tz_pacific:
+-        env['TZ'] = 'PST8PDT'
+-
+-    env['LD_LIBRARY_PATH'] = options.remote_test_root
+-
+-    cmd = ADBDevice._escape_command_line(cmd)
+-    start = datetime.now()
+-    try:
+-        out = device.shell_output(cmd, env=env,
+-                                  cwd=options.remote_test_root,
+-                                  timeout=int(options.timeout))
+-        returncode = 0
+-    except ADBProcessError as e:
+-        out = e.adb_process.stdout
+-        print("exception output: %s" % str(out))
+-        returncode = e.adb_process.exitcode
+-
+-    elapsed = (datetime.now() - start).total_seconds()
+-
+-    # We can't distinguish between stdout and stderr so we pass
+-    # the same buffer to both.
+-    return TestOutput(test, cmd, out, out, returncode, elapsed, False)
+-
+ def check_output(out, err, rc, timed_out, test, options):
+     if timed_out:
+         if os.path.normpath(test.relpath_tests).replace(os.sep, '/') \
+                 in options.ignore_timeouts:
+             return True
+ 
+         # The shell sometimes hangs on shutdown on Windows 7 and Windows
+         # Server 2008. See bug 970063 comment 7 for a description of the
+@@ -659,20 +623,17 @@ def process_test_results(results, num_te
      return print_test_summary(num_tests, failures, complete, doing, options)
      return print_test_summary(num_tests, failures, complete, doing, options)
  
  
  def run_tests(tests, num_tests, prefix, options, remote=False):
  def run_tests(tests, num_tests, prefix, options, remote=False):
@@ -86,7 +139,7 @@ diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py
  
  
  def run_tests_local(tests, num_tests, prefix, options, slog):
  def run_tests_local(tests, num_tests, prefix, options, slog):
      # The jstests tasks runner requires the following options. The names are
      # The jstests tasks runner requires the following options. The names are
-@@ -688,27 +685,16 @@ def run_tests_local(tests, num_tests, pr
+@@ -688,27 +649,16 @@ def run_tests_local(tests, num_tests, pr
      # The test runner wants the prefix as a static on the Test class.
      # The test runner wants the prefix as a static on the Test class.
      JitTest.js_cmd_prefix = prefix
      JitTest.js_cmd_prefix = prefix
  
  
@@ -114,7 +167,7 @@ diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py
  
  
      for file in os.listdir(options.local_lib):
      for file in os.listdir(options.local_lib):
          if file in required_libs:
          if file in required_libs:
-@@ -716,57 +702,10 @@ def push_libs(options, device):
+@@ -716,57 +666,10 @@ def push_libs(options, device):
              device.push(os.path.join(options.local_lib, file), remote_file)
              device.push(os.path.join(options.local_lib, file), remote_file)
  
  
  def push_progs(options, device, progs):
  def push_progs(options, device, progs):
@@ -688,6 +741,670 @@ diff --git a/testing/config/mozbase_requirements.txt b/testing/config/mozbase_re
  ../mozbase/mozlog
  ../mozbase/mozlog
  ../mozbase/moznetwork
  ../mozbase/moznetwork
  ../mozbase/mozprocess
  ../mozbase/mozprocess
+diff --git a/testing/marionette/client/marionette_driver/geckoinstance.py b/testing/marionette/client/marionette_driver/geckoinstance.py
+--- a/testing/marionette/client/marionette_driver/geckoinstance.py
++++ b/testing/marionette/client/marionette_driver/geckoinstance.py
+@@ -11,31 +11,30 @@ import tempfile
+ import time
+ import traceback
+ 
+ from copy import deepcopy
+ 
+ import mozversion
+ 
+ from mozprofile import Profile
+-from mozrunner import Runner, FennecEmulatorRunner
++from mozrunner import Runner
+ import six
+ from six import reraise
+ 
+ from . import errors
+ 
+ 
+ class GeckoInstance(object):
+     required_prefs = {
+         # Increase the APZ content response timeout in tests to 1 minute.
+-        # This is to accommodate the fact that test environments tends to be slower
+-        # than production environments (with the b2g emulator being the slowest of them
+-        # all), resulting in the production timeout value sometimes being exceeded
+-        # and causing false-positive test failures. See bug 1176798, bug 1177018,
+-        # bug 1210465.
++        # This is to accommodate the fact that test environments tends to be
++        # slower than production environments, resulting in the production
++        # timeout value sometimes being exceeded  and causing false-positive
++        # test failures. See bug 1176798, bug 1177018, bug 1210465.
+         "apz.content_response_timeout": 60000,
+ 
+         # Do not send Firefox health reports to the production server
+         "datareporting.healthreport.documentServerURI": "http://%(server)s/dummy/healthreport/",
+ 
+         # Do not show datareporting policy notifications which can interfer with tests
+         "datareporting.policy.dataSubmissionPolicyBypassNotification": True,
+ 
+@@ -324,19 +323,16 @@ class GeckoInstance(object):
+             "symbols_path": self.symbols_path,
+             "process_args": process_args
+         }
+ 
+     def close(self, clean=False):
+         """
+         Close the managed Gecko process.
+ 
+-        Depending on self.runner_class, setting `clean` to True may also kill
+-        the emulator process in which this instance is running.
+-
+         :param clean: If True, also perform runner cleanup.
+         """
+         if self.runner:
+             self.runner.stop()
+             if clean:
+                 self.runner.cleanup()
+ 
+         if clean:
+@@ -355,144 +351,16 @@ class GeckoInstance(object):
+             self.prefs = prefs
+         else:
+             self.prefs = None
+ 
+         self.close(clean=clean)
+         self.start()
+ 
+ 
+-class FennecInstance(GeckoInstance):
+-    fennec_prefs = {
+-        # Enable output of dump()
+-        "browser.dom.window.dump.enabled": True,
+-
+-        # Disable Android snippets
+-        "browser.snippets.enabled": False,
+-        "browser.snippets.syncPromo.enabled": False,
+-        "browser.snippets.firstrunHomepage.enabled": False,
+-
+-        # Disable safebrowsing components
+-        "browser.safebrowsing.blockedURIs.enabled": False,
+-        "browser.safebrowsing.downloads.enabled": False,
+-        "browser.safebrowsing.passwords.enabled": False,
+-        "browser.safebrowsing.malware.enabled": False,
+-        "browser.safebrowsing.phishing.enabled": False,
+-
+-        # Do not restore the last open set of tabs if the browser has crashed
+-        "browser.sessionstore.resume_from_crash": False,
+-
+-        # Disable e10s by default
+-        "browser.tabs.remote.autostart": False,
+-
+-        # Do not allow background tabs to be zombified, otherwise for tests that
+-        # open additional tabs, the test harness tab itself might get unloaded
+-        "browser.tabs.disableBackgroundZombification": True,
+-    }
+-
+-    def __init__(self, emulator_binary=None, avd_home=None, avd=None,
+-                 adb_path=None, serial=None, connect_to_running_emulator=False,
+-                 package_name=None, *args, **kwargs):
+-        super(FennecInstance, self).__init__(*args, **kwargs)
+-        self.required_prefs.update(FennecInstance.fennec_prefs)
+-
+-        self.runner_class = FennecEmulatorRunner
+-        # runner args
+-        self._package_name = package_name
+-        self.emulator_binary = emulator_binary
+-        self.avd_home = avd_home
+-        self.adb_path = adb_path
+-        self.avd = avd
+-        self.serial = serial
+-        self.connect_to_running_emulator = connect_to_running_emulator
+-
+-    @property
+-    def package_name(self):
+-        """
+-        Name of app to run on emulator.
+-
+-        Note that FennecInstance does not use self.binary
+-        """
+-        if self._package_name is None:
+-            self._package_name = "org.mozilla.fennec"
+-            user = os.getenv("USER")
+-            if user:
+-                self._package_name += "_" + user
+-        return self._package_name
+-
+-    def start(self):
+-        self._update_profile(self.profile)
+-        self.runner = self.runner_class(**self._get_runner_args())
+-        try:
+-            if self.connect_to_running_emulator:
+-                self.runner.device.connect()
+-            self.runner.start()
+-        except Exception as e:
+-            exc, val, tb = sys.exc_info()
+-            message = "Error possibly due to runner or device args: {}"
+-            reraise(exc, message.format(e.message), tb)
+-        # gecko_log comes from logcat when running with device/emulator
+-        logcat_args = {
+-            "filterspec": "Gecko",
+-            "serial": self.runner.device.app_ctx.device_serial
+-        }
+-        if self.gecko_log == "-":
+-            logcat_args["stream"] = sys.stdout
+-        else:
+-            logcat_args["logfile"] = self.gecko_log
+-        self.runner.device.start_logcat(**logcat_args)
+-
+-        # forward marionette port (localhost:2828)
+-        self.runner.device.device.forward(
+-            local="tcp:{}".format(self.marionette_port),
+-            remote="tcp:{}".format(self.marionette_port))
+-
+-    def _get_runner_args(self):
+-        process_args = {
+-            "processOutputLine": [NullOutput()],
+-            "universal_newlines": True,
+-        }
+-
+-        runner_args = {
+-            "app": self.package_name,
+-            "avd_home": self.avd_home,
+-            "adb_path": self.adb_path,
+-            "binary": self.emulator_binary,
+-            "profile": self.profile,
+-            "cmdargs": ["-marionette"] + self.app_args,
+-            "symbols_path": self.symbols_path,
+-            "process_args": process_args,
+-            "logdir": self.workspace or os.getcwd(),
+-            "serial": self.serial,
+-        }
+-        if self.avd:
+-            runner_args["avd"] = self.avd
+-
+-        return runner_args
+-
+-    def close(self, clean=False):
+-        """
+-        Close the managed Gecko process.
+-
+-        If `clean` is True and the Fennec instance is running in an
+-        emulator managed by mozrunner, this will stop the emulator.
+-
+-        :param clean: If True, also perform runner cleanup.
+-        """
+-        super(FennecInstance, self).close(clean)
+-        if clean and self.runner and self.runner.device.connected:
+-            try:
+-                self.runner.device.device.remove_forwards(
+-                    "tcp:{}".format(self.marionette_port))
+-                self.unresponsive_count = 0
+-            except Exception:
+-                self.unresponsive_count += 1
+-                traceback.print_exception(*sys.exc_info())
+-
+-
+ class DesktopInstance(GeckoInstance):
+     desktop_prefs = {
+         # Disable application updates
+         "app.update.enabled": False,
+ 
+         # Enable output of dump()
+         "browser.dom.window.dump.enabled": True,
+ 
+@@ -579,18 +447,16 @@ class ThunderbirdInstance(GeckoInstance)
+ 
+ 
+ class NullOutput(object):
+     def __call__(self, line):
+         pass
+ 
+ 
+ apps = {
+-    'fennec': FennecInstance,
+     'fxdesktop': DesktopInstance,
+     'thunderbird': ThunderbirdInstance,
+ }
+ 
+ app_ids = {
+-    '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'fennec',
+     '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'fxdesktop',
+     '{3550f703-e582-4d05-9a08-453d09bdfdc6}': 'thunderbird',
+ }
+diff --git a/testing/marionette/harness/marionette_harness/runner/base.py b/testing/marionette/harness/marionette_harness/runner/base.py
+--- a/testing/marionette/harness/marionette_harness/runner/base.py
++++ b/testing/marionette/harness/marionette_harness/runner/base.py
+@@ -262,23 +262,16 @@ class BaseMarionetteArguments(ArgumentPa
+                                'One or more paths to test files (Python or JS), '
+                                'manifest files (.ini) or directories. '
+                                'When a directory is specified, '
+                                'all test files in the directory will be run.')
+         self.add_argument('--binary',
+                           help='path to gecko executable to launch before running the test')
+         self.add_argument('--address',
+                           help='host:port of running Gecko instance to connect to')
+-        self.add_argument('--emulator',
+-                          action='store_true',
+-                          help='If no --address is given, then the harness will launch an '
+-                               'emulator. (See Remote options group.) '
+-                               'If --address is given, then the harness assumes you are '
+-                               'running an emulator already, and will launch gecko app '
+-                               'on that emulator.')
+         self.add_argument('--app',
+                           help='application to use. see marionette_driver.geckoinstance')
+         self.add_argument('--app-arg',
+                           dest='app_args',
+                           action='append',
+                           default=[],
+                           help='specify a command line argument to be passed onto the application')
+         self.add_argument('--profile',
+@@ -377,17 +370,16 @@ class BaseMarionetteArguments(ArgumentPa
+                           default=None,
+                           help="Path to directory for Marionette output. "
+                                "(Default: .) (Default profile dest: TMP)",
+                           type=dir_path)
+         self.add_argument('-v', '--verbose',
+                           action='count',
+                           help='Increase verbosity to include debug messages with -v, '
+                                'and trace messages with -vv.')
+-        self.register_argument_container(RemoteMarionetteArguments())
+ 
+     def register_argument_container(self, container):
+         group = self.add_argument_group(container.name)
+ 
+         for cli, kwargs in container.args:
+             group.add_argument(*cli, **kwargs)
+ 
+         self.argument_containers.append(container)
+@@ -429,18 +421,18 @@ class BaseMarionetteArguments(ArgumentPa
+     def verify_usage(self, args):
+         if not args.tests:
+             self.error('You must specify one or more test files, manifests, or directories.')
+ 
+         missing_tests = [path for path in args.tests if not os.path.exists(path)]
+         if missing_tests:
+             self.error("Test file(s) not found: " + " ".join([path for path in missing_tests]))
+ 
+-        if not args.address and not args.binary and not args.emulator:
+-            self.error('You must specify --binary, or --address, or --emulator')
++        if not args.address and not args.binary:
++            self.error('You must specify --binary, or --address')
+ 
+         if args.repeat is not None and args.repeat < 0:
+             self.error('The value of --repeat has to be equal or greater than 0.')
+ 
+         if args.total_chunks is not None and args.this_chunk is None:
+             self.error('You must specify which chunk to run.')
+ 
+         if args.this_chunk is not None and args.total_chunks is None:
+@@ -460,47 +452,16 @@ class BaseMarionetteArguments(ArgumentPa
+ 
+         for container in self.argument_containers:
+             if hasattr(container, 'verify_usage_handler'):
+                 container.verify_usage_handler(args)
+ 
+         return args
+ 
+ 
+-class RemoteMarionetteArguments(object):
+-    name = 'Remote (Emulator/Device)'
+-    args = [
+-        [['--emulator-binary'],
+-         {'help': 'Path to emulator binary. By default mozrunner uses `which emulator`',
+-          'dest': 'emulator_bin',
+-          }],
+-        [['--adb'],
+-         {'help': 'Path to the adb. By default mozrunner uses `which adb`',
+-          'dest': 'adb_path'
+-          }],
+-        [['--avd'],
+-         {'help': ('Name of an AVD available in your environment.'
+-                   'See mozrunner.FennecEmulatorRunner'),
+-          }],
+-        [['--avd-home'],
+-         {'help': 'Path to avd parent directory',
+-          }],
+-        [['--device'],
+-         {'help': ('Serial ID to connect to as seen in `adb devices`,'
+-                   'e.g emulator-5444'),
+-          'dest': 'device_serial',
+-          }],
+-        [['--package'],
+-         {'help': 'Name of Android package, e.g. org.mozilla.fennec',
+-          'dest': 'package_name',
+-          }],
+-
+-    ]
+-
+-
+ class Fixtures(object):
+     def where_is(self, uri, on="http"):
+         return serve.where_is(uri, on)
+ 
+ 
+ class BaseMarionetteTestRunner(object):
+ 
+     textrunnerclass = MarionetteTextTestRunner
+@@ -515,31 +476,30 @@ class BaseMarionetteTestRunner(object):
+                  symbols_path=None,
+                  shuffle=False, shuffle_seed=random.randint(0, sys.maxint), this_chunk=1,
+                  total_chunks=1,
+                  server_root=None, gecko_log=None, result_callbacks=None,
+                  prefs=None, test_tags=None,
+                  socket_timeout=None,
+                  startup_timeout=None,
+                  addons=None, workspace=None,
+-                 verbose=0, e10s=True, emulator=False, headless=False, **kwargs):
++                 verbose=0, e10s=True, headless=False, **kwargs):
+         self._appName = None
+         self._capabilities = None
+         self._filename_pattern = None
+         self._version_info = {}
+ 
+         self.fixture_servers = {}
+         self.fixtures = Fixtures()
+         self.extra_kwargs = kwargs
+         self.test_kwargs = deepcopy(kwargs)
+         self.address = address
+         self.app = app
+         self.app_args = app_args or []
+         self.bin = binary
+-        self.emulator = emulator
+         self.profile = profile
+         self.addons = addons
+         self.logger = logger
+         self.marionette = None
+         self.logdir = logdir
+         self.repeat = repeat or 0
+         self.run_until_failure = run_until_failure or False
+         self.symbols_path = symbols_path
+@@ -721,17 +681,17 @@ class BaseMarionetteTestRunner(object):
+ 
+         kwargs = {
+             'socket_timeout': self.socket_timeout,
+             'prefs': self.prefs,
+             'startup_timeout': self.startup_timeout,
+             'verbose': self.verbose,
+             'symbols_path': self.symbols_path,
+         }
+-        if self.bin or self.emulator:
++        if self.bin:
+             kwargs.update({
+                 'host': 'localhost',
+                 'port': 2828,
+                 'app': self.app,
+                 'app_args': self.app_args,
+                 'profile': self.profile,
+                 'addons': self.addons,
+                 'gecko_log': self.gecko_log,
+@@ -739,36 +699,23 @@ class BaseMarionetteTestRunner(object):
+                 'bin': True,
+             })
+ 
+         if self.bin:
+             kwargs.update({
+                 'bin': self.bin,
+             })
+ 
+-        if self.emulator:
+-            kwargs.update({
+-                'avd_home': self.extra_kwargs.get('avd_home'),
+-                'adb_path': self.extra_kwargs.get('adb_path'),
+-                'emulator_binary': self.extra_kwargs.get('emulator_bin'),
+-                'avd': self.extra_kwargs.get('avd'),
+-                'package_name': self.extra_kwargs.get('package_name'),
+-            })
+-
+         if self.address:
+             host, port = self.address.split(':')
+             kwargs.update({
+                 'host': host,
+                 'port': int(port),
+             })
+-            if self.emulator:
+-                kwargs.update({
+-                    'connect_to_running_emulator': True,
+-                })
+-            if not self.bin and not self.emulator:
++            if not self.bin:
+                 try:
+                     # Establish a socket connection so we can vertify the data come back
+                     connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                     connection.connect((host, int(port)))
+                     connection.close()
+                 except Exception as e:
+                     exc, val, tb = sys.exc_info()
+                     msg = "Connection attempt to {0}:{1} failed with error: {2}"
+@@ -864,39 +811,31 @@ class BaseMarionetteTestRunner(object):
+             for url in serve.iter_url(self.fixture_servers):
+                 self.logger.info("Fixture server listening on %s" % url)
+ 
+             # backwards compatibility
+             self.marionette.baseurl = serve.where_is("/")
+ 
+         self._add_tests(tests)
+ 
+-        device_info = None
+-        if self.marionette.instance and self.emulator:
+-            try:
+-                device_info = self.marionette.instance.runner.device.device.get_info()
+-            except Exception:
+-                self.logger.warning('Could not get device info', exc_info=True)
+-
+         self.marionette.start_session()
+         self.logger.info("e10s is {}".format("enabled" if self.is_e10s else "disabled"))
+         if self.e10s != self.is_e10s:
+             self.cleanup()
+             raise AssertionError("BaseMarionetteTestRunner configuration (self.e10s) "
+                                  "does not match browser appinfo (self.is_e10s)")
+         self.marionette.delete_session()
+ 
+         tests_by_group = defaultdict(list)
+         for test in self.tests:
+             tests_by_group[test['group']].append(test['filepath'])
+ 
+         self.logger.suite_start(tests_by_group,
+                                 name='marionette-test',
+-                                version_info=self.version_info,
+-                                device_info=device_info)
++                                version_info=self.version_info)
+ 
+         self._log_skipped_tests()
+ 
+         interrupted = None
+         try:
+             repeat_index = 0
+             while repeat_index <= self.repeat:
+                 if repeat_index > 0:
+diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py
+--- a/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py
++++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/conftest.py
+@@ -24,30 +24,25 @@ def logger():
+ 
+ @pytest.fixture
+ def mach_parsed_kwargs(logger):
+     """
+     Parsed and verified dictionary used during simplest
+     call to mach marionette-test
+     """
+     return {
+-        'adb_path': None,
+         'addons': None,
+         'address': None,
+         'app': None,
+         'app_args': [],
+-        'avd': None,
+-        'avd_home': None,
+         'binary': u'/path/to/firefox',
+         'browsermob_port': None,
+         'browsermob_script': None,
+         'device_serial': None,
+         'e10s': True,
+-        'emulator': False,
+-        'emulator_bin': None,
+         'gecko_log': None,
+         'jsdebugger': False,
+         'log_errorsummary': None,
+         'log_html': None,
+         'log_mach': None,
+         'log_mach_buffer': None,
+         'log_mach_level': None,
+         'log_mach_verbose': None,
+diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py
+--- a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py
++++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_arguments.py
+@@ -43,27 +43,10 @@ def test_parsing_optional_arguments(mach
+     result = vars(parsed_args)
+     assert result.get(arg_dest) == expected_value
+     mach_parsed_kwargs[arg_dest] = result[arg_dest]
+     runner = MarionetteTestRunner(**mach_parsed_kwargs)
+     built_kwargs = runner._build_kwargs()
+     assert built_kwargs[arg_dest] == expected_value
+ 
+ 
+-@pytest.mark.parametrize("arg_name, arg_dest, arg_value, expected_value",
+-                         [('adb', 'adb_path', 'samplevalue', 'samplevalue'),
+-                          ('avd', 'avd', 'samplevalue', 'samplevalue'),
+-                          ('avd-home', 'avd_home', 'samplevalue', 'samplevalue'),
+-                          ('package', 'package_name', 'samplevalue', 'samplevalue')])
+-def test_parse_opt_args_emulator(mach_parsed_kwargs, arg_name, arg_dest, arg_value, expected_value):
+-    parser = MarionetteArguments()
+-    parsed_args = parser.parse_args(['--' + arg_name, arg_value])
+-    result = vars(parsed_args)
+-    assert result.get(arg_dest) == expected_value
+-    mach_parsed_kwargs[arg_dest] = result[arg_dest]
+-    mach_parsed_kwargs["emulator"] = True
+-    runner = MarionetteTestRunner(**mach_parsed_kwargs)
+-    built_kwargs = runner._build_kwargs()
+-    assert built_kwargs[arg_dest] == expected_value
+-
+-
+ if __name__ == '__main__':
+     mozunit.main('-p', 'no:terminalreporter', '--log-tbpl=-')
+diff --git a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py
+--- a/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py
++++ b/testing/marionette/harness/marionette_harness/tests/harness_unit/test_marionette_runner.py
+@@ -50,19 +50,19 @@ def build_kwargs_using(mach_parsed_kwarg
+             return built_kwargs, socket
+         return built_kwargs
+     return kwarg_builder
+ 
+ 
+ @pytest.fixture
+ def expected_driver_args(runner):
+     '''Helper fixture for tests of _build_kwargs
+-    with binary/emulator.
++    with binary.
+     Provides a dictionary of certain arguments
+-    related to binary/emulator settings
++    related to binary settings
+     which we expect to be passed to the
+     driverclass constructor. Expected values can
+     be updated in tests as needed.
+     Provides convenience methods for comparing the
+     expected arguments to the argument dictionary
+     created by _build_kwargs. '''
+ 
+     class ExpectedDict(dict):
+@@ -142,73 +142,16 @@ def test_build_kwargs_basic_args(build_k
+ def test_build_kwargs_with_workspace(build_kwargs_using, workspace):
+     built_kwargs = build_kwargs_using({'workspace': workspace})
+     if workspace:
+         assert built_kwargs['workspace'] == workspace
+     else:
+         assert 'workspace' not in built_kwargs
+ 
+ 
+-@pytest.mark.parametrize('address', ['host:123', None])
+-def test_build_kwargs_with_address(build_kwargs_using, address):
+-    built_kwargs, socket = build_kwargs_using(
+-        {'address': address, 'binary': None, 'emulator': None},
+-        return_socket=True
+-    )
+-    assert 'connect_to_running_emulator' not in built_kwargs
+-    if address is not None:
+-        host, port = address.split(":")
+-        assert built_kwargs['host'] == host and built_kwargs['port'] == int(port)
+-        socket.socket().connect.assert_called_with((host, int(port)))
+-        assert socket.socket().close.called
+-    else:
+-        assert not socket.socket.called
+-
+-
+-@pytest.mark.parametrize('address', ['host:123', None])
+-@pytest.mark.parametrize('binary', ['path/to/bin', None])
+-def test_build_kwargs_with_binary_or_address(expected_driver_args, build_kwargs_using,
+-                                             binary, address):
+-    built_kwargs = build_kwargs_using({'binary': binary, 'address': address, 'emulator': None})
+-    if binary:
+-        expected_driver_args['bin'] = binary
+-        if address:
+-            host, port = address.split(":")
+-            expected_driver_args.update({'host': host, 'port': int(port)})
+-        else:
+-            expected_driver_args.update({'host': 'localhost', 'port': 2828})
+-        expected_driver_args.assert_matches(built_kwargs)
+-    elif address is None:
+-        expected_driver_args.assert_keys_not_in(built_kwargs)
+-
+-
+-@pytest.mark.parametrize('address', ['host:123', None])
+-@pytest.mark.parametrize('emulator', [True, False, None])
+-def test_build_kwargs_with_emulator_or_address(expected_driver_args, build_kwargs_using,
+-                                               emulator, address):
+-    emulator_props = [(a, getattr(sentinel, a)) for a in ['avd_home', 'adb_path', 'emulator_bin']]
+-    built_kwargs = build_kwargs_using(
+-        [('emulator', emulator),  ('address', address), ('binary', None)] + emulator_props
+-    )
+-    if emulator:
+-        expected_driver_args.update(emulator_props)
+-        expected_driver_args['emulator_binary'] = expected_driver_args.pop('emulator_bin')
+-        expected_driver_args['bin'] = True
+-        if address:
+-            expected_driver_args['connect_to_running_emulator'] = True
+-            host, port = address.split(":")
+-            expected_driver_args.update({'host': host, 'port': int(port)})
+-        else:
+-            expected_driver_args.update({'host': 'localhost', 'port': 2828})
+-            assert 'connect_to_running_emulator' not in built_kwargs
+-        expected_driver_args.assert_matches(built_kwargs)
+-    elif not address:
+-        expected_driver_args.assert_keys_not_in(built_kwargs)
+-
+-
+ def test_parsing_testvars(mach_parsed_kwargs):
+     mach_parsed_kwargs.pop('tests')
+     testvars_json_loads = [
+         {"wifi": {"ssid": "blah", "keyManagement": "WPA-PSK", "psk": "foo"}},
+         {"wifi": {"PEAP": "bar"}, "device": {"stuff": "buzz"}}
+     ]
+     expected_dict = {
+          "wifi": {
+diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
+--- a/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
++++ b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
+@@ -41,27 +41,16 @@ class TestPreferences(MarionetteTestCase
+ 
+         for key, value in required_prefs.iteritems():
+             if key in ["browser.tabs.remote.autostart"]:
+                 return
+ 
+             self.assertEqual(self.marionette.get_pref(key), value,
+                              "Preference {} hasn't been set to {}".format(key, value))
+ 
+-    @skip_if_desktop("Only runnable with Fennec")
+-    def test_fennec_instance_preferences(self):
+-        required_prefs = geckoinstance.FennecInstance.required_prefs
+-
+-        for key, value in required_prefs.iteritems():
+-            if key in ["browser.tabs.remote.autostart"]:
+-                return
+-
+-            self.assertEqual(self.marionette.get_pref(key), value,
+-                             "Preference {} hasn't been set to {}".format(key, value))
+-
+     def test_clear_pref(self):
+         self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
+ 
+         self.marionette.set_pref(self.prefs["bool"], True)
+         self.assertTrue(self.marionette.get_pref(self.prefs["bool"]))
+ 
+         self.marionette.clear_pref(self.prefs["bool"])
+         self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
 diff --git a/testing/marionette/harness/requirements.txt b/testing/marionette/harness/requirements.txt
 diff --git a/testing/marionette/harness/requirements.txt b/testing/marionette/harness/requirements.txt
 --- a/testing/marionette/harness/requirements.txt
 --- a/testing/marionette/harness/requirements.txt
 +++ b/testing/marionette/harness/requirements.txt
 +++ b/testing/marionette/harness/requirements.txt
@@ -705,6 +1422,48 @@ diff --git a/testing/marionette/harness/requirements.txt b/testing/marionette/ha
  mozrunner >= 7.4.0
  mozrunner >= 7.4.0
  moztest >= 0.8
  moztest >= 0.8
  mozversion >= 2.1.0
  mozversion >= 2.1.0
+diff --git a/testing/marionette/mach_commands.py b/testing/marionette/mach_commands.py
+--- a/testing/marionette/mach_commands.py
++++ b/testing/marionette/mach_commands.py
+@@ -16,17 +16,17 @@ from mach.decorators import (
+     Command,
+ )
+ 
+ from mozbuild.base import (
+     MachCommandBase,
+     MachCommandConditions as conditions,
+ )
+ 
+-SUPPORTED_APPS = ['firefox', 'android', 'thunderbird']
++SUPPORTED_APPS = ['firefox', 'thunderbird']
+ 
+ def create_parser_tests():
+     from marionette_harness.runtests import MarionetteArguments
+     from mozlog.structured import commandline
+     parser = MarionetteArguments()
+     commandline.add_logging_group(parser)
+     return parser
+ 
+@@ -80,17 +80,13 @@ class MarionetteTest(MachCommandBase):
+         if not tests:
+             if conditions.is_thunderbird(self):
+                 tests = [os.path.join(self.topsrcdir,
+                          "comm/testing/marionette/unit-tests.ini")]
+             else:
+                 tests = [os.path.join(self.topsrcdir,
+                          "testing/marionette/harness/marionette_harness/tests/unit-tests.ini")]
+ 
+-        # Force disable e10s because it is not supported in Fennec
+-        if kwargs.get("app") == "fennec":
+-            kwargs["e10s"] = False
+-
+         if not kwargs.get("binary") and \
+                 (conditions.is_firefox(self) or conditions.is_thunderbird(self)):
+             kwargs["binary"] = self.get_binary_path("app")
+ 
+-        return run_marionette(tests, topsrcdir=self.topsrcdir, **kwargs)
+\ No newline at end of file
++        return run_marionette(tests, topsrcdir=self.topsrcdir, **kwargs)
 diff --git a/testing/mochitest/mach_commands.py b/testing/mochitest/mach_commands.py
 diff --git a/testing/mochitest/mach_commands.py b/testing/mochitest/mach_commands.py
 --- a/testing/mochitest/mach_commands.py
 --- a/testing/mochitest/mach_commands.py
 +++ b/testing/mochitest/mach_commands.py
 +++ b/testing/mochitest/mach_commands.py
@@ -1905,6 +2664,85 @@ deleted file mode 100644
 -
 -
 -if __name__ == "__main__":
 -if __name__ == "__main__":
 -    sys.exit(main())
 -    sys.exit(main())
+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/moz.build b/testing/mozbase/moz.build
 diff --git a/testing/mozbase/moz.build b/testing/mozbase/moz.build
 --- a/testing/mozbase/moz.build
 --- a/testing/mozbase/moz.build
 +++ b/testing/mozbase/moz.build
 +++ b/testing/mozbase/moz.build
@@ -7335,26 +8173,2853 @@ deleted file mode 100644
 -      dm = mozdevice.dmcli:cli
 -      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
+diff --git a/testing/mozbase/mozlog/mozlog/formatters/html/html.py b/testing/mozbase/mozlog/mozlog/formatters/html/html.py
+--- a/testing/mozbase/mozlog/mozlog/formatters/html/html.py
++++ b/testing/mozbase/mozlog/mozlog/formatters/html/html.py
+@@ -82,22 +82,16 @@ class HTMLFormatter(base.BaseFormatter):
  
  
- PACKAGE_NAME = 'mozrunner'
- PACKAGE_VERSION = '7.4.0'
+             if version_info.get("gaia_changeset"):
+                 self.env["Gaia revision"] = html.a(
+                     version_info.get("gaia_changeset")[:12],
+                     href="https://github.com/mozilla-b2g/gaia/commit/%s" % version_info.get(
+                         "gaia_changeset"),
+                     target="_blank")
  
  
- desc = """Reliable start/stop/configuration of Mozilla Applications (Firefox, Thunderbird, etc.)"""
+-        device_info = data.get("device_info")
+-        if device_info:
+-            self.env["Device uptime"] = device_info.get("uptime")
+-            self.env["Device memory"] = device_info.get("memtotal")
+-            self.env["Device serial"] = device_info.get("id")
+-
+     def suite_end(self, data):
+         self.suite_times["end"] = data["time"]
+         return self.generate_html()
+ 
+     def test_start(self, data):
+         self.start_times[data["test"]] = data["time"]
+ 
+     def test_end(self, data):
+diff --git a/testing/mozbase/mozlog/mozlog/structuredlog.py b/testing/mozbase/mozlog/mozlog/structuredlog.py
+--- a/testing/mozbase/mozlog/mozlog/structuredlog.py
++++ b/testing/mozbase/mozlog/mozlog/structuredlog.py
+@@ -298,27 +298,25 @@ class StructuredLogger(object):
+                 return False
+             self._state.suite_started = False
+         return True
+ 
+     @log_action(TestList("tests"),
+                 Unicode("name", default=None, optional=True),
+                 Dict(Any, "run_info", default=None, optional=True),
+                 Dict(Any, "version_info", default=None, optional=True),
+-                Dict(Any, "device_info", default=None, optional=True),
+                 Dict(Any, "extra", default=None, optional=True))
+     def suite_start(self, data):
+         """Log a suite_start message
+ 
+         :param dict tests: Test identifiers that will be run in the suite, keyed by group name.
+         :param str name: Optional name to identify the suite.
+         :param dict run_info: Optional information typically provided by mozinfo.
+         :param dict version_info: Optional target application version information provided
+           by mozversion.
+-        :param dict device_info: Optional target device information provided by mozdevice.
+         """
+         if not self._ensure_suite_state('suite_start', data):
+             return
+ 
+         self._log_data("suite_start", data)
+ 
+     @log_action(Dict(Any, "extra", default=None, optional=True))
+     def suite_end(self, data):
+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,
+ }
+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/mozversion/mozversion/mozversion.py b/testing/mozbase/mozversion/mozversion/mozversion.py
+--- a/testing/mozbase/mozversion/mozversion/mozversion.py
++++ b/testing/mozbase/mozversion/mozversion/mozversion.py
+@@ -47,38 +47,16 @@ class Version(object):
+             self._info['%s_%s' % (type, name)] = config.has_option(
+                 section, key) and config.get(section, key) or None
+ 
+         if not self._info.get('application_display_name'):
+             self._info['application_display_name'] = \
+                 self._info.get('application_name')
+ 
+ 
+-class LocalFennecVersion(Version):
+-
+-    def __init__(self, path, **kwargs):
+-        Version.__init__(self, **kwargs)
+-        self.get_gecko_info(path)
+-
+-    def get_gecko_info(self, path):
+-        archive = zipfile.ZipFile(path, 'r')
+-        archive_list = archive.namelist()
+-        for type, section in INI_DATA_MAPPING:
+-            filename = "%s.ini" % type
+-            if filename in archive_list:
+-                fp = io.TextIOWrapper(archive.open(filename))
+-                self._parse_ini_file(fp, type, section)
+-            else:
+-                self._logger.warning('Unable to find %s' % filename)
+-
+-        if "package-name.txt" in archive_list:
+-            fp = io.TextIOWrapper(archive.open("package-name.txt"))
+-            self._info["package_name"] = fp.readlines()[0].strip()
+-
+-
+ class LocalVersion(Version):
+ 
+     def __init__(self, binary, **kwargs):
+         Version.__init__(self, **kwargs)
+ 
+         if binary:
+             # on Windows, the binary may be specified with or without the
+             # .exe extension
+@@ -106,28 +84,23 @@ class LocalVersion(Version):
+     def check_location(self, path):
+         return (os.path.exists(os.path.join(path, 'application.ini'))
+                 and os.path.exists(os.path.join(path, 'platform.ini')))
+ 
+ 
+ def get_version(binary=None):
+     """
+     Returns the application version information as a dict. You can specify
+-    a path to the binary of the application or an Android APK file (to get
+-    version information for Firefox for Android). If this is omitted then the
++    a path to the binary of the application. If this is omitted then the
+     current directory is checked for the existance of an application.ini
+     file.
+ 
+-    :param binary: Path to the binary for the application or Android APK file
++    :param binary: Path to the binary for the application
+     """
+-    if binary and zipfile.is_zipfile(binary) and 'AndroidManifest.xml' in \
+-       zipfile.ZipFile(binary, 'r').namelist():
+-        version = LocalFennecVersion(binary)
+-    else:
+-        version = LocalVersion(binary)
++    version = LocalVersion(binary)
+ 
+     for (key, value) in sorted(version._info.items()):
+         if value:
+             version._logger.info('%s: %s' % (key, value))
+ 
+     return version._info
  
  
- 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
 diff --git a/testing/mozbase/packages.txt b/testing/mozbase/packages.txt
 --- a/testing/mozbase/packages.txt
 --- a/testing/mozbase/packages.txt

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

@@ -1,2781 +0,0 @@
-# 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,
- }

+ 1 - 2
mozilla-release/patches/series

@@ -7100,5 +7100,4 @@ TOP-1905160-NSS3903-11513.patch
 1902851-1-version-prebeta-mr-25320.patch
 1902851-1-version-prebeta-mr-25320.patch
 1902935-seamonkey-credits-25320.patch
 1902935-seamonkey-credits-25320.patch
 1585358-71a1.patch
 1585358-71a1.patch
-TOP-1906540-mozrunner-devices-removal-25320.patch
-TOP-1906540-mozdevice-removal-25320.patch
+TOP-1906540-mozdevice-removal-mozilla-25320.patch