| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Provides a variety of device interactions based on adb. | 5 # pylint: disable=unused-wildcard-import |
| 6 # pylint: disable=wildcard-import |
| 6 | 7 |
| 7 Eventually, this will be based on adb_wrapper. | 8 from devil.android.device_utils import * |
| 8 """ | |
| 9 # pylint: disable=unused-argument | |
| 10 | |
| 11 import collections | |
| 12 import contextlib | |
| 13 import itertools | |
| 14 import logging | |
| 15 import multiprocessing | |
| 16 import os | |
| 17 import posixpath | |
| 18 import re | |
| 19 import shutil | |
| 20 import sys | |
| 21 import tempfile | |
| 22 import threading | |
| 23 import time | |
| 24 import zipfile | |
| 25 | |
| 26 from pylib import cmd_helper | |
| 27 from pylib import constants | |
| 28 from pylib import device_signal | |
| 29 from pylib.constants import keyevent | |
| 30 from pylib.device import adb_wrapper | |
| 31 from pylib.device import decorators | |
| 32 from pylib.device import device_blacklist | |
| 33 from pylib.device import device_errors | |
| 34 from pylib.device import intent | |
| 35 from pylib.device import logcat_monitor | |
| 36 from pylib.device.commands import install_commands | |
| 37 from pylib.sdk import split_select | |
| 38 from pylib.utils import apk_helper | |
| 39 from pylib.utils import base_error | |
| 40 from pylib.utils import device_temp_file | |
| 41 from pylib.utils import host_utils | |
| 42 from pylib.utils import md5sum | |
| 43 from pylib.utils import parallelizer | |
| 44 from pylib.utils import timeout_retry | |
| 45 from pylib.utils import zip_utils | |
| 46 | |
| 47 _DEFAULT_TIMEOUT = 30 | |
| 48 _DEFAULT_RETRIES = 3 | |
| 49 | |
| 50 # A sentinel object for default values | |
| 51 # TODO(jbudorick,perezju): revisit how default values are handled by | |
| 52 # the timeout_retry decorators. | |
| 53 DEFAULT = object() | |
| 54 | |
| 55 _CONTROL_CHARGING_COMMANDS = [ | |
| 56 { | |
| 57 # Nexus 4 | |
| 58 'witness_file': '/sys/module/pm8921_charger/parameters/disabled', | |
| 59 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled', | |
| 60 'disable_command': | |
| 61 'echo 1 > /sys/module/pm8921_charger/parameters/disabled', | |
| 62 }, | |
| 63 { | |
| 64 # Nexus 5 | |
| 65 # Setting the HIZ bit of the bq24192 causes the charger to actually ignore | |
| 66 # energy coming from USB. Setting the power_supply offline just updates the | |
| 67 # Android system to reflect that. | |
| 68 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT', | |
| 69 'enable_command': ( | |
| 70 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' | |
| 71 'echo 1 > /sys/class/power_supply/usb/online'), | |
| 72 'disable_command': ( | |
| 73 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' | |
| 74 'chmod 644 /sys/class/power_supply/usb/online && ' | |
| 75 'echo 0 > /sys/class/power_supply/usb/online'), | |
| 76 }, | |
| 77 ] | |
| 78 | |
| 79 _RESTART_ADBD_SCRIPT = """ | |
| 80 trap '' HUP | |
| 81 trap '' TERM | |
| 82 trap '' PIPE | |
| 83 function restart() { | |
| 84 stop adbd | |
| 85 start adbd | |
| 86 } | |
| 87 restart & | |
| 88 """ | |
| 89 | |
| 90 _CURRENT_FOCUS_CRASH_RE = re.compile( | |
| 91 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') | |
| 92 | |
| 93 | |
| 94 @decorators.WithExplicitTimeoutAndRetries( | |
| 95 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) | |
| 96 def GetAVDs(): | |
| 97 """Returns a list of Android Virtual Devices. | |
| 98 | |
| 99 Returns: | |
| 100 A list containing the configured AVDs. | |
| 101 """ | |
| 102 lines = cmd_helper.GetCmdOutput([ | |
| 103 os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android'), | |
| 104 'list', 'avd']).splitlines() | |
| 105 avds = [] | |
| 106 for line in lines: | |
| 107 if 'Name:' not in line: | |
| 108 continue | |
| 109 key, value = (s.strip() for s in line.split(':', 1)) | |
| 110 if key == 'Name': | |
| 111 avds.append(value) | |
| 112 return avds | |
| 113 | |
| 114 | |
| 115 @decorators.WithExplicitTimeoutAndRetries( | |
| 116 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) | |
| 117 def RestartServer(): | |
| 118 """Restarts the adb server. | |
| 119 | |
| 120 Raises: | |
| 121 CommandFailedError if we fail to kill or restart the server. | |
| 122 """ | |
| 123 def adb_killed(): | |
| 124 return not adb_wrapper.AdbWrapper.IsServerOnline() | |
| 125 | |
| 126 def adb_started(): | |
| 127 return adb_wrapper.AdbWrapper.IsServerOnline() | |
| 128 | |
| 129 adb_wrapper.AdbWrapper.KillServer() | |
| 130 if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5): | |
| 131 # TODO(perezju): raise an exception after fixng http://crbug.com/442319 | |
| 132 logging.warning('Failed to kill adb server') | |
| 133 adb_wrapper.AdbWrapper.StartServer() | |
| 134 if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5): | |
| 135 raise device_errors.CommandFailedError('Failed to start adb server') | |
| 136 | |
| 137 | |
| 138 def _GetTimeStamp(): | |
| 139 """Return a basic ISO 8601 time stamp with the current local time.""" | |
| 140 return time.strftime('%Y%m%dT%H%M%S', time.localtime()) | |
| 141 | |
| 142 | |
| 143 def _JoinLines(lines): | |
| 144 # makes sure that the last line is also terminated, and is more memory | |
| 145 # efficient than first appending an end-line to each line and then joining | |
| 146 # all of them together. | |
| 147 return ''.join(s for line in lines for s in (line, '\n')) | |
| 148 | |
| 149 | |
| 150 class DeviceUtils(object): | |
| 151 | |
| 152 _MAX_ADB_COMMAND_LENGTH = 512 | |
| 153 _MAX_ADB_OUTPUT_LENGTH = 32768 | |
| 154 _LAUNCHER_FOCUSED_RE = re.compile( | |
| 155 '\s*mCurrentFocus.*(Launcher|launcher).*') | |
| 156 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') | |
| 157 | |
| 158 # Property in /data/local.prop that controls Java assertions. | |
| 159 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' | |
| 160 | |
| 161 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, | |
| 162 default_retries=_DEFAULT_RETRIES): | |
| 163 """DeviceUtils constructor. | |
| 164 | |
| 165 Args: | |
| 166 device: Either a device serial, an existing AdbWrapper instance, or an | |
| 167 an existing AndroidCommands instance. | |
| 168 default_timeout: An integer containing the default number of seconds to | |
| 169 wait for an operation to complete if no explicit value | |
| 170 is provided. | |
| 171 default_retries: An integer containing the default number or times an | |
| 172 operation should be retried on failure if no explicit | |
| 173 value is provided. | |
| 174 """ | |
| 175 self.adb = None | |
| 176 if isinstance(device, basestring): | |
| 177 self.adb = adb_wrapper.AdbWrapper(device) | |
| 178 elif isinstance(device, adb_wrapper.AdbWrapper): | |
| 179 self.adb = device | |
| 180 else: | |
| 181 raise ValueError('Unsupported device value: %r' % device) | |
| 182 self._commands_installed = None | |
| 183 self._default_timeout = default_timeout | |
| 184 self._default_retries = default_retries | |
| 185 self._cache = {} | |
| 186 self._client_caches = {} | |
| 187 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) | |
| 188 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) | |
| 189 | |
| 190 self._ClearCache() | |
| 191 | |
| 192 def __eq__(self, other): | |
| 193 """Checks whether |other| refers to the same device as |self|. | |
| 194 | |
| 195 Args: | |
| 196 other: The object to compare to. This can be a basestring, an instance | |
| 197 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. | |
| 198 Returns: | |
| 199 Whether |other| refers to the same device as |self|. | |
| 200 """ | |
| 201 return self.adb.GetDeviceSerial() == str(other) | |
| 202 | |
| 203 def __lt__(self, other): | |
| 204 """Compares two instances of DeviceUtils. | |
| 205 | |
| 206 This merely compares their serial numbers. | |
| 207 | |
| 208 Args: | |
| 209 other: The instance of DeviceUtils to compare to. | |
| 210 Returns: | |
| 211 Whether |self| is less than |other|. | |
| 212 """ | |
| 213 return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial() | |
| 214 | |
| 215 def __str__(self): | |
| 216 """Returns the device serial.""" | |
| 217 return self.adb.GetDeviceSerial() | |
| 218 | |
| 219 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 220 def IsOnline(self, timeout=None, retries=None): | |
| 221 """Checks whether the device is online. | |
| 222 | |
| 223 Args: | |
| 224 timeout: timeout in seconds | |
| 225 retries: number of retries | |
| 226 | |
| 227 Returns: | |
| 228 True if the device is online, False otherwise. | |
| 229 | |
| 230 Raises: | |
| 231 CommandTimeoutError on timeout. | |
| 232 """ | |
| 233 try: | |
| 234 return self.adb.GetState() == 'device' | |
| 235 except base_error.BaseError as exc: | |
| 236 logging.info('Failed to get state: %s', exc) | |
| 237 return False | |
| 238 | |
| 239 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 240 def HasRoot(self, timeout=None, retries=None): | |
| 241 """Checks whether or not adbd has root privileges. | |
| 242 | |
| 243 Args: | |
| 244 timeout: timeout in seconds | |
| 245 retries: number of retries | |
| 246 | |
| 247 Returns: | |
| 248 True if adbd has root privileges, False otherwise. | |
| 249 | |
| 250 Raises: | |
| 251 CommandTimeoutError on timeout. | |
| 252 DeviceUnreachableError on missing device. | |
| 253 """ | |
| 254 try: | |
| 255 self.RunShellCommand('ls /root', check_return=True) | |
| 256 return True | |
| 257 except device_errors.AdbCommandFailedError: | |
| 258 return False | |
| 259 | |
| 260 def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT): | |
| 261 """Checks whether 'su' is needed to access protected resources. | |
| 262 | |
| 263 Args: | |
| 264 timeout: timeout in seconds | |
| 265 retries: number of retries | |
| 266 | |
| 267 Returns: | |
| 268 True if 'su' is available on the device and is needed to to access | |
| 269 protected resources; False otherwise if either 'su' is not available | |
| 270 (e.g. because the device has a user build), or not needed (because adbd | |
| 271 already has root privileges). | |
| 272 | |
| 273 Raises: | |
| 274 CommandTimeoutError on timeout. | |
| 275 DeviceUnreachableError on missing device. | |
| 276 """ | |
| 277 if 'needs_su' not in self._cache: | |
| 278 try: | |
| 279 self.RunShellCommand( | |
| 280 '%s && ! ls /root' % self._Su('ls /root'), check_return=True, | |
| 281 timeout=self._default_timeout if timeout is DEFAULT else timeout, | |
| 282 retries=self._default_retries if retries is DEFAULT else retries) | |
| 283 self._cache['needs_su'] = True | |
| 284 except device_errors.AdbCommandFailedError: | |
| 285 self._cache['needs_su'] = False | |
| 286 return self._cache['needs_su'] | |
| 287 | |
| 288 def _Su(self, command): | |
| 289 if (self.build_version_sdk | |
| 290 >= constants.ANDROID_SDK_VERSION_CODES.MARSHMALLOW): | |
| 291 return 'su 0 %s' % command | |
| 292 return 'su -c %s' % command | |
| 293 | |
| 294 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 295 def EnableRoot(self, timeout=None, retries=None): | |
| 296 """Restarts adbd with root privileges. | |
| 297 | |
| 298 Args: | |
| 299 timeout: timeout in seconds | |
| 300 retries: number of retries | |
| 301 | |
| 302 Raises: | |
| 303 CommandFailedError if root could not be enabled. | |
| 304 CommandTimeoutError on timeout. | |
| 305 """ | |
| 306 if self.IsUserBuild(): | |
| 307 raise device_errors.CommandFailedError( | |
| 308 'Cannot enable root in user builds.', str(self)) | |
| 309 if 'needs_su' in self._cache: | |
| 310 del self._cache['needs_su'] | |
| 311 self.adb.Root() | |
| 312 self.WaitUntilFullyBooted() | |
| 313 | |
| 314 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 315 def IsUserBuild(self, timeout=None, retries=None): | |
| 316 """Checks whether or not the device is running a user build. | |
| 317 | |
| 318 Args: | |
| 319 timeout: timeout in seconds | |
| 320 retries: number of retries | |
| 321 | |
| 322 Returns: | |
| 323 True if the device is running a user build, False otherwise (i.e. if | |
| 324 it's running a userdebug build). | |
| 325 | |
| 326 Raises: | |
| 327 CommandTimeoutError on timeout. | |
| 328 DeviceUnreachableError on missing device. | |
| 329 """ | |
| 330 return self.build_type == 'user' | |
| 331 | |
| 332 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 333 def GetExternalStoragePath(self, timeout=None, retries=None): | |
| 334 """Get the device's path to its SD card. | |
| 335 | |
| 336 Args: | |
| 337 timeout: timeout in seconds | |
| 338 retries: number of retries | |
| 339 | |
| 340 Returns: | |
| 341 The device's path to its SD card. | |
| 342 | |
| 343 Raises: | |
| 344 CommandFailedError if the external storage path could not be determined. | |
| 345 CommandTimeoutError on timeout. | |
| 346 DeviceUnreachableError on missing device. | |
| 347 """ | |
| 348 if 'external_storage' in self._cache: | |
| 349 return self._cache['external_storage'] | |
| 350 | |
| 351 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', | |
| 352 single_line=True, | |
| 353 check_return=True) | |
| 354 if not value: | |
| 355 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', | |
| 356 str(self)) | |
| 357 self._cache['external_storage'] = value | |
| 358 return value | |
| 359 | |
| 360 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 361 def GetApplicationPaths(self, package, timeout=None, retries=None): | |
| 362 """Get the paths of the installed apks on the device for the given package. | |
| 363 | |
| 364 Args: | |
| 365 package: Name of the package. | |
| 366 | |
| 367 Returns: | |
| 368 List of paths to the apks on the device for the given package. | |
| 369 """ | |
| 370 return self._GetApplicationPathsInternal(package) | |
| 371 | |
| 372 def _GetApplicationPathsInternal(self, package, skip_cache=False): | |
| 373 cached_result = self._cache['package_apk_paths'].get(package) | |
| 374 if cached_result is not None and not skip_cache: | |
| 375 return list(cached_result) | |
| 376 # 'pm path' is liable to incorrectly exit with a nonzero number starting | |
| 377 # in Lollipop. | |
| 378 # TODO(jbudorick): Check if this is fixed as new Android versions are | |
| 379 # released to put an upper bound on this. | |
| 380 should_check_return = (self.build_version_sdk < | |
| 381 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | |
| 382 output = self.RunShellCommand( | |
| 383 ['pm', 'path', package], check_return=should_check_return) | |
| 384 apks = [] | |
| 385 for line in output: | |
| 386 if not line.startswith('package:'): | |
| 387 raise device_errors.CommandFailedError( | |
| 388 'pm path returned: %r' % '\n'.join(output), str(self)) | |
| 389 apks.append(line[len('package:'):]) | |
| 390 self._cache['package_apk_paths'][package] = list(apks) | |
| 391 return apks | |
| 392 | |
| 393 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 394 def GetApplicationVersion(self, package, timeout=None, retries=None): | |
| 395 """Get the version name of a package installed on the device. | |
| 396 | |
| 397 Args: | |
| 398 package: Name of the package. | |
| 399 | |
| 400 Returns: | |
| 401 A string with the version name or None if the package is not found | |
| 402 on the device. | |
| 403 """ | |
| 404 output = self.RunShellCommand( | |
| 405 ['dumpsys', 'package', package], check_return=True) | |
| 406 if not output: | |
| 407 return None | |
| 408 for line in output: | |
| 409 line = line.strip() | |
| 410 if line.startswith('versionName='): | |
| 411 return line[len('versionName='):] | |
| 412 raise device_errors.CommandFailedError( | |
| 413 'Version name for %s not found on dumpsys output' % package, str(self)) | |
| 414 | |
| 415 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 416 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): | |
| 417 """Get the data directory on the device for the given package. | |
| 418 | |
| 419 Args: | |
| 420 package: Name of the package. | |
| 421 | |
| 422 Returns: | |
| 423 The package's data directory, or None if the package doesn't exist on the | |
| 424 device. | |
| 425 """ | |
| 426 try: | |
| 427 output = self._RunPipedShellCommand( | |
| 428 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package)) | |
| 429 for line in output: | |
| 430 _, _, dataDir = line.partition('dataDir=') | |
| 431 if dataDir: | |
| 432 return dataDir | |
| 433 except device_errors.CommandFailedError: | |
| 434 logging.exception('Could not find data directory for %s', package) | |
| 435 return None | |
| 436 | |
| 437 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 438 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): | |
| 439 """Wait for the device to fully boot. | |
| 440 | |
| 441 This means waiting for the device to boot, the package manager to be | |
| 442 available, and the SD card to be ready. It can optionally mean waiting | |
| 443 for wifi to come up, too. | |
| 444 | |
| 445 Args: | |
| 446 wifi: A boolean indicating if we should wait for wifi to come up or not. | |
| 447 timeout: timeout in seconds | |
| 448 retries: number of retries | |
| 449 | |
| 450 Raises: | |
| 451 CommandFailedError on failure. | |
| 452 CommandTimeoutError if one of the component waits times out. | |
| 453 DeviceUnreachableError if the device becomes unresponsive. | |
| 454 """ | |
| 455 def sd_card_ready(): | |
| 456 try: | |
| 457 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], | |
| 458 check_return=True) | |
| 459 return True | |
| 460 except device_errors.AdbCommandFailedError: | |
| 461 return False | |
| 462 | |
| 463 def pm_ready(): | |
| 464 try: | |
| 465 return self._GetApplicationPathsInternal('android', skip_cache=True) | |
| 466 except device_errors.CommandFailedError: | |
| 467 return False | |
| 468 | |
| 469 def boot_completed(): | |
| 470 return self.GetProp('sys.boot_completed') == '1' | |
| 471 | |
| 472 def wifi_enabled(): | |
| 473 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], | |
| 474 check_return=False) | |
| 475 | |
| 476 self.adb.WaitForDevice() | |
| 477 timeout_retry.WaitFor(sd_card_ready) | |
| 478 timeout_retry.WaitFor(pm_ready) | |
| 479 timeout_retry.WaitFor(boot_completed) | |
| 480 if wifi: | |
| 481 timeout_retry.WaitFor(wifi_enabled) | |
| 482 | |
| 483 REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT | |
| 484 REBOOT_DEFAULT_RETRIES = _DEFAULT_RETRIES | |
| 485 | |
| 486 @decorators.WithTimeoutAndRetriesDefaults( | |
| 487 REBOOT_DEFAULT_TIMEOUT, | |
| 488 REBOOT_DEFAULT_RETRIES) | |
| 489 def Reboot(self, block=True, wifi=False, timeout=None, retries=None): | |
| 490 """Reboot the device. | |
| 491 | |
| 492 Args: | |
| 493 block: A boolean indicating if we should wait for the reboot to complete. | |
| 494 wifi: A boolean indicating if we should wait for wifi to be enabled after | |
| 495 the reboot. The option has no effect unless |block| is also True. | |
| 496 timeout: timeout in seconds | |
| 497 retries: number of retries | |
| 498 | |
| 499 Raises: | |
| 500 CommandTimeoutError on timeout. | |
| 501 DeviceUnreachableError on missing device. | |
| 502 """ | |
| 503 def device_offline(): | |
| 504 return not self.IsOnline() | |
| 505 | |
| 506 self.adb.Reboot() | |
| 507 self._ClearCache() | |
| 508 timeout_retry.WaitFor(device_offline, wait_period=1) | |
| 509 if block: | |
| 510 self.WaitUntilFullyBooted(wifi=wifi) | |
| 511 | |
| 512 INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT | |
| 513 INSTALL_DEFAULT_RETRIES = _DEFAULT_RETRIES | |
| 514 | |
| 515 @decorators.WithTimeoutAndRetriesDefaults( | |
| 516 INSTALL_DEFAULT_TIMEOUT, | |
| 517 INSTALL_DEFAULT_RETRIES) | |
| 518 def Install(self, apk_path, reinstall=False, timeout=None, retries=None): | |
| 519 """Install an APK. | |
| 520 | |
| 521 Noop if an identical APK is already installed. | |
| 522 | |
| 523 Args: | |
| 524 apk_path: A string containing the path to the APK to install. | |
| 525 reinstall: A boolean indicating if we should keep any existing app data. | |
| 526 timeout: timeout in seconds | |
| 527 retries: number of retries | |
| 528 | |
| 529 Raises: | |
| 530 CommandFailedError if the installation fails. | |
| 531 CommandTimeoutError if the installation times out. | |
| 532 DeviceUnreachableError on missing device. | |
| 533 """ | |
| 534 package_name = apk_helper.GetPackageName(apk_path) | |
| 535 device_paths = self._GetApplicationPathsInternal(package_name) | |
| 536 if device_paths: | |
| 537 if len(device_paths) > 1: | |
| 538 logging.warning( | |
| 539 'Installing single APK (%s) when split APKs (%s) are currently ' | |
| 540 'installed.', apk_path, ' '.join(device_paths)) | |
| 541 apks_to_install, host_checksums = ( | |
| 542 self._ComputeStaleApks(package_name, [apk_path])) | |
| 543 should_install = bool(apks_to_install) | |
| 544 if should_install and not reinstall: | |
| 545 self.Uninstall(package_name) | |
| 546 else: | |
| 547 should_install = True | |
| 548 host_checksums = None | |
| 549 | |
| 550 if should_install: | |
| 551 # We won't know the resulting device apk names. | |
| 552 self._cache['package_apk_paths'].pop(package_name, 0) | |
| 553 self.adb.Install(apk_path, reinstall=reinstall) | |
| 554 self._cache['package_apk_checksums'][package_name] = host_checksums | |
| 555 | |
| 556 @decorators.WithTimeoutAndRetriesDefaults( | |
| 557 INSTALL_DEFAULT_TIMEOUT, | |
| 558 INSTALL_DEFAULT_RETRIES) | |
| 559 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, | |
| 560 timeout=None, retries=None): | |
| 561 """Install a split APK. | |
| 562 | |
| 563 Noop if all of the APK splits are already installed. | |
| 564 | |
| 565 Args: | |
| 566 base_apk: A string of the path to the base APK. | |
| 567 split_apks: A list of strings of paths of all of the APK splits. | |
| 568 reinstall: A boolean indicating if we should keep any existing app data. | |
| 569 timeout: timeout in seconds | |
| 570 retries: number of retries | |
| 571 | |
| 572 Raises: | |
| 573 CommandFailedError if the installation fails. | |
| 574 CommandTimeoutError if the installation times out. | |
| 575 DeviceUnreachableError on missing device. | |
| 576 DeviceVersionError if device SDK is less than Android L. | |
| 577 """ | |
| 578 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | |
| 579 | |
| 580 all_apks = [base_apk] + split_select.SelectSplits( | |
| 581 self, base_apk, split_apks) | |
| 582 package_name = apk_helper.GetPackageName(base_apk) | |
| 583 device_apk_paths = self._GetApplicationPathsInternal(package_name) | |
| 584 | |
| 585 if device_apk_paths: | |
| 586 partial_install_package = package_name | |
| 587 apks_to_install, host_checksums = ( | |
| 588 self._ComputeStaleApks(package_name, all_apks)) | |
| 589 if apks_to_install and not reinstall: | |
| 590 self.Uninstall(package_name) | |
| 591 partial_install_package = None | |
| 592 apks_to_install = all_apks | |
| 593 else: | |
| 594 partial_install_package = None | |
| 595 apks_to_install = all_apks | |
| 596 host_checksums = None | |
| 597 | |
| 598 if apks_to_install: | |
| 599 # We won't know the resulting device apk names. | |
| 600 self._cache['package_apk_paths'].pop(package_name, 0) | |
| 601 self.adb.InstallMultiple( | |
| 602 apks_to_install, partial=partial_install_package, | |
| 603 reinstall=reinstall) | |
| 604 self._cache['package_apk_checksums'][package_name] = host_checksums | |
| 605 | |
| 606 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 607 def Uninstall(self, package_name, keep_data=False, timeout=None, | |
| 608 retries=None): | |
| 609 """Remove the app |package_name| from the device. | |
| 610 | |
| 611 Args: | |
| 612 package_name: The package to uninstall. | |
| 613 keep_data: (optional) Whether to keep the data and cache directories. | |
| 614 timeout: Timeout in seconds. | |
| 615 retries: Number of retries. | |
| 616 | |
| 617 Raises: | |
| 618 CommandFailedError if the uninstallation fails. | |
| 619 CommandTimeoutError if the uninstallation times out. | |
| 620 DeviceUnreachableError on missing device. | |
| 621 """ | |
| 622 try: | |
| 623 self.adb.Uninstall(package_name, keep_data) | |
| 624 self._cache['package_apk_paths'][package_name] = [] | |
| 625 self._cache['package_apk_checksums'][package_name] = set() | |
| 626 except: | |
| 627 # Clear cache since we can't be sure of the state. | |
| 628 self._cache['package_apk_paths'].pop(package_name, 0) | |
| 629 self._cache['package_apk_checksums'].pop(package_name, 0) | |
| 630 raise | |
| 631 | |
| 632 def _CheckSdkLevel(self, required_sdk_level): | |
| 633 """Raises an exception if the device does not have the required SDK level. | |
| 634 """ | |
| 635 if self.build_version_sdk < required_sdk_level: | |
| 636 raise device_errors.DeviceVersionError( | |
| 637 ('Requires SDK level %s, device is SDK level %s' % | |
| 638 (required_sdk_level, self.build_version_sdk)), | |
| 639 device_serial=self.adb.GetDeviceSerial()) | |
| 640 | |
| 641 | |
| 642 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 643 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, | |
| 644 as_root=False, single_line=False, large_output=False, | |
| 645 timeout=None, retries=None): | |
| 646 """Run an ADB shell command. | |
| 647 | |
| 648 The command to run |cmd| should be a sequence of program arguments or else | |
| 649 a single string. | |
| 650 | |
| 651 When |cmd| is a sequence, it is assumed to contain the name of the command | |
| 652 to run followed by its arguments. In this case, arguments are passed to the | |
| 653 command exactly as given, without any further processing by the shell. This | |
| 654 allows to easily pass arguments containing spaces or special characters | |
| 655 without having to worry about getting quoting right. Whenever possible, it | |
| 656 is recomended to pass |cmd| as a sequence. | |
| 657 | |
| 658 When |cmd| is given as a string, it will be interpreted and run by the | |
| 659 shell on the device. | |
| 660 | |
| 661 This behaviour is consistent with that of command runners in cmd_helper as | |
| 662 well as Python's own subprocess.Popen. | |
| 663 | |
| 664 TODO(perezju) Change the default of |check_return| to True when callers | |
| 665 have switched to the new behaviour. | |
| 666 | |
| 667 Args: | |
| 668 cmd: A string with the full command to run on the device, or a sequence | |
| 669 containing the command and its arguments. | |
| 670 check_return: A boolean indicating whether or not the return code should | |
| 671 be checked. | |
| 672 cwd: The device directory in which the command should be run. | |
| 673 env: The environment variables with which the command should be run. | |
| 674 as_root: A boolean indicating whether the shell command should be run | |
| 675 with root privileges. | |
| 676 single_line: A boolean indicating if only a single line of output is | |
| 677 expected. | |
| 678 large_output: Uses a work-around for large shell command output. Without | |
| 679 this large output will be truncated. | |
| 680 timeout: timeout in seconds | |
| 681 retries: number of retries | |
| 682 | |
| 683 Returns: | |
| 684 If single_line is False, the output of the command as a list of lines, | |
| 685 otherwise, a string with the unique line of output emmited by the command | |
| 686 (with the optional newline at the end stripped). | |
| 687 | |
| 688 Raises: | |
| 689 AdbCommandFailedError if check_return is True and the exit code of | |
| 690 the command run on the device is non-zero. | |
| 691 CommandFailedError if single_line is True but the output contains two or | |
| 692 more lines. | |
| 693 CommandTimeoutError on timeout. | |
| 694 DeviceUnreachableError on missing device. | |
| 695 """ | |
| 696 def env_quote(key, value): | |
| 697 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): | |
| 698 raise KeyError('Invalid shell variable name %r' % key) | |
| 699 # using double quotes here to allow interpolation of shell variables | |
| 700 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) | |
| 701 | |
| 702 def run(cmd): | |
| 703 return self.adb.Shell(cmd) | |
| 704 | |
| 705 def handle_check_return(cmd): | |
| 706 try: | |
| 707 return run(cmd) | |
| 708 except device_errors.AdbCommandFailedError as exc: | |
| 709 if check_return: | |
| 710 raise | |
| 711 else: | |
| 712 return exc.output | |
| 713 | |
| 714 def handle_large_command(cmd): | |
| 715 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH: | |
| 716 return handle_check_return(cmd) | |
| 717 else: | |
| 718 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: | |
| 719 self._WriteFileWithPush(script.name, cmd) | |
| 720 logging.info('Large shell command will be run from file: %s ...', | |
| 721 cmd[:100]) | |
| 722 return handle_check_return('sh %s' % script.name_quoted) | |
| 723 | |
| 724 def handle_large_output(cmd, large_output_mode): | |
| 725 if large_output_mode: | |
| 726 with device_temp_file.DeviceTempFile(self.adb) as large_output_file: | |
| 727 cmd = '%s > %s' % (cmd, large_output_file.name) | |
| 728 logging.debug('Large output mode enabled. Will write output to ' | |
| 729 'device and read results from file.') | |
| 730 handle_large_command(cmd) | |
| 731 return self.ReadFile(large_output_file.name, force_pull=True) | |
| 732 else: | |
| 733 try: | |
| 734 return handle_large_command(cmd) | |
| 735 except device_errors.AdbCommandFailedError as exc: | |
| 736 if exc.status is None: | |
| 737 logging.exception('No output found for %s', cmd) | |
| 738 logging.warning('Attempting to run in large_output mode.') | |
| 739 logging.warning('Use RunShellCommand(..., large_output=True) for ' | |
| 740 'shell commands that expect a lot of output.') | |
| 741 return handle_large_output(cmd, True) | |
| 742 else: | |
| 743 raise | |
| 744 | |
| 745 if not isinstance(cmd, basestring): | |
| 746 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) | |
| 747 if env: | |
| 748 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) | |
| 749 cmd = '%s %s' % (env, cmd) | |
| 750 if cwd: | |
| 751 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) | |
| 752 if as_root and self.NeedsSU(): | |
| 753 # "su -c sh -c" allows using shell features in |cmd| | |
| 754 cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd)) | |
| 755 | |
| 756 output = handle_large_output(cmd, large_output).splitlines() | |
| 757 | |
| 758 if single_line: | |
| 759 if not output: | |
| 760 return '' | |
| 761 elif len(output) == 1: | |
| 762 return output[0] | |
| 763 else: | |
| 764 msg = 'one line of output was expected, but got: %s' | |
| 765 raise device_errors.CommandFailedError(msg % output, str(self)) | |
| 766 else: | |
| 767 return output | |
| 768 | |
| 769 def _RunPipedShellCommand(self, script, **kwargs): | |
| 770 PIPESTATUS_LEADER = 'PIPESTATUS: ' | |
| 771 | |
| 772 script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER | |
| 773 kwargs['check_return'] = True | |
| 774 output = self.RunShellCommand(script, **kwargs) | |
| 775 pipestatus_line = output[-1] | |
| 776 | |
| 777 if not pipestatus_line.startswith(PIPESTATUS_LEADER): | |
| 778 logging.error('Pipe exit statuses of shell script missing.') | |
| 779 raise device_errors.AdbShellCommandFailedError( | |
| 780 script, output, status=None, | |
| 781 device_serial=self.adb.GetDeviceSerial()) | |
| 782 | |
| 783 output = output[:-1] | |
| 784 statuses = [ | |
| 785 int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()] | |
| 786 if any(statuses): | |
| 787 raise device_errors.AdbShellCommandFailedError( | |
| 788 script, output, status=statuses, | |
| 789 device_serial=self.adb.GetDeviceSerial()) | |
| 790 return output | |
| 791 | |
| 792 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 793 def KillAll(self, process_name, exact=False, signum=device_signal.SIGKILL, | |
| 794 as_root=False, blocking=False, quiet=False, | |
| 795 timeout=None, retries=None): | |
| 796 """Kill all processes with the given name on the device. | |
| 797 | |
| 798 Args: | |
| 799 process_name: A string containing the name of the process to kill. | |
| 800 exact: A boolean indicating whether to kill all processes matching | |
| 801 the string |process_name| exactly, or all of those which contain | |
| 802 |process_name| as a substring. Defaults to False. | |
| 803 signum: An integer containing the signal number to send to kill. Defaults | |
| 804 to SIGKILL (9). | |
| 805 as_root: A boolean indicating whether the kill should be executed with | |
| 806 root privileges. | |
| 807 blocking: A boolean indicating whether we should wait until all processes | |
| 808 with the given |process_name| are dead. | |
| 809 quiet: A boolean indicating whether to ignore the fact that no processes | |
| 810 to kill were found. | |
| 811 timeout: timeout in seconds | |
| 812 retries: number of retries | |
| 813 | |
| 814 Returns: | |
| 815 The number of processes attempted to kill. | |
| 816 | |
| 817 Raises: | |
| 818 CommandFailedError if no process was killed and |quiet| is False. | |
| 819 CommandTimeoutError on timeout. | |
| 820 DeviceUnreachableError on missing device. | |
| 821 """ | |
| 822 procs_pids = self.GetPids(process_name) | |
| 823 if exact: | |
| 824 procs_pids = {process_name: procs_pids.get(process_name, [])} | |
| 825 pids = set(itertools.chain(*procs_pids.values())) | |
| 826 if not pids: | |
| 827 if quiet: | |
| 828 return 0 | |
| 829 else: | |
| 830 raise device_errors.CommandFailedError( | |
| 831 'No process "%s"' % process_name, str(self)) | |
| 832 | |
| 833 logging.info( | |
| 834 'KillAll(%r, ...) attempting to kill the following:', process_name) | |
| 835 for name, ids in procs_pids.iteritems(): | |
| 836 for i in ids: | |
| 837 logging.info(' %05s %s', str(i), name) | |
| 838 | |
| 839 cmd = ['kill', '-%d' % signum] + sorted(pids) | |
| 840 self.RunShellCommand(cmd, as_root=as_root, check_return=True) | |
| 841 | |
| 842 def all_pids_killed(): | |
| 843 procs_pids_remain = self.GetPids(process_name) | |
| 844 return not pids.intersection(itertools.chain(*procs_pids_remain.values())) | |
| 845 | |
| 846 if blocking: | |
| 847 timeout_retry.WaitFor(all_pids_killed, wait_period=0.1) | |
| 848 | |
| 849 return len(pids) | |
| 850 | |
| 851 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 852 def StartActivity(self, intent_obj, blocking=False, trace_file_name=None, | |
| 853 force_stop=False, timeout=None, retries=None): | |
| 854 """Start package's activity on the device. | |
| 855 | |
| 856 Args: | |
| 857 intent_obj: An Intent object to send. | |
| 858 blocking: A boolean indicating whether we should wait for the activity to | |
| 859 finish launching. | |
| 860 trace_file_name: If present, a string that both indicates that we want to | |
| 861 profile the activity and contains the path to which the | |
| 862 trace should be saved. | |
| 863 force_stop: A boolean indicating whether we should stop the activity | |
| 864 before starting it. | |
| 865 timeout: timeout in seconds | |
| 866 retries: number of retries | |
| 867 | |
| 868 Raises: | |
| 869 CommandFailedError if the activity could not be started. | |
| 870 CommandTimeoutError on timeout. | |
| 871 DeviceUnreachableError on missing device. | |
| 872 """ | |
| 873 cmd = ['am', 'start'] | |
| 874 if blocking: | |
| 875 cmd.append('-W') | |
| 876 if trace_file_name: | |
| 877 cmd.extend(['--start-profiler', trace_file_name]) | |
| 878 if force_stop: | |
| 879 cmd.append('-S') | |
| 880 cmd.extend(intent_obj.am_args) | |
| 881 for line in self.RunShellCommand(cmd, check_return=True): | |
| 882 if line.startswith('Error:'): | |
| 883 raise device_errors.CommandFailedError(line, str(self)) | |
| 884 | |
| 885 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 886 def StartInstrumentation(self, component, finish=True, raw=False, | |
| 887 extras=None, timeout=None, retries=None): | |
| 888 if extras is None: | |
| 889 extras = {} | |
| 890 | |
| 891 cmd = ['am', 'instrument'] | |
| 892 if finish: | |
| 893 cmd.append('-w') | |
| 894 if raw: | |
| 895 cmd.append('-r') | |
| 896 for k, v in extras.iteritems(): | |
| 897 cmd.extend(['-e', str(k), str(v)]) | |
| 898 cmd.append(component) | |
| 899 return self.RunShellCommand(cmd, check_return=True, large_output=True) | |
| 900 | |
| 901 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 902 def BroadcastIntent(self, intent_obj, timeout=None, retries=None): | |
| 903 """Send a broadcast intent. | |
| 904 | |
| 905 Args: | |
| 906 intent: An Intent to broadcast. | |
| 907 timeout: timeout in seconds | |
| 908 retries: number of retries | |
| 909 | |
| 910 Raises: | |
| 911 CommandTimeoutError on timeout. | |
| 912 DeviceUnreachableError on missing device. | |
| 913 """ | |
| 914 cmd = ['am', 'broadcast'] + intent_obj.am_args | |
| 915 self.RunShellCommand(cmd, check_return=True) | |
| 916 | |
| 917 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 918 def GoHome(self, timeout=None, retries=None): | |
| 919 """Return to the home screen and obtain launcher focus. | |
| 920 | |
| 921 This command launches the home screen and attempts to obtain | |
| 922 launcher focus until the timeout is reached. | |
| 923 | |
| 924 Args: | |
| 925 timeout: timeout in seconds | |
| 926 retries: number of retries | |
| 927 | |
| 928 Raises: | |
| 929 CommandTimeoutError on timeout. | |
| 930 DeviceUnreachableError on missing device. | |
| 931 """ | |
| 932 def is_launcher_focused(): | |
| 933 output = self.RunShellCommand(['dumpsys', 'window', 'windows'], | |
| 934 check_return=True, large_output=True) | |
| 935 return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output) | |
| 936 | |
| 937 def dismiss_popups(): | |
| 938 # There is a dialog present; attempt to get rid of it. | |
| 939 # Not all dialogs can be dismissed with back. | |
| 940 self.SendKeyEvent(keyevent.KEYCODE_ENTER) | |
| 941 self.SendKeyEvent(keyevent.KEYCODE_BACK) | |
| 942 return is_launcher_focused() | |
| 943 | |
| 944 # If Home is already focused, return early to avoid unnecessary work. | |
| 945 if is_launcher_focused(): | |
| 946 return | |
| 947 | |
| 948 self.StartActivity( | |
| 949 intent.Intent(action='android.intent.action.MAIN', | |
| 950 category='android.intent.category.HOME'), | |
| 951 blocking=True) | |
| 952 | |
| 953 if not is_launcher_focused(): | |
| 954 timeout_retry.WaitFor(dismiss_popups, wait_period=1) | |
| 955 | |
| 956 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 957 def ForceStop(self, package, timeout=None, retries=None): | |
| 958 """Close the application. | |
| 959 | |
| 960 Args: | |
| 961 package: A string containing the name of the package to stop. | |
| 962 timeout: timeout in seconds | |
| 963 retries: number of retries | |
| 964 | |
| 965 Raises: | |
| 966 CommandTimeoutError on timeout. | |
| 967 DeviceUnreachableError on missing device. | |
| 968 """ | |
| 969 self.RunShellCommand(['am', 'force-stop', package], check_return=True) | |
| 970 | |
| 971 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 972 def ClearApplicationState(self, package, timeout=None, retries=None): | |
| 973 """Clear all state for the given package. | |
| 974 | |
| 975 Args: | |
| 976 package: A string containing the name of the package to stop. | |
| 977 timeout: timeout in seconds | |
| 978 retries: number of retries | |
| 979 | |
| 980 Raises: | |
| 981 CommandTimeoutError on timeout. | |
| 982 DeviceUnreachableError on missing device. | |
| 983 """ | |
| 984 # Check that the package exists before clearing it for android builds below | |
| 985 # JB MR2. Necessary because calling pm clear on a package that doesn't exist | |
| 986 # may never return. | |
| 987 if ((self.build_version_sdk >= | |
| 988 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) | |
| 989 or self._GetApplicationPathsInternal(package)): | |
| 990 self.RunShellCommand(['pm', 'clear', package], check_return=True) | |
| 991 | |
| 992 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 993 def SendKeyEvent(self, keycode, timeout=None, retries=None): | |
| 994 """Sends a keycode to the device. | |
| 995 | |
| 996 See the pylib.constants.keyevent module for suitable keycode values. | |
| 997 | |
| 998 Args: | |
| 999 keycode: A integer keycode to send to the device. | |
| 1000 timeout: timeout in seconds | |
| 1001 retries: number of retries | |
| 1002 | |
| 1003 Raises: | |
| 1004 CommandTimeoutError on timeout. | |
| 1005 DeviceUnreachableError on missing device. | |
| 1006 """ | |
| 1007 self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')], | |
| 1008 check_return=True) | |
| 1009 | |
| 1010 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT | |
| 1011 PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES | |
| 1012 | |
| 1013 @decorators.WithTimeoutAndRetriesDefaults( | |
| 1014 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT, | |
| 1015 PUSH_CHANGED_FILES_DEFAULT_RETRIES) | |
| 1016 def PushChangedFiles(self, host_device_tuples, timeout=None, | |
| 1017 retries=None, delete_device_stale=False): | |
| 1018 """Push files to the device, skipping files that don't need updating. | |
| 1019 | |
| 1020 When a directory is pushed, it is traversed recursively on the host and | |
| 1021 all files in it are pushed to the device as needed. | |
| 1022 Additionally, if delete_device_stale option is True, | |
| 1023 files that exist on the device but don't exist on the host are deleted. | |
| 1024 | |
| 1025 Args: | |
| 1026 host_device_tuples: A list of (host_path, device_path) tuples, where | |
| 1027 |host_path| is an absolute path of a file or directory on the host | |
| 1028 that should be minimially pushed to the device, and |device_path| is | |
| 1029 an absolute path of the destination on the device. | |
| 1030 timeout: timeout in seconds | |
| 1031 retries: number of retries | |
| 1032 delete_device_stale: option to delete stale files on device | |
| 1033 | |
| 1034 Raises: | |
| 1035 CommandFailedError on failure. | |
| 1036 CommandTimeoutError on timeout. | |
| 1037 DeviceUnreachableError on missing device. | |
| 1038 """ | |
| 1039 | |
| 1040 all_changed_files = [] | |
| 1041 all_stale_files = [] | |
| 1042 missing_dirs = [] | |
| 1043 for h, d in host_device_tuples: | |
| 1044 changed_files, up_to_date_files, stale_files = ( | |
| 1045 self._GetChangedAndStaleFiles(h, d, delete_device_stale)) | |
| 1046 all_changed_files += changed_files | |
| 1047 all_stale_files += stale_files | |
| 1048 if (os.path.isdir(h) and changed_files and not up_to_date_files | |
| 1049 and not stale_files): | |
| 1050 missing_dirs.append(d) | |
| 1051 | |
| 1052 if delete_device_stale and all_stale_files: | |
| 1053 self.RunShellCommand(['rm', '-f'] + all_stale_files, | |
| 1054 check_return=True) | |
| 1055 | |
| 1056 if all_changed_files: | |
| 1057 if missing_dirs: | |
| 1058 self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True) | |
| 1059 self._PushFilesImpl(host_device_tuples, all_changed_files) | |
| 1060 | |
| 1061 def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False): | |
| 1062 """Get files to push and delete | |
| 1063 | |
| 1064 Args: | |
| 1065 host_path: an absolute path of a file or directory on the host | |
| 1066 device_path: an absolute path of a file or directory on the device | |
| 1067 track_stale: whether to bother looking for stale files (slower) | |
| 1068 | |
| 1069 Returns: | |
| 1070 a three-element tuple | |
| 1071 1st element: a list of (host_files_path, device_files_path) tuples to push | |
| 1072 2nd element: a list of host_files_path that are up-to-date | |
| 1073 3rd element: a list of stale files under device_path, or [] when | |
| 1074 track_stale == False | |
| 1075 """ | |
| 1076 real_host_path = os.path.realpath(host_path) | |
| 1077 try: | |
| 1078 real_device_path = self.RunShellCommand( | |
| 1079 ['realpath', device_path], single_line=True, check_return=True) | |
| 1080 except device_errors.CommandFailedError: | |
| 1081 real_device_path = None | |
| 1082 if not real_device_path: | |
| 1083 return ([(host_path, device_path)], [], []) | |
| 1084 | |
| 1085 try: | |
| 1086 host_checksums = md5sum.CalculateHostMd5Sums([real_host_path]) | |
| 1087 interesting_device_paths = [real_device_path] | |
| 1088 if not track_stale and os.path.isdir(real_host_path): | |
| 1089 interesting_device_paths = [ | |
| 1090 posixpath.join(real_device_path, os.path.relpath(p, real_host_path)) | |
| 1091 for p in host_checksums.keys()] | |
| 1092 device_checksums = md5sum.CalculateDeviceMd5Sums( | |
| 1093 interesting_device_paths, self) | |
| 1094 except EnvironmentError as e: | |
| 1095 logging.warning('Error calculating md5: %s', e) | |
| 1096 return ([(host_path, device_path)], [], []) | |
| 1097 | |
| 1098 to_push = [] | |
| 1099 up_to_date = [] | |
| 1100 to_delete = [] | |
| 1101 if os.path.isfile(host_path): | |
| 1102 host_checksum = host_checksums.get(real_host_path) | |
| 1103 device_checksum = device_checksums.get(real_device_path) | |
| 1104 if host_checksum == device_checksum: | |
| 1105 up_to_date.append(host_path) | |
| 1106 else: | |
| 1107 to_push.append((host_path, device_path)) | |
| 1108 else: | |
| 1109 for host_abs_path, host_checksum in host_checksums.iteritems(): | |
| 1110 device_abs_path = posixpath.join( | |
| 1111 real_device_path, os.path.relpath(host_abs_path, real_host_path)) | |
| 1112 device_checksum = device_checksums.pop(device_abs_path, None) | |
| 1113 if device_checksum == host_checksum: | |
| 1114 up_to_date.append(host_abs_path) | |
| 1115 else: | |
| 1116 to_push.append((host_abs_path, device_abs_path)) | |
| 1117 to_delete = device_checksums.keys() | |
| 1118 return (to_push, up_to_date, to_delete) | |
| 1119 | |
| 1120 def _ComputeDeviceChecksumsForApks(self, package_name): | |
| 1121 ret = self._cache['package_apk_checksums'].get(package_name) | |
| 1122 if ret is None: | |
| 1123 device_paths = self._GetApplicationPathsInternal(package_name) | |
| 1124 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self) | |
| 1125 ret = set(file_to_checksums.values()) | |
| 1126 self._cache['package_apk_checksums'][package_name] = ret | |
| 1127 return ret | |
| 1128 | |
| 1129 def _ComputeStaleApks(self, package_name, host_apk_paths): | |
| 1130 host_checksums_holder = [None] | |
| 1131 def compute_host_checksums(): | |
| 1132 host_checksums_holder[0] = md5sum.CalculateHostMd5Sums(host_apk_paths) | |
| 1133 | |
| 1134 host_thread = threading.Thread(target=compute_host_checksums) | |
| 1135 host_thread.start() | |
| 1136 device_checksums = self._ComputeDeviceChecksumsForApks(package_name) | |
| 1137 host_thread.join() | |
| 1138 host_checksums = host_checksums_holder[0] | |
| 1139 stale_apks = [k for (k, v) in host_checksums.iteritems() | |
| 1140 if v not in device_checksums] | |
| 1141 return stale_apks, set(host_checksums.values()) | |
| 1142 | |
| 1143 def _PushFilesImpl(self, host_device_tuples, files): | |
| 1144 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) | |
| 1145 file_count = len(files) | |
| 1146 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) | |
| 1147 for h, _ in host_device_tuples) | |
| 1148 dir_file_count = 0 | |
| 1149 for h, _ in host_device_tuples: | |
| 1150 if os.path.isdir(h): | |
| 1151 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) | |
| 1152 else: | |
| 1153 dir_file_count += 1 | |
| 1154 | |
| 1155 push_duration = self._ApproximateDuration( | |
| 1156 file_count, file_count, size, False) | |
| 1157 dir_push_duration = self._ApproximateDuration( | |
| 1158 len(host_device_tuples), dir_file_count, dir_size, False) | |
| 1159 zip_duration = self._ApproximateDuration(1, 1, size, True) | |
| 1160 | |
| 1161 self._InstallCommands() | |
| 1162 | |
| 1163 if dir_push_duration < push_duration and ( | |
| 1164 dir_push_duration < zip_duration or not self._commands_installed): | |
| 1165 self._PushChangedFilesIndividually(host_device_tuples) | |
| 1166 elif push_duration < zip_duration or not self._commands_installed: | |
| 1167 self._PushChangedFilesIndividually(files) | |
| 1168 else: | |
| 1169 self._PushChangedFilesZipped(files) | |
| 1170 self.RunShellCommand( | |
| 1171 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples], | |
| 1172 as_root=True, check_return=True) | |
| 1173 | |
| 1174 def _InstallCommands(self): | |
| 1175 if self._commands_installed is None: | |
| 1176 try: | |
| 1177 if not install_commands.Installed(self): | |
| 1178 install_commands.InstallCommands(self) | |
| 1179 self._commands_installed = True | |
| 1180 except Exception as e: | |
| 1181 logging.warning('unzip not available: %s' % str(e)) | |
| 1182 self._commands_installed = False | |
| 1183 | |
| 1184 @staticmethod | |
| 1185 def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping): | |
| 1186 # We approximate the time to push a set of files to a device as: | |
| 1187 # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where | |
| 1188 # t: total time (sec) | |
| 1189 # c1: adb call time delay (sec) | |
| 1190 # a: number of times adb is called (unitless) | |
| 1191 # c2: push time delay (sec) | |
| 1192 # f: number of files pushed via adb (unitless) | |
| 1193 # c3: zip time delay (sec) | |
| 1194 # c4: zip rate (bytes/sec) | |
| 1195 # b: total number of bytes (bytes) | |
| 1196 # c5: transfer rate (bytes/sec) | |
| 1197 # c6: compression ratio (unitless) | |
| 1198 | |
| 1199 # All of these are approximations. | |
| 1200 ADB_CALL_PENALTY = 0.1 # seconds | |
| 1201 ADB_PUSH_PENALTY = 0.01 # seconds | |
| 1202 ZIP_PENALTY = 2.0 # seconds | |
| 1203 ZIP_RATE = 10000000.0 # bytes / second | |
| 1204 TRANSFER_RATE = 2000000.0 # bytes / second | |
| 1205 COMPRESSION_RATIO = 2.0 # unitless | |
| 1206 | |
| 1207 adb_call_time = ADB_CALL_PENALTY * adb_calls | |
| 1208 adb_push_setup_time = ADB_PUSH_PENALTY * file_count | |
| 1209 if is_zipping: | |
| 1210 zip_time = ZIP_PENALTY + byte_count / ZIP_RATE | |
| 1211 transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO) | |
| 1212 else: | |
| 1213 zip_time = 0 | |
| 1214 transfer_time = byte_count / TRANSFER_RATE | |
| 1215 return adb_call_time + adb_push_setup_time + zip_time + transfer_time | |
| 1216 | |
| 1217 def _PushChangedFilesIndividually(self, files): | |
| 1218 for h, d in files: | |
| 1219 self.adb.Push(h, d) | |
| 1220 | |
| 1221 def _PushChangedFilesZipped(self, files): | |
| 1222 if not files: | |
| 1223 return | |
| 1224 | |
| 1225 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: | |
| 1226 zip_proc = multiprocessing.Process( | |
| 1227 target=DeviceUtils._CreateDeviceZip, | |
| 1228 args=(zip_file.name, files)) | |
| 1229 zip_proc.start() | |
| 1230 zip_proc.join() | |
| 1231 | |
| 1232 zip_on_device = '%s/tmp.zip' % self.GetExternalStoragePath() | |
| 1233 try: | |
| 1234 self.adb.Push(zip_file.name, zip_on_device) | |
| 1235 self.RunShellCommand( | |
| 1236 ['unzip', zip_on_device], | |
| 1237 as_root=True, | |
| 1238 env={'PATH': '%s:$PATH' % install_commands.BIN_DIR}, | |
| 1239 check_return=True) | |
| 1240 finally: | |
| 1241 if zip_proc.is_alive(): | |
| 1242 zip_proc.terminate() | |
| 1243 if self.IsOnline(): | |
| 1244 self.RunShellCommand(['rm', zip_on_device], check_return=True) | |
| 1245 | |
| 1246 @staticmethod | |
| 1247 def _CreateDeviceZip(zip_path, host_device_tuples): | |
| 1248 with zipfile.ZipFile(zip_path, 'w') as zip_file: | |
| 1249 for host_path, device_path in host_device_tuples: | |
| 1250 zip_utils.WriteToZipFile(zip_file, host_path, device_path) | |
| 1251 | |
| 1252 # TODO(nednguyen): remove this and migrate the callsite to PathExists(). | |
| 1253 def FileExists(self, device_path, timeout=None, retries=None): | |
| 1254 """Checks whether the given file exists on the device. | |
| 1255 | |
| 1256 Arguments are the same as PathExists. | |
| 1257 """ | |
| 1258 return self.PathExists(device_path, timeout=timeout, retries=retries) | |
| 1259 | |
| 1260 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1261 def PathExists(self, device_path, timeout=None, retries=None): | |
| 1262 """Checks whether the given path exists on the device. | |
| 1263 | |
| 1264 Args: | |
| 1265 device_path: A string containing the absolute path to the file on the | |
| 1266 device. | |
| 1267 timeout: timeout in seconds | |
| 1268 retries: number of retries | |
| 1269 | |
| 1270 Returns: | |
| 1271 True if the file exists on the device, False otherwise. | |
| 1272 | |
| 1273 Raises: | |
| 1274 CommandTimeoutError on timeout. | |
| 1275 DeviceUnreachableError on missing device. | |
| 1276 """ | |
| 1277 try: | |
| 1278 self.RunShellCommand(['test', '-e', device_path], check_return=True) | |
| 1279 return True | |
| 1280 except device_errors.AdbCommandFailedError: | |
| 1281 return False | |
| 1282 | |
| 1283 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1284 def PullFile(self, device_path, host_path, timeout=None, retries=None): | |
| 1285 """Pull a file from the device. | |
| 1286 | |
| 1287 Args: | |
| 1288 device_path: A string containing the absolute path of the file to pull | |
| 1289 from the device. | |
| 1290 host_path: A string containing the absolute path of the destination on | |
| 1291 the host. | |
| 1292 timeout: timeout in seconds | |
| 1293 retries: number of retries | |
| 1294 | |
| 1295 Raises: | |
| 1296 CommandFailedError on failure. | |
| 1297 CommandTimeoutError on timeout. | |
| 1298 """ | |
| 1299 # Create the base dir if it doesn't exist already | |
| 1300 dirname = os.path.dirname(host_path) | |
| 1301 if dirname and not os.path.exists(dirname): | |
| 1302 os.makedirs(dirname) | |
| 1303 self.adb.Pull(device_path, host_path) | |
| 1304 | |
| 1305 def _ReadFileWithPull(self, device_path): | |
| 1306 try: | |
| 1307 d = tempfile.mkdtemp() | |
| 1308 host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull') | |
| 1309 self.adb.Pull(device_path, host_temp_path) | |
| 1310 with open(host_temp_path, 'r') as host_temp: | |
| 1311 return host_temp.read() | |
| 1312 finally: | |
| 1313 if os.path.exists(d): | |
| 1314 shutil.rmtree(d) | |
| 1315 | |
| 1316 _LS_RE = re.compile( | |
| 1317 r'(?P<perms>\S+) +(?P<owner>\S+) +(?P<group>\S+) +(?:(?P<size>\d+) +)?' | |
| 1318 + r'(?P<date>\S+) +(?P<time>\S+) +(?P<name>.+)$') | |
| 1319 | |
| 1320 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1321 def ReadFile(self, device_path, as_root=False, force_pull=False, | |
| 1322 timeout=None, retries=None): | |
| 1323 """Reads the contents of a file from the device. | |
| 1324 | |
| 1325 Args: | |
| 1326 device_path: A string containing the absolute path of the file to read | |
| 1327 from the device. | |
| 1328 as_root: A boolean indicating whether the read should be executed with | |
| 1329 root privileges. | |
| 1330 force_pull: A boolean indicating whether to force the operation to be | |
| 1331 performed by pulling a file from the device. The default is, when the | |
| 1332 contents are short, to retrieve the contents using cat instead. | |
| 1333 timeout: timeout in seconds | |
| 1334 retries: number of retries | |
| 1335 | |
| 1336 Returns: | |
| 1337 The contents of |device_path| as a string. Contents are intepreted using | |
| 1338 universal newlines, so the caller will see them encoded as '\n'. Also, | |
| 1339 all lines will be terminated. | |
| 1340 | |
| 1341 Raises: | |
| 1342 AdbCommandFailedError if the file can't be read. | |
| 1343 CommandTimeoutError on timeout. | |
| 1344 DeviceUnreachableError on missing device. | |
| 1345 """ | |
| 1346 def get_size(path): | |
| 1347 # TODO(jbudorick): Implement a generic version of Stat() that handles | |
| 1348 # as_root=True, then switch this implementation to use that. | |
| 1349 ls_out = self.RunShellCommand(['ls', '-l', device_path], as_root=as_root, | |
| 1350 check_return=True) | |
| 1351 for line in ls_out: | |
| 1352 m = self._LS_RE.match(line) | |
| 1353 if m and m.group('name') == posixpath.basename(device_path): | |
| 1354 return int(m.group('size')) | |
| 1355 logging.warning('Could not determine size of %s.', device_path) | |
| 1356 return None | |
| 1357 | |
| 1358 if (not force_pull | |
| 1359 and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH): | |
| 1360 return _JoinLines(self.RunShellCommand( | |
| 1361 ['cat', device_path], as_root=as_root, check_return=True)) | |
| 1362 elif as_root and self.NeedsSU(): | |
| 1363 with device_temp_file.DeviceTempFile(self.adb) as device_temp: | |
| 1364 self.RunShellCommand(['cp', device_path, device_temp.name], | |
| 1365 as_root=True, check_return=True) | |
| 1366 return self._ReadFileWithPull(device_temp.name) | |
| 1367 else: | |
| 1368 return self._ReadFileWithPull(device_path) | |
| 1369 | |
| 1370 def _WriteFileWithPush(self, device_path, contents): | |
| 1371 with tempfile.NamedTemporaryFile() as host_temp: | |
| 1372 host_temp.write(contents) | |
| 1373 host_temp.flush() | |
| 1374 self.adb.Push(host_temp.name, device_path) | |
| 1375 | |
| 1376 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1377 def WriteFile(self, device_path, contents, as_root=False, force_push=False, | |
| 1378 timeout=None, retries=None): | |
| 1379 """Writes |contents| to a file on the device. | |
| 1380 | |
| 1381 Args: | |
| 1382 device_path: A string containing the absolute path to the file to write | |
| 1383 on the device. | |
| 1384 contents: A string containing the data to write to the device. | |
| 1385 as_root: A boolean indicating whether the write should be executed with | |
| 1386 root privileges (if available). | |
| 1387 force_push: A boolean indicating whether to force the operation to be | |
| 1388 performed by pushing a file to the device. The default is, when the | |
| 1389 contents are short, to pass the contents using a shell script instead. | |
| 1390 timeout: timeout in seconds | |
| 1391 retries: number of retries | |
| 1392 | |
| 1393 Raises: | |
| 1394 CommandFailedError if the file could not be written on the device. | |
| 1395 CommandTimeoutError on timeout. | |
| 1396 DeviceUnreachableError on missing device. | |
| 1397 """ | |
| 1398 if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH: | |
| 1399 # If the contents are small, for efficieny we write the contents with | |
| 1400 # a shell command rather than pushing a file. | |
| 1401 cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), | |
| 1402 cmd_helper.SingleQuote(device_path)) | |
| 1403 self.RunShellCommand(cmd, as_root=as_root, check_return=True) | |
| 1404 elif as_root and self.NeedsSU(): | |
| 1405 # Adb does not allow to "push with su", so we first push to a temp file | |
| 1406 # on a safe location, and then copy it to the desired location with su. | |
| 1407 with device_temp_file.DeviceTempFile(self.adb) as device_temp: | |
| 1408 self._WriteFileWithPush(device_temp.name, contents) | |
| 1409 # Here we need 'cp' rather than 'mv' because the temp and | |
| 1410 # destination files might be on different file systems (e.g. | |
| 1411 # on internal storage and an external sd card). | |
| 1412 self.RunShellCommand(['cp', device_temp.name, device_path], | |
| 1413 as_root=True, check_return=True) | |
| 1414 else: | |
| 1415 # If root is not needed, we can push directly to the desired location. | |
| 1416 self._WriteFileWithPush(device_path, contents) | |
| 1417 | |
| 1418 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1419 def Ls(self, device_path, timeout=None, retries=None): | |
| 1420 """Lists the contents of a directory on the device. | |
| 1421 | |
| 1422 Args: | |
| 1423 device_path: A string containing the path of the directory on the device | |
| 1424 to list. | |
| 1425 timeout: timeout in seconds | |
| 1426 retries: number of retries | |
| 1427 | |
| 1428 Returns: | |
| 1429 A list of pairs (filename, stat) for each file found in the directory, | |
| 1430 where the stat object has the properties: st_mode, st_size, and st_time. | |
| 1431 | |
| 1432 Raises: | |
| 1433 AdbCommandFailedError if |device_path| does not specify a valid and | |
| 1434 accessible directory in the device. | |
| 1435 CommandTimeoutError on timeout. | |
| 1436 DeviceUnreachableError on missing device. | |
| 1437 """ | |
| 1438 return self.adb.Ls(device_path) | |
| 1439 | |
| 1440 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1441 def Stat(self, device_path, timeout=None, retries=None): | |
| 1442 """Get the stat attributes of a file or directory on the device. | |
| 1443 | |
| 1444 Args: | |
| 1445 device_path: A string containing the path of from which to get attributes | |
| 1446 on the device. | |
| 1447 timeout: timeout in seconds | |
| 1448 retries: number of retries | |
| 1449 | |
| 1450 Returns: | |
| 1451 A stat object with the properties: st_mode, st_size, and st_time | |
| 1452 | |
| 1453 Raises: | |
| 1454 CommandFailedError if device_path cannot be found on the device. | |
| 1455 CommandTimeoutError on timeout. | |
| 1456 DeviceUnreachableError on missing device. | |
| 1457 """ | |
| 1458 dirname, target = device_path.rsplit('/', 1) | |
| 1459 for filename, stat in self.adb.Ls(dirname): | |
| 1460 if filename == target: | |
| 1461 return stat | |
| 1462 raise device_errors.CommandFailedError( | |
| 1463 'Cannot find file or directory: %r' % device_path, str(self)) | |
| 1464 | |
| 1465 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1466 def SetJavaAsserts(self, enabled, timeout=None, retries=None): | |
| 1467 """Enables or disables Java asserts. | |
| 1468 | |
| 1469 Args: | |
| 1470 enabled: A boolean indicating whether Java asserts should be enabled | |
| 1471 or disabled. | |
| 1472 timeout: timeout in seconds | |
| 1473 retries: number of retries | |
| 1474 | |
| 1475 Returns: | |
| 1476 True if the device-side property changed and a restart is required as a | |
| 1477 result, False otherwise. | |
| 1478 | |
| 1479 Raises: | |
| 1480 CommandTimeoutError on timeout. | |
| 1481 """ | |
| 1482 def find_property(lines, property_name): | |
| 1483 for index, line in enumerate(lines): | |
| 1484 if line.strip() == '': | |
| 1485 continue | |
| 1486 key, value = (s.strip() for s in line.split('=', 1)) | |
| 1487 if key == property_name: | |
| 1488 return index, value | |
| 1489 return None, '' | |
| 1490 | |
| 1491 new_value = 'all' if enabled else '' | |
| 1492 | |
| 1493 # First ensure the desired property is persisted. | |
| 1494 try: | |
| 1495 properties = self.ReadFile( | |
| 1496 constants.DEVICE_LOCAL_PROPERTIES_PATH).splitlines() | |
| 1497 except device_errors.CommandFailedError: | |
| 1498 properties = [] | |
| 1499 index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY) | |
| 1500 if new_value != value: | |
| 1501 if new_value: | |
| 1502 new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value) | |
| 1503 if index is None: | |
| 1504 properties.append(new_line) | |
| 1505 else: | |
| 1506 properties[index] = new_line | |
| 1507 else: | |
| 1508 assert index is not None # since new_value == '' and new_value != value | |
| 1509 properties.pop(index) | |
| 1510 self.WriteFile(constants.DEVICE_LOCAL_PROPERTIES_PATH, | |
| 1511 _JoinLines(properties)) | |
| 1512 | |
| 1513 # Next, check the current runtime value is what we need, and | |
| 1514 # if not, set it and report that a reboot is required. | |
| 1515 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) | |
| 1516 if new_value != value: | |
| 1517 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) | |
| 1518 return True | |
| 1519 else: | |
| 1520 return False | |
| 1521 | |
| 1522 @property | |
| 1523 def language(self): | |
| 1524 """Returns the language setting on the device.""" | |
| 1525 return self.GetProp('persist.sys.language', cache=False) | |
| 1526 | |
| 1527 @property | |
| 1528 def country(self): | |
| 1529 """Returns the country setting on the device.""" | |
| 1530 return self.GetProp('persist.sys.country', cache=False) | |
| 1531 | |
| 1532 @property | |
| 1533 def screen_density(self): | |
| 1534 """Returns the screen density of the device.""" | |
| 1535 DPI_TO_DENSITY = { | |
| 1536 120: 'ldpi', | |
| 1537 160: 'mdpi', | |
| 1538 240: 'hdpi', | |
| 1539 320: 'xhdpi', | |
| 1540 480: 'xxhdpi', | |
| 1541 640: 'xxxhdpi', | |
| 1542 } | |
| 1543 dpi = int(self.GetProp('ro.sf.lcd_density', cache=True)) | |
| 1544 return DPI_TO_DENSITY.get(dpi, 'tvdpi') | |
| 1545 | |
| 1546 @property | |
| 1547 def build_description(self): | |
| 1548 """Returns the build description of the system. | |
| 1549 | |
| 1550 For example: | |
| 1551 nakasi-user 4.4.4 KTU84P 1227136 release-keys | |
| 1552 """ | |
| 1553 return self.GetProp('ro.build.description', cache=True) | |
| 1554 | |
| 1555 @property | |
| 1556 def build_fingerprint(self): | |
| 1557 """Returns the build fingerprint of the system. | |
| 1558 | |
| 1559 For example: | |
| 1560 google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys | |
| 1561 """ | |
| 1562 return self.GetProp('ro.build.fingerprint', cache=True) | |
| 1563 | |
| 1564 @property | |
| 1565 def build_id(self): | |
| 1566 """Returns the build ID of the system (e.g. 'KTU84P').""" | |
| 1567 return self.GetProp('ro.build.id', cache=True) | |
| 1568 | |
| 1569 @property | |
| 1570 def build_product(self): | |
| 1571 """Returns the build product of the system (e.g. 'grouper').""" | |
| 1572 return self.GetProp('ro.build.product', cache=True) | |
| 1573 | |
| 1574 @property | |
| 1575 def build_type(self): | |
| 1576 """Returns the build type of the system (e.g. 'user').""" | |
| 1577 return self.GetProp('ro.build.type', cache=True) | |
| 1578 | |
| 1579 @property | |
| 1580 def build_version_sdk(self): | |
| 1581 """Returns the build version sdk of the system as a number (e.g. 19). | |
| 1582 | |
| 1583 For version code numbers see: | |
| 1584 http://developer.android.com/reference/android/os/Build.VERSION_CODES.html | |
| 1585 | |
| 1586 For named constants see: | |
| 1587 pylib.constants.ANDROID_SDK_VERSION_CODES | |
| 1588 | |
| 1589 Raises: | |
| 1590 CommandFailedError if the build version sdk is not a number. | |
| 1591 """ | |
| 1592 value = self.GetProp('ro.build.version.sdk', cache=True) | |
| 1593 try: | |
| 1594 return int(value) | |
| 1595 except ValueError: | |
| 1596 raise device_errors.CommandFailedError( | |
| 1597 'Invalid build version sdk: %r' % value) | |
| 1598 | |
| 1599 @property | |
| 1600 def product_cpu_abi(self): | |
| 1601 """Returns the product cpu abi of the device (e.g. 'armeabi-v7a').""" | |
| 1602 return self.GetProp('ro.product.cpu.abi', cache=True) | |
| 1603 | |
| 1604 @property | |
| 1605 def product_model(self): | |
| 1606 """Returns the name of the product model (e.g. 'Nexus 7').""" | |
| 1607 return self.GetProp('ro.product.model', cache=True) | |
| 1608 | |
| 1609 @property | |
| 1610 def product_name(self): | |
| 1611 """Returns the product name of the device (e.g. 'nakasi').""" | |
| 1612 return self.GetProp('ro.product.name', cache=True) | |
| 1613 | |
| 1614 def GetProp(self, property_name, cache=False, timeout=DEFAULT, | |
| 1615 retries=DEFAULT): | |
| 1616 """Gets a property from the device. | |
| 1617 | |
| 1618 Args: | |
| 1619 property_name: A string containing the name of the property to get from | |
| 1620 the device. | |
| 1621 cache: A boolean indicating whether to cache the value of this property. | |
| 1622 timeout: timeout in seconds | |
| 1623 retries: number of retries | |
| 1624 | |
| 1625 Returns: | |
| 1626 The value of the device's |property_name| property. | |
| 1627 | |
| 1628 Raises: | |
| 1629 CommandTimeoutError on timeout. | |
| 1630 """ | |
| 1631 assert isinstance(property_name, basestring), ( | |
| 1632 "property_name is not a string: %r" % property_name) | |
| 1633 | |
| 1634 cache_key = '_prop:' + property_name | |
| 1635 if cache and cache_key in self._cache: | |
| 1636 return self._cache[cache_key] | |
| 1637 else: | |
| 1638 # timeout and retries are handled down at run shell, because we don't | |
| 1639 # want to apply them in the other branch when reading from the cache | |
| 1640 value = self.RunShellCommand( | |
| 1641 ['getprop', property_name], single_line=True, check_return=True, | |
| 1642 timeout=self._default_timeout if timeout is DEFAULT else timeout, | |
| 1643 retries=self._default_retries if retries is DEFAULT else retries) | |
| 1644 if cache or cache_key in self._cache: | |
| 1645 self._cache[cache_key] = value | |
| 1646 return value | |
| 1647 | |
| 1648 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1649 def SetProp(self, property_name, value, check=False, timeout=None, | |
| 1650 retries=None): | |
| 1651 """Sets a property on the device. | |
| 1652 | |
| 1653 Args: | |
| 1654 property_name: A string containing the name of the property to set on | |
| 1655 the device. | |
| 1656 value: A string containing the value to set to the property on the | |
| 1657 device. | |
| 1658 check: A boolean indicating whether to check that the property was | |
| 1659 successfully set on the device. | |
| 1660 timeout: timeout in seconds | |
| 1661 retries: number of retries | |
| 1662 | |
| 1663 Raises: | |
| 1664 CommandFailedError if check is true and the property was not correctly | |
| 1665 set on the device (e.g. because it is not rooted). | |
| 1666 CommandTimeoutError on timeout. | |
| 1667 """ | |
| 1668 assert isinstance(property_name, basestring), ( | |
| 1669 "property_name is not a string: %r" % property_name) | |
| 1670 assert isinstance(value, basestring), "value is not a string: %r" % value | |
| 1671 | |
| 1672 self.RunShellCommand(['setprop', property_name, value], check_return=True) | |
| 1673 cache_key = '_prop:' + property_name | |
| 1674 if cache_key in self._cache: | |
| 1675 del self._cache[cache_key] | |
| 1676 # TODO(perezju) remove the option and make the check mandatory, but using a | |
| 1677 # single shell script to both set- and getprop. | |
| 1678 if check and value != self.GetProp(property_name): | |
| 1679 raise device_errors.CommandFailedError( | |
| 1680 'Unable to set property %r on the device to %r' | |
| 1681 % (property_name, value), str(self)) | |
| 1682 | |
| 1683 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1684 def GetABI(self, timeout=None, retries=None): | |
| 1685 """Gets the device main ABI. | |
| 1686 | |
| 1687 Args: | |
| 1688 timeout: timeout in seconds | |
| 1689 retries: number of retries | |
| 1690 | |
| 1691 Returns: | |
| 1692 The device's main ABI name. | |
| 1693 | |
| 1694 Raises: | |
| 1695 CommandTimeoutError on timeout. | |
| 1696 """ | |
| 1697 return self.GetProp('ro.product.cpu.abi') | |
| 1698 | |
| 1699 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1700 def GetPids(self, process_name, timeout=None, retries=None): | |
| 1701 """Returns the PIDs of processes with the given name. | |
| 1702 | |
| 1703 Note that the |process_name| is often the package name. | |
| 1704 | |
| 1705 Args: | |
| 1706 process_name: A string containing the process name to get the PIDs for. | |
| 1707 timeout: timeout in seconds | |
| 1708 retries: number of retries | |
| 1709 | |
| 1710 Returns: | |
| 1711 A dict mapping process name to a list of PIDs for each process that | |
| 1712 contained the provided |process_name|. | |
| 1713 | |
| 1714 Raises: | |
| 1715 CommandTimeoutError on timeout. | |
| 1716 DeviceUnreachableError on missing device. | |
| 1717 """ | |
| 1718 procs_pids = collections.defaultdict(list) | |
| 1719 try: | |
| 1720 ps_output = self._RunPipedShellCommand( | |
| 1721 'ps | grep -F %s' % cmd_helper.SingleQuote(process_name)) | |
| 1722 except device_errors.AdbShellCommandFailedError as e: | |
| 1723 if e.status and isinstance(e.status, list) and not e.status[0]: | |
| 1724 # If ps succeeded but grep failed, there were no processes with the | |
| 1725 # given name. | |
| 1726 return procs_pids | |
| 1727 else: | |
| 1728 raise | |
| 1729 | |
| 1730 for line in ps_output: | |
| 1731 try: | |
| 1732 ps_data = line.split() | |
| 1733 if process_name in ps_data[-1]: | |
| 1734 pid, process = ps_data[1], ps_data[-1] | |
| 1735 procs_pids[process].append(pid) | |
| 1736 except IndexError: | |
| 1737 pass | |
| 1738 return procs_pids | |
| 1739 | |
| 1740 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1741 def TakeScreenshot(self, host_path=None, timeout=None, retries=None): | |
| 1742 """Takes a screenshot of the device. | |
| 1743 | |
| 1744 Args: | |
| 1745 host_path: A string containing the path on the host to save the | |
| 1746 screenshot to. If None, a file name in the current | |
| 1747 directory will be generated. | |
| 1748 timeout: timeout in seconds | |
| 1749 retries: number of retries | |
| 1750 | |
| 1751 Returns: | |
| 1752 The name of the file on the host to which the screenshot was saved. | |
| 1753 | |
| 1754 Raises: | |
| 1755 CommandFailedError on failure. | |
| 1756 CommandTimeoutError on timeout. | |
| 1757 DeviceUnreachableError on missing device. | |
| 1758 """ | |
| 1759 if not host_path: | |
| 1760 host_path = os.path.abspath('screenshot-%s.png' % _GetTimeStamp()) | |
| 1761 with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp: | |
| 1762 self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name], | |
| 1763 check_return=True) | |
| 1764 self.PullFile(device_tmp.name, host_path) | |
| 1765 return host_path | |
| 1766 | |
| 1767 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1768 def GetMemoryUsageForPid(self, pid, timeout=None, retries=None): | |
| 1769 """Gets the memory usage for the given PID. | |
| 1770 | |
| 1771 Args: | |
| 1772 pid: PID of the process. | |
| 1773 timeout: timeout in seconds | |
| 1774 retries: number of retries | |
| 1775 | |
| 1776 Returns: | |
| 1777 A dict containing memory usage statistics for the PID. May include: | |
| 1778 Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean, | |
| 1779 Private_Dirty, VmHWM | |
| 1780 | |
| 1781 Raises: | |
| 1782 CommandTimeoutError on timeout. | |
| 1783 """ | |
| 1784 result = collections.defaultdict(int) | |
| 1785 | |
| 1786 try: | |
| 1787 result.update(self._GetMemoryUsageForPidFromSmaps(pid)) | |
| 1788 except device_errors.CommandFailedError: | |
| 1789 logging.exception('Error getting memory usage from smaps') | |
| 1790 | |
| 1791 try: | |
| 1792 result.update(self._GetMemoryUsageForPidFromStatus(pid)) | |
| 1793 except device_errors.CommandFailedError: | |
| 1794 logging.exception('Error getting memory usage from status') | |
| 1795 | |
| 1796 return result | |
| 1797 | |
| 1798 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1799 def DismissCrashDialogIfNeeded(self, timeout=None, retries=None): | |
| 1800 """Dismiss the error/ANR dialog if present. | |
| 1801 | |
| 1802 Returns: Name of the crashed package if a dialog is focused, | |
| 1803 None otherwise. | |
| 1804 """ | |
| 1805 def _FindFocusedWindow(): | |
| 1806 match = None | |
| 1807 # TODO(jbudorick): Try to grep the output on the device instead of using | |
| 1808 # large_output if/when DeviceUtils exposes a public interface for piped | |
| 1809 # shell command handling. | |
| 1810 for line in self.RunShellCommand(['dumpsys', 'window', 'windows'], | |
| 1811 check_return=True, large_output=True): | |
| 1812 match = re.match(_CURRENT_FOCUS_CRASH_RE, line) | |
| 1813 if match: | |
| 1814 break | |
| 1815 return match | |
| 1816 | |
| 1817 match = _FindFocusedWindow() | |
| 1818 if not match: | |
| 1819 return None | |
| 1820 package = match.group(2) | |
| 1821 logging.warning('Trying to dismiss %s dialog for %s' % match.groups()) | |
| 1822 self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) | |
| 1823 self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) | |
| 1824 self.SendKeyEvent(keyevent.KEYCODE_ENTER) | |
| 1825 match = _FindFocusedWindow() | |
| 1826 if match: | |
| 1827 logging.error('Still showing a %s dialog for %s' % match.groups()) | |
| 1828 return package | |
| 1829 | |
| 1830 def _GetMemoryUsageForPidFromSmaps(self, pid): | |
| 1831 SMAPS_COLUMNS = ( | |
| 1832 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean', | |
| 1833 'Private_Dirty') | |
| 1834 | |
| 1835 showmap_out = self._RunPipedShellCommand( | |
| 1836 'showmap %d | grep TOTAL' % int(pid), as_root=True) | |
| 1837 | |
| 1838 split_totals = showmap_out[-1].split() | |
| 1839 if (not split_totals | |
| 1840 or len(split_totals) != 9 | |
| 1841 or split_totals[-1] != 'TOTAL'): | |
| 1842 raise device_errors.CommandFailedError( | |
| 1843 'Invalid output from showmap: %s' % '\n'.join(showmap_out)) | |
| 1844 | |
| 1845 return dict(itertools.izip(SMAPS_COLUMNS, (int(n) for n in split_totals))) | |
| 1846 | |
| 1847 def _GetMemoryUsageForPidFromStatus(self, pid): | |
| 1848 for line in self.ReadFile( | |
| 1849 '/proc/%s/status' % str(pid), as_root=True).splitlines(): | |
| 1850 if line.startswith('VmHWM:'): | |
| 1851 return {'VmHWM': int(line.split()[1])} | |
| 1852 else: | |
| 1853 raise device_errors.CommandFailedError( | |
| 1854 'Could not find memory peak value for pid %s', str(pid)) | |
| 1855 | |
| 1856 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1857 def GetLogcatMonitor(self, timeout=None, retries=None, *args, **kwargs): | |
| 1858 """Returns a new LogcatMonitor associated with this device. | |
| 1859 | |
| 1860 Parameters passed to this function are passed directly to | |
| 1861 |logcat_monitor.LogcatMonitor| and are documented there. | |
| 1862 | |
| 1863 Args: | |
| 1864 timeout: timeout in seconds | |
| 1865 retries: number of retries | |
| 1866 """ | |
| 1867 return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs) | |
| 1868 | |
| 1869 def GetClientCache(self, client_name): | |
| 1870 """Returns client cache.""" | |
| 1871 if client_name not in self._client_caches: | |
| 1872 self._client_caches[client_name] = {} | |
| 1873 return self._client_caches[client_name] | |
| 1874 | |
| 1875 def _ClearCache(self): | |
| 1876 """Clears all caches.""" | |
| 1877 for client in self._client_caches: | |
| 1878 self._client_caches[client].clear() | |
| 1879 self._cache = { | |
| 1880 # Map of packageId -> list of on-device .apk paths | |
| 1881 'package_apk_paths': {}, | |
| 1882 # Map of packageId -> set of on-device .apk checksums | |
| 1883 'package_apk_checksums': {}, | |
| 1884 } | |
| 1885 | |
| 1886 @classmethod | |
| 1887 def parallel(cls, devices, async=False): | |
| 1888 """Creates a Parallelizer to operate over the provided list of devices. | |
| 1889 | |
| 1890 If |devices| is either |None| or an empty list, the Parallelizer will | |
| 1891 operate over all attached devices that have not been blacklisted. | |
| 1892 | |
| 1893 Args: | |
| 1894 devices: A list of either DeviceUtils instances or objects from | |
| 1895 from which DeviceUtils instances can be constructed. If None, | |
| 1896 all attached devices will be used. | |
| 1897 async: If true, returns a Parallelizer that runs operations | |
| 1898 asynchronously. | |
| 1899 | |
| 1900 Returns: | |
| 1901 A Parallelizer operating over |devices|. | |
| 1902 """ | |
| 1903 if not devices: | |
| 1904 raise device_errors.NoDevicesError() | |
| 1905 | |
| 1906 devices = [d if isinstance(d, cls) else cls(d) for d in devices] | |
| 1907 if async: | |
| 1908 return parallelizer.Parallelizer(devices) | |
| 1909 else: | |
| 1910 return parallelizer.SyncParallelizer(devices) | |
| 1911 | |
| 1912 @classmethod | |
| 1913 def HealthyDevices(cls, blacklist=None): | |
| 1914 if not blacklist: | |
| 1915 # TODO(jbudorick): Remove once clients pass in the blacklist. | |
| 1916 blacklist = device_blacklist.Blacklist(device_blacklist.BLACKLIST_JSON) | |
| 1917 | |
| 1918 blacklisted_devices = blacklist.Read() | |
| 1919 def blacklisted(adb): | |
| 1920 if adb.GetDeviceSerial() in blacklisted_devices: | |
| 1921 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) | |
| 1922 return True | |
| 1923 return False | |
| 1924 | |
| 1925 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() | |
| 1926 if not blacklisted(adb)] | |
| 1927 | |
| 1928 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 1929 def RestartAdbd(self, timeout=None, retries=None): | |
| 1930 logging.info('Restarting adbd on device.') | |
| 1931 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: | |
| 1932 self.WriteFile(script.name, _RESTART_ADBD_SCRIPT) | |
| 1933 self.RunShellCommand(['source', script.name], as_root=True) | |
| 1934 self.adb.WaitForDevice() | |
| OLD | NEW |