Bug 1440714 - Convert Android browser test harnesses to adb.py; r=bc
authorGeoff Brown <gbrown@mozilla.com>
Fri, 23 Mar 2018 18:06:27 -0600
changeset 409753 aad77f9bd0931feb72b3cc04a2030e4cc97573f1
parent 409752 3e2fb7146ea97a7ec2160f3e41ea3fc3bb483b17
child 409754 5b91410deb5b02599f99e67a615b613bf7b9971b
push id33699
push userccoroiu@mozilla.com
push dateSat, 24 Mar 2018 09:42:51 +0000
treeherdermozilla-central@945baa8bc960 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbc
bugs1440714
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1440714 - Convert Android browser test harnesses to adb.py; r=bc This affects Android robocop, mochitest (all flavors) and reftests (all flavors).
build/mobile/remoteautomation.py
layout/tools/reftest/reftestcommandline.py
layout/tools/reftest/remotereftest.py
testing/mochitest/mochitest_options.py
testing/mochitest/rungeckoview.py
testing/mochitest/runrobocop.py
testing/mochitest/runtestsremote.py
testing/mozharness/configs/android/android_common.py
testing/xpcshell/remotexpcshelltests.py
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -8,65 +8,45 @@ import time
 import re
 import os
 import posixpath
 import tempfile
 import shutil
 import sys
 
 from automation import Automation
-from mozdevice import DMError, DeviceManager
 from mozlog import get_default_logger
 from mozscreenshot import dump_screen
 import mozcrash
 
 # signatures for logcat messages that we don't care about much
 fennecLogcatFilters = [ "The character encoding of the HTML document was not declared",
                         "Use of Mutation Events is deprecated. Use MutationObserver instead.",
                         "Unexpected value from nativeGetEnabledTags: 0" ]
 
 class RemoteAutomation(Automation):
-    _devicemanager = None
 
-    def __init__(self, deviceManager, appName = '', remoteLog = None,
+    def __init__(self, device, appName = '', remoteProfile = None, remoteLog = None,
                  processArgs=None):
-        self._dm = deviceManager
+        self._device = device
         self._appName = appName
-        self._remoteProfile = None
+        self._remoteProfile = remoteProfile
         self._remoteLog = remoteLog
         self._processArgs = processArgs or {};
 
         self.lastTestSeen = "remoteautomation.py"
         Automation.__init__(self)
 
-    def setDeviceManager(self, deviceManager):
-        self._dm = deviceManager
-
-    def setAppName(self, appName):
-        self._appName = appName
-
-    def setRemoteProfile(self, remoteProfile):
-        self._remoteProfile = remoteProfile
-
-    def setRemoteLog(self, logfile):
-        self._remoteLog = logfile
-
     # Set up what we need for the remote environment
     def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, lsanPath=None, ubsanPath=None):
         # Because we are running remote, we don't want to mimic the local env
         # so no copying of os.environ
         if env is None:
             env = {}
 
-        # Except for the mochitest results table hiding option, which isn't
-        # passed to runtestsremote.py as an actual option, but through the
-        # MOZ_HIDE_RESULTS_TABLE environment variable.
-        if 'MOZ_HIDE_RESULTS_TABLE' in os.environ:
-            env['MOZ_HIDE_RESULTS_TABLE'] = os.environ['MOZ_HIDE_RESULTS_TABLE']
-
         if crashreporter and not debugger:
             env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
             env['MOZ_CRASHREPORTER'] = '1'
         else:
             env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
         # Crash on non-local network connections by default.
         # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
@@ -96,17 +76,17 @@ class RemoteAutomation(Automation):
             If maxTime seconds elapse or no output is detected for timeout
             seconds, kill the process and fail the test.
         """
         proc.utilityPath = utilityPath
         # maxTime is used to override the default timeout, we should honor that
         status = proc.wait(timeout = maxTime, noOutputTimeout = timeout)
         self.lastTestSeen = proc.getLastTestSeen
 
-        topActivity = self._dm.getTopActivity()
+        topActivity = self._device.get_top_activity(timeout=60)
         if topActivity == proc.procName:
             print "Browser unexpectedly found running. Killing..."
             proc.kill(True)
         if status == 1:
             if maxTime:
                 print "TEST-UNEXPECTED-FAIL | %s | application ran for longer than " \
                       "allowed maximum time of %s seconds" % (self.lastTestSeen, maxTime)
             else:
@@ -118,71 +98,53 @@ class RemoteAutomation(Automation):
 
         return status
 
     def deleteANRs(self):
         # empty ANR traces.txt file; usually need root permissions
         # we make it empty and writable so we can test the ANR reporter later
         traces = "/data/anr/traces.txt"
         try:
-            self._dm.shellCheckOutput(['echo', '', '>', traces], root=True,
-                                       timeout=DeviceManager.short_timeout)
-            self._dm.shellCheckOutput(['chmod', '666', traces], root=True,
-                                       timeout=DeviceManager.short_timeout)
-        except DMError:
-            print "Error deleting %s" % traces
-            pass
+            self._device.shell_output('echo > %s' % traces, root=True)
+            self._device.shell_output('chmod 666 %s' % traces, root=True)
+        except Exception as e:
+            print "Error deleting %s: %s" % (traces, str(e))
 
     def checkForANRs(self):
         traces = "/data/anr/traces.txt"
-        if self._dm.fileExists(traces):
+        if self._device.is_file(traces):
             try:
-                t = self._dm.pullFile(traces)
+                t = self._device.get_file(traces)
                 if t:
                     stripped = t.strip()
                     if len(stripped) > 0:
                         print "Contents of %s:" % traces
                         print t
                 # Once reported, delete traces
                 self.deleteANRs()
-            except DMError:
-                print "Error pulling %s" % traces
-            except IOError:
-                print "Error pulling %s" % traces
+            except Exception as e:
+                print "Error pulling %s: %s" % (traces, str(e))
         else:
             print "%s not found" % traces
 
     def deleteTombstones(self):
         # delete any tombstone files from device
-        tombstones = "/data/tombstones/*"
-        try:
-            self._dm.shellCheckOutput(['rm', '-r', tombstones], root=True,
-                                       timeout=DeviceManager.short_timeout)
-        except DMError:
-            # This may just indicate that the tombstone directory is missing
-            pass
+        self._device.rm("/data/tombstones", force=True, recursive=True, root=True)
 
     def checkForTombstones(self):
         # pull any tombstones from device and move to MOZ_UPLOAD_DIR
         remoteDir = "/data/tombstones"
         uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
         if uploadDir:
             if not os.path.exists(uploadDir):
                 os.mkdir(uploadDir)
-            if self._dm.dirExists(remoteDir):
+            if self._device.is_dir(remoteDir):
                 # copy tombstone files from device to local upload directory
-                try:
-                    self._dm.shellCheckOutput(['chmod', '777', remoteDir], root=True,
-                                               timeout=DeviceManager.short_timeout)
-                    self._dm.shellCheckOutput(['chmod', '666', os.path.join(remoteDir, '*')],
-                                               root=True, timeout=DeviceManager.short_timeout)
-                    self._dm.getDirectory(remoteDir, uploadDir, False)
-                except DMError:
-                    # This may just indicate that no tombstone files are present
-                    pass
+                self._device.chmod(remoteDir, recursive=True, root=True)
+                self._device.pull(remoteDir, uploadDir)
                 self.deleteTombstones()
                 for f in glob.glob(os.path.join(uploadDir, "tombstone_??")):
                     # add a unique integer to the file name, in case there are
                     # multiple tombstones generated with the same name, for
                     # instance, after multiple robocop tests
                     for i in xrange(1, sys.maxint):
                         newname = "%s.%d.txt" % (f, i)
                         if not os.path.exists(newname):
@@ -192,121 +154,135 @@ class RemoteAutomation(Automation):
                 print "%s does not exist; tombstone check skipped" % remoteDir
         else:
             print "MOZ_UPLOAD_DIR not defined; tombstone check skipped"
 
     def checkForCrashes(self, directory, symbolsPath):
         self.checkForANRs()
         self.checkForTombstones()
 
-        logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
+        logcat = self._device.get_logcat(filter_out_regexps=fennecLogcatFilters)
 
         javaException = mozcrash.check_for_java_exception(logcat, test_name=self.lastTestSeen)
         if javaException:
             return True
 
         # If crash reporting is disabled (MOZ_CRASHREPORTER!=1), we can't say
         # anything.
         if not self.CRASHREPORTER:
             return False
 
         try:
             dumpDir = tempfile.mkdtemp()
             remoteCrashDir = posixpath.join(self._remoteProfile, 'minidumps')
-            if not self._dm.dirExists(remoteCrashDir):
+            if not self._device.is_dir(remoteCrashDir):
                 # If crash reporting is enabled (MOZ_CRASHREPORTER=1), the
                 # minidumps directory is automatically created when Fennec
                 # (first) starts, so its lack of presence is a hint that
                 # something went wrong.
                 print "Automation Error: No crash directory (%s) found on remote device" % remoteCrashDir
                 return True
-            self._dm.getDirectory(remoteCrashDir, dumpDir)
+            self._device.pull(remoteCrashDir, dumpDir)
 
             logger = get_default_logger()
             crashed = mozcrash.log_crashes(logger, dumpDir, symbolsPath, test=self.lastTestSeen)
 
         finally:
             try:
                 shutil.rmtree(dumpDir)
-            except:
-                print "WARNING: unable to remove directory: %s" % dumpDir
+            except Exception as e:
+                print "WARNING: unable to remove directory %s: %s" % (dumpDir, str(e))
         return crashed
 
     def buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs):
         # If remote profile is specified, use that instead
         if self._remoteProfile:
             profileDir = self._remoteProfile
 
-        # Hack for robocop, if app & testURL == None and extraArgs contains the rest of the stuff, lets
+        # Hack for robocop, if app is "am" and extraArgs contains the rest of the stuff, lets
         # assume extraArgs is all we need
         if app == "am" and extraArgs[0] in ('instrument', 'start'):
             return app, extraArgs
 
         cmd, args = Automation.buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs)
         try:
             args.remove('-foreground')
         except:
             pass
         return app, args
 
     def Process(self, cmd, stdout = None, stderr = None, env = None, cwd = None):
-        return self.RProcess(self._dm, cmd, self._remoteLog, env, cwd, self._appName,
+        return self.RProcess(self._device, cmd, self._remoteLog, env, cwd, self._appName,
                              **self._processArgs)
 
     class RProcess(object):
-        dm = None
-        def __init__(self, dm, cmd, stdout=None, env=None, cwd=None, app=None,
+        def __init__(self, device, cmd, stdout=None, env=None, cwd=None, app=None,
                      messageLogger=None, counts=None):
-            self.dm = dm
-            self.stdoutlen = 0
+            self.device = device
             self.lastTestSeen = "remoteautomation.py"
-            self.proc = dm.launchProcess(cmd, stdout, cwd, env, True)
             self.messageLogger = messageLogger
+            self.proc =  stdout
+            self.procName = cmd[0].split(posixpath.sep)[-1]
+            self.stdoutlen = 0
             self.utilityPath = None
 
             self.counts = counts
             if self.counts is not None:
                 self.counts['pass'] = 0
                 self.counts['fail'] = 0
                 self.counts['todo'] = 0
 
-            if self.proc is None:
-                self.proc = stdout
-            self.procName = cmd[0].split(posixpath.sep)[-1]
-            if cmd[0] == 'am' and cmd[1] in ('instrument', 'start'):
+            if cmd[0] == 'am':
+                cmd = ' '.join(cmd)
                 self.procName = app
+                if not self.device.shell_bool(cmd):
+                    print "remote_automation.py failed to launch %s" % cmd
+            else:
+                args = cmd
+                if args[0] == app:
+                    args = args[1:]
+                url = args[-1:][0]
+                if url.startswith('/'):
+                    # this is probably a reftest profile directory, not a url
+                    url = None
+                else:
+                    args = args[:-1]
+                if 'geckoview' in app:
+                    self.device.launch_geckoview_example(app, moz_env=env, extra_args=args, url=url)
+                else:
+                    self.device.launch_fennec(app, moz_env=env, extra_args=args, url=url)
 
             # Setting timeout at 1 hour since on a remote device this takes much longer.
             # Temporarily increased to 90 minutes because no more chunks can be created.
             self.timeout = 5400
 
             # Used to buffer log messages until we meet a line break
             self.logBuffer = ""
 
         @property
         def pid(self):
-            pid = self.dm.processExist(self.procName)
-            # HACK: we should probably be more sophisticated about monitoring
-            # running processes for the remote case, but for now we'll assume
-            # that this method can be called when nothing exists and it is not
-            # an error
-            if pid is None:
+            procs = self.device.get_process_list()
+            # limit the comparison to the first 75 characters due to a
+            # limitation in processname length in android.
+            pids = [proc[0] for proc in procs if proc[1] == self.procName[:75]]
+
+            if pids is None or len(pids) < 1:
                 return 0
-            return pid
+            return pids[0]
 
         def read_stdout(self):
             """
