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