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