-            Fetch the full remote log file using devicemanager, process them and
-            return whether there were any new log entries since the last call.
+            Fetch the full remote log file, log any new content and return True if new
+            content processed.
             """
-            if not self.dm.fileExists(self.proc):
+            if not self.device.is_file(self.proc):
                 return False
             try:
-                newLogContent = self.dm.pullFile(self.proc, self.stdoutlen)
-            except DMError:
+                newLogContent = self.device.get_file(self.proc, offset=self.stdoutlen)
+            except Exception:
                 return False
             if not newLogContent:
                 return False
 
             self.stdoutlen += len(newLogContent)
 
             if self.messageLogger is None:
                 testStartFilenames = re.findall(r"TEST-START \| ([^\s]*)", newLogContent)
@@ -370,17 +346,17 @@ class RemoteAutomation(Automation):
             noOutputTimer = 0
             interval = 10
             if timeout == None:
                 timeout = self.timeout
             status = 0
             top = self.procName
             slowLog = False
             endTime = datetime.datetime.now() + datetime.timedelta(seconds = timeout)
-            while (top == self.procName):
+            while top == self.procName:
                 # Get log updates on each interval, but if it is taking
                 # too long, only do it every 60 seconds
                 hasOutput = False
                 if (not slowLog) or (timer % 60 == 0):
                     startRead = datetime.datetime.now()
                     hasOutput = self.read_stdout()
                     if (datetime.datetime.now() - startRead) > datetime.timedelta(seconds=5):
                         slowLog = True
@@ -393,46 +369,53 @@ class RemoteAutomation(Automation):
                 noOutputTimer += interval
                 if datetime.datetime.now() > endTime:
                     status = 1
                     break
                 if (noOutputTimeout and noOutputTimer > noOutputTimeout):
                     status = 2
                     break
                 if not hasOutput:
-                    top = self.dm.getTopActivity()
-                    if top == "":
+                    top = self.device.get_top_activity(timeout=60)
+                    if top is None:
                         print "Failed to get top activity, retrying, once..."
-                        top = self.dm.getTopActivity()
+                        top = self.device.get_top_activity(timeout=60)
             # Flush anything added to stdout during the sleep
             self.read_stdout()
             return status
 
         def kill(self, stagedShutdown = False):
             if self.utilityPath:
                 # Take a screenshot to capture the screen state just before
                 # the application is killed. There are on-device screenshot
                 # options but they rarely work well with Firefox on the
                 # Android emulator. dump_screen provides an effective
                 # screenshot of the emulator and its host desktop.
                 dump_screen(self.utilityPath, get_default_logger())
             if stagedShutdown:
                 # Trigger an ANR report with "kill -3" (SIGQUIT)
-                self.dm.killProcess(self.procName, 3)
+                try:
+                    self.device.pkill(self.procName, sig=3, attempts=1)
+                except:
+                    pass
                 time.sleep(3)
                 # Trigger a breakpad dump with "kill -6" (SIGABRT)
-                self.dm.killProcess(self.procName, 6)
+                try:
+                    self.device.pkill(self.procName, sig=6, attempts=1)
+                except:
+                    pass
                 # Wait for process to end
                 retries = 0
                 while retries < 3:
-                    pid = self.dm.processExist(self.procName)
-                    if pid and pid > 0:
+                    if self.device.process_exist(self.procName):
                         print "%s still alive after SIGABRT: waiting..." % self.procName
                         time.sleep(5)
                     else:
                         return
                     retries += 1
-                self.dm.killProcess(self.procName, 9)
-                pid = self.dm.processExist(self.procName)
-                if pid and pid > 0:
-                    self.dm.killProcess(self.procName)
+                try:
+                    self.device.pkill(self.procName, sig=9, attempts=1)
+                except:
+                    print "%s still alive after SIGKILL!" % self.procName
+                if self.device.process_exist(self.procName):
+                    self.device.pkill(self.procName)
             else:
-                self.dm.killProcess(self.procName)
+                self.device.pkill(self.procName)
--- a/layout/tools/reftest/reftestcommandline.py
+++ b/layout/tools/reftest/reftestcommandline.py
@@ -401,50 +401,29 @@ class RemoteArgumentsParser(ReftestArgum
 
         # app, xrePath and utilityPath variables are set in main function
         self.set_defaults(logFile="reftest.log",
                           app="",
                           xrePath="",
                           utilityPath="",
                           localLogName=None)
 
-        self.add_argument("--remote-app-path",
-                          action="store",
-                          type=str,
-                          dest="remoteAppPath",
-                          help="Path to remote executable relative to device root using only "
-                               "forward slashes.  Either this or app must be specified, "
-                               "but not both.")
-
         self.add_argument("--adbpath",
                           action="store",
                           type=str,
                           dest="adb_path",
-                          default=None,
+                          default="adb",
                           help="path to adb")
 
-        self.add_argument("--deviceIP",
-                          action="store",
-                          type=str,
-                          dest="deviceIP",
-                          help="ip address of remote device to test")
-
         self.add_argument("--deviceSerial",
                           action="store",
                           type=str,
                           dest="deviceSerial",
                           help="adb serial number of remote device to test")
 
-        self.add_argument("--devicePort",
-                          action="store",
-                          type=str,
-                          default="20701",
-                          dest="devicePort",
-                          help="port of remote device to test")
-
         self.add_argument("--remote-webserver",
                           action="store",
                           type=str,
                           dest="remoteWebServer",
                           help="IP Address of the webserver hosting the reftest content")
 
         self.add_argument("--http-port",
                           action="store",
@@ -453,31 +432,16 @@ class RemoteArgumentsParser(ReftestArgum
                           help="port of the web server for http traffic")
 
         self.add_argument("--ssl-port",
                           action="store",
                           type=str,
                           dest="sslPort",
                           help="Port for https traffic to the web server")
 
-        self.add_argument("--remote-logfile",
-                          action="store",
-                          type=str,
-                          dest="remoteLogFile",
-                          default="reftest.log",
-                          help="Name of log file on the device relative to device root.  "
-                               "PLEASE USE ONLY A FILENAME.")
-
-        self.add_argument("--pidfile",
-                          action="store",
-                          type=str,
-                          dest="pidFile",
-                          default="",
-                          help="name of the pidfile to generate")
-
         self.add_argument("--remoteTestRoot",
                           action="store",
                           type=str,
                           dest="remoteTestRoot",
                           help="remote directory to use as test root "
                                "(eg. /mnt/sdcard/tests or /data/local/tests)")
 
         self.add_argument("--httpd-path",
@@ -488,88 +452,40 @@ class RemoteArgumentsParser(ReftestArgum
 
         self.add_argument("--no-device-info",
                           action="store_false",
                           dest="printDeviceInfo",
                           default=True,
                           help="do not display verbose diagnostics about the remote device")
 
     def validate_remote(self, options, automation):
-        # Ensure our defaults are set properly for everything we can infer
-        if not options.remoteTestRoot:
-            options.remoteTestRoot = automation._dm.deviceRoot + \
-                '/reftest'
-        options.remoteProfile = options.remoteTestRoot + "/profile"
-
         if options.remoteWebServer is None:
             options.remoteWebServer = self.get_ip()
 
-        # Verify that our remotewebserver is set properly
         if options.remoteWebServer == '127.0.0.1':
             self.error("ERROR: Either you specified the loopback for the remote webserver or ",
                        "your local IP cannot be detected.  "
                        "Please provide the local ip in --remote-webserver")
 
         if not options.httpPort:
             options.httpPort = automation.DEFAULT_HTTP_PORT
 
         if not options.sslPort:
             options.sslPort = automation.DEFAULT_SSL_PORT
 
-        # One of remoteAppPath (relative path to application) or the app (executable) must be
-        # set, but not both.  If both are set, we destroy the user's selection for app
-        # so instead of silently destroying a user specificied setting, we
-        # error.
-        if options.remoteAppPath and options.app:
-            self.error(
-                "ERROR: You cannot specify both the remoteAppPath and the app")
-        elif options.remoteAppPath:
-            options.app = options.remoteTestRoot + "/" + options.remoteAppPath
-        elif options.app is None:
-            # Neither remoteAppPath nor app are set -- error
-            self.error("ERROR: You must specify either appPath or app")
-
         if options.xrePath is None:
             self.error(
                 "ERROR: You must specify the path to the controller xre directory")
         else:
             # Ensure xrepath is a full path
             options.xrePath = os.path.abspath(options.xrePath)
 
-        options.localLogName = options.remoteLogFile
-        options.remoteLogFile = options.remoteTestRoot + \
-            '/' + options.remoteLogFile
-
-        # Ensure that the options.logfile (which the base class uses) is set to
-        # the remote setting when running remote. Also, if the user set the
-        # log file name there, use that instead of reusing the remotelogfile as
-        # above.
-        if options.logFile:
-            # If the user specified a local logfile name use that
-            options.localLogName = options.logFile
-
-        options.logFile = options.remoteLogFile
-
-        if options.pidFile != "":
-            with open(options.pidFile, 'w') as f:
-                f.write(str(os.getpid()))
-
         # httpd-path is specified by standard makefile targets and may be specified
         # on the command line to select a particular version of httpd.js. If not
         # specified, try to select the one from hostutils.zip, as required in
         # bug 882932.
         if not options.httpdPath:
             options.httpdPath = os.path.join(options.utilityPath, "components")
 
-        if not options.ignoreWindowSize:
-            parts = automation._dm.getInfo(
-                'screen')['screen'][0].split()
-            width = int(parts[0].split(':')[1])
-            height = int(parts[1].split(':')[1])
-            if (width < 1366 or height < 1050):
-                self.error("ERROR: Invalid screen resolution %sx%s, "
-                           "please adjust to 1366x1050 or higher" % (
-                            width, height))
-
         # Disable e10s by default on Android because we don't run Android
         # e10s jobs anywhere yet.
         options.e10s = False
         return options
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -1,24 +1,24 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import logging
 import os
+import posixpath
 import psutil
 import signal
 import sys
 import tempfile
 import time
 import traceback
 import urllib2
 from contextlib import closing
 
-import mozdevice
+from mozdevice import ADBAndroid
 import mozinfo
 from automation import Automation
 from remoteautomation import RemoteAutomation, fennecLogcatFilters
 
 from output import OutputHandler
 from runreftest import RefTest, ReftestResolver
 import reftestcommandline
 
@@ -49,75 +49,69 @@ class ReftestServer:
     """ Web server used to serve Reftests, for closer fidelity to the real web.
         It is virtually identical to the server used in mochitest and will only
         be used for running reftests remotely.
         Bug 581257 has been filed to refactor this wrapper around httpd.js into
         it's own class and use it in both remote and non-remote testing. """
 
     def __init__(self, automation, options, scriptDir):
         self.automation = automation
