1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Provides an interface to communicate with the device via the adb command.
7 Assumes adb binary is currently on system path.
26 import system_properties
29 from pylib import pexpect
33 sys.path.append(os.path.join(
34 constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
36 import am_instrument_parser
40 # Pattern to search for the next whole line of pexpect output and capture it
41 # into a match group. We can't use ^ and $ for line start end with pexpect,
42 # see http://www.noah.org/python/pexpect/#doc for explanation why.
43 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
45 # Set the adb shell prompt to be a unique marker that will [hopefully] not
46 # appear at the start of any line of a command's output.
47 SHELL_PROMPT = '~+~PQ\x17RS~+~'
49 # Java properties file
50 LOCAL_PROPERTIES_PATH = '/data/local.prop'
52 # Property in /data/local.prop that controls Java assertions.
53 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
55 MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$')
56 NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*'
57 '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$')
59 # Keycode "enum" suitable for passing to AndroidCommands.SendKey().
63 KEYCODE_DPAD_DOWN = 20
64 KEYCODE_DPAD_RIGHT = 22
68 MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/'
69 MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin'
70 MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER
74 """Returns a list of AVDs."""
75 re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE)
76 avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd']))
80 def GetAttachedDevices(hardware=True, emulator=True, offline=False):
81 """Returns a list of attached, android devices and emulators.
83 If a preferred device has been set with ANDROID_SERIAL, it will be first in
84 the returned list. The arguments specify what devices to include in the list.
88 * daemon not running. starting it now on port 5037 *
89 * daemon started successfully *
90 List of devices attached
91 027c10494100b4d7 device
95 hardware: Include attached actual devices that are online.
96 emulator: Include emulators (i.e. AVD's) currently on host.
97 offline: Include devices and emulators that are offline.
99 Returns: List of devices.
101 adb_devices_output = cmd_helper.GetCmdOutput([constants.GetAdbPath(),
104 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
105 online_devices = re_device.findall(adb_devices_output)
107 re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE)
108 emulator_devices = re_device.findall(adb_devices_output)
110 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\toffline$', re.MULTILINE)
111 offline_devices = re_device.findall(adb_devices_output)
114 # First determine list of online devices (e.g. hardware and/or emulator).
115 if hardware and emulator:
116 devices = online_devices
118 devices = [device for device in online_devices
119 if device not in emulator_devices]
121 devices = emulator_devices
123 # Now add offline devices if offline is true
125 devices = devices + offline_devices
127 preferred_device = os.environ.get('ANDROID_SERIAL')
128 if preferred_device in devices:
129 devices.remove(preferred_device)
130 devices.insert(0, preferred_device)
134 def IsDeviceAttached(device):
135 """Return true if the device is attached and online."""
136 return device in GetAttachedDevices()
139 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
140 """Gets a list of files from `ls` command output.
142 Python's os.walk isn't used because it doesn't work over adb shell.
145 path: The path to list.
146 ls_output: A list of lines returned by an `ls -lR` command.
147 re_file: A compiled regular expression which parses a line into named groups
148 consisting of at minimum "filename", "date", "time", "size" and
149 optionally "timezone".
150 utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
151 2-digit string giving the number of UTC offset hours, and MM is a
152 2-digit string giving the number of UTC offset minutes. If the input
153 utc_offset is None, will try to look for the value of "timezone" if it
154 is specified in re_file.
157 A dict of {"name": (size, lastmod), ...} where:
158 name: The file name relative to |path|'s directory.
159 size: The file size in bytes (0 for directories).
160 lastmod: The file last modification date in UTC.
162 re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path))
163 path_dir = os.path.dirname(path)
167 for line in ls_output:
168 directory_match = re_directory.match(line)
170 current_dir = directory_match.group('dir')
172 file_match = re_file.match(line)
174 filename = os.path.join(current_dir, file_match.group('filename'))
175 if filename.startswith(path_dir):
176 filename = filename[len(path_dir) + 1:]
177 lastmod = datetime.datetime.strptime(
178 file_match.group('date') + ' ' + file_match.group('time')[:5],
180 if not utc_offset and 'timezone' in re_file.groupindex:
181 utc_offset = file_match.group('timezone')
182 if isinstance(utc_offset, str) and len(utc_offset) == 5:
183 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
184 minutes=int(utc_offset[3:5]))
185 if utc_offset[0:1] == '-':
186 utc_delta = -utc_delta
188 files[filename] = (int(file_match.group('size')), lastmod)
192 def _ParseMd5SumOutput(md5sum_output):
193 """Returns a list of tuples from the provided md5sum output.
196 md5sum_output: output directly from md5sum binary.
199 List of namedtuples with attributes |hash| and |path|, where |path| is the
200 absolute path to the file with an Md5Sum of |hash|.
202 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path'])
203 split_lines = [line.split(' ') for line in md5sum_output]
204 return [HashAndPath._make(s) for s in split_lines if len(s) == 2]
207 def _HasAdbPushSucceeded(command_output):
208 """Returns whether adb push has succeeded from the provided output."""
209 # TODO(frankf): We should look at the return code instead of the command
210 # output for many of the commands in this file.
211 if not command_output:
213 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
214 # Errors look like this: "failed to copy ... "
215 if not re.search('^[0-9]', command_output.splitlines()[-1]):
216 logging.critical('PUSH FAILED: ' + command_output)
221 def GetLogTimestamp(log_line, year):
222 """Returns the timestamp of the given |log_line| in the given year."""
224 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
225 '%Y-%m-%d %H:%M:%S.%f')
226 except (ValueError, IndexError):
227 logging.critical('Error reading timestamp from ' + log_line)
231 class AndroidCommands(object):
232 """Helper class for communicating with Android device via adb."""
234 def __init__(self, device=None, api_strict_mode=False):
238 device: If given, adb commands are only send to the device of this ID.
239 Otherwise commands are sent to all attached devices.
240 api_strict_mode: A boolean indicating whether fatal errors should be
241 raised if this API is used improperly.
243 adb_dir = os.path.dirname(constants.GetAdbPath())
244 if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
245 # Required by third_party/android_testrunner to call directly 'adb'.
246 os.environ['PATH'] += os.pathsep + adb_dir
247 self._adb = adb_interface.AdbInterface()
249 self._adb.SetTargetSerial(device)
250 self._device = device
252 self.logcat_process = None
253 self._logcat_tmpoutfile = None
254 self._pushed_files = []
255 self._device_utc_offset = None
256 self._potential_push_size = 0
257 self._actual_push_size = 0
258 self._external_storage = ''
259 self._util_wrapper = ''
260 self._api_strict_mode = api_strict_mode
261 self._system_properties = system_properties.SystemProperties(self.Adb())
262 self._push_if_needed_cache = {}
264 if not self._api_strict_mode:
266 'API STRICT MODE IS DISABLED.\n'
267 'It should be enabled as soon as possible as it will eventually '
268 'become the default.')
271 def system_properties(self):
272 return self._system_properties
274 def _LogShell(self, cmd):
275 """Logs the adb shell command."""
277 device_repr = self._device[-4:]
280 logging.info('[%s]> %s', device_repr, cmd)
283 """Returns our AdbInterface to avoid us wrapping all its methods."""
284 # TODO(tonyg): Disable this method when in _api_strict_mode.
288 """Returns the device serial."""
292 """Checks whether the device is online.
295 True if device is in 'device' mode, False otherwise.
297 out = self._adb.SendCommand('get-state')
298 return out.strip() == 'device'
300 def IsRootEnabled(self):
301 """Checks if root is enabled on the device."""
302 root_test_output = self.RunShellCommand('ls /root') or ['']
303 return not 'Permission denied' in root_test_output[0]
305 def EnableAdbRoot(self):
306 """Enables adb root on the device.
309 True: if output from executing adb root was as expected.
312 if self.GetBuildType() == 'user':
313 logging.warning("Can't enable root in production builds with type user")
316 return_value = self._adb.EnableAdbRoot()
317 # EnableAdbRoot inserts a call for wait-for-device only when adb logcat
318 # output matches what is expected. Just to be safe add a call to
320 self._adb.SendCommand('wait-for-device')
323 def GetDeviceYear(self):
324 """Returns the year information of the date on device."""
325 return self.RunShellCommand('date +%Y')[0]
327 def GetExternalStorage(self):
328 if not self._external_storage:
329 self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0]
330 assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE'
331 return self._external_storage
333 def WaitForDevicePm(self):
334 """Blocks until the device's package manager is available.
336 To workaround http://b/5201039, we restart the shell and retry if the
337 package manager isn't back after 120 seconds.
340 errors.WaitForResponseTimedOutError after max retries reached.
346 self._adb.WaitForDevicePm()
348 except errors.WaitForResponseTimedOutError as e:
350 logging.warning('Restarting and retrying after timeout: %s', e)
353 raise last_err # Only reached after max retries, re-raise the last error.
355 def RestartShell(self):
356 """Restarts the shell on the device. Does not block for it to return."""
357 self.RunShellCommand('stop')
358 self.RunShellCommand('start')
360 def Reboot(self, full_reboot=True):
361 """Reboots the device and waits for the package manager to return.
364 full_reboot: Whether to fully reboot the device or just restart the shell.
366 # TODO(torne): hive can't reboot the device either way without breaking the
367 # connection; work out if we can handle this better
368 if os.environ.get('USING_HIVE'):
369 logging.warning('Ignoring reboot request as we are on hive')
371 if full_reboot or not self.IsRootEnabled():
372 self._adb.SendCommand('reboot')
373 self._system_properties = system_properties.SystemProperties(self.Adb())
376 # Wait for the device to disappear.
377 while retries < 10 and self.IsOnline():
383 # To run tests we need at least the package manager and the sd card (or
384 # other external storage) to be ready.
385 self.WaitForDevicePm()
386 self.WaitForSdCardReady(timeout)
389 """Shuts down the device."""
390 self._adb.SendCommand('reboot -p')
391 self._system_properties = system_properties.SystemProperties(self.Adb())
393 def Uninstall(self, package):
394 """Uninstalls the specified package from the device.
397 package: Name of the package to remove.
400 A status string returned by adb uninstall
402 uninstall_command = 'uninstall %s' % package
404 self._LogShell(uninstall_command)
405 return self._adb.SendCommand(uninstall_command, timeout_time=60)
407 def Install(self, package_file_path, reinstall=False):
408 """Installs the specified package to the device.
411 package_file_path: Path to .apk file to install.
412 reinstall: Reinstall an existing apk, keeping the data.
415 A status string returned by adb install
417 assert os.path.isfile(package_file_path), ('<%s> is not file' %
420 install_cmd = ['install']
423 install_cmd.append('-r')
425 install_cmd.append(package_file_path)
426 install_cmd = ' '.join(install_cmd)
428 self._LogShell(install_cmd)
429 # FIXME(wang16): Change the timeout here to five minutes. Revert
430 # the change when slaves can run kvm enabled x86 android emulators.
431 return self._adb.SendCommand(install_cmd,
435 def ManagedInstall(self, apk_path, keep_data=False, package_name=None,
436 reboots_on_timeout=2):
437 """Installs specified package and reboots device on timeouts.
439 If package_name is supplied, checks if the package is already installed and
440 doesn't reinstall if the apk md5sums match.
443 apk_path: Path to .apk file to install.
444 keep_data: Reinstalls instead of uninstalling first, preserving the
446 package_name: Package name (only needed if keep_data=False).
447 reboots_on_timeout: number of time to reboot if package manager is frozen.
449 # Check if package is already installed and up to date.
451 installed_apk_path = self.GetApplicationPath(package_name)
452 if (installed_apk_path and
453 not self.GetFilesChanged(apk_path, installed_apk_path,
454 ignore_filenames=True)):
455 logging.info('Skipped install: identical %s APK already installed' %
459 reboots_left = reboots_on_timeout
464 self.Uninstall(package_name)
465 install_status = self.Install(apk_path, reinstall=keep_data)
466 if 'Success' in install_status:
469 raise Exception('Install failure: %s' % install_status)
470 except errors.WaitForResponseTimedOutError:
471 print '@@@STEP_WARNINGS@@@'
472 logging.info('Timeout on installing %s on device %s', apk_path,
475 if reboots_left <= 0:
476 raise Exception('Install timed out')
478 # Force a hard reboot on last attempt
479 self.Reboot(full_reboot=(reboots_left == 1))
482 def MakeSystemFolderWritable(self):
483 """Remounts the /system folder rw."""
484 out = self._adb.SendCommand('remount')
485 if out.strip() != 'remount succeeded':
486 raise errors.MsgException('Remount failed: %s' % out)
488 def RestartAdbdOnDevice(self):
489 logging.info('Killing adbd on the device...')
490 adb_pids = self.ExtractPid('adbd')
492 raise errors.MsgException('Unable to obtain adbd pid')
494 self.KillAll('adbd', signal=signal.SIGTERM, with_su=True)
495 logging.info('Waiting for device to settle...')
496 self._adb.SendCommand('wait-for-device')
497 new_adb_pids = self.ExtractPid('adbd')
498 if new_adb_pids == adb_pids:
499 logging.warning('adbd on the device may not have been restarted.')
500 except Exception as e:
501 logging.error('Exception when trying to kill adbd on the device [%s]', e)
503 def RestartAdbServer(self):
504 """Restart the adb server."""
505 ret = self.KillAdbServer()
507 raise errors.MsgException('KillAdbServer: %d' % ret)
509 ret = self.StartAdbServer()
511 raise errors.MsgException('StartAdbServer: %d' % ret)
513 def KillAdbServer(self):
514 """Kill adb server."""
515 adb_cmd = [constants.GetAdbPath(), 'kill-server']
516 ret = cmd_helper.RunCmd(adb_cmd)
519 ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
521 # pgrep didn't find adb, kill-server succeeded.
527 def StartAdbServer(self):
528 """Start adb server."""
529 adb_cmd = ['taskset', '-c', '0', constants.GetAdbPath(), 'start-server']
530 ret, _ = cmd_helper.GetCmdStatusAndOutput(adb_cmd)
533 ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
535 # pgrep found adb, start-server succeeded.
536 # Waiting for device to reconnect before returning success.
537 self._adb.SendCommand('wait-for-device')
543 def WaitForSystemBootCompleted(self, wait_time):
544 """Waits for targeted system's boot_completed flag to be set.
547 wait_time: time in seconds to wait
550 WaitForResponseTimedOutError if wait_time elapses and flag still not
553 logging.info('Waiting for system boot completed...')
554 self._adb.SendCommand('wait-for-device')
555 # Now the device is there, but system not boot completed.
556 # Query the sys.boot_completed flag with a basic command
557 boot_completed = False
560 while not boot_completed and (attempts * wait_period) < wait_time:
561 output = self.system_properties['sys.boot_completed']
562 output = output.strip()
564 boot_completed = True
566 # If 'error: xxx' returned when querying the flag, it means
567 # adb server lost the connection to the emulator, so restart the adb
569 if 'error:' in output:
570 self.RestartAdbServer()
571 time.sleep(wait_period)
573 if not boot_completed:
574 raise errors.WaitForResponseTimedOutError(
575 'sys.boot_completed flag was not set after %s seconds' % wait_time)
577 def WaitForSdCardReady(self, timeout_time):
578 """Wait for the SD card ready before pushing data into it."""
579 logging.info('Waiting for SD card ready...')
583 external_storage = self.GetExternalStorage()
584 while not sdcard_ready and attempts * wait_period < timeout_time:
585 output = self.RunShellCommand('ls ' + external_storage)
589 time.sleep(wait_period)
592 raise errors.WaitForResponseTimedOutError(
593 'SD card not ready after %s seconds' % timeout_time)
595 def _CheckCommandIsValid(self, command):
596 """Raises a ValueError if the command is not valid."""
598 # A dict of commands the user should not run directly and a mapping to the
599 # API they should use instead.
601 'getprop': 'system_properties[<PROPERTY>]',
602 'setprop': 'system_properties[<PROPERTY>]',
603 'su': 'RunShellCommandWithSU()',
606 # A dict of commands to methods that may call them.
607 whitelisted_callers = {
608 'su': 'RunShellCommandWithSU',
611 base_command = shlex.split(command)[0]
612 if (base_command in preferred_apis and
613 (base_command not in whitelisted_callers or
614 whitelisted_callers[base_command] not in [
615 f[3] for f in inspect.stack()])):
616 error_msg = ('%s cannot be run directly. Instead use: %s' %
617 (base_command, preferred_apis[base_command]))
618 if self._api_strict_mode:
619 raise ValueError(error_msg)
621 logging.warning(error_msg)
623 # It is tempting to turn this function into a generator, however this is not
624 # possible without using a private (local) adb_shell instance (to ensure no
625 # other command interleaves usage of it), which would defeat the main aim of
626 # being able to reuse the adb shell instance across commands.
627 def RunShellCommand(self, command, timeout_time=20, log_result=False):
628 """Send a command to the adb shell and return the result.
631 command: String containing the shell command to send. Must not include
632 the single quotes as we use them to escape the whole command.
633 timeout_time: Number of seconds to wait for command to respond before
634 retrying, used by AdbInterface.SendShellCommand.
635 log_result: Boolean to indicate whether we should log the result of the
639 list containing the lines of output received from running the command
641 self._CheckCommandIsValid(command)
642 self._LogShell(command)
643 if "'" in command: logging.warning(command + " contains ' quotes")
644 result = self._adb.SendShellCommand(
645 "'%s'" % command, timeout_time).splitlines()
646 # TODO(b.kelemen): we should really be able to drop the stderr of the
647 # command or raise an exception based on what the caller wants.
648 result = [ l for l in result if not l.startswith('WARNING') ]
649 if ['error: device not found'] == result:
650 raise errors.DeviceUnresponsiveError('device not found')
652 self._LogShell('\n'.join(result))
655 def GetShellCommandStatusAndOutput(self, command, timeout_time=20,
657 """See RunShellCommand() above.
660 The tuple (exit code, list of output lines).
662 lines = self.RunShellCommand(
663 command + '; echo %$?', timeout_time, log_result)
664 last_line = lines[-1]
665 status_pos = last_line.rfind('%')
666 assert status_pos >= 0
667 status = int(last_line[status_pos + 1:])
671 lines = lines[:-1] + [last_line[:status_pos]]
672 return (status, lines)
674 def KillAll(self, process, signal=9, with_su=False):
675 """Android version of killall, connected via adb.
678 process: name of the process to kill off.
679 signal: signal to use, 9 (SIGKILL) by default.
680 with_su: wether or not to use su to kill the processes.
683 the number of processes killed
685 pids = self.ExtractPid(process)
687 cmd = 'kill -%d %s' % (signal, ' '.join(pids))
689 self.RunShellCommandWithSU(cmd)
691 self.RunShellCommand(cmd)
694 def KillAllBlocking(self, process, timeout_sec):
695 """Blocking version of killall, connected via adb.
697 This waits until no process matching the corresponding name appears in ps'
701 process: name of the process to kill off
702 timeout_sec: the timeout in seconds
705 the number of processes killed
707 processes_killed = self.KillAll(process)
711 # Note that this doesn't take into account the time spent in ExtractPid().
712 while self.ExtractPid(process) and elapsed < timeout_sec:
713 time.sleep(wait_period)
714 elapsed += wait_period
715 if elapsed >= timeout_sec:
717 return processes_killed
719 def _GetActivityCommand(self, package, activity, wait_for_completion, action,
720 category, data, extras, trace_file_name, force_stop,
722 """Creates command to start |package|'s activity on the device.
724 Args - as for StartActivity
727 the command to run on the target to start the activity
729 cmd = 'am start -a %s' % action
732 if wait_for_completion:
735 cmd += ' -c %s' % category
736 if package and activity:
737 cmd += ' -n %s/%s' % (package, activity)
739 cmd += ' -d "%s"' % data
743 if isinstance(value, str):
745 elif isinstance(value, bool):
747 elif isinstance(value, int):
750 raise NotImplementedError(
751 'Need to teach StartActivity how to pass %s extras' % type(value))
752 cmd += ' %s %s' % (key, value)
754 cmd += ' --start-profiler ' + trace_file_name
756 cmd += ' -f %s' % flags
759 def StartActivity(self, package, activity, wait_for_completion=False,
760 action='android.intent.action.VIEW',
761 category=None, data=None,
762 extras=None, trace_file_name=None,
763 force_stop=False, flags=None):
764 """Starts |package|'s activity on the device.
767 package: Name of package to start (e.g. 'com.google.android.apps.chrome').
768 activity: Name of activity (e.g. '.Main' or
769 'com.google.android.apps.chrome.Main').
770 wait_for_completion: wait for the activity to finish launching (-W flag).
771 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW.
772 category: string (e.g. "android.intent.category.HOME")
773 data: Data string to pass to activity (e.g. 'http://www.example.com/').
774 extras: Dict of extras to pass to activity. Values are significant.
775 trace_file_name: If used, turns on and saves the trace to this file name.
776 force_stop: force stop the target app before starting the activity (-S
779 cmd = self._GetActivityCommand(package, activity, wait_for_completion,
780 action, category, data, extras,
781 trace_file_name, force_stop, flags)
782 self.RunShellCommand(cmd)
784 def StartActivityTimed(self, package, activity, wait_for_completion=False,
785 action='android.intent.action.VIEW',
786 category=None, data=None,
787 extras=None, trace_file_name=None,
788 force_stop=False, flags=None):
789 """Starts |package|'s activity on the device, returning the start time
791 Args - as for StartActivity
794 a timestamp string for the time at which the activity started
796 cmd = self._GetActivityCommand(package, activity, wait_for_completion,
797 action, category, data, extras,
798 trace_file_name, force_stop, flags)
799 self.StartMonitoringLogcat()
800 self.RunShellCommand('log starting activity; ' + cmd)
801 activity_started_re = re.compile('.*starting activity.*')
802 m = self.WaitForLogMatch(activity_started_re, None)
804 start_line = m.group(0)
805 return GetLogTimestamp(start_line, self.GetDeviceYear())
807 def StartCrashUploadService(self, package):
808 # TODO(frankf): We really need a python wrapper around Intent
809 # to be shared with StartActivity/BroadcastIntent.
811 'am startservice -a %s.crash.ACTION_FIND_ALL -n '
812 '%s/%s.crash.MinidumpUploadService' %
813 (constants.PACKAGE_INFO['chrome'].package,
815 constants.PACKAGE_INFO['chrome'].package))
816 am_output = self.RunShellCommandWithSU(cmd)
817 assert am_output and 'Starting' in am_output[-1], (
818 'Service failed to start: %s' % am_output)
821 def BroadcastIntent(self, package, intent, *args):
822 """Send a broadcast intent.
825 package: Name of package containing the intent.
826 intent: Name of the intent.
827 args: Optional extra arguments for the intent.
829 cmd = 'am broadcast -a %s.%s %s' % (package, intent, ' '.join(args))
830 self.RunShellCommand(cmd)
833 """Tell the device to return to the home screen. Blocks until completion."""
834 self.RunShellCommand('am start -W '
835 '-a android.intent.action.MAIN -c android.intent.category.HOME')
837 def CloseApplication(self, package):
838 """Attempt to close down the application, using increasing violence.
841 package: Name of the process to kill off, e.g.
842 com.google.android.apps.chrome
844 self.RunShellCommand('am force-stop ' + package)
846 def GetApplicationPath(self, package):
847 """Get the installed apk path on the device for the given package.
850 package: Name of the package.
853 Path to the apk on the device if it exists, None otherwise.
855 pm_path_output = self.RunShellCommand('pm path ' + package)
856 # The path output contains anything if and only if the package
859 # pm_path_output is of the form: "package:/path/to/foo.apk"
860 return pm_path_output[0].split(':')[1]
864 def ClearApplicationState(self, package):
865 """Closes and clears all state for the given |package|."""
866 # Check that the package exists before clearing it. Necessary because
867 # calling pm clear on a package that doesn't exist may never return.
868 pm_path_output = self.RunShellCommand('pm path ' + package)
869 # The path output only contains anything if and only if the package exists.
871 self.RunShellCommand('pm clear ' + package)
873 def SendKeyEvent(self, keycode):
874 """Sends keycode to the device.
877 keycode: Numeric keycode to send (see "enum" at top of file).
879 self.RunShellCommand('input keyevent %d' % keycode)
881 def _RunMd5Sum(self, host_path, device_path):
882 """Gets the md5sum of a host path and device path.
885 host_path: Path (file or directory) on the host.
886 device_path: Path on the device.
889 A tuple containing lists of the host and device md5sum results as
890 created by _ParseMd5SumOutput().
892 md5sum_dist_path = os.path.join(constants.GetOutDirectory(),
894 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.'
895 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER)
896 assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
898 cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' +
899 MD5SUM_DEVICE_PATH + ' ' + device_path)
900 device_hash_tuples = _ParseMd5SumOutput(
901 self.RunShellCommand(cmd, timeout_time=2 * 60))
902 assert os.path.exists(host_path), 'Local path not found %s' % host_path
903 md5sum_output = cmd_helper.GetCmdOutput(
904 [os.path.join(constants.GetOutDirectory(), 'md5sum_bin_host'),
906 host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines())
907 return (host_hash_tuples, device_hash_tuples)
909 def GetFilesChanged(self, host_path, device_path, ignore_filenames=False):
910 """Compares the md5sum of a host path against a device path.
912 Note: Ignores extra files on the device.
915 host_path: Path (file or directory) on the host.
916 device_path: Path on the device.
917 ignore_filenames: If True only the file contents are considered when
918 checking whether a file has changed, otherwise the relative path
922 A list of tuples of the form (host_path, device_path) for files whose
923 md5sums do not match.
926 # Md5Sum resolves symbolic links in path names so the calculation of
927 # relative path names from its output will need the real path names of the
928 # base directories. Having calculated these they are used throughout the
929 # function since this makes us less subject to any future changes to Md5Sum.
930 real_host_path = os.path.realpath(host_path)
931 real_device_path = self.RunShellCommand('realpath "%s"' % device_path)[0]
933 host_hash_tuples, device_hash_tuples = self._RunMd5Sum(
934 real_host_path, real_device_path)
936 # Ignore extra files on the device.
937 if not ignore_filenames:
938 host_files = [os.path.relpath(os.path.normpath(p.path),
939 real_host_path) for p in host_hash_tuples]
942 return any(path in fname for path in host_files)
944 device_hash_tuples = [h for h in device_hash_tuples if HostHas(h.path)]
946 if len(host_hash_tuples) > len(device_hash_tuples):
947 logging.info('%s files do not exist on the device' %
948 (len(host_hash_tuples) - len(device_hash_tuples)))
950 # Constructs the target device path from a given host path. Don't use when
951 # only a single file is given as the base name given in device_path may
952 # differ from that in host_path.
953 def HostToDevicePath(host_file_path):
954 return os.path.join(device_path, os.path.relpath(host_file_path,
957 device_hashes = [h.hash for h in device_hash_tuples]
958 return [(t.path, HostToDevicePath(t.path) if
959 os.path.isdir(real_host_path) else real_device_path)
960 for t in host_hash_tuples if t.hash not in device_hashes]
962 def PushIfNeeded(self, host_path, device_path):
963 """Pushes |host_path| to |device_path|.
965 Works for files and directories. This method skips copying any paths in
966 |test_data_paths| that already exist on the device with the same hash.
968 All pushed files can be removed by calling RemovePushedFiles().
970 MAX_INDIVIDUAL_PUSHES = 50
971 assert os.path.exists(host_path), 'Local path not found %s' % host_path
973 # See if the file on the host changed since the last push (if any) and
974 # return early if it didn't. Note that this shortcut assumes that the tests
975 # on the device don't modify the files.
976 if not os.path.isdir(host_path):
977 if host_path in self._push_if_needed_cache:
978 host_path_mtime = self._push_if_needed_cache[host_path]
979 if host_path_mtime == os.stat(host_path).st_mtime:
982 def GetHostSize(path):
983 return int(cmd_helper.GetCmdOutput(['du', '-sb', path]).split()[0])
985 size = GetHostSize(host_path)
986 self._pushed_files.append(device_path)
987 self._potential_push_size += size
989 changed_files = self.GetFilesChanged(host_path, device_path)
990 logging.info('Found %d files that need to be pushed to %s',
991 len(changed_files), device_path)
992 if not changed_files:
995 def Push(host, device):
996 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout
997 # of 60 seconds which isn't sufficient for a lot of users of this method.
998 push_command = 'push %s %s' % (host, device)
999 self._LogShell(push_command)
1001 # Retry push with increasing backoff if the device is busy.
1004 output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
1005 if _HasAdbPushSucceeded(output):
1006 if not os.path.isdir(host_path):
1007 self._push_if_needed_cache[host] = os.stat(host).st_mtime
1011 wait_time = 5 * retry
1012 logging.error('Push failed, retrying in %d seconds: %s' %
1013 (wait_time, output))
1014 time.sleep(wait_time)
1016 raise Exception('Push failed: %s' % output)
1019 if len(changed_files) <= MAX_INDIVIDUAL_PUSHES:
1020 diff_size = sum(GetHostSize(f[0]) for f in changed_files)
1022 # TODO(craigdh): Replace this educated guess with a heuristic that
1023 # approximates the push time for each method.
1024 if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size:
1025 self._actual_push_size += size
1026 if os.path.isdir(host_path):
1027 self.RunShellCommand('mkdir -p %s' % device_path)
1028 Push(host_path, device_path)
1030 for f in changed_files:
1032 self._actual_push_size += diff_size
1034 def GetPushSizeInfo(self):
1035 """Get total size of pushes to the device done via PushIfNeeded()
1039 1. Total size of push requests to PushIfNeeded (MB)
1040 2. Total size that was actually pushed (MB)
1042 return (self._potential_push_size, self._actual_push_size)
1044 def GetFileContents(self, filename, log_result=False):
1045 """Gets contents from the file specified by |filename|."""
1046 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename,
1047 log_result=log_result)
1049 def SetFileContents(self, filename, contents):
1050 """Writes |contents| to the file specified by |filename|."""
1051 with tempfile.NamedTemporaryFile() as f:
1054 self._adb.Push(f.name, filename)
1056 _TEMP_FILE_BASE_FMT = 'temp_file_%d'
1057 _TEMP_SCRIPT_FILE_BASE_FMT = 'temp_script_file_%d.sh'
1059 def _GetDeviceTempFileName(self, base_name):
1061 while self.FileExistsOnDevice(
1062 self.GetExternalStorage() + '/' + base_name % i):
1064 return self.GetExternalStorage() + '/' + base_name % i
1066 def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False):
1067 return self.RunShellCommand('su -c %s' % command, timeout_time, log_result)
1069 def CanAccessProtectedFileContents(self):
1070 """Returns True if Get/SetProtectedFileContents would work via "su".
1072 Devices running user builds don't have adb root, but may provide "su" which
1073 can be used for accessing protected files.
1075 r = self.RunShellCommandWithSU('cat /dev/null')
1076 return r == [] or r[0].strip() == ''
1078 def GetProtectedFileContents(self, filename, log_result=False):
1079 """Gets contents from the protected file specified by |filename|.
1081 This is less efficient than GetFileContents, but will work for protected
1082 files and device files.
1084 # Run the script as root
1085 return self.RunShellCommandWithSU('cat "%s" 2> /dev/null' % filename)
1087 def SetProtectedFileContents(self, filename, contents):
1088 """Writes |contents| to the protected file specified by |filename|.
1090 This is less efficient than SetFileContents, but will work for protected
1091 files and device files.
1093 temp_file = self._GetDeviceTempFileName(AndroidCommands._TEMP_FILE_BASE_FMT)
1094 temp_script = self._GetDeviceTempFileName(
1095 AndroidCommands._TEMP_SCRIPT_FILE_BASE_FMT)
1097 # Put the contents in a temporary file
1098 self.SetFileContents(temp_file, contents)
1099 # Create a script to copy the file contents to its final destination
1100 self.SetFileContents(temp_script, 'cat %s > %s' % (temp_file, filename))
1101 # Run the script as root
1102 self.RunShellCommandWithSU('sh %s' % temp_script)
1103 # And remove the temporary files
1104 self.RunShellCommand('rm ' + temp_file)
1105 self.RunShellCommand('rm ' + temp_script)
1107 def RemovePushedFiles(self):
1108 """Removes all files pushed with PushIfNeeded() from the device."""
1109 for p in self._pushed_files:
1110 self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60)
1112 def ListPathContents(self, path):
1113 """Lists files in all subdirectories of |path|.
1116 path: The path to list.
1119 A dict of {"name": (size, lastmod), ...}.
1123 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt
1124 re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
1125 '(?P<user>[^\s]+)\s+'
1126 '(?P<group>[^\s]+)\s+'
1127 '(?P<size>[^\s]+)\s+'
1128 '(?P<date>[^\s]+)\s+'
1129 '(?P<time>[^\s]+)\s+'
1130 '(?P<filename>[^\s]+)$')
1131 return _GetFilesFromRecursiveLsOutput(
1132 path, self.RunShellCommand('ls -lR %s' % path), re_file,
1133 self.GetUtcOffset())
1135 def GetUtcOffset(self):
1136 if not self._device_utc_offset:
1137 self._device_utc_offset = self.RunShellCommand('date +%z')[0]
1138 return self._device_utc_offset
1140 def SetJavaAssertsEnabled(self, enable):
1141 """Sets or removes the device java assertions property.
1144 enable: If True the property will be set.
1147 True if the file was modified (reboot is required for it to take effect).
1149 # First ensure the desired property is persisted.
1150 temp_props_file = tempfile.NamedTemporaryFile()
1152 if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
1153 properties = file(temp_props_file.name).read()
1154 re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1155 r'\s*=\s*all\s*$', re.MULTILINE)
1156 if enable != bool(re.search(re_search, properties)):
1157 re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1158 r'\s*=\s*\w+\s*$', re.MULTILINE)
1159 properties = re.sub(re_replace, '', properties)
1161 properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
1163 file(temp_props_file.name, 'w').write(properties)
1164 self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
1166 # Next, check the current runtime value is what we need, and
1167 # if not, set it and report that a reboot is required.
1168 was_set = 'all' in self.system_properties[JAVA_ASSERT_PROPERTY]
1169 if was_set == enable:
1171 self.system_properties[JAVA_ASSERT_PROPERTY] = enable and 'all' or ''
1174 def GetBuildId(self):
1175 """Returns the build ID of the system (e.g. JRM79C)."""
1176 build_id = self.system_properties['ro.build.id']
1180 def GetBuildType(self):
1181 """Returns the build type of the system (e.g. eng)."""
1182 build_type = self.system_properties['ro.build.type']
1186 def GetBuildProduct(self):
1187 """Returns the build product of the device (e.g. maguro)."""
1188 build_product = self.system_properties['ro.build.product']
1189 assert build_product
1190 return build_product
1192 def GetProductName(self):
1193 """Returns the product name of the device (e.g. takju)."""
1194 name = self.system_properties['ro.product.name']
1198 def GetBuildFingerprint(self):
1199 """Returns the build fingerprint of the device."""
1200 build_fingerprint = self.system_properties['ro.build.fingerprint']
1201 assert build_fingerprint
1202 return build_fingerprint
1204 def GetDescription(self):
1205 """Returns the description of the system.
1207 For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys".
1209 description = self.system_properties['ro.build.description']
1213 def GetProductModel(self):
1214 """Returns the name of the product model (e.g. "Galaxy Nexus") """
1215 model = self.system_properties['ro.product.model']
1219 def GetWifiIP(self):
1220 """Returns the wifi IP on the device."""
1221 wifi_ip = self.system_properties['dhcp.wlan0.ipaddress']
1222 # Do not assert here. Devices (e.g. emulators) may not have a WifiIP.
1225 def GetSubscriberInfo(self):
1226 """Returns the device subscriber info (e.g. GSM and device ID) as string."""
1227 iphone_sub = self.RunShellCommand('dumpsys iphonesubinfo')
1229 return '\n'.join(iphone_sub)
1231 def GetBatteryInfo(self):
1232 """Returns the device battery info (e.g. status, level, etc) as string."""
1233 battery = self.RunShellCommand('dumpsys battery')
1235 return '\n'.join(battery)
1237 def GetSetupWizardStatus(self):
1238 """Returns the status of the device setup wizard (e.g. DISABLED)."""
1239 status = self.system_properties['ro.setupwizard.mode']
1240 # On some devices, the status is empty if not otherwise set. In such cases
1241 # the caller should expect an empty string to be returned.
1244 def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None):
1245 """Starts monitoring the output of logcat, for use with WaitForLogMatch.
1248 clear: If True the existing logcat output will be cleared, to avoiding
1249 matching historical output lurking in the log.
1250 filters: A list of logcat filters to be used.
1253 self.RunShellCommand('logcat -c')
1255 if self._adb._target_arg:
1256 args += shlex.split(self._adb._target_arg)
1257 args += ['logcat', '-v', 'threadtime']
1259 args.extend(filters)
1264 logfile = NewLineNormalizer(logfile)
1266 # Spawn logcat and synchronize with it.
1268 self._logcat = pexpect.spawn(constants.GetAdbPath(), args, timeout=10,
1270 if not clear or self.SyncLogCat():
1272 self._logcat.close(force=True)
1274 logging.critical('Error reading from logcat: ' + str(self._logcat.match))
1277 def SyncLogCat(self):
1278 """Synchronize with logcat.
1280 Synchronize with the monitored logcat so that WaitForLogMatch will only
1281 consider new message that are received after this point in time.
1284 True if the synchronization succeeded.
1287 tag = 'logcat_sync_%s' % time.time()
1288 self.RunShellCommand('log ' + tag)
1289 return self._logcat.expect([tag, pexpect.EOF, pexpect.TIMEOUT]) == 0
1291 def GetMonitoredLogCat(self):
1292 """Returns an "adb logcat" command as created by pexpected.spawn."""
1293 if not self._logcat:
1294 self.StartMonitoringLogcat(clear=False)
1297 def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10):
1298 """Blocks until a matching line is logged or a timeout occurs.
1301 success_re: A compiled re to search each line for.
1302 error_re: A compiled re which, if found, terminates the search for
1303 |success_re|. If None is given, no error condition will be detected.
1304 clear: If True the existing logcat output will be cleared, defaults to
1306 timeout: Timeout in seconds to wait for a log match.
1309 pexpect.TIMEOUT after |timeout| seconds without a match for |success_re|
1313 The re match object if |success_re| is matched first or None if |error_re|
1316 logging.info('<<< Waiting for logcat:' + str(success_re.pattern))
1319 if not self._logcat:
1320 self.StartMonitoringLogcat(clear)
1323 # Note this will block for upto the timeout _per log line_, so we need
1324 # to calculate the overall timeout remaining since t0.
1325 time_remaining = t0 + timeout - time.time()
1326 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat)
1327 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
1328 line = self._logcat.match.group(1)
1330 error_match = error_re.search(line)
1333 success_match = success_re.search(line)
1335 return success_match
1336 logging.info('<<< Skipped Logcat Line:' + str(line))
1337 except pexpect.TIMEOUT:
1338 raise pexpect.TIMEOUT(
1339 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
1341 (timeout, success_re.pattern))
1343 # It seems that sometimes logcat can end unexpectedly. This seems
1344 # to happen during Chrome startup after a reboot followed by a cache
1345 # clean. I don't understand why this happens, but this code deals with
1346 # getting EOF in logcat.
1347 logging.critical('Found EOF in adb logcat. Restarting...')
1348 # Rerun spawn with original arguments. Note that self._logcat.args[0] is
1349 # the path of adb, so we don't want it in the arguments.
1350 self._logcat = pexpect.spawn(constants.GetAdbPath(),
1351 self._logcat.args[1:],
1352 timeout=self._logcat.timeout,
1353 logfile=self._logcat.logfile)
1355 def StartRecordingLogcat(self, clear=True, filters=['*:v']):
1356 """Starts recording logcat output to eventually be saved as a string.
1358 This call should come before some series of tests are run, with either
1359 StopRecordingLogcat or SearchLogcatRecord following the tests.
1362 clear: True if existing log output should be cleared.
1363 filters: A list of logcat filters to be used.
1366 self._adb.SendCommand('logcat -c')
1367 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg,
1369 self._logcat_tmpoutfile = tempfile.NamedTemporaryFile(bufsize=0)
1370 self.logcat_process = subprocess.Popen(logcat_command, shell=True,
1371 stdout=self._logcat_tmpoutfile)
1373 def GetCurrentRecordedLogcat(self):
1374 """Return the current content of the logcat being recorded.
1375 Call this after StartRecordingLogcat() and before StopRecordingLogcat().
1376 This can be useful to perform timed polling/parsing.
1378 Current logcat output as a single string, or None if
1379 StopRecordingLogcat() was already called.
1381 if not self._logcat_tmpoutfile:
1384 with open(self._logcat_tmpoutfile.name) as f:
1387 def StopRecordingLogcat(self):
1388 """Stops an existing logcat recording subprocess and returns output.
1391 The logcat output as a string or an empty string if logcat was not
1392 being recorded at the time.
1394 if not self.logcat_process:
1396 # Cannot evaluate directly as 0 is a possible value.
1397 # Better to read the self.logcat_process.stdout before killing it,
1398 # Otherwise the communicate may return incomplete output due to pipe break.
1399 if self.logcat_process.poll() is None:
1400 self.logcat_process.kill()
1401 self.logcat_process.wait()
1402 self.logcat_process = None
1403 self._logcat_tmpoutfile.seek(0)
1404 output = self._logcat_tmpoutfile.read()
1405 self._logcat_tmpoutfile.close()
1406 self._logcat_tmpoutfile = None
1409 def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None,
1410 log_level=None, component=None):
1411 """Searches the specified logcat output and returns results.
1413 This method searches through the logcat output specified by record for a
1414 certain message, narrowing results by matching them against any other
1415 specified criteria. It returns all matching lines as described below.
1418 record: A string generated by Start/StopRecordingLogcat to search.
1419 message: An output string to search for.
1420 thread_id: The thread id that is the origin of the message.
1421 proc_id: The process that is the origin of the message.
1422 log_level: The log level of the message.
1423 component: The name of the component that would create the message.
1426 A list of dictionaries represeting matching entries, each containing keys
1427 thread_id, proc_id, log_level, component, and message.
1430 thread_id = str(thread_id)
1432 proc_id = str(proc_id)
1434 reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
1436 log_list = reg.findall(record)
1437 for (tid, pid, log_lev, comp, msg) in log_list:
1438 if ((not thread_id or thread_id == tid) and
1439 (not proc_id or proc_id == pid) and
1440 (not log_level or log_level == log_lev) and
1441 (not component or component == comp) and msg.find(message) > -1):
1442 match = dict({'thread_id': tid, 'proc_id': pid,
1443 'log_level': log_lev, 'component': comp,
1445 results.append(match)
1448 def ExtractPid(self, process_name):
1449 """Extracts Process Ids for a given process name from Android Shell.
1452 process_name: name of the process on the device.
1455 List of all the process ids (as strings) that match the given name.
1456 If the name of a process exactly matches the given name, the pid of
1457 that process will be inserted to the front of the pid list.
1460 for line in self.RunShellCommand('ps', log_result=False):
1463 if process_name in data[-1]: # name is in the last column
1464 if process_name == data[-1]:
1465 pids.insert(0, data[1]) # PID is in the second column
1467 pids.append(data[1])
1472 def GetIoStats(self):
1473 """Gets cumulative disk IO stats since boot (for all processes).
1476 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
1479 IoStats = collections.namedtuple(
1486 'num_writes_completed',
1487 'num_writes_merged',
1488 'num_sectors_written',
1490 'num_ios_in_progress',
1491 'ms_spent_doing_io',
1492 'ms_spent_doing_io_weighted',
1495 for line in self.GetFileContents('/proc/diskstats', log_result=False):
1496 fields = line.split()
1497 stats = IoStats._make([fields[2]] + [int(f) for f in fields[3:]])
1498 if stats.device == 'mmcblk0':
1500 'num_reads': stats.num_reads_issued,
1501 'num_writes': stats.num_writes_completed,
1502 'read_ms': stats.ms_spent_reading,
1503 'write_ms': stats.ms_spent_writing,
1505 logging.warning('Could not find disk IO stats.')
1508 def GetMemoryUsageForPid(self, pid):
1509 """Returns the memory usage for given pid.
1512 pid: The pid number of the specific process running on device.
1516 [0]: Dict of {metric:usage_kb}, for the process which has specified pid.
1517 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1518 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
1519 KernelPageSize, MMUPageSize, Nvidia (tablet only), VmHWM.
1520 [1]: Detailed /proc/[PID]/smaps information.
1522 usage_dict = collections.defaultdict(int)
1523 smaps = collections.defaultdict(dict)
1525 for line in self.GetProtectedFileContents('/proc/%s/smaps' % pid,
1527 items = line.split()
1528 # See man 5 proc for more details. The format is:
1529 # address perms offset dev inode pathname
1531 current_smap = ' '.join(items[5:])
1532 elif len(items) > 3:
1533 current_smap = ' '.join(items[3:])
1534 match = re.match(MEMORY_INFO_RE, line)
1536 key = match.group('key')
1537 usage_kb = int(match.group('usage_kb'))
1538 usage_dict[key] += usage_kb
1539 if key not in smaps[current_smap]:
1540 smaps[current_smap][key] = 0
1541 smaps[current_smap][key] += usage_kb
1542 if not usage_dict or not any(usage_dict.values()):
1543 # Presumably the process died between ps and calling this method.
1544 logging.warning('Could not find memory usage for pid ' + str(pid))
1546 for line in self.GetProtectedFileContents('/d/nvmap/generic-0/clients',
1548 match = re.match(NVIDIA_MEMORY_INFO_RE, line)
1549 if match and match.group('pid') == pid:
1550 usage_bytes = int(match.group('usage_bytes'))
1551 usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0)) # kB
1555 for line in self.GetProtectedFileContents('/proc/%s/status' % pid,
1557 if not line.startswith('VmHWM:'): # Format: 'VmHWM: +[0-9]+ kB'
1559 peak_value_kb = int(line.split(':')[1].strip().split(' ')[0])
1560 usage_dict['VmHWM'] = peak_value_kb
1561 if not peak_value_kb:
1562 logging.warning('Could not find memory peak value for pid ' + str(pid))
1564 return (usage_dict, smaps)
1566 def GetMemoryUsageForPackage(self, package):
1567 """Returns the memory usage for all processes whose name contains |pacakge|.
1570 package: A string holding process name to lookup pid list for.
1574 [0]: Dict of {metric:usage_kb}, summed over all pids associated with
1576 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1577 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
1578 KernelPageSize, MMUPageSize, Nvidia (tablet only).
1579 [1]: a list with detailed /proc/[PID]/smaps information.
1581 usage_dict = collections.defaultdict(int)
1582 pid_list = self.ExtractPid(package)
1583 smaps = collections.defaultdict(dict)
1585 for pid in pid_list:
1586 usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid)
1587 smaps[pid] = smaps_per_pid
1588 for (key, value) in usage_dict_per_pid.items():
1589 usage_dict[key] += value
1591 return usage_dict, smaps
1593 def ProcessesUsingDevicePort(self, device_port):
1594 """Lists processes using the specified device port on loopback interface.
1597 device_port: Port on device we want to check.
1600 A list of (pid, process_name) tuples using the specified port.
1602 tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False)
1603 tcp_address = '0100007F:%04X' % device_port
1605 for single_connect in tcp_results:
1606 connect_results = single_connect.split()
1607 # Column 1 is the TCP port, and Column 9 is the inode of the socket
1608 if connect_results[1] == tcp_address:
1609 socket_inode = connect_results[9]
1610 socket_name = 'socket:[%s]' % socket_inode
1611 lsof_results = self.RunShellCommand('lsof', log_result=False)
1612 for single_process in lsof_results:
1613 process_results = single_process.split()
1614 # Ignore the line if it has less than nine columns in it, which may
1615 # be the case when a process stops while lsof is executing.
1616 if len(process_results) <= 8:
1618 # Column 0 is the executable name
1619 # Column 1 is the pid
1620 # Column 8 is the Inode in use
1621 if process_results[8] == socket_name:
1622 pids.append((int(process_results[1]), process_results[0]))
1624 logging.info('PidsUsingDevicePort: %s', pids)
1627 def FileExistsOnDevice(self, file_name):
1628 """Checks whether the given file exists on the device.
1631 file_name: Full path of file to check.
1634 True if the file exists, False otherwise.
1636 assert '"' not in file_name, 'file_name cannot contain double quotes'
1638 status = self._adb.SendShellCommand(
1639 '\'test -e "%s"; echo $?\'' % (file_name))
1640 if 'test: not found' not in status:
1641 return int(status) == 0
1643 status = self._adb.SendShellCommand(
1644 '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name))
1645 return int(status) == 0
1647 if IsDeviceAttached(self._device):
1648 raise errors.DeviceUnresponsiveError('Device may be offline.')
1652 def IsFileWritableOnDevice(self, file_name):
1653 """Checks whether the given file (or directory) is writable on the device.
1656 file_name: Full path of file/directory to check.
1659 True if writable, False otherwise.
1661 assert '"' not in file_name, 'file_name cannot contain double quotes'
1663 status = self._adb.SendShellCommand(
1664 '\'test -w "%s"; echo $?\'' % (file_name))
1665 if 'test: not found' not in status:
1666 return int(status) == 0
1667 raise errors.AbortError('"test" binary not found. OS too old.')
1670 if IsDeviceAttached(self._device):
1671 raise errors.DeviceUnresponsiveError('Device may be offline.')
1675 def TakeScreenshot(self, host_file):
1676 """Saves a screenshot image to |host_file| on the host.
1679 host_file: Absolute path to the image file to store on the host or None to
1680 use an autogenerated file name.
1683 Resulting host file name of the screenshot.
1685 return screenshot.TakeScreenshot(self, host_file)
1687 def PullFileFromDevice(self, device_file, host_file):
1688 """Download |device_file| on the device from to |host_file| on the host.
1691 device_file: Absolute path to the file to retrieve from the device.
1692 host_file: Absolute path to the file to store on the host.
1694 assert self._adb.Pull(device_file, host_file)
1695 assert os.path.exists(host_file)
1697 def SetUtilWrapper(self, util_wrapper):
1698 """Sets a wrapper prefix to be used when running a locally-built
1699 binary on the device (ex.: md5sum_bin).
1701 self._util_wrapper = util_wrapper
1703 def RunInstrumentationTest(self, test, test_package, instr_args, timeout):
1704 """Runs a single instrumentation test.
1707 test: Test class/method.
1708 test_package: Package name of test apk.
1709 instr_args: Extra key/value to pass to am instrument.
1710 timeout: Timeout time in seconds.
1713 An instance of am_instrument_parser.TestResult object.
1715 instrumentation_path = ('%s/android.test.InstrumentationTestRunner' %
1717 args_with_filter = dict(instr_args)
1718 args_with_filter['class'] = test
1719 logging.info(args_with_filter)
1720 (raw_results, _) = self._adb.StartInstrumentation(
1721 instrumentation_path=instrumentation_path,
1722 instrumentation_args=args_with_filter,
1723 timeout_time=timeout)
1724 assert len(raw_results) == 1
1725 return raw_results[0]
1727 def RunUIAutomatorTest(self, test, test_package, timeout):
1728 """Runs a single uiautomator test.
1731 test: Test class/method.
1732 test_package: Name of the test jar.
1733 timeout: Timeout time in seconds.
1736 An instance of am_instrument_parser.TestResult object.
1738 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test)
1740 output = self._adb.SendShellCommand(cmd, timeout_time=timeout)
1741 # uiautomator doesn't fully conform to the instrumenation test runner
1742 # convention and doesn't terminate with INSTRUMENTATION_CODE.
1743 # Just assume the first result is valid.
1744 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output)
1745 if not test_results:
1746 raise errors.InstrumentationError(
1747 'no test results... device setup correctly?')
1748 return test_results[0]
1750 def DismissCrashDialogIfNeeded(self):
1751 """Dismiss the error/ANR dialog if present.
1753 Returns: Name of the crashed package if a dialog is focused,
1756 re_focus = re.compile(
1757 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
1759 def _FindFocusedWindow():
1761 for line in self.RunShellCommand('dumpsys window windows'):
1762 match = re.match(re_focus, line)
1767 match = _FindFocusedWindow()
1770 package = match.group(2)
1771 logging.warning('Trying to dismiss %s dialog for %s' % match.groups())
1772 self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1773 self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1774 self.SendKeyEvent(KEYCODE_ENTER)
1775 match = _FindFocusedWindow()
1777 logging.error('Still showing a %s dialog for %s' % match.groups())
1781 class NewLineNormalizer(object):
1782 """A file-like object to normalize EOLs to '\n'.
1784 Pexpect runs adb within a pseudo-tty device (see
1785 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written
1786 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate
1787 lines, the log ends up having '\r\r\n' at the end of each line. This
1788 filter replaces the above with a single '\n' in the data stream.
1790 def __init__(self, output):
1791 self._output = output
1793 def write(self, data):
1794 data = data.replace('\r\r\n', '\n')
1795 self._output.write(data)
1798 self._output.flush()