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