-        self._utilityPath = options.utilityPath
-        self._xrePath = options.xrePath
-        self._profileDir = options.serverProfilePath
+        self.utilityPath = options.utilityPath
+        self.xrePath = options.xrePath
+        self.profileDir = options.serverProfilePath
         self.webServer = options.remoteWebServer
         self.httpPort = options.httpPort
         self.scriptDir = scriptDir
-        self.pidFile = options.pidFile
-        self._httpdPath = os.path.abspath(options.httpdPath)
+        self.httpdPath = os.path.abspath(options.httpdPath)
         if options.remoteWebServer == "10.0.2.2":
             # probably running an Android emulator and 10.0.2.2 will
             # not be visible from host
             shutdownServer = "127.0.0.1"
         else:
             shutdownServer = self.webServer
         self.shutdownURL = "http://%(server)s:%(port)s/server/shutdown" % {
                            "server": shutdownServer, "port": self.httpPort}
 
     def start(self):
         "Run the Refest server, returning the process ID of the server."
 
-        env = self.automation.environment(xrePath=self._xrePath)
+        env = self.automation.environment(xrePath=self.xrePath)
         env["XPCOM_DEBUG_BREAK"] = "warn"
         if self.automation.IS_WIN32:
-            env["PATH"] = env["PATH"] + ";" + self._xrePath
+            env["PATH"] = env["PATH"] + ";" + self.xrePath
 
