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