Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / build / android / pylib / android_commands.py
blob7c73b526da7e35f42e32da4e7e6b8e4a7b49a62a
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.
8 """
9 # pylint: disable-all
11 import collections
12 import datetime
13 import inspect
14 import logging
15 import os
16 import random
17 import re
18 import shlex
19 import signal
20 import subprocess
21 import sys
22 import tempfile
23 import time
25 import cmd_helper
26 import constants
27 import system_properties
28 from utils import host_utils
30 try:
31 from pylib import pexpect
32 except ImportError:
33 pexpect = None
35 sys.path.append(os.path.join(
36 constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
37 import adb_interface
38 import am_instrument_parser
39 import errors
41 from pylib.device import device_blacklist
42 from pylib.device import device_errors
44 # Pattern to search for the next whole line of pexpect output and capture it
45 # into a match group. We can't use ^ and $ for line start end with pexpect,
46 # see http://www.noah.org/python/pexpect/#doc for explanation why.
47 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
49 # Set the adb shell prompt to be a unique marker that will [hopefully] not
50 # appear at the start of any line of a command's output.
51 SHELL_PROMPT = '~+~PQ\x17RS~+~'
53 # Java properties file
54 LOCAL_PROPERTIES_PATH = constants.DEVICE_LOCAL_PROPERTIES_PATH
56 # Property in /data/local.prop that controls Java assertions.
57 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
59 # Keycode "enum" suitable for passing to AndroidCommands.SendKey().
60 KEYCODE_HOME = 3
61 KEYCODE_BACK = 4
62 KEYCODE_DPAD_UP = 19
63 KEYCODE_DPAD_DOWN = 20
64 KEYCODE_DPAD_RIGHT = 22
65 KEYCODE_ENTER = 66
66 KEYCODE_MENU = 82
68 MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/'
69 MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin'
71 PIE_WRAPPER_PATH = constants.TEST_EXECUTABLE_DIR + '/run_pie'
73 CONTROL_USB_CHARGING_COMMANDS = [
75 # Nexus 4
76 'witness_file': '/sys/module/pm8921_charger/parameters/disabled',
77 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled',
78 'disable_command':
79 'echo 1 > /sys/module/pm8921_charger/parameters/disabled',
83 class DeviceTempFile(object):
84 def __init__(self, android_commands, prefix='temp_file', suffix=''):
85 """Find an unused temporary file path in the devices external directory.
87 When this object is closed, the file will be deleted on the device.
88 """
89 self.android_commands = android_commands
90 while True:
91 # TODO(cjhopman): This could actually return the same file in multiple
92 # calls if the caller doesn't write to the files immediately. This is
93 # expected to never happen.
94 i = random.randint(0, 1000000)
95 self.name = '%s/%s-%d-%010d%s' % (
96 android_commands.GetExternalStorage(),
97 prefix, int(time.time()), i, suffix)
98 if not android_commands.FileExistsOnDevice(self.name):
99 break
101 def __enter__(self):
102 return self
104 def __exit__(self, type, value, traceback):
105 self.close()
107 def close(self):
108 self.android_commands.RunShellCommand('rm ' + self.name)
111 def GetAVDs():
112 """Returns a list of AVDs."""
113 re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE)
114 avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd']))
115 return avds
117 def ResetBadDevices():
118 """Removes the blacklist that keeps track of bad devices for a current
119 build.
121 device_blacklist.ResetBlacklist()
123 def ExtendBadDevices(devices):
124 """Adds devices to the blacklist that keeps track of bad devices for a
125 current build.
127 The devices listed in the bad devices file will not be returned by
128 GetAttachedDevices.
130 Args:
131 devices: list of bad devices to be added to the bad devices file.
133 device_blacklist.ExtendBlacklist(devices)
136 def GetAttachedDevices(hardware=True, emulator=True, offline=False):
137 """Returns a list of attached, android devices and emulators.
139 If a preferred device has been set with ANDROID_SERIAL, it will be first in
140 the returned list. The arguments specify what devices to include in the list.
142 Example output:
144 * daemon not running. starting it now on port 5037 *
145 * daemon started successfully *
146 List of devices attached
147 027c10494100b4d7 device
148 emulator-5554 offline
150 Args:
151 hardware: Include attached actual devices that are online.
152 emulator: Include emulators (i.e. AVD's) currently on host.
153 offline: Include devices and emulators that are offline.
155 Returns: List of devices.
157 adb_devices_output = cmd_helper.GetCmdOutput([constants.GetAdbPath(),
158 'devices'])
160 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
161 online_devices = re_device.findall(adb_devices_output)
163 re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE)
164 emulator_devices = re_device.findall(adb_devices_output)
166 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\toffline$', re.MULTILINE)
167 offline_devices = re_device.findall(adb_devices_output)
169 devices = []
170 # First determine list of online devices (e.g. hardware and/or emulator).
171 if hardware and emulator:
172 devices = online_devices
173 elif hardware:
174 devices = [device for device in online_devices
175 if device not in emulator_devices]
176 elif emulator:
177 devices = emulator_devices
179 # Now add offline devices if offline is true
180 if offline:
181 devices = devices + offline_devices
183 # Remove any devices in the blacklist.
184 blacklist = device_blacklist.ReadBlacklist()
185 if len(blacklist):
186 logging.info('Avoiding bad devices %s', ' '.join(blacklist))
187 devices = [device for device in devices if device not in blacklist]
189 preferred_device = os.environ.get('ANDROID_SERIAL')
190 if preferred_device in devices:
191 devices.remove(preferred_device)
192 devices.insert(0, preferred_device)
193 return devices
196 def IsDeviceAttached(device):
197 """Return true if the device is attached and online."""
198 return device in GetAttachedDevices()
201 def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
202 """Gets a list of files from `ls` command output.
204 Python's os.walk isn't used because it doesn't work over adb shell.
206 Args:
207 path: The path to list.
208 ls_output: A list of lines returned by an `ls -lR` command.
209 re_file: A compiled regular expression which parses a line into named groups
210 consisting of at minimum "filename", "date", "time", "size" and
211 optionally "timezone".
212 utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
213 2-digit string giving the number of UTC offset hours, and MM is a
214 2-digit string giving the number of UTC offset minutes. If the input
215 utc_offset is None, will try to look for the value of "timezone" if it
216 is specified in re_file.
218 Returns:
219 A dict of {"name": (size, lastmod), ...} where:
220 name: The file name relative to |path|'s directory.
221 size: The file size in bytes (0 for directories).
222 lastmod: The file last modification date in UTC.
224 re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path))
225 path_dir = os.path.dirname(path)
227 current_dir = ''
228 files = {}
229 for line in ls_output:
230 directory_match = re_directory.match(line)
231 if directory_match:
232 current_dir = directory_match.group('dir')
233 continue
234 file_match = re_file.match(line)
235 if file_match:
236 filename = os.path.join(current_dir, file_match.group('filename'))
237 if filename.startswith(path_dir):
238 filename = filename[len(path_dir) + 1:]
239 lastmod = datetime.datetime.strptime(
240 file_match.group('date') + ' ' + file_match.group('time')[:5],
241 '%Y-%m-%d %H:%M')
242 if not utc_offset and 'timezone' in re_file.groupindex:
243 utc_offset = file_match.group('timezone')
244 if isinstance(utc_offset, str) and len(utc_offset) == 5:
245 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
246 minutes=int(utc_offset[3:5]))
247 if utc_offset[0:1] == '-':
248 utc_delta = -utc_delta
249 lastmod -= utc_delta
250 files[filename] = (int(file_match.group('size')), lastmod)
251 return files
254 def _ParseMd5SumOutput(md5sum_output):
255 """Returns a list of tuples from the provided md5sum output.
257 Args:
258 md5sum_output: output directly from md5sum binary.
260 Returns:
261 List of namedtuples with attributes |hash| and |path|, where |path| is the
262 absolute path to the file with an Md5Sum of |hash|.
264 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path'])
265 split_lines = [line.split(' ') for line in md5sum_output]
266 return [HashAndPath._make(s) for s in split_lines if len(s) == 2]
269 def _HasAdbPushSucceeded(command_output):
270 """Returns whether adb push has succeeded from the provided output."""
271 # TODO(frankf): We should look at the return code instead of the command
272 # output for many of the commands in this file.
273 if not command_output:
274 return True
275 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
276 # Errors look like this: "failed to copy ... "
277 if not re.search('^[0-9]', command_output.splitlines()[-1]):
278 logging.critical('PUSH FAILED: ' + command_output)
279 return False
280 return True
283 def GetLogTimestamp(log_line, year):
284 """Returns the timestamp of the given |log_line| in the given year."""
285 try:
286 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
287 '%Y-%m-%d %H:%M:%S.%f')
288 except (ValueError, IndexError):
289 logging.critical('Error reading timestamp from ' + log_line)
290 return None
293 class AndroidCommands(object):
294 """Helper class for communicating with Android device via adb."""
296 def __init__(self, device=None):
297 """Constructor.
299 Args:
300 device: If given, adb commands are only send to the device of this ID.
301 Otherwise commands are sent to all attached devices.
303 adb_dir = os.path.dirname(constants.GetAdbPath())
304 if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
305 # Required by third_party/android_testrunner to call directly 'adb'.
306 os.environ['PATH'] += os.pathsep + adb_dir
307 self._adb = adb_interface.AdbInterface()
308 if device:
309 self._adb.SetTargetSerial(device)
310 self._device = device
311 self._logcat = None
312 self.logcat_process = None
313 self._logcat_tmpoutfile = None
314 self._pushed_files = []
315 self._device_utc_offset = None
316 self._potential_push_size = 0
317 self._actual_push_size = 0
318 self._external_storage = ''
319 self._util_wrapper = ''
320 self._system_properties = system_properties.SystemProperties(self.Adb())
321 self._push_if_needed_cache = {}
322 self._control_usb_charging_command = {
323 'command': None,
324 'cached': False,
326 self._protected_file_access_method_initialized = None
327 self._privileged_command_runner = None
328 self._pie_wrapper = None
330 @property
331 def system_properties(self):
332 return self._system_properties
334 def _LogShell(self, cmd):
335 """Logs the adb shell command."""
336 if self._device:
337 device_repr = self._device[-4:]
338 else:
339 device_repr = '????'
340 logging.info('[%s]> %s', device_repr, cmd)
342 def Adb(self):
343 """Returns our AdbInterface to avoid us wrapping all its methods."""
344 # TODO(tonyg): Goal should be to git rid of this method by making this API
345 # complete and alleviating the need.
346 return self._adb
348 def GetDevice(self):
349 """Returns the device serial."""
350 return self._device
352 def IsOnline(self):
353 """Checks whether the device is online.
355 Returns:
356 True if device is in 'device' mode, False otherwise.
358 # TODO(aurimas): revert to using adb get-state when android L adb is fixed.
359 #out = self._adb.SendCommand('get-state')
360 #return out.strip() == 'device'
362 out = self._adb.SendCommand('devices')
363 for line in out.split('\n'):
364 if self._device in line and 'device' in line:
365 return True
366 return False
368 def IsRootEnabled(self):
369 """Checks if root is enabled on the device."""
370 root_test_output = self.RunShellCommand('ls /root') or ['']
371 return not 'Permission denied' in root_test_output[0]
373 def EnableAdbRoot(self):
374 """Enables adb root on the device.
376 Returns:
377 True: if output from executing adb root was as expected.
378 False: otherwise.
380 if self.GetBuildType() == 'user':
381 logging.warning("Can't enable root in production builds with type user")
382 return False
383 else:
384 return_value = self._adb.EnableAdbRoot()
385 # EnableAdbRoot inserts a call for wait-for-device only when adb logcat
386 # output matches what is expected. Just to be safe add a call to
387 # wait-for-device.
388 self._adb.SendCommand('wait-for-device')
389 return return_value
391 def GetDeviceYear(self):
392 """Returns the year information of the date on device."""
393 return self.RunShellCommand('date +%Y')[0]
395 def GetExternalStorage(self):
396 if not self._external_storage:
397 self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0]
398 if not self._external_storage:
399 raise device_errors.CommandFailedError(
400 ['shell', "'echo $EXTERNAL_STORAGE'"],
401 'Unable to find $EXTERNAL_STORAGE')
402 return self._external_storage
404 def WaitForDevicePm(self, timeout=120):
405 """Blocks until the device's package manager is available.
407 To workaround http://b/5201039, we restart the shell and retry if the
408 package manager isn't back after 120 seconds.
410 Raises:
411 errors.WaitForResponseTimedOutError after max retries reached.
413 last_err = None
414 retries = 3
415 while retries:
416 try:
417 self._adb.WaitForDevicePm(wait_time=timeout)
418 return # Success
419 except errors.WaitForResponseTimedOutError as e:
420 last_err = e
421 logging.warning('Restarting and retrying after timeout: %s', e)
422 retries -= 1
423 self.RestartShell()
424 raise last_err # Only reached after max retries, re-raise the last error.
426 def RestartShell(self):
427 """Restarts the shell on the device. Does not block for it to return."""
428 self.RunShellCommand('stop')
429 self.RunShellCommand('start')
431 def Reboot(self, full_reboot=True):
432 """Reboots the device and waits for the package manager to return.
434 Args:
435 full_reboot: Whether to fully reboot the device or just restart the shell.
437 # TODO(torne): hive can't reboot the device either way without breaking the
438 # connection; work out if we can handle this better
439 if os.environ.get('USING_HIVE'):
440 logging.warning('Ignoring reboot request as we are on hive')
441 return
442 if full_reboot or not self.IsRootEnabled():
443 self._adb.SendCommand('reboot')
444 self._system_properties = system_properties.SystemProperties(self.Adb())
445 timeout = 300
446 retries = 1
447 # Wait for the device to disappear.
448 while retries < 10 and self.IsOnline():
449 time.sleep(1)
450 retries += 1
451 else:
452 self.RestartShell()
453 timeout = 120
454 # To run tests we need at least the package manager and the sd card (or
455 # other external storage) to be ready.
456 self.WaitForDevicePm(timeout)
457 self.WaitForSdCardReady(timeout)
459 def Shutdown(self):
460 """Shuts down the device."""
461 self._adb.SendCommand('reboot -p')
462 self._system_properties = system_properties.SystemProperties(self.Adb())
464 def Uninstall(self, package):
465 """Uninstalls the specified package from the device.
467 Args:
468 package: Name of the package to remove.
470 Returns:
471 A status string returned by adb uninstall
473 uninstall_command = 'uninstall %s' % package
475 self._LogShell(uninstall_command)
476 return self._adb.SendCommand(uninstall_command, timeout_time=60)
478 def Install(self, package_file_path, reinstall=False):
479 """Installs the specified package to the device.
481 Args:
482 package_file_path: Path to .apk file to install.
483 reinstall: Reinstall an existing apk, keeping the data.
485 Returns:
486 A status string returned by adb install
488 assert os.path.isfile(package_file_path), ('<%s> is not file' %
489 package_file_path)
491 install_cmd = ['install']
493 if reinstall:
494 install_cmd.append('-r')
496 install_cmd.append(package_file_path)
497 install_cmd = ' '.join(install_cmd)
499 self._LogShell(install_cmd)
500 return self._adb.SendCommand(install_cmd,
501 timeout_time=2 * 60,
502 retry_count=0)
504 def ManagedInstall(self, apk_path, keep_data=False, package_name=None,
505 reboots_on_timeout=2):
506 """Installs specified package and reboots device on timeouts.
508 If package_name is supplied, checks if the package is already installed and
509 doesn't reinstall if the apk md5sums match.
511 Args:
512 apk_path: Path to .apk file to install.
513 keep_data: Reinstalls instead of uninstalling first, preserving the
514 application data.
515 package_name: Package name (only needed if keep_data=False).
516 reboots_on_timeout: number of time to reboot if package manager is frozen.
518 # Check if package is already installed and up to date.
519 if package_name:
520 installed_apk_path = self.GetApplicationPath(package_name)
521 if (installed_apk_path and
522 not self.GetFilesChanged(apk_path, installed_apk_path,
523 ignore_filenames=True)):
524 logging.info('Skipped install: identical %s APK already installed' %
525 package_name)
526 return
527 # Install.
528 reboots_left = reboots_on_timeout
529 while True:
530 try:
531 if not keep_data:
532 assert package_name
533 self.Uninstall(package_name)
534 install_status = self.Install(apk_path, reinstall=keep_data)
535 if 'Success' in install_status:
536 return
537 else:
538 raise Exception('Install failure: %s' % install_status)
539 except errors.WaitForResponseTimedOutError:
540 print '@@@STEP_WARNINGS@@@'
541 logging.info('Timeout on installing %s on device %s', apk_path,
542 self._device)
544 if reboots_left <= 0:
545 raise Exception('Install timed out')
547 # Force a hard reboot on last attempt
548 self.Reboot(full_reboot=(reboots_left == 1))
549 reboots_left -= 1
551 def MakeSystemFolderWritable(self):
552 """Remounts the /system folder rw."""
553 out = self._adb.SendCommand('remount')
554 if out.strip() != 'remount succeeded':
555 raise errors.MsgException('Remount failed: %s' % out)
557 def RestartAdbdOnDevice(self):
558 logging.info('Restarting adbd on the device...')
559 with DeviceTempFile(self, suffix=".sh") as temp_script_file:
560 host_script_path = os.path.join(constants.DIR_SOURCE_ROOT,
561 'build',
562 'android',
563 'pylib',
564 'restart_adbd.sh')
565 self._adb.Push(host_script_path, temp_script_file.name)
566 self.RunShellCommand('. %s' % temp_script_file.name)
567 self._adb.SendCommand('wait-for-device')
569 def RestartAdbServer(self):
570 """Restart the adb server."""
571 ret = self.KillAdbServer()
572 if ret != 0:
573 raise errors.MsgException('KillAdbServer: %d' % ret)
575 ret = self.StartAdbServer()
576 if ret != 0:
577 raise errors.MsgException('StartAdbServer: %d' % ret)
579 @staticmethod
580 def KillAdbServer():
581 """Kill adb server."""
582 adb_cmd = [constants.GetAdbPath(), 'kill-server']
583 ret = cmd_helper.RunCmd(adb_cmd)
584 retry = 0
585 while retry < 3:
586 ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
587 if ret != 0:
588 # pgrep didn't find adb, kill-server succeeded.
589 return 0
590 retry += 1
591 time.sleep(retry)
592 return ret
594 def StartAdbServer(self):
595 """Start adb server."""
596 adb_cmd = ['taskset', '-c', '0', constants.GetAdbPath(), 'start-server']
597 ret, _ = cmd_helper.GetCmdStatusAndOutput(adb_cmd)
598 retry = 0
599 while retry < 3:
600 ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
601 if ret == 0:
602 # pgrep found adb, start-server succeeded.
603 # Waiting for device to reconnect before returning success.
604 self._adb.SendCommand('wait-for-device')
605 return 0
606 retry += 1
607 time.sleep(retry)
608 return ret
610 def WaitForSystemBootCompleted(self, wait_time):
611 """Waits for targeted system's boot_completed flag to be set.
613 Args:
614 wait_time: time in seconds to wait
616 Raises:
617 WaitForResponseTimedOutError if wait_time elapses and flag still not
618 set.
620 logging.info('Waiting for system boot completed...')
621 self._adb.SendCommand('wait-for-device')
622 # Now the device is there, but system not boot completed.
623 # Query the sys.boot_completed flag with a basic command
624 boot_completed = False
625 attempts = 0
626 wait_period = 5
627 while not boot_completed and (attempts * wait_period) < wait_time:
628 output = self.system_properties['sys.boot_completed']
629 output = output.strip()
630 if output == '1':
631 boot_completed = True
632 else:
633 # If 'error: xxx' returned when querying the flag, it means
634 # adb server lost the connection to the emulator, so restart the adb
635 # server.
636 if 'error:' in output:
637 self.RestartAdbServer()
638 time.sleep(wait_period)
639 attempts += 1
640 if not boot_completed:
641 raise errors.WaitForResponseTimedOutError(
642 'sys.boot_completed flag was not set after %s seconds' % wait_time)
644 def WaitForSdCardReady(self, timeout_time):
645 """Wait for the SD card ready before pushing data into it."""
646 logging.info('Waiting for SD card ready...')
647 sdcard_ready = False
648 attempts = 0
649 wait_period = 5
650 external_storage = self.GetExternalStorage()
651 while not sdcard_ready and attempts * wait_period < timeout_time:
652 output = self.RunShellCommand('ls ' + external_storage)
653 if output:
654 sdcard_ready = True
655 else:
656 time.sleep(wait_period)
657 attempts += 1
658 if not sdcard_ready:
659 raise errors.WaitForResponseTimedOutError(
660 'SD card not ready after %s seconds' % timeout_time)
662 def GetAndroidToolStatusAndOutput(self, command, lib_path=None, *args, **kw):
663 """Runs a native Android binary, wrapping the command as necessary.
665 This is a specialization of GetShellCommandStatusAndOutput, which is meant
666 for running tools/android/ binaries and handle properly: (1) setting the
667 lib path (for component=shared_library), (2) using the PIE wrapper on ICS.
668 See crbug.com/373219 for more context.
670 Args:
671 command: String containing the command to send.
672 lib_path: (optional) path to the folder containing the dependent libs.
673 Same other arguments of GetCmdStatusAndOutput.
675 # The first time this command is run the device is inspected to check
676 # whether a wrapper for running PIE executable is needed (only Android ICS)
677 # or not. The results is cached, so the wrapper is pushed only once.
678 if self._pie_wrapper is None:
679 # None: did not check; '': did check and not needed; '/path': use /path.
680 self._pie_wrapper = ''
681 if self.GetBuildId().startswith('I'): # Ixxxx = Android ICS.
682 run_pie_dist_path = os.path.join(constants.GetOutDirectory(), 'run_pie')
683 assert os.path.exists(run_pie_dist_path), 'Please build run_pie'
684 # The PIE loader must be pushed manually (i.e. no PushIfNeeded) because
685 # PushIfNeeded requires md5sum and md5sum requires the wrapper as well.
686 command = 'push %s %s' % (run_pie_dist_path, PIE_WRAPPER_PATH)
687 assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
688 self._pie_wrapper = PIE_WRAPPER_PATH
690 if self._pie_wrapper:
691 command = '%s %s' % (self._pie_wrapper, command)
692 if lib_path:
693 command = 'LD_LIBRARY_PATH=%s %s' % (lib_path, command)
694 return self.GetShellCommandStatusAndOutput(command, *args, **kw)
696 # It is tempting to turn this function into a generator, however this is not
697 # possible without using a private (local) adb_shell instance (to ensure no
698 # other command interleaves usage of it), which would defeat the main aim of
699 # being able to reuse the adb shell instance across commands.
700 def RunShellCommand(self, command, timeout_time=20, log_result=False):
701 """Send a command to the adb shell and return the result.
703 Args:
704 command: String containing the shell command to send. Must not include
705 the single quotes as we use them to escape the whole command.
706 timeout_time: Number of seconds to wait for command to respond before
707 retrying, used by AdbInterface.SendShellCommand.
708 log_result: Boolean to indicate whether we should log the result of the
709 shell command.
711 Returns:
712 list containing the lines of output received from running the command
714 self._LogShell(command)
715 if "'" in command:
716 command = command.replace('\'', '\'\\\'\'')
717 result = self._adb.SendShellCommand(
718 "'%s'" % command, timeout_time).splitlines()
719 # TODO(b.kelemen): we should really be able to drop the stderr of the
720 # command or raise an exception based on what the caller wants.
721 result = [ l for l in result if not l.startswith('WARNING') ]
722 if ['error: device not found'] == result:
723 raise errors.DeviceUnresponsiveError('device not found')
724 if log_result:
725 self._LogShell('\n'.join(result))
726 return result
728 def GetShellCommandStatusAndOutput(self, command, timeout_time=20,
729 log_result=False):
730 """See RunShellCommand() above.
732 Returns:
733 The tuple (exit code, list of output lines).
735 lines = self.RunShellCommand(
736 command + '; echo %$?', timeout_time, log_result)
737 last_line = lines[-1]
738 status_pos = last_line.rfind('%')
739 assert status_pos >= 0
740 status = int(last_line[status_pos + 1:])
741 if status_pos == 0:
742 lines = lines[:-1]
743 else:
744 lines = lines[:-1] + [last_line[:status_pos]]
745 return (status, lines)
747 def KillAll(self, process, signum=9, with_su=False):
748 """Android version of killall, connected via adb.
750 Args:
751 process: name of the process to kill off.
752 signum: signal to use, 9 (SIGKILL) by default.
753 with_su: wether or not to use su to kill the processes.
755 Returns:
756 the number of processes killed
758 pids = self.ExtractPid(process)
759 if pids:
760 cmd = 'kill -%d %s' % (signum, ' '.join(pids))
761 if with_su:
762 self.RunShellCommandWithSU(cmd)
763 else:
764 self.RunShellCommand(cmd)
765 return len(pids)
767 def KillAllBlocking(self, process, timeout_sec, signum=9, with_su=False):
768 """Blocking version of killall, connected via adb.
770 This waits until no process matching the corresponding name appears in ps'
771 output anymore.
773 Args:
774 process: name of the process to kill off
775 timeout_sec: the timeout in seconds
776 signum: same as |KillAll|
777 with_su: same as |KillAll|
778 Returns:
779 the number of processes killed
781 processes_killed = self.KillAll(process, signum=signum, with_su=with_su)
782 if processes_killed:
783 elapsed = 0
784 wait_period = 0.1
785 # Note that this doesn't take into account the time spent in ExtractPid().
786 while self.ExtractPid(process) and elapsed < timeout_sec:
787 time.sleep(wait_period)
788 elapsed += wait_period
789 if elapsed >= timeout_sec:
790 return processes_killed - self.ExtractPid(process)
791 return processes_killed
793 @staticmethod
794 def _GetActivityCommand(package, activity, wait_for_completion, action,
795 category, data, extras, trace_file_name, force_stop,
796 flags):
797 """Creates command to start |package|'s activity on the device.
799 Args - as for StartActivity
801 Returns:
802 the command to run on the target to start the activity
804 cmd = 'am start -a %s' % action
805 if force_stop:
806 cmd += ' -S'
807 if wait_for_completion:
808 cmd += ' -W'
809 if category:
810 cmd += ' -c %s' % category
811 if package and activity:
812 cmd += ' -n %s/%s' % (package, activity)
813 if data:
814 cmd += ' -d "%s"' % data
815 if extras:
816 for key in extras:
817 value = extras[key]
818 if isinstance(value, str):
819 cmd += ' --es'
820 elif isinstance(value, bool):
821 cmd += ' --ez'
822 elif isinstance(value, int):
823 cmd += ' --ei'
824 else:
825 raise NotImplementedError(
826 'Need to teach StartActivity how to pass %s extras' % type(value))
827 cmd += ' %s %s' % (key, value)
828 if trace_file_name:
829 cmd += ' --start-profiler ' + trace_file_name
830 if flags:
831 cmd += ' -f %s' % flags
832 return cmd
834 def StartActivity(self, package, activity, wait_for_completion=False,
835 action='android.intent.action.VIEW',
836 category=None, data=None,
837 extras=None, trace_file_name=None,
838 force_stop=False, flags=None):
839 """Starts |package|'s activity on the device.
841 Args:
842 package: Name of package to start (e.g. 'com.google.android.apps.chrome').
843 activity: Name of activity (e.g. '.Main' or
844 'com.google.android.apps.chrome.Main').
845 wait_for_completion: wait for the activity to finish launching (-W flag).
846 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW.
847 category: string (e.g. "android.intent.category.HOME")
848 data: Data string to pass to activity (e.g. 'http://www.example.com/').
849 extras: Dict of extras to pass to activity. Values are significant.
850 trace_file_name: If used, turns on and saves the trace to this file name.
851 force_stop: force stop the target app before starting the activity (-S
852 flag).
853 Returns:
854 The output of the underlying command as a list of lines.
856 cmd = self._GetActivityCommand(package, activity, wait_for_completion,
857 action, category, data, extras,
858 trace_file_name, force_stop, flags)
859 return self.RunShellCommand(cmd)
861 def StartActivityTimed(self, package, activity, wait_for_completion=False,
862 action='android.intent.action.VIEW',
863 category=None, data=None,
864 extras=None, trace_file_name=None,
865 force_stop=False, flags=None):
866 """Starts |package|'s activity on the device, returning the start time
868 Args - as for StartActivity
870 Returns:
871 A tuple containing:
872 - the output of the underlying command as a list of lines, and
873 - a timestamp string for the time at which the activity started
875 cmd = self._GetActivityCommand(package, activity, wait_for_completion,
876 action, category, data, extras,
877 trace_file_name, force_stop, flags)
878 self.StartMonitoringLogcat()
879 out = self.RunShellCommand('log starting activity; ' + cmd)
880 activity_started_re = re.compile('.*starting activity.*')
881 m = self.WaitForLogMatch(activity_started_re, None)
882 assert m
883 start_line = m.group(0)
884 return (out, GetLogTimestamp(start_line, self.GetDeviceYear()))
886 def StartCrashUploadService(self, package):
887 # TODO(frankf): We really need a python wrapper around Intent
888 # to be shared with StartActivity/BroadcastIntent.
889 cmd = (
890 'am startservice -a %s.crash.ACTION_FIND_ALL -n '
891 '%s/%s.crash.MinidumpUploadService' %
892 (constants.PACKAGE_INFO['chrome'].package,
893 package,
894 constants.PACKAGE_INFO['chrome'].package))
895 am_output = self.RunShellCommandWithSU(cmd)
896 assert am_output and 'Starting' in am_output[-1], (
897 'Service failed to start: %s' % am_output)
898 time.sleep(15)
900 def BroadcastIntent(self, package, intent, *args):
901 """Send a broadcast intent.
903 Args:
904 package: Name of package containing the intent.
905 intent: Name of the intent.
906 args: Optional extra arguments for the intent.
908 cmd = 'am broadcast -a %s.%s %s' % (package, intent, ' '.join(args))
909 self.RunShellCommand(cmd)
911 def GoHome(self):
912 """Tell the device to return to the home screen. Blocks until completion."""
913 self.RunShellCommand('am start -W '
914 '-a android.intent.action.MAIN -c android.intent.category.HOME')
916 def CloseApplication(self, package):
917 """Attempt to close down the application, using increasing violence.
919 Args:
920 package: Name of the process to kill off, e.g.
921 com.google.android.apps.chrome
923 self.RunShellCommand('am force-stop ' + package)
925 def GetApplicationPath(self, package):
926 """Get the installed apk path on the device for the given package.
928 Args:
929 package: Name of the package.
931 Returns:
932 Path to the apk on the device if it exists, None otherwise.
934 pm_path_output = self.RunShellCommand('pm path ' + package)
935 # The path output contains anything if and only if the package
936 # exists.
937 if pm_path_output:
938 # pm_path_output is of the form: "package:/path/to/foo.apk"
939 return pm_path_output[0].split(':')[1]
940 else:
941 return None
943 def ClearApplicationState(self, package):
944 """Closes and clears all state for the given |package|."""
945 # Check that the package exists before clearing it. Necessary because
946 # calling pm clear on a package that doesn't exist may never return.
947 pm_path_output = self.RunShellCommand('pm path ' + package)
948 # The path output only contains anything if and only if the package exists.
949 if pm_path_output:
950 self.RunShellCommand('pm clear ' + package)
952 def SendKeyEvent(self, keycode):
953 """Sends keycode to the device.
955 Args:
956 keycode: Numeric keycode to send (see "enum" at top of file).
958 self.RunShellCommand('input keyevent %d' % keycode)
960 def _RunMd5Sum(self, host_path, device_path):
961 """Gets the md5sum of a host path and device path.
963 Args:
964 host_path: Path (file or directory) on the host.
965 device_path: Path on the device.
967 Returns:
968 A tuple containing lists of the host and device md5sum results as
969 created by _ParseMd5SumOutput().
971 md5sum_dist_path = os.path.join(constants.GetOutDirectory(),
972 'md5sum_dist')
973 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.'
974 md5sum_dist_mtime = os.stat(md5sum_dist_path).st_mtime
975 if (md5sum_dist_path not in self._push_if_needed_cache or
976 self._push_if_needed_cache[md5sum_dist_path] != md5sum_dist_mtime):
977 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER)
978 assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
979 self._push_if_needed_cache[md5sum_dist_path] = md5sum_dist_mtime
981 (_, md5_device_output) = self.GetAndroidToolStatusAndOutput(
982 self._util_wrapper + ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path,
983 lib_path=MD5SUM_DEVICE_FOLDER,
984 timeout_time=2 * 60)
985 device_hash_tuples = _ParseMd5SumOutput(md5_device_output)
986 assert os.path.exists(host_path), 'Local path not found %s' % host_path
987 md5sum_output = cmd_helper.GetCmdOutput(
988 [os.path.join(constants.GetOutDirectory(), 'md5sum_bin_host'),
989 host_path])
990 host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines())
991 return (host_hash_tuples, device_hash_tuples)
993 def GetFilesChanged(self, host_path, device_path, ignore_filenames=False):
994 """Compares the md5sum of a host path against a device path.
996 Note: Ignores extra files on the device.
998 Args:
999 host_path: Path (file or directory) on the host.
1000 device_path: Path on the device.
1001 ignore_filenames: If True only the file contents are considered when
1002 checking whether a file has changed, otherwise the relative path
1003 must also match.
1005 Returns:
1006 A list of tuples of the form (host_path, device_path) for files whose
1007 md5sums do not match.
1010 # Md5Sum resolves symbolic links in path names so the calculation of
1011 # relative path names from its output will need the real path names of the
1012 # base directories. Having calculated these they are used throughout the
1013 # function since this makes us less subject to any future changes to Md5Sum.
1014 real_host_path = os.path.realpath(host_path)
1015 real_device_path = self.RunShellCommand('realpath "%s"' % device_path)[0]
1017 host_hash_tuples, device_hash_tuples = self._RunMd5Sum(
1018 real_host_path, real_device_path)
1020 if len(host_hash_tuples) > len(device_hash_tuples):
1021 logging.info('%s files do not exist on the device' %
1022 (len(host_hash_tuples) - len(device_hash_tuples)))
1024 host_rel = [(os.path.relpath(os.path.normpath(t.path), real_host_path),
1025 t.hash)
1026 for t in host_hash_tuples]
1028 if os.path.isdir(real_host_path):
1029 def RelToRealPaths(rel_path):
1030 return (os.path.join(real_host_path, rel_path),
1031 os.path.join(real_device_path, rel_path))
1032 else:
1033 assert len(host_rel) == 1
1034 def RelToRealPaths(_):
1035 return (real_host_path, real_device_path)
1037 if ignore_filenames:
1038 # If we are ignoring file names, then we want to push any file for which
1039 # a file with an equivalent MD5 sum does not exist on the device.
1040 device_hashes = set([h.hash for h in device_hash_tuples])
1041 ShouldPush = lambda p, h: h not in device_hashes
1042 else:
1043 # Otherwise, we want to push any file on the host for which a file with
1044 # an equivalent MD5 sum does not exist at the same relative path on the
1045 # device.
1046 device_rel = dict([(os.path.relpath(os.path.normpath(t.path),
1047 real_device_path),
1048 t.hash)
1049 for t in device_hash_tuples])
1050 ShouldPush = lambda p, h: p not in device_rel or h != device_rel[p]
1052 return [RelToRealPaths(path) for path, host_hash in host_rel
1053 if ShouldPush(path, host_hash)]
1055 def PushIfNeeded(self, host_path, device_path):
1056 """Pushes |host_path| to |device_path|.
1058 Works for files and directories. This method skips copying any paths in
1059 |test_data_paths| that already exist on the device with the same hash.
1061 All pushed files can be removed by calling RemovePushedFiles().
1063 MAX_INDIVIDUAL_PUSHES = 50
1064 if not os.path.exists(host_path):
1065 raise device_errors.CommandFailedError(
1066 'Local path not found %s' % host_path, device=str(self))
1068 # See if the file on the host changed since the last push (if any) and
1069 # return early if it didn't. Note that this shortcut assumes that the tests
1070 # on the device don't modify the files.
1071 if not os.path.isdir(host_path):
1072 if host_path in self._push_if_needed_cache:
1073 host_path_mtime = self._push_if_needed_cache[host_path]
1074 if host_path_mtime == os.stat(host_path).st_mtime:
1075 return
1077 size = host_utils.GetRecursiveDiskUsage(host_path)
1078 self._pushed_files.append(device_path)
1079 self._potential_push_size += size
1081 if os.path.isdir(host_path):
1082 self.RunShellCommand('mkdir -p "%s"' % device_path)
1084 changed_files = self.GetFilesChanged(host_path, device_path)
1085 logging.info('Found %d files that need to be pushed to %s',
1086 len(changed_files), device_path)
1087 if not changed_files:
1088 return
1090 def Push(host, device):
1091 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout
1092 # of 60 seconds which isn't sufficient for a lot of users of this method.
1093 push_command = 'push %s %s' % (host, device)
1094 self._LogShell(push_command)
1096 # Retry push with increasing backoff if the device is busy.
1097 retry = 0
1098 while True:
1099 output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
1100 if _HasAdbPushSucceeded(output):
1101 if not os.path.isdir(host_path):
1102 self._push_if_needed_cache[host] = os.stat(host).st_mtime
1103 return
1104 if retry < 3:
1105 retry += 1
1106 wait_time = 5 * retry
1107 logging.error('Push failed, retrying in %d seconds: %s' %
1108 (wait_time, output))
1109 time.sleep(wait_time)
1110 else:
1111 raise Exception('Push failed: %s' % output)
1113 diff_size = 0
1114 if len(changed_files) <= MAX_INDIVIDUAL_PUSHES:
1115 diff_size = sum(host_utils.GetRecursiveDiskUsage(f[0])
1116 for f in changed_files)
1118 # TODO(craigdh): Replace this educated guess with a heuristic that
1119 # approximates the push time for each method.
1120 if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size:
1121 self._actual_push_size += size
1122 Push(host_path, device_path)
1123 else:
1124 for f in changed_files:
1125 Push(f[0], f[1])
1126 self._actual_push_size += diff_size
1128 def GetPushSizeInfo(self):
1129 """Get total size of pushes to the device done via PushIfNeeded()
1131 Returns:
1132 A tuple:
1133 1. Total size of push requests to PushIfNeeded (MB)
1134 2. Total size that was actually pushed (MB)
1136 return (self._potential_push_size, self._actual_push_size)
1138 def GetFileContents(self, filename, log_result=False):
1139 """Gets contents from the file specified by |filename|."""
1140 return self.RunShellCommand('cat "%s" 2>/dev/null' % filename,
1141 log_result=log_result)
1143 def SetFileContents(self, filename, contents):
1144 """Writes |contents| to the file specified by |filename|."""
1145 with tempfile.NamedTemporaryFile() as f:
1146 f.write(contents)
1147 f.flush()
1148 self._adb.Push(f.name, filename)
1150 def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False):
1151 return self.RunShellCommand('su -c %s' % command, timeout_time, log_result)
1153 def CanAccessProtectedFileContents(self):
1154 """Returns True if Get/SetProtectedFileContents would work via "su" or adb
1155 shell running as root.
1157 Devices running user builds don't have adb root, but may provide "su" which
1158 can be used for accessing protected files.
1160 return (self._GetProtectedFileCommandRunner() != None)
1162 def _GetProtectedFileCommandRunner(self):
1163 """Finds the best method to access protected files on the device.
1165 Returns:
1166 1. None when privileged files cannot be accessed on the device.
1167 2. Otherwise: A function taking a single parameter: a string with command
1168 line arguments. Running that function executes the command with
1169 the appropriate method.
1171 if self._protected_file_access_method_initialized:
1172 return self._privileged_command_runner
1174 self._privileged_command_runner = None
1175 self._protected_file_access_method_initialized = True
1177 for cmd in [self.RunShellCommand, self.RunShellCommandWithSU]:
1178 # Get contents of the auxv vector for the init(8) process from a small
1179 # binary file that always exists on linux and is always read-protected.
1180 contents = cmd('cat /proc/1/auxv')
1181 # The leading 4 or 8-bytes of auxv vector is a_type. There are not many
1182 # reserved a_type values, hence byte 2 must always be '\0' for a realistic
1183 # auxv. See /usr/include/elf.h.
1184 if len(contents) > 0 and (contents[0][2] == '\0'):
1185 self._privileged_command_runner = cmd
1186 break
1187 return self._privileged_command_runner
1189 def GetProtectedFileContents(self, filename):
1190 """Gets contents from the protected file specified by |filename|.
1192 This is potentially less efficient than GetFileContents.
1194 command = 'cat "%s" 2> /dev/null' % filename
1195 command_runner = self._GetProtectedFileCommandRunner()
1196 if command_runner:
1197 return command_runner(command)
1198 else:
1199 logging.warning('Could not access protected file: %s' % filename)
1200 return []
1202 def SetProtectedFileContents(self, filename, contents):
1203 """Writes |contents| to the protected file specified by |filename|.
1205 This is less efficient than SetFileContents.
1207 with DeviceTempFile(self) as temp_file:
1208 with DeviceTempFile(self, suffix=".sh") as temp_script:
1209 # Put the contents in a temporary file
1210 self.SetFileContents(temp_file.name, contents)
1211 # Create a script to copy the file contents to its final destination
1212 self.SetFileContents(temp_script.name,
1213 'cat %s > %s' % (temp_file.name, filename))
1215 command = 'sh %s' % temp_script.name
1216 command_runner = self._GetProtectedFileCommandRunner()
1217 if command_runner:
1218 return command_runner(command)
1219 else:
1220 logging.warning(
1221 'Could not set contents of protected file: %s' % filename)
1224 def RemovePushedFiles(self):
1225 """Removes all files pushed with PushIfNeeded() from the device."""
1226 for p in self._pushed_files:
1227 self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60)
1229 def ListPathContents(self, path):
1230 """Lists files in all subdirectories of |path|.
1232 Args:
1233 path: The path to list.
1235 Returns:
1236 A dict of {"name": (size, lastmod), ...}.
1238 # Example output:
1239 # /foo/bar:
1240 # -rw-r----- user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt
1241 re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
1242 '(?P<user>[^\s]+)\s+'
1243 '(?P<group>[^\s]+)\s+'
1244 '(?P<size>[^\s]+)\s+'
1245 '(?P<date>[^\s]+)\s+'
1246 '(?P<time>[^\s]+)\s+'
1247 '(?P<filename>[^\s]+)$')
1248 return _GetFilesFromRecursiveLsOutput(
1249 path, self.RunShellCommand('ls -lR %s' % path), re_file,
1250 self.GetUtcOffset())
1252 def GetUtcOffset(self):
1253 if not self._device_utc_offset:
1254 self._device_utc_offset = self.RunShellCommand('date +%z')[0]
1255 return self._device_utc_offset
1257 def SetJavaAssertsEnabled(self, enable):
1258 """Sets or removes the device java assertions property.
1260 Args:
1261 enable: If True the property will be set.
1263 Returns:
1264 True if the file was modified (reboot is required for it to take effect).
1266 # First ensure the desired property is persisted.
1267 temp_props_file = tempfile.NamedTemporaryFile()
1268 properties = ''
1269 if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
1270 with open(temp_props_file.name) as f:
1271 properties = f.read()
1272 re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1273 r'\s*=\s*all\s*$', re.MULTILINE)
1274 if enable != bool(re.search(re_search, properties)):
1275 re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1276 r'\s*=\s*\w+\s*$', re.MULTILINE)
1277 properties = re.sub(re_replace, '', properties)
1278 if enable:
1279 properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
1281 file(temp_props_file.name, 'w').write(properties)
1282 self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
1284 # Next, check the current runtime value is what we need, and
1285 # if not, set it and report that a reboot is required.
1286 was_set = 'all' in self.system_properties[JAVA_ASSERT_PROPERTY]
1287 if was_set == enable:
1288 return False
1289 self.system_properties[JAVA_ASSERT_PROPERTY] = enable and 'all' or ''
1290 return True
1292 def GetBuildId(self):
1293 """Returns the build ID of the system (e.g. JRM79C)."""
1294 build_id = self.system_properties['ro.build.id']
1295 assert build_id
1296 return build_id
1298 def GetBuildType(self):
1299 """Returns the build type of the system (e.g. eng)."""
1300 build_type = self.system_properties['ro.build.type']
1301 assert build_type
1302 return build_type
1304 def GetBuildProduct(self):
1305 """Returns the build product of the device (e.g. maguro)."""
1306 build_product = self.system_properties['ro.build.product']
1307 assert build_product
1308 return build_product
1310 def GetProductName(self):
1311 """Returns the product name of the device (e.g. takju)."""
1312 name = self.system_properties['ro.product.name']
1313 assert name
1314 return name
1316 def GetBuildFingerprint(self):
1317 """Returns the build fingerprint of the device."""
1318 build_fingerprint = self.system_properties['ro.build.fingerprint']
1319 assert build_fingerprint
1320 return build_fingerprint
1322 def GetDescription(self):
1323 """Returns the description of the system.
1325 For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys".
1327 description = self.system_properties['ro.build.description']
1328 assert description
1329 return description
1331 def GetProductModel(self):
1332 """Returns the name of the product model (e.g. "Galaxy Nexus") """
1333 model = self.system_properties['ro.product.model']
1334 assert model
1335 return model
1337 def GetWifiIP(self):
1338 """Returns the wifi IP on the device."""
1339 wifi_ip = self.system_properties['dhcp.wlan0.ipaddress']
1340 # Do not assert here. Devices (e.g. emulators) may not have a WifiIP.
1341 return wifi_ip
1343 def GetSubscriberInfo(self):
1344 """Returns the device subscriber info (e.g. GSM and device ID) as string."""
1345 iphone_sub = self.RunShellCommand('dumpsys iphonesubinfo')
1346 # Do not assert here. Devices (e.g. Nakasi on K) may not have iphonesubinfo.
1347 return '\n'.join(iphone_sub)
1349 def GetBatteryInfo(self):
1350 """Returns a {str: str} dict of battery info (e.g. status, level, etc)."""
1351 battery = self.RunShellCommand('dumpsys battery')
1352 assert battery
1353 battery_info = {}
1354 for line in battery[1:]:
1355 k, _, v = line.partition(': ')
1356 battery_info[k.strip()] = v.strip()
1357 return battery_info
1359 def GetSetupWizardStatus(self):
1360 """Returns the status of the device setup wizard (e.g. DISABLED)."""
1361 status = self.system_properties['ro.setupwizard.mode']
1362 # On some devices, the status is empty if not otherwise set. In such cases
1363 # the caller should expect an empty string to be returned.
1364 return status
1366 def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None):
1367 """Starts monitoring the output of logcat, for use with WaitForLogMatch.
1369 Args:
1370 clear: If True the existing logcat output will be cleared, to avoiding
1371 matching historical output lurking in the log.
1372 filters: A list of logcat filters to be used.
1374 if clear:
1375 self.RunShellCommand('logcat -c')
1376 args = []
1377 if self._adb._target_arg:
1378 args += shlex.split(self._adb._target_arg)
1379 args += ['logcat', '-v', 'threadtime']
1380 if filters:
1381 args.extend(filters)
1382 else:
1383 args.append('*:v')
1385 if logfile:
1386 logfile = NewLineNormalizer(logfile)
1388 # Spawn logcat and synchronize with it.
1389 for _ in range(4):
1390 self._logcat = pexpect.spawn(constants.GetAdbPath(), args, timeout=10,
1391 logfile=logfile)
1392 if not clear or self.SyncLogCat():
1393 break
1394 self._logcat.close(force=True)
1395 else:
1396 logging.critical('Error reading from logcat: ' + str(self._logcat.match))
1397 sys.exit(1)
1399 def SyncLogCat(self):
1400 """Synchronize with logcat.
1402 Synchronize with the monitored logcat so that WaitForLogMatch will only
1403 consider new message that are received after this point in time.
1405 Returns:
1406 True if the synchronization succeeded.
1408 assert self._logcat
1409 tag = 'logcat_sync_%s' % time.time()
1410 self.RunShellCommand('log ' + tag)
1411 return self._logcat.expect([tag, pexpect.EOF, pexpect.TIMEOUT]) == 0
1413 def GetMonitoredLogCat(self):
1414 """Returns an "adb logcat" command as created by pexpected.spawn."""
1415 if not self._logcat:
1416 self.StartMonitoringLogcat(clear=False)
1417 return self._logcat
1419 def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10):
1420 """Blocks until a matching line is logged or a timeout occurs.
1422 Args:
1423 success_re: A compiled re to search each line for.
1424 error_re: A compiled re which, if found, terminates the search for
1425 |success_re|. If None is given, no error condition will be detected.
1426 clear: If True the existing logcat output will be cleared, defaults to
1427 false.
1428 timeout: Timeout in seconds to wait for a log match.
1430 Raises:
1431 pexpect.TIMEOUT after |timeout| seconds without a match for |success_re|
1432 or |error_re|.
1434 Returns:
1435 The re match object if |success_re| is matched first or None if |error_re|
1436 is matched first.
1438 logging.info('<<< Waiting for logcat:' + str(success_re.pattern))
1439 t0 = time.time()
1440 while True:
1441 if not self._logcat:
1442 self.StartMonitoringLogcat(clear)
1443 try:
1444 while True:
1445 # Note this will block for upto the timeout _per log line_, so we need
1446 # to calculate the overall timeout remaining since t0.
1447 time_remaining = t0 + timeout - time.time()
1448 if time_remaining < 0:
1449 raise pexpect.TIMEOUT(self._logcat)
1450 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
1451 line = self._logcat.match.group(1)
1452 if error_re:
1453 error_match = error_re.search(line)
1454 if error_match:
1455 return None
1456 success_match = success_re.search(line)
1457 if success_match:
1458 return success_match
1459 logging.info('<<< Skipped Logcat Line:' + str(line))
1460 except pexpect.TIMEOUT:
1461 raise pexpect.TIMEOUT(
1462 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
1463 'to debug)' %
1464 (timeout, success_re.pattern))
1465 except pexpect.EOF:
1466 # It seems that sometimes logcat can end unexpectedly. This seems
1467 # to happen during Chrome startup after a reboot followed by a cache
1468 # clean. I don't understand why this happens, but this code deals with
1469 # getting EOF in logcat.
1470 logging.critical('Found EOF in adb logcat. Restarting...')
1471 # Rerun spawn with original arguments. Note that self._logcat.args[0] is
1472 # the path of adb, so we don't want it in the arguments.
1473 self._logcat = pexpect.spawn(constants.GetAdbPath(),
1474 self._logcat.args[1:],
1475 timeout=self._logcat.timeout,
1476 logfile=self._logcat.logfile)
1478 def StartRecordingLogcat(self, clear=True, filters=None):
1479 """Starts recording logcat output to eventually be saved as a string.
1481 This call should come before some series of tests are run, with either
1482 StopRecordingLogcat or SearchLogcatRecord following the tests.
1484 Args:
1485 clear: True if existing log output should be cleared.
1486 filters: A list of logcat filters to be used.
1488 if not filters:
1489 filters = ['*:v']
1490 if clear:
1491 self._adb.SendCommand('logcat -c')
1492 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg,
1493 ' '.join(filters))
1494 self._logcat_tmpoutfile = tempfile.NamedTemporaryFile(bufsize=0)
1495 self.logcat_process = subprocess.Popen(logcat_command, shell=True,
1496 stdout=self._logcat_tmpoutfile)
1498 def GetCurrentRecordedLogcat(self):
1499 """Return the current content of the logcat being recorded.
1500 Call this after StartRecordingLogcat() and before StopRecordingLogcat().
1501 This can be useful to perform timed polling/parsing.
1502 Returns:
1503 Current logcat output as a single string, or None if
1504 StopRecordingLogcat() was already called.
1506 if not self._logcat_tmpoutfile:
1507 return None
1509 with open(self._logcat_tmpoutfile.name) as f:
1510 return f.read()
1512 def StopRecordingLogcat(self):
1513 """Stops an existing logcat recording subprocess and returns output.
1515 Returns:
1516 The logcat output as a string or an empty string if logcat was not
1517 being recorded at the time.
1519 if not self.logcat_process:
1520 return ''
1521 # Cannot evaluate directly as 0 is a possible value.
1522 # Better to read the self.logcat_process.stdout before killing it,
1523 # Otherwise the communicate may return incomplete output due to pipe break.
1524 if self.logcat_process.poll() is None:
1525 self.logcat_process.kill()
1526 self.logcat_process.wait()
1527 self.logcat_process = None
1528 self._logcat_tmpoutfile.seek(0)
1529 output = self._logcat_tmpoutfile.read()
1530 self._logcat_tmpoutfile.close()
1531 self._logcat_tmpoutfile = None
1532 return output
1534 @staticmethod
1535 def SearchLogcatRecord(record, message, thread_id=None, proc_id=None,
1536 log_level=None, component=None):
1537 """Searches the specified logcat output and returns results.
1539 This method searches through the logcat output specified by record for a
1540 certain message, narrowing results by matching them against any other
1541 specified criteria. It returns all matching lines as described below.
1543 Args:
1544 record: A string generated by Start/StopRecordingLogcat to search.
1545 message: An output string to search for.
1546 thread_id: The thread id that is the origin of the message.
1547 proc_id: The process that is the origin of the message.
1548 log_level: The log level of the message.
1549 component: The name of the component that would create the message.
1551 Returns:
1552 A list of dictionaries represeting matching entries, each containing keys
1553 thread_id, proc_id, log_level, component, and message.
1555 if thread_id:
1556 thread_id = str(thread_id)
1557 if proc_id:
1558 proc_id = str(proc_id)
1559 results = []
1560 reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
1561 re.MULTILINE)
1562 log_list = reg.findall(record)
1563 for (tid, pid, log_lev, comp, msg) in log_list:
1564 if ((not thread_id or thread_id == tid) and
1565 (not proc_id or proc_id == pid) and
1566 (not log_level or log_level == log_lev) and
1567 (not component or component == comp) and msg.find(message) > -1):
1568 match = dict({'thread_id': tid, 'proc_id': pid,
1569 'log_level': log_lev, 'component': comp,
1570 'message': msg})
1571 results.append(match)
1572 return results
1574 def ExtractPid(self, process_name):
1575 """Extracts Process Ids for a given process name from Android Shell.
1577 Args:
1578 process_name: name of the process on the device.
1580 Returns:
1581 List of all the process ids (as strings) that match the given name.
1582 If the name of a process exactly matches the given name, the pid of
1583 that process will be inserted to the front of the pid list.
1585 pids = []
1586 for line in self.RunShellCommand('ps', log_result=False):
1587 data = line.split()
1588 try:
1589 if process_name in data[-1]: # name is in the last column
1590 if process_name == data[-1]:
1591 pids.insert(0, data[1]) # PID is in the second column
1592 else:
1593 pids.append(data[1])
1594 except IndexError:
1595 pass
1596 return pids
1598 def GetIoStats(self):
1599 """Gets cumulative disk IO stats since boot (for all processes).
1601 Returns:
1602 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
1603 was an error.
1605 IoStats = collections.namedtuple(
1606 'IoStats',
1607 ['device',
1608 'num_reads_issued',
1609 'num_reads_merged',
1610 'num_sectors_read',
1611 'ms_spent_reading',
1612 'num_writes_completed',
1613 'num_writes_merged',
1614 'num_sectors_written',
1615 'ms_spent_writing',
1616 'num_ios_in_progress',
1617 'ms_spent_doing_io',
1618 'ms_spent_doing_io_weighted',
1621 for line in self.GetFileContents('/proc/diskstats', log_result=False):
1622 fields = line.split()
1623 stats = IoStats._make([fields[2]] + [int(f) for f in fields[3:]])
1624 if stats.device == 'mmcblk0':
1625 return {
1626 'num_reads': stats.num_reads_issued,
1627 'num_writes': stats.num_writes_completed,
1628 'read_ms': stats.ms_spent_reading,
1629 'write_ms': stats.ms_spent_writing,
1631 logging.warning('Could not find disk IO stats.')
1632 return None
1634 def GetMemoryUsageForPid(self, pid):
1635 """Returns the memory usage for given pid.
1637 Args:
1638 pid: The pid number of the specific process running on device.
1640 Returns:
1641 Dict of {metric:usage_kb}, for the process which has specified pid.
1642 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1643 Shared_Dirty, Private_Clean, Private_Dirty, VmHWM.
1645 showmap = self.RunShellCommand('showmap %d' % pid)
1646 if not showmap or not showmap[-1].endswith('TOTAL'):
1647 logging.warning('Invalid output for showmap %s', str(showmap))
1648 return {}
1649 items = showmap[-1].split()
1650 if len(items) != 9:
1651 logging.warning('Invalid TOTAL for showmap %s', str(items))
1652 return {}
1653 usage_dict = collections.defaultdict(int)
1654 usage_dict.update({
1655 'Size': int(items[0].strip()),
1656 'Rss': int(items[1].strip()),
1657 'Pss': int(items[2].strip()),
1658 'Shared_Clean': int(items[3].strip()),
1659 'Shared_Dirty': int(items[4].strip()),
1660 'Private_Clean': int(items[5].strip()),
1661 'Private_Dirty': int(items[6].strip()),
1663 peak_value_kb = 0
1664 for line in self.GetProtectedFileContents('/proc/%s/status' % pid):
1665 if not line.startswith('VmHWM:'): # Format: 'VmHWM: +[0-9]+ kB'
1666 continue
1667 peak_value_kb = int(line.split(':')[1].strip().split(' ')[0])
1668 break
1669 usage_dict['VmHWM'] = peak_value_kb
1670 if not peak_value_kb:
1671 logging.warning('Could not find memory peak value for pid ' + str(pid))
1673 return usage_dict
1675 def ProcessesUsingDevicePort(self, device_port):
1676 """Lists processes using the specified device port on loopback interface.
1678 Args:
1679 device_port: Port on device we want to check.
1681 Returns:
1682 A list of (pid, process_name) tuples using the specified port.
1684 tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False)
1685 tcp_address = '0100007F:%04X' % device_port
1686 pids = []
1687 for single_connect in tcp_results:
1688 connect_results = single_connect.split()
1689 # Column 1 is the TCP port, and Column 9 is the inode of the socket
1690 if connect_results[1] == tcp_address:
1691 socket_inode = connect_results[9]
1692 socket_name = 'socket:[%s]' % socket_inode
1693 lsof_results = self.RunShellCommand('lsof', log_result=False)
1694 for single_process in lsof_results:
1695 process_results = single_process.split()
1696 # Ignore the line if it has less than nine columns in it, which may
1697 # be the case when a process stops while lsof is executing.
1698 if len(process_results) <= 8:
1699 continue
1700 # Column 0 is the executable name
1701 # Column 1 is the pid
1702 # Column 8 is the Inode in use
1703 if process_results[8] == socket_name:
1704 pids.append((int(process_results[1]), process_results[0]))
1705 break
1706 logging.info('PidsUsingDevicePort: %s', pids)
1707 return pids
1709 def FileExistsOnDevice(self, file_name):
1710 """Checks whether the given file exists on the device.
1712 Args:
1713 file_name: Full path of file to check.
1715 Returns:
1716 True if the file exists, False otherwise.
1718 assert '"' not in file_name, 'file_name cannot contain double quotes'
1719 try:
1720 status = self._adb.SendShellCommand(
1721 '\'test -e "%s"; echo $?\'' % (file_name))
1722 if 'test: not found' not in status:
1723 return int(status) == 0
1725 status = self._adb.SendShellCommand(
1726 '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name))
1727 return int(status) == 0
1728 except ValueError:
1729 if IsDeviceAttached(self._device):
1730 raise errors.DeviceUnresponsiveError('Device may be offline.')
1732 return False
1734 def IsFileWritableOnDevice(self, file_name):
1735 """Checks whether the given file (or directory) is writable on the device.
1737 Args:
1738 file_name: Full path of file/directory to check.
1740 Returns:
1741 True if writable, False otherwise.
1743 assert '"' not in file_name, 'file_name cannot contain double quotes'
1744 try:
1745 status = self._adb.SendShellCommand(
1746 '\'test -w "%s"; echo $?\'' % (file_name))
1747 if 'test: not found' not in status:
1748 return int(status) == 0
1749 raise errors.AbortError('"test" binary not found. OS too old.')
1751 except ValueError:
1752 if IsDeviceAttached(self._device):
1753 raise errors.DeviceUnresponsiveError('Device may be offline.')
1755 return False
1757 @staticmethod
1758 def GetTimestamp():
1759 return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
1761 @staticmethod
1762 def EnsureHostDirectory(host_file):
1763 host_dir = os.path.dirname(os.path.abspath(host_file))
1764 if not os.path.exists(host_dir):
1765 os.makedirs(host_dir)
1767 def TakeScreenshot(self, host_file=None):
1768 """Saves a screenshot image to |host_file| on the host.
1770 Args:
1771 host_file: Absolute path to the image file to store on the host or None to
1772 use an autogenerated file name.
1774 Returns:
1775 Resulting host file name of the screenshot.
1777 host_file = os.path.abspath(host_file or
1778 'screenshot-%s.png' % self.GetTimestamp())
1779 self.EnsureHostDirectory(host_file)
1780 device_file = '%s/screenshot.png' % self.GetExternalStorage()
1781 self.RunShellCommand(
1782 '/system/bin/screencap -p %s' % device_file)
1783 self.PullFileFromDevice(device_file, host_file)
1784 self.RunShellCommand('rm -f "%s"' % device_file)
1785 return host_file
1787 def PullFileFromDevice(self, device_file, host_file):
1788 """Download |device_file| on the device from to |host_file| on the host.
1790 Args:
1791 device_file: Absolute path to the file to retrieve from the device.
1792 host_file: Absolute path to the file to store on the host.
1794 if not self._adb.Pull(device_file, host_file):
1795 raise device_errors.AdbCommandFailedError(
1796 ['pull', device_file, host_file], 'Failed to pull file from device.')
1797 assert os.path.exists(host_file)
1799 def SetUtilWrapper(self, util_wrapper):
1800 """Sets a wrapper prefix to be used when running a locally-built
1801 binary on the device (ex.: md5sum_bin).
1803 self._util_wrapper = util_wrapper
1805 def RunUIAutomatorTest(self, test, test_package, timeout):
1806 """Runs a single uiautomator test.
1808 Args:
1809 test: Test class/method.
1810 test_package: Name of the test jar.
1811 timeout: Timeout time in seconds.
1813 Returns:
1814 An instance of am_instrument_parser.TestResult object.
1816 cmd = 'uiautomator runtest %s -e class %s' % (test_package, test)
1817 self._LogShell(cmd)
1818 output = self._adb.SendShellCommand(cmd, timeout_time=timeout)
1819 # uiautomator doesn't fully conform to the instrumenation test runner
1820 # convention and doesn't terminate with INSTRUMENTATION_CODE.
1821 # Just assume the first result is valid.
1822 (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output)
1823 if not test_results:
1824 raise errors.InstrumentationError(
1825 'no test results... device setup correctly?')
1826 return test_results[0]
1828 def DismissCrashDialogIfNeeded(self):
1829 """Dismiss the error/ANR dialog if present.
1831 Returns: Name of the crashed package if a dialog is focused,
1832 None otherwise.
1834 re_focus = re.compile(
1835 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
1837 def _FindFocusedWindow():
1838 match = None
1839 for line in self.RunShellCommand('dumpsys window windows'):
1840 match = re.match(re_focus, line)
1841 if match:
1842 break
1843 return match
1845 match = _FindFocusedWindow()
1846 if not match:
1847 return
1848 package = match.group(2)
1849 logging.warning('Trying to dismiss %s dialog for %s' % match.groups())
1850 self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1851 self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1852 self.SendKeyEvent(KEYCODE_ENTER)
1853 match = _FindFocusedWindow()
1854 if match:
1855 logging.error('Still showing a %s dialog for %s' % match.groups())
1856 return package
1858 def EfficientDeviceDirectoryCopy(self, source, dest):
1859 """ Copy a directory efficiently on the device
1861 Uses a shell script running on the target to copy new and changed files the
1862 source directory to the destination directory and remove added files. This
1863 is in some cases much faster than cp -r.
1865 Args:
1866 source: absolute path of source directory
1867 dest: absolute path of destination directory
1869 logging.info('In EfficientDeviceDirectoryCopy %s %s', source, dest)
1870 with DeviceTempFile(self, suffix=".sh") as temp_script_file:
1871 host_script_path = os.path.join(constants.DIR_SOURCE_ROOT,
1872 'build',
1873 'android',
1874 'pylib',
1875 'efficient_android_directory_copy.sh')
1876 self._adb.Push(host_script_path, temp_script_file.name)
1877 out = self.RunShellCommand(
1878 'sh %s %s %s' % (temp_script_file.name, source, dest),
1879 timeout_time=120)
1880 if self._device:
1881 device_repr = self._device[-4:]
1882 else:
1883 device_repr = '????'
1884 for line in out:
1885 logging.info('[%s]> %s', device_repr, line)
1887 def _GetControlUsbChargingCommand(self):
1888 if self._control_usb_charging_command['cached']:
1889 return self._control_usb_charging_command['command']
1890 self._control_usb_charging_command['cached'] = True
1891 if not self.IsRootEnabled():
1892 return None
1893 for command in CONTROL_USB_CHARGING_COMMANDS:
1894 # Assert command is valid.
1895 assert 'disable_command' in command
1896 assert 'enable_command' in command
1897 assert 'witness_file' in command
1898 witness_file = command['witness_file']
1899 if self.FileExistsOnDevice(witness_file):
1900 self._control_usb_charging_command['command'] = command
1901 return command
1902 return None
1904 def CanControlUsbCharging(self):
1905 return self._GetControlUsbChargingCommand() is not None
1907 def DisableUsbCharging(self, timeout=10):
1908 command = self._GetControlUsbChargingCommand()
1909 if not command:
1910 raise Exception('Unable to act on usb charging.')
1911 disable_command = command['disable_command']
1912 t0 = time.time()
1913 # Do not loop directly on self.IsDeviceCharging to cut the number of calls
1914 # to the device.
1915 while True:
1916 if t0 + timeout - time.time() < 0:
1917 raise pexpect.TIMEOUT('Unable to enable USB charging in time.')
1918 self.RunShellCommand(disable_command)
1919 if not self.IsDeviceCharging():
1920 break
1922 def EnableUsbCharging(self, timeout=10):
1923 command = self._GetControlUsbChargingCommand()
1924 if not command:
1925 raise Exception('Unable to act on usb charging.')
1926 disable_command = command['enable_command']
1927 t0 = time.time()
1928 # Do not loop directly on self.IsDeviceCharging to cut the number of calls
1929 # to the device.
1930 while True:
1931 if t0 + timeout - time.time() < 0:
1932 raise pexpect.TIMEOUT('Unable to enable USB charging in time.')
1933 self.RunShellCommand(disable_command)
1934 if self.IsDeviceCharging():
1935 break
1937 def IsDeviceCharging(self):
1938 for line in self.RunShellCommand('dumpsys battery'):
1939 if 'powered: ' in line:
1940 if line.split('powered: ')[1] == 'true':
1941 return True
1944 class NewLineNormalizer(object):
1945 """A file-like object to normalize EOLs to '\n'.
1947 Pexpect runs adb within a pseudo-tty device (see
1948 http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written
1949 as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate
1950 lines, the log ends up having '\r\r\n' at the end of each line. This
1951 filter replaces the above with a single '\n' in the data stream.
1953 def __init__(self, output):
1954 self._output = output
1956 def write(self, data):
1957 data = data.replace('\r\r\n', '\n')
1958 self._output.write(data)
1960 def flush(self):
1961 self._output.flush()