-        args = ["-g", self._xrePath,
-                "-f", os.path.join(self._httpdPath, "httpd.js"),
+        args = ["-g", self.xrePath,
+                "-f", os.path.join(self.httpdPath, "httpd.js"),
                 "-e", "const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = "
                       "'%(port)s'; const _SERVER_ADDR ='%(server)s';" % {
-                      "profile": self._profileDir.replace('\\', '\\\\'), "port": self.httpPort,
+                      "profile": self.profileDir.replace('\\', '\\\\'), "port": self.httpPort,
                       "server": self.webServer},
                 "-f", os.path.join(self.scriptDir, "server.js")]
 
-        xpcshell = os.path.join(self._utilityPath,
+        xpcshell = os.path.join(self.utilityPath,
                                 "xpcshell" + self.automation.BIN_SUFFIX)
 
         if not os.access(xpcshell, os.F_OK):
             raise Exception('xpcshell not found at %s' % xpcshell)
         if self.automation.elf_arm(xpcshell):
             raise Exception('xpcshell at %s is an ARM binary; please use '
                             'the --utility-path argument to specify the path '
                             'to a desktop version.' % xpcshell)
 
         self._process = self.automation.Process([xpcshell] + args, env=env)
         pid = self._process.pid
         if pid < 0:
             print "TEST-UNEXPECTED-FAIL | remotereftests.py | Error starting server."
             return 2
         self.automation.log.info("INFO | remotereftests.py | Server pid: %d", pid)
 
-        if (self.pidFile != ""):
-            f = open(self.pidFile + ".xpcshell.pid", 'w')
-            f.write("%s" % pid)
-            f.close()
-
     def ensureReady(self, timeout):
         assert timeout >= 0
 
-        aliveFile = os.path.join(self._profileDir, "server_alive.txt")
+        aliveFile = os.path.join(self.profileDir, "server_alive.txt")
         i = 0
         while i < timeout:
             if os.path.exists(aliveFile):
                 break
             time.sleep(1)
             i += 1
         else:
             print ("TEST-UNEXPECTED-FAIL | remotereftests.py | "
@@ -138,47 +132,84 @@ class ReftestServer:
                 self.automation.log.info("Failed to shutdown server at %s" %
                                          self.shutdownURL)
                 traceback.print_exc()
                 self._process.kill()
 
 
 class RemoteReftest(RefTest):
     use_marionette = False
-    remoteApp = ''
     resolver_cls = RemoteReftestResolver
 
-    def __init__(self, automation, devicemanager, options, scriptDir):
+    def __init__(self, options, scriptDir):
         RefTest.__init__(self, options.suite)
         self.run_by_manifest = False
-        self.automation = automation
-        self._devicemanager = devicemanager
         self.scriptDir = scriptDir
-        self.remoteApp = options.app
+        self.localLogName = options.localLogName
+
+        verbose = False
+        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+            verbose = True
+            print "set verbose!"
+        self.device = ADBAndroid(adb=options.adb_path,
+                                 device=options.deviceSerial,
+                                 test_root=options.remoteTestRoot,
+                                 verbose=verbose)
+
+        if options.remoteTestRoot is None:
+            options.remoteTestRoot = posixpath.join(self.device.test_root, "reftest")
+        options.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
+        options.remoteLogFile = posixpath.join(options.remoteTestRoot, "reftest.log")
+        options.logFile = options.remoteLogFile
         self.remoteProfile = options.remoteProfile
         self.remoteTestRoot = options.remoteTestRoot
-        self.remoteLogFile = options.remoteLogFile
-        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
-        self.localLogName = options.localLogName
-        self.pidFile = options.pidFile
-        if self.automation.IS_DEBUG_BUILD:
-            self.SERVER_STARTUP_TIMEOUT = 180
-        else:
-            self.SERVER_STARTUP_TIMEOUT = 90
-        self.automation.deleteANRs()
-        self.automation.deleteTombstones()
-        self._devicemanager.removeDir(self.remoteCache)
+
+        if not options.ignoreWindowSize:
+            parts = self.device.get_info(
+                'screen')['screen'][0].split()
+            width = int(parts[0].split(':')[1])
+            height = int(parts[1].split(':')[1])
+            if (width < 1366 or height < 1050):
+                self.error("ERROR: Invalid screen resolution %sx%s, "
+                           "please adjust to 1366x1050 or higher" % (
+                            width, height))
 
         self._populate_logger(options)
         self.outputHandler = OutputHandler(self.log, options.utilityPath, options.symbolsPath)
         # RemoteAutomation.py's 'messageLogger' is also used by mochitest. Mimic a mochitest
         # MessageLogger object to re-use this code path.
         self.outputHandler.write = self.outputHandler.__call__
+        self.automation = RemoteAutomation(self.device, options.app, self.remoteProfile,
+                                           options.remoteLogFile, processArgs=None)
         self.automation._processArgs['messageLogger'] = self.outputHandler
 
+        self.environment = self.automation.environment
+        if self.automation.IS_DEBUG_BUILD:
+            self.SERVER_STARTUP_TIMEOUT = 180
+        else:
+            self.SERVER_STARTUP_TIMEOUT = 90
+
+        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
+
+        # Check that Firefox is installed
+        expected = options.app.split('/')[-1]
+        if not self.device.is_app_installed(expected):
+            raise Exception("%s is not installed on this device" % expected)
+
+        self.automation.deleteANRs()
+        self.automation.deleteTombstones()
+        self.device.clear_logcat()
+
+        self.device.rm(self.remoteCache, force=True, recursive=True)
+
+        procName = options.app.split('/')[-1]
+        self.device.pkill(procName)
+        if self.device.process_exist(procName):
+            self.log.error("unable to kill %s before starting tests!" % procName)
+
     def findPath(self, paths, filename=None):
         for path in paths:
             p = path
             if filename:
                 p = os.path.join(p, filename)
             if os.path.exists(self.getFullPath(p)):
                 return path
         return None
@@ -278,51 +309,52 @@ class RemoteReftest(RefTest):
         # Because Fennec is a little wacky (see bug 1156817) we need to load the
         # reftest pages at 1.0 zoom, rather than zooming to fit the CSS viewport.
         prefs["apz.allow_zooming"] = False
 
         # Set the extra prefs.
         profile.set_preferences(prefs)
 
         try:
-            self._devicemanager.pushDir(profileDir, options.remoteProfile)
-            self._devicemanager.chmodDir(options.remoteProfile)
-        except mozdevice.DMError:
+            self.device.push(profileDir, options.remoteProfile)
+            self.device.chmod(options.remoteProfile, recursive=True)
+        except Exception:
             print "Automation Error: Failed to copy profiledir to device"
             raise
 
         return profile
 
     def copyExtraFilesToProfile(self, options, profile):
         profileDir = profile.profile
         RefTest.copyExtraFilesToProfile(self, options, profile)
-        try:
-            self._devicemanager.pushDir(profileDir, options.remoteProfile)
-            self._devicemanager.chmodDir(options.remoteProfile)
-        except mozdevice.DMError:
-            print "Automation Error: Failed to copy extra files to device"
-            raise
+        if len(os.listdir(profileDir)) > 0:
+            try:
+                self.device.push(profileDir, options.remoteProfile)
+                self.device.chmod(options.remoteProfile, recursive=True)
+            except Exception:
+                print "Automation Error: Failed to copy extra files to device"
+                raise
 
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
-                logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
+                logcat = self.device.get_logcat(filter_out_regexps=fennecLogcatFilters)
                 print ''.join(logcat)
             print "Device info:"
-            devinfo = self._devicemanager.getInfo()
+            devinfo = self.device.get_info()
             for category in devinfo:
                 if type(devinfo[category]) is list:
                     print "  %s:" % category
                     for item in devinfo[category]:
                         print "     %s" % item
                 else:
                     print "  %s: %s" % (category, devinfo[category])
-            print "Test root: %s" % self._devicemanager.deviceRoot
-        except mozdevice.DMError:
-            print "WARNING: Error getting device information"
+            print "Test root: %s" % self.device.test_root
+        except Exception as e:
+            print "WARNING: Error getting device information: %s" % str(e)
 
     def environment(self, **kwargs):
         return self.automation.environment(**kwargs)
 
     def buildBrowserEnv(self, options, profileDir):
         browserEnv = RefTest.buildBrowserEnv(self, options, profileDir)
         # remove desktop environment not used on device
         if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
@@ -353,74 +385,25 @@ class RemoteReftest(RefTest):
                                                            debuggerInfo=debuggerInfo,
                                                            symbolsPath=symbolsPath,
                                                            timeout=timeout)
 
         self.cleanup(profile.profile)
         return status
 
     def cleanup(self, profileDir):
-        # Pull results back from device
-        if self.remoteLogFile and \
-                self._devicemanager.fileExists(self.remoteLogFile):
-            self._devicemanager.getFile(self.remoteLogFile, self.localLogName)
-        else:
-            print "WARNING: Unable to retrieve log file (%s) from remote " \
-                "device" % self.remoteLogFile
-        self._devicemanager.removeDir(self.remoteProfile)
-        self._devicemanager.removeDir(self.remoteCache)
-        self._devicemanager.removeDir(self.remoteTestRoot)
+        self.device.rm(self.remoteTestRoot,  force=True, recursive=True)
+        self.device.rm(self.remoteProfile, force=True, recursive=True)
+        self.device.rm(self.remoteCache, force=True, recursive=True)
         RefTest.cleanup(self, profileDir)
-        if (self.pidFile != ""):
-            try:
-                os.remove(self.pidFile)
-                os.remove(self.pidFile + ".xpcshell.pid")
-            except Exception:
-                print ("Warning: cleaning up pidfile '%s' was unsuccessful "
-                       "from the test harness" % self.pidFile)
 
 
 def run_test_harness(parser, options):
-    dm_args = {
-        'deviceRoot': options.remoteTestRoot,
-        'host': options.deviceIP,
-        'port': options.devicePort,
-    }
-
-    dm_args['adbPath'] = options.adb_path
-    if not dm_args['host']:
-        dm_args['deviceSerial'] = options.deviceSerial
-    if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
-        dm_args['logLevel'] = logging.DEBUG
-
-    try:
-        dm = mozdevice.DroidADB(**dm_args)
-    except mozdevice.DMError:
-        traceback.print_exc()
-        print ("Automation Error: exception while initializing devicemanager.  "
-               "Most likely the device is not in a testable state.")
-        return 1
-
-    automation = RemoteAutomation(None)
-    automation.setDeviceManager(dm)
-
-    # Set up the defaults and ensure options are set
-    parser.validate_remote(options, automation)
-
-    # Check that Firefox is installed
-    expected = options.app.split('/')[-1]
-    installed = dm.shellCheckOutput(['pm', 'list', 'packages', expected])
-    if expected not in installed:
-        print "%s is not installed on this device" % expected
-        return 1
-
-    automation.setAppName(options.app)
-    automation.setRemoteProfile(options.remoteProfile)
-    automation.setRemoteLog(options.remoteLogFile)
-    reftest = RemoteReftest(automation, dm, options, SCRIPT_DIRECTORY)
+    reftest = RemoteReftest(options, SCRIPT_DIRECTORY)
+    parser.validate_remote(options, reftest.automation)
     parser.validate(options, reftest)
 
     if mozinfo.info['debug']:
         print "changing timeout for remote debug reftests from %s to 600 seconds" % options.timeout
         options.timeout = 600
 
     # Hack in a symbolic link for jsreftest
     os.system("ln -s ../jsreftest " + str(os.path.join(SCRIPT_DIRECTORY, "jsreftest")))
@@ -433,30 +416,21 @@ def run_test_harness(parser, options):
     reftest.killNamedProc('ssltunnel')
     reftest.killNamedProc('xpcshell')
 
     # Start the webserver
     retVal = reftest.startWebServer(options)
     if retVal:
         return retVal
 
-    procName = options.app.split('/')[-1]
-    dm.killProcess(procName)
-    if dm.processExist(procName):
-        print "unable to kill %s before starting tests!" % procName
-
     if options.printDeviceInfo:
         reftest.printDeviceInfo()
 
-# an example manifest name to use on the cli
-# manifest = "http://" + options.remoteWebServer +
-# "/reftests/layout/reftests/reftest-sanity/reftest.list"
     retVal = 0
     try:
-        dm.recordLogcat()
         if options.verify:
             retVal = reftest.verifyTests(options.tests, options)
         else:
             retVal = reftest.runTests(options.tests, options)
     except Exception:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         retVal = 1
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -3,22 +3,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from abc import ABCMeta, abstractmethod, abstractproperty
 from argparse import ArgumentParser, SUPPRESS
 from distutils.util import strtobool
 from distutils import spawn
 from itertools import chain
 from urlparse import urlparse
-import logging
 import json
 import os
 import tempfile
 
-from mozdevice import DroidADB
 from mozprofile import DEFAULT_PORTS
 import mozinfo
 import mozlog
 import moznetwork
 
 
 here = os.path.abspath(os.path.dirname(__file__))
 
@@ -845,51 +843,27 @@ class MochitestArguments(ArgumentContain
 
         return options
 
 
 class AndroidArguments(ArgumentContainer):
     """Android specific arguments."""
 
     args = [
-        [["--remote-app-path"],
-         {"dest": "remoteAppPath",
-          "help": "Path to remote executable relative to device root using \
-                   only forward slashes. Either this or app must be specified \
-                   but not both.",
-          "default": None,
-          }],
-        [["--deviceIP"],
-         {"dest": "deviceIP",
-          "help": "ip address of remote device to test",
-          "default": None,
-          }],
         [["--deviceSerial"],
          {"dest": "deviceSerial",
           "help": "ip address of remote device to test",
           "default": None,
           }],
         [["--adbpath"],
          {"dest": "adbPath",
-          "default": None,
+          "default": "adb",
           "help": "Path to adb binary.",
           "suppress": True,
           }],
-        [["--devicePort"],
-         {"dest": "devicePort",
-          "type": int,
-          "default": 20701,
-          "help": "port of remote device to test",
-          }],
-        [["--remote-logfile"],
-         {"dest": "remoteLogFile",
-          "default": None,
-          "help": "Name of log file on the device relative to the device \
-                   root. PLEASE ONLY USE A FILENAME.",
-          }],
         [["--remote-webserver"],
          {"dest": "remoteWebServer",
           "default": None,
           "help": "ip address where the remote web server is hosted at",
           }],
         [["--http-port"],
          {"dest": "httpPort",
           "default": DEFAULT_PORTS['http'],
@@ -897,93 +871,58 @@ class AndroidArguments(ArgumentContainer
           "suppress": True,
           }],
         [["--ssl-port"],
          {"dest": "sslPort",
           "default": DEFAULT_PORTS['https'],
           "help": "ssl port of the remote web server",
           "suppress": True,
           }],
-        [["--robocop-ini"],
-         {"dest": "robocopIni",
-          "default": "",
-          "help": "name of the .ini file containing the list of tests to run",
-          }],
         [["--robocop-apk"],
          {"dest": "robocopApk",
           "default": "",
           "help": "name of the Robocop APK to use for ADB test running",
           }],
         [["--remoteTestRoot"],
          {"dest": "remoteTestRoot",
           "default": None,
           "help": "remote directory to use as test root \
                    (eg. /mnt/sdcard/tests or /data/local/tests)",
           "suppress": True,
           }],
     ]
 
     defaults = {
-        'dm': None,
         # we don't want to exclude specialpowers on android just yet
         'extensionsToExclude': [],
         # mochijar doesn't get installed via marionette on android
         'extensionsToInstall': [os.path.join(here, 'mochijar')],
         'logFile': 'mochitest.log',
         'utilityPath': None,
     }
 
     def validate(self, parser, options, context):
         """Validate android options."""
 
         if build_obj:
             options.log_mach = '-'
 
-        device_args = {'deviceRoot': options.remoteTestRoot}
-        device_args['adbPath'] = options.adbPath
-        if options.deviceIP:
-            device_args['host'] = options.deviceIP
-            device_args['port'] = options.devicePort
-        elif options.deviceSerial:
-            device_args['deviceSerial'] = options.deviceSerial
-
-        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
-            device_args['logLevel'] = logging.DEBUG
-        options.dm = DroidADB(**device_args)
-
-        if not options.remoteTestRoot:
-            options.remoteTestRoot = options.dm.deviceRoot
-
         if options.remoteWebServer is None:
             if os.name != "nt":
                 options.remoteWebServer = moznetwork.get_ip()
             else:
                 parser.error(
                     "you must specify a --remote-webserver=<ip address>")
 
         options.webServer = options.remoteWebServer
 
-        if options.remoteLogFile is None:
-            options.remoteLogFile = options.remoteTestRoot + \
-                '/logs/mochitest.log'
-
-        if options.remoteLogFile.count('/') < 1:
-            options.remoteLogFile = options.remoteTestRoot + \
-                '/' + options.remoteLogFile
-
-        if options.remoteAppPath and options.app:
-            parser.error(
-                "You cannot specify both the remoteAppPath and the app setting")
-        elif options.remoteAppPath:
-            options.app = options.remoteTestRoot + "/" + options.remoteAppPath
-        elif options.app is None:
+        if options.app is None:
             if build_obj:
                 options.app = build_obj.substs['ANDROID_PACKAGE_NAME']
             else:
-                # Neither remoteAppPath nor app are set -- error
                 parser.error("You must specify either appPath or app")
 
         if build_obj and 'MOZ_HOST_BIN' in os.environ:
             options.xrePath = os.environ['MOZ_HOST_BIN']
 
         # Only reset the xrePath if it wasn't provided
         if options.xrePath is None:
             options.xrePath = options.utilityPath
@@ -991,26 +930,18 @@ class AndroidArguments(ArgumentContainer
         if build_obj:
             options.topsrcdir = build_obj.topsrcdir
 
         if options.pidFile != "":
             f = open(options.pidFile, 'w')
             f.write("%s" % os.getpid())
             f.close()
 
-        # Robocop specific options
-        if options.robocopIni != "":
-            if not os.path.exists(options.robocopIni):
-                parser.error(
-                    "Unable to find specified robocop .ini manifest '%s'" %
-                    options.robocopIni)
-            options.robocopIni = os.path.abspath(options.robocopIni)
-
-            if not options.robocopApk and build_obj:
-                options.robocopApk = build_obj.substs.get('GRADLE_ANDROID_APP_ANDROIDTEST_APK')
+        if not options.robocopApk and build_obj:
+            options.robocopApk = build_obj.substs.get('GRADLE_ANDROID_APP_ANDROIDTEST_APK')
 
         if options.robocopApk != "":
             if not os.path.exists(options.robocopApk):
                 parser.error(
                     "Unable to find robocop APK '%s'" %
                     options.robocopApk)
             options.robocopApk = os.path.abspath(options.robocopApk)
 
--- a/testing/mochitest/rungeckoview.py
+++ b/testing/mochitest/rungeckoview.py
@@ -220,17 +220,17 @@ def run_test_harness(log, parser, option
         log.error(
             "rungeckoview.py | Received unexpected exception while running tests")
         result = 1
     finally:
         try:
             runner.cleanup()
         except Exception:
             # ignore device error while cleaning up
-            pass
+            traceback.print_exc()
     return result
 
 
 def main(args=sys.argv[1:]):
     parser = GeckoviewOptions()
     mozlog.commandline.add_logging_group(parser)
     options, args = parser.parse_args()
     if args:
--- a/testing/mochitest/runrobocop.py
+++ b/testing/mochitest/runrobocop.py
@@ -1,14 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import json
 import os
+import posixpath
 import sys
 import tempfile
 import traceback
 from collections import defaultdict
 
 sys.path.insert(
     0, os.path.abspath(
         os.path.realpath(
@@ -16,62 +17,73 @@ sys.path.insert(
 
 from automation import Automation
 from remoteautomation import RemoteAutomation, fennecLogcatFilters
 from runtests import KeyValueParseError, MochitestDesktop, MessageLogger, parseKeyValue
 from mochitest_options import MochitestArgumentParser
 
 from manifestparser import TestManifest
 from manifestparser.filters import chunk_by_slice
-import mozdevice
+from mozdevice import ADBAndroid
+import mozfile
 import mozinfo
 
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
 
 
 class RobocopTestRunner(MochitestDesktop):
     """
        A test harness for Robocop. Robocop tests are UI tests for Firefox for Android,
        based on the Robotium test framework. This harness leverages some functionality
        from mochitest, for convenience.
     """
-    auto = None
-    dm = None
     # Some robocop tests run for >60 seconds without generating any output.
     NO_OUTPUT_TIMEOUT = 180
 
-    def __init__(self, automation, devmgr, options):
+    def __init__(self, options, message_logger):
         """
            Simple one-time initialization.
         """
         MochitestDesktop.__init__(self, options.flavor, vars(options))
 
-        self.auto = automation
-        self.dm = devmgr
-        self.dm.default_timeout = 320
+        verbose = False
+        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+            verbose = True
+        self.device = ADBAndroid(adb=options.adbPath,
+                                 device=options.deviceSerial,
+                                 test_root=options.remoteTestRoot,
+                                 verbose=verbose)
+
+        # Check that Firefox is installed
+        expected = options.app.split('/')[-1]
+        if not self.device.is_app_installed(expected):
+            raise Exception("%s is not installed on this device" % expected)
+
+        options.logFile = "robocop.log"
+        if options.remoteTestRoot is None:
+            options.remoteTestRoot = self.device.test_root
+        self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
+        self.remoteProfileCopy = posixpath.join(options.remoteTestRoot, "profile-copy")
+
+        self.remoteConfigFile = posixpath.join(options.remoteTestRoot, "robotium.config")
+        self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs", "robocop.log")
+
         self.options = options
-        self.options.logFile = "robocop.log"
+
+        process_args = {'messageLogger': message_logger}
+        self.auto = RemoteAutomation(self.device, options.remoteappname, self.remoteProfile,
+                                     self.remoteLogFile, processArgs=process_args)
         self.environment = self.auto.environment
-        self.deviceRoot = self.dm.getDeviceRoot()
-        self.remoteProfile = options.remoteTestRoot + "/profile"
-        self.remoteProfileCopy = options.remoteTestRoot + "/profile-copy"
-        self.auto.setRemoteProfile(self.remoteProfile)
-        self.remoteConfigFile = os.path.join(
-            self.deviceRoot, "robotium.config")
-        self.remoteLog = options.remoteLogFile
-        self.auto.setRemoteLog(self.remoteLog)
+
         self.remoteScreenshots = "/mnt/sdcard/Robotium-Screenshots"
-        self.remoteMozLog = os.path.join(options.remoteTestRoot, "mozlog")
-        self.auto.setServerInfo(
-            self.options.webServer, self.options.httpPort, self.options.sslPort)
+        self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
+
         self.localLog = options.logFile
         self.localProfile = None
-        self.auto.setAppName(self.options.remoteappname)
         self.certdbNew = True
-        self.remoteCopyAvailable = True
         self.passed = 0
         self.failed = 0
         self.todo = 0
 
     def startup(self):
         """
            Second-stage initialization: One-time initialization which may require cleanup.
         """
@@ -80,33 +92,33 @@ class RobocopTestRunner(MochitestDesktop
         # with future tests, typically because the old server is keeping the port in use.
         # Try to avoid those failures by checking for and killing servers before
         # trying to start new ones.
         self.killNamedProc('ssltunnel')
         self.killNamedProc('xpcshell')
         self.auto.deleteANRs()
         self.auto.deleteTombstones()
         procName = self.options.app.split('/')[-1]
-        self.dm.killProcess(procName)
-        if self.dm.processExist(procName):
+        self.device.pkill(procName)
+        if self.device.process_exist(procName):
             self.log.warning("unable to kill %s before running tests!" % procName)
-        self.dm.removeDir(self.remoteScreenshots)
-        self.dm.removeDir(self.remoteMozLog)
-        self.dm.mkDir(self.remoteMozLog)
-        self.dm.mkDir(os.path.dirname(self.options.remoteLogFile))
+        self.device.rm(self.remoteScreenshots, force=True, recursive=True)
+        self.device.rm(self.remoteMozLog, force=True, recursive=True)
+        self.device.mkdir(self.remoteMozLog)
+        logParent = posixpath.dirname(self.remoteLogFile)
+        self.device.rm(logParent, force=True, recursive=True)
+        self.device.mkdir(logParent)
         # Add Android version (SDK level) to mozinfo so that manifest entries
         # can be conditional on android_version.
-        androidVersion = self.dm.shellCheckOutput(
-            ['getprop', 'ro.build.version.sdk'])
         self.log.info(
             "Android sdk version '%s'; will use this to filter manifests" %
-            str(androidVersion))
-        mozinfo.info['android_version'] = androidVersion
+            str(self.device.version))
+        mozinfo.info['android_version'] = str(self.device.version)
         if self.options.robocopApk:
-            self.dm._checkCmd(["install", "-r", self.options.robocopApk])
+            self.device.install_app(self.options.robocopApk, replace=True)
             self.log.debug("Robocop APK %s installed" %
                            self.options.robocopApk)
         # Display remote diagnostics; if running in mach, keep output terse.
         if self.options.log_mach is None:
             self.printDeviceInfo()
         self.setupLocalPaths()
         self.buildProfile()
         # ignoreSSLTunnelExts is a workaround for bug 1109310
@@ -117,33 +129,32 @@ class RobocopTestRunner(MochitestDesktop
         self.log.debug("Servers started")
 
     def cleanup(self):
         """
            Cleanup at end of job run.
         """
         self.log.debug("Cleaning up...")
         self.stopServers()
-        self.dm.killProcess(self.options.app.split('/')[-1])
-        blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
-        if blobberUploadDir:
+        self.device.pkill(self.options.app.split('/')[-1])
+        uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
+        if uploadDir:
             self.log.debug("Pulling any remote moz logs and screenshots to %s." %
-                           blobberUploadDir)
-            self.dm.getDirectory(self.remoteMozLog, blobberUploadDir)
-            self.dm.getDirectory(self.remoteScreenshots, blobberUploadDir)
+                           uploadDir)
+            self.device.pull(self.remoteMozLog, uploadDir)
+            self.device.pull(self.remoteScreenshots, uploadDir)
         MochitestDesktop.cleanup(self, self.options)
         if self.localProfile:
-            os.system("rm -Rf %s" % self.localProfile)
-        self.dm.removeDir(self.remoteProfile)
-        self.dm.removeDir(self.remoteProfileCopy)
-        self.dm.removeDir(self.remoteScreenshots)
-        self.dm.removeDir(self.remoteMozLog)
-        self.dm.removeFile(self.remoteConfigFile)
-        if self.dm.fileExists(self.remoteLog):
-            self.dm.removeFile(self.remoteLog)
+            mozfile.remove(self.localProfile)
+        self.device.rm(self.remoteProfile, force=True, recursive=True)
+        self.device.rm(self.remoteProfileCopy, force=True, recursive=True)
+        self.device.rm(self.remoteScreenshots, force=True, recursive=True)
+        self.device.rm(self.remoteMozLog, force=True, recursive=True)
+        self.device.rm(self.remoteConfigFile, force=True)
+        self.device.rm(self.remoteLogFile, force=True)
         self.log.debug("Cleanup complete.")
 
     def findPath(self, paths, filename=None):
         for path in paths:
             p = path
             if filename:
                 p = os.path.join(p, filename)
             if os.path.exists(self.getFullPath(p)):
@@ -231,43 +242,31 @@ class RobocopTestRunner(MochitestDesktop
         ])
 
         manifest = MochitestDesktop.buildProfile(self, self.options)
         self.localProfile = self.options.profilePath
         self.log.debug("Profile created at %s" % self.localProfile)
         # some files are not needed for robocop; save time by not pushing
         os.remove(os.path.join(self.localProfile, 'userChrome.css'))
         try:
-            self.dm.pushDir(self.localProfile, self.remoteProfileCopy)
-        except mozdevice.DMError:
+            self.device.push(self.localProfile, self.remoteProfileCopy)
+        except Exception:
             self.log.error(
                 "Automation Error: Unable to copy profile to device.")
             raise
 
         return manifest
 
     def setupRemoteProfile(self):
         """
            Remove any remote profile and re-create it.
         """
         self.log.debug("Updating remote profile at %s" % self.remoteProfile)
-        self.dm.removeDir(self.remoteProfile)
-        if self.remoteCopyAvailable:
-            try:
-                self.dm.shellCheckOutput(
-                    ['cp', '-r', self.remoteProfileCopy, self.remoteProfile],
-                    root=True, timeout=60)
-            except mozdevice.DMError:
-                # For instance, cp is not available on some older versions of
-                # Android.
-                self.log.info(
-                    "Unable to copy remote profile; falling back to push.")
-                self.remoteCopyAvailable = False
-        if not self.remoteCopyAvailable:
-            self.dm.pushDir(self.localProfile, self.remoteProfile)
+        self.device.rm(self.remoteProfile, force=True, recursive=True)
+        self.device.cp(self.remoteProfileCopy, self.remoteProfile, recursive=True)
 
     def parseLocalLog(self):
         """
            Read and parse the local log file, noting any failures.
         """
         with open(self.localLog) as currentLog:
             data = currentLog.readlines()
         os.unlink(self.localLog)
@@ -323,46 +322,46 @@ class RobocopTestRunner(MochitestDesktop
     def printDeviceInfo(self, printLogcat=False):
         """
            Log remote device information and logcat (if requested).
 
            This is similar to printDeviceInfo in runtestsremote.py
         """
         try:
             if printLogcat:
-                logcat = self.dm.getLogcat(
+                logcat = self.device.get_logcat(
                     filterOutRegexps=fennecLogcatFilters)
                 self.log.info(
                     '\n' +
                     ''.join(logcat).decode(
                         'utf-8',
                         'replace'))
             self.log.info("Device info:")
-            devinfo = self.dm.getInfo()
+            devinfo = self.device.get_info()
             for category in devinfo:
                 if type(devinfo[category]) is list:
                     self.log.info("  %s:" % category)
                     for item in devinfo[category]:
                         self.log.info("     %s" % item)
                 else:
                     self.log.info("  %s: %s" % (category, devinfo[category]))
-            self.log.info("Test root: %s" % self.dm.deviceRoot)
-        except mozdevice.DMError:
-            self.log.warning("Error getting device information")
+            self.log.info("Test root: %s" % self.device.test_root)
+        except Exception as e:
+            self.log.warning("Error getting device information: %s" % str(e))
 
     def setupRobotiumConfig(self, browserEnv):
         """
            Create robotium.config and push it to the device.
         """
         fHandle = tempfile.NamedTemporaryFile(suffix='.config',
                                               prefix='robotium-',
                                               dir=os.getcwd(),
                                               delete=False)
-        fHandle.write("profile=%s\n" % (self.remoteProfile))
-        fHandle.write("logfile=%s\n" % (self.options.remoteLogFile))
+        fHandle.write("profile=%s\n" % self.remoteProfile)
+        fHandle.write("logfile=%s\n" % self.remoteLogFile)
         fHandle.write("host=http://mochi.test:8888/tests\n")
         fHandle.write(
             "rawhost=http://%s:%s/tests\n" %
             (self.options.remoteWebServer, self.options.httpPort))
         if browserEnv:
             envstr = ""
             delim = ""
             for key, value in browserEnv.items():
@@ -372,18 +371,18 @@ class RobocopTestRunner(MochitestDesktop
                                    "in our value, unable to process value. key=%s,value=%s" %
                                    (key, value))
                     self.log.error("browserEnv=%s" % browserEnv)
                 except ValueError:
                     envstr += "%s%s=%s" % (delim, key, value)
                     delim = ","
             fHandle.write("envvars=%s\n" % envstr)
         fHandle.close()
-        self.dm.removeFile(self.remoteConfigFile)
-        self.dm.pushFile(fHandle.name, self.remoteConfigFile)
+        self.device.rm(self.remoteConfigFile, force=True)
+        self.device.push(fHandle.name, self.remoteConfigFile)
         os.unlink(fHandle.name)
 
     def buildBrowserEnv(self):
         """
            Return an environment dictionary suitable for remote use.
 
            This is similar to buildBrowserEnv in runtestsremote.py.
         """
@@ -414,62 +413,58 @@ class RobocopTestRunner(MochitestDesktop
            Run the specified test.
         """
         self.log.debug("Running test %s" % test['name'])
         self.mozLogName = "moz-%s.log" % test['name']
         browserEnv = self.buildBrowserEnv()
         self.setupRobotiumConfig(browserEnv)
         self.setupRemoteProfile()
         self.options.app = "am"
+        timeout = None
         if self.options.autorun:
             # This launches a test (using "am instrument") and instructs
             # Fennec to /quit/ the browser (using Robocop:Quit) and to
             # /finish/ all opened activities.
             browserArgs = [
                 "instrument",
-                "-w",
                 "-e", "quit_and_finish", "1",
-                "-e", "deviceroot", self.deviceRoot,
+                "-e", "deviceroot", self.device.test_root,
                 "-e", "class",
                 "org.mozilla.gecko.tests.%s" % test['name'].split('/')[-1].split('.java')[0],
                 "org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner"]
         else:
             # This does not launch a test at all. It launches an activity
             # that starts Fennec and then waits indefinitely, since cat
             # never returns.
             browserArgs = ["start", "-n",
                            "org.mozilla.roboexample.test/org.mozilla."
                            "gecko.LaunchFennecWithConfigurationActivity", "&&", "cat"]
-            self.dm.default_timeout = sys.maxint  # Forever.
+            timeout = sys.maxint  # Forever.
+
             self.log.info("")
             self.log.info("Serving mochi.test Robocop root at http://%s:%s/tests/robocop/" %
                           (self.options.remoteWebServer, self.options.httpPort))
             self.log.info("")
         result = -1
         log_result = -1
         try:
-            self.dm.recordLogcat()
-            timeout = self.options.timeout
+            self.device.clear_logcat()
+            if not timeout:
+                timeout = self.options.timeout
             if not timeout:
                 timeout = self.NO_OUTPUT_TIMEOUT
             result, _ = self.auto.runApp(
                 None, browserEnv, "am", self.localProfile, browserArgs,
                 timeout=timeout, symbolsPath=self.options.symbolsPath)
             self.log.debug("runApp completes with status %d" % result)
             if result != 0:
                 self.log.error("runApp() exited with code %s" % result)
-            if self.dm.fileExists(self.remoteLog):
-                self.dm.getFile(self.remoteLog, self.localLog)
-                self.dm.removeFile(self.remoteLog)
-                self.log.debug("Remote log %s retrieved to %s" %
-                               (self.remoteLog, self.localLog))
-            else:
-                self.log.warning(
-                    "Unable to retrieve log file (%s) from remote device" %
-                    self.remoteLog)
+            if self.device.is_file(self.remoteLogFile):
+                self.device.pull(self.remoteLogFile, self.localLog)
+                self.device.rm(self.remoteLogFile)
             log_result = self.parseLocalLog()
             if result != 0 or log_result != 0:
                 # Display remote diagnostics; if running in mach, keep output
                 # terse.
                 if self.options.log_mach is None:
                     self.printDeviceInfo(printLogcat=True)
         except Exception:
             self.log.error(
@@ -481,17 +476,17 @@ class RobocopTestRunner(MochitestDesktop
         return result
 
     def runTests(self):
         self.startup()
         if isinstance(self.options.manifestFile, TestManifest):
             mp = self.options.manifestFile
         else:
             mp = TestManifest(strict=False)
-            mp.read(self.options.robocopIni)
+            mp.read("robocop.ini")
         filters = []
         if self.options.totalChunks:
             filters.append(
                 chunk_by_slice(self.options.thisChunk, self.options.totalChunks))
         robocop_tests = mp.active_tests(
             exists=False, filters=filters, **mozinfo.info)
         if not self.options.autorun:
             # Force a single loop iteration. The iteration will start Fennec and
@@ -532,28 +527,18 @@ class RobocopTestRunner(MochitestDesktop
 
 def run_test_harness(parser, options):
     parser.validate(options)
 
     if options is None:
         raise ValueError(
             "Invalid options specified, use --help for a list of valid options")
     message_logger = MessageLogger(logger=None)
-    process_args = {'messageLogger': message_logger}
-    auto = RemoteAutomation(None, "fennec", processArgs=process_args)
-    auto.setDeviceManager(options.dm)
     runResult = -1
-    robocop = RobocopTestRunner(auto, options.dm, options)
-
-    # Check that Firefox is installed
-    expected = options.app.split('/')[-1]
-    installed = options.dm.shellCheckOutput(['pm', 'list', 'packages', expected])
-    if expected not in installed:
-        robocop.log.error("%s is not installed on this device" % expected)
-        return 1
+    robocop = RobocopTestRunner(options, message_logger)
 
     try:
         message_logger.logger = robocop.log
         message_logger.buffering = False
         robocop.message_logger = message_logger
         robocop.log.debug("options=%s" % vars(options))
         runResult = robocop.runTests()
     except KeyboardInterrupt:
@@ -562,19 +547,19 @@ def run_test_harness(parser, options):
     except Exception:
         traceback.print_exc()
         robocop.log.error(
             "runrobocop.py | Received unexpected exception while running tests")
         runResult = 1
     finally:
         try:
             robocop.cleanup()
-        except mozdevice.DMError:
+        except Exception:
             # ignore device error while cleaning up
-            pass
+            traceback.print_exc()
         message_logger.finish()
     return runResult
 
 
 def main(args=sys.argv[1:]):
     parser = MochitestArgumentParser(app='android')
     options = parser.parse_args(args)
     return run_test_harness(parser, options)
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -1,87 +1,123 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
+import posixpath
 import sys
 import traceback
 
 sys.path.insert(
     0, os.path.abspath(
         os.path.realpath(
             os.path.dirname(__file__))))
 
 from automation import Automation
 from remoteautomation import RemoteAutomation, fennecLogcatFilters
 from runtests import MochitestDesktop, MessageLogger
 from mochitest_options import MochitestArgumentParser
 
-import mozdevice
+from mozdevice import ADBAndroid
 import mozinfo
 
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
 
 
 class MochiRemote(MochitestDesktop):
-    _automation = None
-    _dm = None
     localProfile = None
     logMessages = []
 
-    def __init__(self, automation, devmgr, options):
+    def __init__(self, options):
         MochitestDesktop.__init__(self, options.flavor, vars(options))
 
+        verbose = False
+        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+            verbose = True
         if hasattr(options, 'log'):
             delattr(options, 'log')
 
-        self._automation = automation
-        self._dm = devmgr
+        self.certdbNew = True
         self.chromePushed = False
-        self.environment = self._automation.environment
-        self.remoteProfile = os.path.join(options.remoteTestRoot, "profile/")
-        self.remoteModulesDir = os.path.join(options.remoteTestRoot, "modules/")
-        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
-        self._automation.setRemoteProfile(self.remoteProfile)
-        self.remoteLog = options.remoteLogFile
-        self.localLog = options.logFile
-        self._automation.deleteANRs()
-        self._automation.deleteTombstones()
-        self.certdbNew = True
-        self.remoteMozLog = os.path.join(options.remoteTestRoot, "mozlog")
-        self._dm.removeDir(self.remoteMozLog)
-        self._dm.mkDir(self.remoteMozLog)
-        self.remoteChromeTestDir = os.path.join(
-            options.remoteTestRoot,
-            "chrome")
-        self._dm.removeDir(self.remoteChromeTestDir)
-        self._dm.mkDir(self.remoteChromeTestDir)
-        self._dm.removeDir(self.remoteProfile)
-        self._dm.removeDir(self.remoteCache)
+        self.mozLogName = "moz.log"
+
+        self.device = ADBAndroid(adb=options.adbPath,
+                                 device=options.deviceSerial,
+                                 test_root=options.remoteTestRoot,
+                                 verbose=verbose)
+
+        if options.remoteTestRoot is None:
+            options.remoteTestRoot = self.device.test_root
+        options.dumpOutputDirectory = options.remoteTestRoot
+        self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs", "mochitest.log")
+        logParent = posixpath.dirname(self.remoteLogFile)
+        self.device.rm(logParent, force=True, recursive=True)
+        self.device.mkdir(logParent)
+
+        self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile/")
+        self.device.rm(self.remoteProfile, force=True, recursive=True)
+
+        self.counts = dict()
+        self.message_logger = MessageLogger(logger=None)
+        self.message_logger.logger = self.log
+        process_args = {'messageLogger': self.message_logger, 'counts': self.counts}
+        self.automation = RemoteAutomation(self.device, options.remoteappname, self.remoteProfile,
+                                           self.remoteLogFile, processArgs=process_args)
+        self.environment = self.automation.environment
+
+        # Check that Firefox is installed
+        expected = options.app.split('/')[-1]
+        if not self.device.is_app_installed(expected):
+            raise Exception("%s is not installed on this device" % expected)
+
+        self.automation.deleteANRs()
+        self.automation.deleteTombstones()
+        self.device.clear_logcat()
+
+        self.remoteModulesDir = posixpath.join(options.remoteTestRoot, "modules/")
+
+        self.remoteCache = posixpath.join(options.remoteTestRoot, "cache/")
+        self.device.rm(self.remoteCache, force=True, recursive=True)
+
         # move necko cache to a location that can be cleaned up
         options.extraPrefs += ["browser.cache.disk.parent_directory=%s" % self.remoteCache]
 
+        self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
+        self.device.rm(self.remoteMozLog, force=True, recursive=True)
+        self.device.mkdir(self.remoteMozLog)
+
+        self.remoteChromeTestDir = posixpath.join(
+            options.remoteTestRoot,
+            "chrome")
+        self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
+        self.device.mkdir(self.remoteChromeTestDir)
+
+        procName = options.app.split('/')[-1]
+        self.device.pkill(procName)
+        if self.device.process_exist(procName):
+            self.log.warning("unable to kill %s before running tests!" % procName)
+
+        # Add Android version (SDK level) to mozinfo so that manifest entries
+        # can be conditional on android_version.
+        self.log.info(
+            "Android sdk version '%s'; will use this to filter manifests" %
+            str(self.device.version))
+        mozinfo.info['android_version'] = str(self.device.version)
+
     def cleanup(self, options, final=False):
         if final:
-            self._dm.removeDir(self.remoteChromeTestDir)
+            self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
             self.chromePushed = False
-            blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
-            if blobberUploadDir:
-                self._dm.getDirectory(self.remoteMozLog, blobberUploadDir)
-        else:
-            if self._dm.fileExists(self.remoteLog):
-                self._dm.getFile(self.remoteLog, self.localLog)
-                self._dm.removeFile(self.remoteLog)
-            else:
-                self.log.warning(
-                    "Unable to retrieve log file (%s) from remote device" %
-                    self.remoteLog)
-        self._dm.removeDir(self.remoteProfile)
-        self._dm.removeDir(self.remoteCache)
+            uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
+            if uploadDir:
+                self.device.pull(self.remoteMozLog, uploadDir)
+        self.device.rm(self.remoteLogFile, force=True)
+        self.device.rm(self.remoteProfile, force=True, recursive=True)
+        self.device.rm(self.remoteCache, force=True, recursive=True)
         MochitestDesktop.cleanup(self, options, final)
         self.localProfile = None
 
     def findPath(self, paths, filename=None):
         for path in paths:
             p = path
             if filename:
                 p = os.path.join(p, filename)
@@ -174,19 +210,19 @@ class MochiRemote(MochitestDesktop):
             debuggerInfo,
             ignoreSSLTunnelExts=True)
         restoreRemotePaths()
 
     def buildProfile(self, options):
         restoreRemotePaths = self.switchToLocalPaths(options)
         if options.testingModulesDir:
             try:
-                self._dm.pushDir(options.testingModulesDir, self.remoteModulesDir)
-                self._dm.chmodDir(self.remoteModulesDir)
-            except mozdevice.DMError:
+                self.device.push(options.testingModulesDir, self.remoteModulesDir)
+                self.device.chmod(self.remoteModulesDir, recursive=True)
+            except Exception:
                 self.log.error(
                     "Automation Error: Unable to copy test modules to device.")
                 raise
             savedTestingModulesDir = options.testingModulesDir
             options.testingModulesDir = self.remoteModulesDir
         else:
             savedTestingModulesDir = None
         manifest = MochitestDesktop.buildProfile(self, options)
@@ -194,71 +230,69 @@ class MochiRemote(MochitestDesktop):
             options.testingModulesDir = savedTestingModulesDir
         self.localProfile = options.profilePath
 
         restoreRemotePaths()
         options.profilePath = self.remoteProfile
         return manifest
 
     def buildURLOptions(self, options, env):
-        self.localLog = options.logFile
-        options.logFile = self.remoteLog
-        options.fileLevel = 'INFO'
+        saveLogFile = options.logFile
+        options.logFile = self.remoteLogFile
         options.profilePath = self.localProfile
         env["MOZ_HIDE_RESULTS_TABLE"] = "1"
         retVal = MochitestDesktop.buildURLOptions(self, options, env)
 
         # we really need testConfig.js (for browser chrome)
         try:
-            self._dm.pushDir(options.profilePath, self.remoteProfile)
-            self._dm.chmodDir(self.remoteProfile)
-        except mozdevice.DMError:
-            self.log.error(
-                "Automation Error: Unable to copy profile to device.")
+            self.device.push(options.profilePath, self.remoteProfile)
+            self.device.chmod(self.remoteProfile, recursive=True)
+        except Exception:
+            self.log.error("Automation Error: Unable to copy profile to device.")
             raise
 
         options.profilePath = self.remoteProfile
-        options.logFile = self.localLog
+        options.logFile = saveLogFile
         return retVal
 
     def getChromeTestDir(self, options):
         local = super(MochiRemote, self).getChromeTestDir(options)
         remote = self.remoteChromeTestDir
         if options.flavor == 'chrome' and not self.chromePushed:
             self.log.info("pushing %s to %s on device..." % (local, remote))
             local = os.path.join(local, "chrome")
-            self._dm.pushDir(local, remote)
+            self.device.push(local, remote)
             self.chromePushed = True
         return remote
 
     def getLogFilePath(self, logFile):
         return logFile
 
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
-                logcat = self._dm.getLogcat(
+                logcat = self.device.get_logcat(
                     filterOutRegexps=fennecLogcatFilters)
                 self.log.info(
                     '\n' +
                     ''.join(logcat).decode(
                         'utf-8',
                         'replace'))
             self.log.info("Device info:")
-            devinfo = self._dm.getInfo()
+            devinfo = self.device.get_info()
             for category in devinfo:
                 if type(devinfo[category]) is list:
                     self.log.info("  %s:" % category)
                     for item in devinfo[category]:
                         self.log.info("     %s" % item)
                 else:
                     self.log.info("  %s: %s" % (category, devinfo[category]))
-            self.log.info("Test root: %s" % self._dm.deviceRoot)
-        except mozdevice.DMError:
-            self.log.warning("Error getting device information")
+            self.log.info("Test root: %s" % self.device.test_root)
+        except Exception as e:
+            self.log.warning("Error getting device information: %s" % str(e))
 
     def getGMPPluginPath(self, options):
         # TODO: bug 1149374
         return None
 
     def buildBrowserEnv(self, options, debugger=False):
         browserEnv = MochitestDesktop.buildBrowserEnv(
             self,
@@ -282,107 +316,60 @@ class MochiRemote(MochitestDesktop):
         # automation.py/remoteautomation `runApp` takes the profile path,
         # whereas runtest.py's `runApp` takes a mozprofile object.
         if 'profileDir' not in kwargs and 'profile' in kwargs:
             kwargs['profileDir'] = kwargs.pop('profile').profile
 
         # remove args not supported by automation.py
         kwargs.pop('marionette_args', None)
 
-        ret, _ = self._automation.runApp(*args, **kwargs)
+        ret, _ = self.automation.runApp(*args, **kwargs)
         self.countpass += self.counts['pass']
         self.countfail += self.counts['fail']
         self.counttodo += self.counts['todo']
 
         return ret, None
 
 
 def run_test_harness(parser, options):
     parser.validate(options)
 
-    message_logger = MessageLogger(logger=None)
-    counts = dict()
-    process_args = {'messageLogger': message_logger, 'counts': counts}
-    auto = RemoteAutomation(None, options.app, processArgs=process_args)
-
     if options is None:
         raise ValueError("Invalid options specified, use --help for a list of valid options")
 
     options.runByManifest = True
     # roboextender is used by mochitest-chrome tests like test_java_addons.html,
     # but not by any plain mochitests
     if options.flavor != 'chrome':
         options.extensionsToExclude.append('roboextender@mozilla.org')
 
-    dm = options.dm
-    auto.setDeviceManager(dm)
-    mochitest = MochiRemote(auto, dm, options)
-    options.dm = None
-
-    log = mochitest.log
-    message_logger.logger = log
-    mochitest.message_logger = message_logger
-    mochitest.counts = counts
-
-    # Check that Firefox is installed
-    expected = options.app.split('/')[-1]
-    installed = dm.shellCheckOutput(['pm', 'list', 'packages', expected])
-    if expected not in installed:
-        log.error("%s is not installed on this device" % expected)
-        return 1
-
-    auto.setAppName(options.remoteappname)
-
-    logParent = os.path.dirname(options.remoteLogFile)
-    dm.removeDir(logParent)
-    dm.mkDir(logParent)
-    auto.setRemoteLog(options.remoteLogFile)
-    auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
+    mochitest = MochiRemote(options)
 
     if options.log_mach is None:
         mochitest.printDeviceInfo()
 
-    # Add Android version (SDK level) to mozinfo so that manifest entries
-    # can be conditional on android_version.
-    androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
-    log.info(
-        "Android sdk version '%s'; will use this to filter manifests" %
-        str(androidVersion))
-    mozinfo.info['android_version'] = androidVersion
-
-    deviceRoot = dm.deviceRoot
-    options.dumpOutputDirectory = deviceRoot
-
-    procName = options.app.split('/')[-1]
-    dm.killProcess(procName)
-    if dm.processExist(procName):
-        log.warning("unable to kill %s before running tests!" % procName)
-
-    mochitest.mozLogName = "moz.log"
     try:
-        dm.recordLogcat()
         if options.verify:
             retVal = mochitest.verifyTests(options)
         else:
             retVal = mochitest.runTests(options)
     except Exception:
-        log.error("Automation Error: Exception caught while running tests")
+        mochitest.log.error("Automation Error: Exception caught while running tests")
         traceback.print_exc()
-        mochitest.stopServers()
         try:
             mochitest.cleanup(options)
-        except mozdevice.DMError:
+        except Exception:
             # device error cleaning up... oh well!
-            pass
+            traceback.print_exc()
         retVal = 1
 
     if options.log_mach is None:
         mochitest.printDeviceInfo(printLogcat=True)
 
-    message_logger.finish()
+    mochitest.message_logger.finish()
 
     return retVal
 
 
 def main(args=sys.argv[1:]):
     parser = MochitestArgumentParser(app='android')
     options = parser.parse_args(args)
 
--- a/testing/mozharness/configs/android/android_common.py
+++ b/testing/mozharness/configs/android/android_common.py
@@ -143,17 +143,16 @@ config = {
                 "--xre-path=%(xre_path)s",
                 "--utility-path=%(utility_path)s",
                 "--certificate-path=%(certs_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
                 "--robocop-apk=../../robocop.apk",
-                "--robocop-ini=robocop.ini",
             ],
         },
         "reftest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [
                 "--app=%(app)s",
                 "--ignore-window-size",
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -243,18 +243,17 @@ class XPCShellRemote(xpcshell.XPCShellTe
             verbose = True
         self.device = ADBAndroid(adb=options['adbPath'],
                                  device=options['deviceSerial'],
                                  test_root=options['remoteTestRoot'],
                                  verbose=verbose)
         self.remoteTestRoot = posixpath.join(self.device.test_root, "xpc")
         # Add Android version (SDK level) to mozinfo so that manifest entries
         # can be conditional on android_version.
-        androidVersion = self.device.get_prop('ro.build.version.sdk')
-        mozinfo.info['android_version'] = androidVersion
+        mozinfo.info['android_version'] = self.device.version
 
         self.localLib = options['localLib']
         self.localBin = options['localBin']
         self.pathMapping = []
         # remoteBinDir contains xpcshell and its wrapper script, both of which must
         # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
         # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
         # /data/local, always.