8
8
class DeviceManagerADB(DeviceManager):
10
def __init__(self, host = None, port = 20701, retrylimit = 5, packageName = None):
10
def __init__(self, host=None, port=20701, retrylimit=5, packageName='fennec',
11
adbPath='adb', deviceSerial=None):
13
14
self.retrylimit = retrylimit
16
17
self.useRunAs = False
17
18
self.haveRoot = False
19
self.useDDCopy = False
18
20
self.useZip = False
19
21
self.packageName = None
20
22
self.tempDir = None
21
if packageName == None:
24
# the path to adb, or 'adb' to assume that it's on the PATH
25
self.adbPath = adbPath
27
# The serial number of the device to use with adb, used in cases
28
# where multiple devices are being managed by the same adb instance.
29
self.deviceSerial = deviceSerial
31
if packageName == 'fennec':
22
32
if os.getenv('USER'):
23
packageName = 'org.mozilla.fennec_' + os.getenv('USER')
33
self.packageName = 'org.mozilla.fennec_' + os.getenv('USER')
25
packageName = 'org.mozilla.fennec_'
26
self.Init(packageName)
35
self.packageName = 'org.mozilla.fennec_'
37
self.packageName = packageName
39
# verify that we can run the adb command. can't continue otherwise
42
# try to connect to the device over tcp/ip if we have a hostname
30
self.disconnectRemoteADB()
32
def Init(self, packageName):
33
# Initialization code that may fail: Catch exceptions here to allow
34
# successful initialization even if, for example, adb is not installed.
38
self.connectRemoteADB()
39
self.verifyRunAs(packageName)
42
self.packageName = packageName
49
# a test to see if we have root privs
50
files = self.listFiles("/data/data")
52
if (files[0].find("Permission denied") != -1):
53
print "NOT running as root"
54
raise Exception("not running as root")
44
self.connectRemoteADB()
46
# verify that we can connect to the device. can't continue
49
# Can we use run-as? (currently not required)
55
# Can we run things as root? (currently not required)
56
useRunAsTmp = self.useRunAs
61
62
self.checkCmd(["root"])
62
63
# The root command does not fail even if ADB cannot get
63
64
# root rights (e.g. due to production builds), so we have
64
65
# to check again ourselves that we have root now.
68
69
print "restarting as root failed, but run-as available"
70
71
print "restarting as root failed"
72
self.useRunAs = useRunAsTmp
74
# can we use zip to speed up some file operations? (currently not
83
self.disconnectRemoteADB()
72
85
# external function: executes shell command on device
94
107
cmdline = envstr + "; " + cmdline
96
109
# all output should be in stdout
97
proc = subprocess.Popen(["adb", "shell", cmdline],
111
if self.deviceSerial:
112
args.extend(['-s', self.deviceSerial])
113
args.extend(["shell", cmdline])
114
proc = subprocess.Popen(args,
98
115
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
99
116
(stdout, stderr) = proc.communicate()
100
117
outputfile.write(stdout.rstrip('\n'))
375
398
# external function
377
# success: output from testagent
379
def killProcess(self, appname):
402
def killProcess(self, appname, forceKill=False):
380
403
procs = self.getProcessList()
404
didKillProcess = False
381
405
for (pid, name, user) in procs:
382
406
if name == appname:
383
p = self.runCmdAs(["shell", "kill", pid])
384
return p.stdout.read()
407
args = ["shell", "kill"]
411
p = self.runCmdAs(args)
412
didKillProcess = True
414
return didKillProcess
388
416
# external function
416
444
outerr = self.runCmd(["pull", remoteFile, localFile]).communicate()
418
446
# Now check stderr for errors
419
errl = outerr[1].splitlines()
421
if (((errl[0].find("Permission denied") != -1)
422
or (errl[0].find("does not exist") != -1))
424
# If we lack permissions to read but have run-as, then we should try
425
# to copy the file to a world-readable location first before attempting
427
remoteTmpFile = self.getTempDir() + "/" + os.path.basename(remoteFile)
428
self.checkCmdAs(["shell", "dd", "if=" + remoteFile, "of=" + remoteTmpFile])
429
self.checkCmdAs(["shell", "chmod", "777", remoteTmpFile])
430
self.runCmd(["pull", remoteTmpFile, localFile]).stdout.read()
431
# Clean up temporary file
432
self.checkCmdAs(["shell", "rm", remoteTmpFile])
448
errl = outerr[1].splitlines()
450
if (((errl[0].find("Permission denied") != -1)
451
or (errl[0].find("does not exist") != -1))
453
# If we lack permissions to read but have run-as, then we should try
454
# to copy the file to a world-readable location first before attempting
456
remoteTmpFile = self.getTempDir() + "/" + os.path.basename(remoteFile)
457
self.checkCmdAs(["shell", "dd", "if=" + remoteFile, "of=" + remoteTmpFile])
458
self.checkCmdAs(["shell", "chmod", "777", remoteTmpFile])
459
self.runCmd(["pull", remoteTmpFile, localFile]).stdout.read()
460
# Clean up temporary file
461
self.checkCmdAs(["shell", "rm", remoteTmpFile])
434
463
f = open(localFile)
663
696
def runCmd(self, args):
664
697
# If we are not root but have run-as, and we're trying to execute
665
698
# a shell command then using run-as is the best we can do
699
finalArgs = [self.adbPath]
700
if self.deviceSerial:
701
finalArgs.extend(['-s', self.deviceSerial])
666
702
if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
667
703
args.insert(1, "run-as")
668
704
args.insert(2, self.packageName)
669
args.insert(0, "adb")
670
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
705
finalArgs.extend(args)
706
return subprocess.Popen(finalArgs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
672
708
def runCmdAs(self, args):
673
709
if self.useRunAs:
678
714
def checkCmd(self, args):
679
715
# If we are not root but have run-as, and we're trying to execute
680
716
# a shell command then using run-as is the best we can do
717
finalArgs = [self.adbPath]
718
if self.deviceSerial:
719
finalArgs.extend(['-s', self.deviceSerial])
681
720
if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
682
721
args.insert(1, "run-as")
683
722
args.insert(2, self.packageName)
684
args.insert(0, "adb")
685
return subprocess.check_call(args)
723
finalArgs.extend(args)
724
return subprocess.check_call(finalArgs)
687
726
def checkCmdAs(self, args):
688
727
if (self.useRunAs):
708
747
def verifyADB(self):
709
748
# Check to see if adb itself can be executed.
711
self.runCmd(["version"])
713
print "unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH"
749
if self.adbPath != 'adb':
750
if not os.access(self.adbPath, os.X_OK):
751
raise DMError("invalid adb path, or adb not executable: %s", self.adbPath)
754
self.checkCmd(["version"])
755
except os.error, err:
756
raise DMError("unable to execute ADB (%s): ensure Android SDK is installed and adb is in your $PATH" % err)
757
except subprocess.CalledProcessError:
758
raise DMError("unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH")
760
def verifyDevice(self):
761
# If there is a device serial number, see if adb is connected to it
762
if self.deviceSerial:
764
proc = subprocess.Popen([self.adbPath, "devices"],
765
stdout=subprocess.PIPE,
766
stderr=subprocess.STDOUT)
767
for line in proc.stdout:
768
m = re.match('(.+)?\s+(.+)$', line)
770
if self.deviceSerial == m.group(1):
771
deviceStatus = m.group(2)
772
if deviceStatus == None:
773
raise DMError("device not found: %s" % self.deviceSerial)
774
elif deviceStatus != "device":
775
raise DMError("bad status for device %s: %s" % (self.deviceSerial,
778
# Check to see if we can connect to device and run a simple command
780
self.checkCmd(["shell", "echo"])
781
except subprocess.CalledProcessError:
782
raise DMError("unable to connect to device: is it plugged in?")
784
def verifyRoot(self):
785
# a test to see if we have root privs
786
files = self.listFiles("/data/data")
787
if (len(files) == 0):
788
print "NOT running as root"
789
raise DMError("not running as root")
715
793
def isCpAvailable(self):
716
794
# Some Android systems may not have a cp command installed,
717
# or it may not be executable by the user.
795
# or it may not be executable by the user.
718
796
data = self.runCmd(["shell", "cp"]).stdout.read()
719
797
if (re.search('Usage', data)):
727
805
print "unable to execute 'cp' on device; consider installing busybox from Android Market"
730
def verifyRunAs(self, packageName):
808
def verifyRunAs(self):
731
809
# If a valid package name is available, and certain other
732
810
# conditions are met, devicemanagerADB can execute file operations
733
811
# via the "run-as" command, so that pushed files and directories
737
815
# file copy via run-as.
738
816
self.useRunAs = False
739
817
devroot = self.getDeviceRoot()
740
if (packageName and self.isCpAvailable() and devroot):
818
if (self.packageName and self.isCpAvailable() and devroot):
741
819
tmpDir = self.getTempDir()
743
821
# The problem here is that run-as doesn't cause a non-zero exit code
744
822
# when failing because of a non-existent or non-debuggable package :(
745
runAsOut = self.runCmd(["shell", "run-as", packageName, "mkdir", devroot + "/sanity"]).communicate()[0]
746
if runAsOut.startswith("run-as:") and ("not debuggable" in runAsOut[0] or
747
"is unknown" in runAsOut[0]):
823
runAsOut = self.runCmd(["shell", "run-as", self.packageName, "mkdir", devroot + "/sanity"]).communicate()[0]
824
if runAsOut.startswith("run-as:") and ("not debuggable" in runAsOut or
825
"is unknown" in runAsOut):
748
826
raise DMError("run-as failed sanity check")
750
self.checkCmd(["push", os.path.abspath(sys.argv[0]), tmpDir + "/tmpfile"])
828
tmpfile = tempfile.NamedTemporaryFile()
829
self.checkCmd(["push", tmpfile.name, tmpDir + "/tmpfile"])
751
830
if self.useDDCopy:
752
self.checkCmd(["shell", "run-as", packageName, "dd", "if=" + tmpDir + "/tmpfile", "of=" + devroot + "/sanity/tmpfile"])
831
self.checkCmd(["shell", "run-as", self.packageName, "dd", "if=" + tmpDir + "/tmpfile", "of=" + devroot + "/sanity/tmpfile"])
754
self.checkCmd(["shell", "run-as", packageName, "cp", tmpDir + "/tmpfile", devroot + "/sanity"])
833
self.checkCmd(["shell", "run-as", self.packageName, "cp", tmpDir + "/tmpfile", devroot + "/sanity"])
755
834
if (self.fileExists(devroot + "/sanity/tmpfile")):
756
print "will execute commands via run-as " + packageName
757
self.packageName = packageName
835
print "will execute commands via run-as " + self.packageName
758
836
self.useRunAs = True
759
837
self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
760
self.checkCmd(["shell", "run-as", packageName, "rm", "-r", devroot + "/sanity"])
838
self.checkCmd(["shell", "run-as", self.packageName, "rm", "-r", devroot + "/sanity"])
762
840
def isUnzipAvailable(self):
763
841
data = self.runCmdAs(["shell", "unzip"]).stdout.read()
764
842
if (re.search('Usage', data)):