1# Copyright 2014 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 a variety of device interactions based on adb. 6 7Eventually, this will be based on adb_wrapper. 8""" 9# pylint: disable=unused-argument 10 11import calendar 12import collections 13import itertools 14import json 15import logging 16import multiprocessing 17import os 18import posixpath 19import re 20import shutil 21import stat 22import tempfile 23import time 24import threading 25import uuid 26import zipfile 27 28from devil import base_error 29from devil import devil_env 30from devil.utils import cmd_helper 31from devil.android import apk_helper 32from devil.android import device_signal 33from devil.android import decorators 34from devil.android import device_errors 35from devil.android import device_temp_file 36from devil.android import install_commands 37from devil.android import logcat_monitor 38from devil.android import md5sum 39from devil.android.constants import chrome 40from devil.android.sdk import adb_wrapper 41from devil.android.sdk import gce_adb_wrapper 42from devil.android.sdk import intent 43from devil.android.sdk import keyevent 44from devil.android.sdk import split_select 45from devil.android.sdk import version_codes 46from devil.utils import host_utils 47from devil.utils import parallelizer 48from devil.utils import reraiser_thread 49from devil.utils import timeout_retry 50from devil.utils import zip_utils 51 52_DEFAULT_TIMEOUT = 30 53_DEFAULT_RETRIES = 3 54 55# A sentinel object for default values 56# TODO(jbudorick,perezju): revisit how default values are handled by 57# the timeout_retry decorators. 58DEFAULT = object() 59 60_RESTART_ADBD_SCRIPT = """ 61 trap '' HUP 62 trap '' TERM 63 trap '' PIPE 64 function restart() { 65 stop adbd 66 start adbd 67 } 68 restart & 69""" 70 71# Not all permissions can be set. 72_PERMISSIONS_BLACKLIST = [ 73 'android.permission.ACCESS_MOCK_LOCATION', 74 'android.permission.ACCESS_NETWORK_STATE', 75 'android.permission.ACCESS_WIFI_STATE', 76 'android.permission.AUTHENTICATE_ACCOUNTS', 77 'android.permission.BLUETOOTH', 78 'android.permission.BLUETOOTH_ADMIN', 79 'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION', 80 'android.permission.INTERNET', 81 'android.permission.MANAGE_ACCOUNTS', 82 'android.permission.MODIFY_AUDIO_SETTINGS', 83 'android.permission.NFC', 84 'android.permission.READ_SYNC_SETTINGS', 85 'android.permission.READ_SYNC_STATS', 86 'android.permission.RECEIVE_BOOT_COMPLETED', 87 'android.permission.RECORD_VIDEO', 88 'android.permission.RUN_INSTRUMENTATION', 89 'android.permission.USE_CREDENTIALS', 90 'android.permission.VIBRATE', 91 'android.permission.WAKE_LOCK', 92 'android.permission.WRITE_SYNC_SETTINGS', 93 'com.android.browser.permission.READ_HISTORY_BOOKMARKS', 94 'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS', 95 'com.android.launcher.permission.INSTALL_SHORTCUT', 96 'com.chrome.permission.DEVICE_EXTRAS', 97 'com.google.android.c2dm.permission.RECEIVE', 98 'com.google.android.providers.gsf.permission.READ_GSERVICES', 99 'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER', 100] 101for package_info in chrome.PACKAGE_INFO.itervalues(): 102 _PERMISSIONS_BLACKLIST.extend([ 103 '%s.permission.C2D_MESSAGE' % package_info.package, 104 '%s.permission.READ_WRITE_BOOKMARK_FOLDERS' % package_info.package, 105 '%s.TOS_ACKED' % package_info.package]) 106 107_CURRENT_FOCUS_CRASH_RE = re.compile( 108 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') 109 110_GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]') 111_IPV4_ADDRESS_RE = re.compile(r'([0-9]{1,3}\.){3}[0-9]{1,3}\:[0-9]{4,5}') 112 113# Regex to parse the long (-l) output of 'ls' command, c.f. 114# https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446 115_LONG_LS_OUTPUT_RE = re.compile( 116 r'(?P<st_mode>[\w-]{10})\s+' # File permissions 117 r'(?:(?P<st_nlink>\d+)\s+)?' # Number of links (optional) 118 r'(?P<st_owner>\w+)\s+' # Name of owner 119 r'(?P<st_group>\w+)\s+' # Group of owner 120 r'(?:' # Either ... 121 r'(?P<st_rdev_major>\d+),\s+' # Device major, and 122 r'(?P<st_rdev_minor>\d+)\s+' # Device minor 123 r'|' # .. or 124 r'(?P<st_size>\d+)\s+' # Size in bytes 125 r')?' # .. or nothing 126 r'(?P<st_mtime>\d{4}-\d\d-\d\d \d\d:\d\d)\s+' # Modification date/time 127 r'(?P<filename>.+?)' # File name 128 r'(?: -> (?P<symbolic_link_to>.+))?' # Symbolic link (optional) 129 r'$' # End of string 130) 131_LS_DATE_FORMAT = '%Y-%m-%d %H:%M' 132_FILE_MODE_RE = re.compile(r'[dbclps-](?:[r-][w-][xSs-]){2}[r-][w-][xTt-]$') 133_FILE_MODE_KIND = { 134 'd': stat.S_IFDIR, 'b': stat.S_IFBLK, 'c': stat.S_IFCHR, 135 'l': stat.S_IFLNK, 'p': stat.S_IFIFO, 's': stat.S_IFSOCK, 136 '-': stat.S_IFREG} 137_FILE_MODE_PERMS = [ 138 stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, 139 stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, 140 stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH, 141] 142_FILE_MODE_SPECIAL = [ 143 ('s', stat.S_ISUID), 144 ('s', stat.S_ISGID), 145 ('t', stat.S_ISVTX), 146] 147 148@decorators.WithExplicitTimeoutAndRetries( 149 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 150def GetAVDs(): 151 """Returns a list of Android Virtual Devices. 152 153 Returns: 154 A list containing the configured AVDs. 155 """ 156 lines = cmd_helper.GetCmdOutput([ 157 os.path.join(devil_env.config.LocalPath('android_sdk'), 158 'tools', 'android'), 159 'list', 'avd']).splitlines() 160 avds = [] 161 for line in lines: 162 if 'Name:' not in line: 163 continue 164 key, value = (s.strip() for s in line.split(':', 1)) 165 if key == 'Name': 166 avds.append(value) 167 return avds 168 169 170@decorators.WithExplicitTimeoutAndRetries( 171 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 172def RestartServer(): 173 """Restarts the adb server. 174 175 Raises: 176 CommandFailedError if we fail to kill or restart the server. 177 """ 178 def adb_killed(): 179 return not adb_wrapper.AdbWrapper.IsServerOnline() 180 181 def adb_started(): 182 return adb_wrapper.AdbWrapper.IsServerOnline() 183 184 adb_wrapper.AdbWrapper.KillServer() 185 if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5): 186 # TODO(perezju): raise an exception after fixng http://crbug.com/442319 187 logging.warning('Failed to kill adb server') 188 adb_wrapper.AdbWrapper.StartServer() 189 if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5): 190 raise device_errors.CommandFailedError('Failed to start adb server') 191 192 193def _ParseModeString(mode_str): 194 """Parse a mode string, e.g. 'drwxrwxrwx', into a st_mode value. 195 196 Effectively the reverse of |mode_to_string| in, e.g.: 197 https://github.com/landley/toybox/blob/master/lib/lib.c#L896 198 """ 199 if not _FILE_MODE_RE.match(mode_str): 200 raise ValueError('Unexpected file mode %r', mode_str) 201 mode = _FILE_MODE_KIND[mode_str[0]] 202 for c, flag in zip(mode_str[1:], _FILE_MODE_PERMS): 203 if c != '-' and c.islower(): 204 mode |= flag 205 for c, (t, flag) in zip(mode_str[3::3], _FILE_MODE_SPECIAL): 206 if c.lower() == t: 207 mode |= flag 208 return mode 209 210 211def _GetTimeStamp(): 212 """Return a basic ISO 8601 time stamp with the current local time.""" 213 return time.strftime('%Y%m%dT%H%M%S', time.localtime()) 214 215 216def _JoinLines(lines): 217 # makes sure that the last line is also terminated, and is more memory 218 # efficient than first appending an end-line to each line and then joining 219 # all of them together. 220 return ''.join(s for line in lines for s in (line, '\n')) 221 222 223def _IsGceInstance(serial): 224 return _IPV4_ADDRESS_RE.match(serial) 225 226 227def _CreateAdbWrapper(device): 228 if _IsGceInstance(str(device)): 229 return gce_adb_wrapper.GceAdbWrapper(str(device)) 230 else: 231 if isinstance(device, adb_wrapper.AdbWrapper): 232 return device 233 else: 234 return adb_wrapper.AdbWrapper(device) 235 236 237def _FormatPartialOutputError(output): 238 lines = output.splitlines() if isinstance(output, basestring) else output 239 message = ['Partial output found:'] 240 if len(lines) > 11: 241 message.extend('- %s' % line for line in lines[:5]) 242 message.extend('<snip>') 243 message.extend('- %s' % line for line in lines[-5:]) 244 else: 245 message.extend('- %s' % line for line in lines) 246 return '\n'.join(message) 247 248 249class DeviceUtils(object): 250 251 _MAX_ADB_COMMAND_LENGTH = 512 252 _MAX_ADB_OUTPUT_LENGTH = 32768 253 _LAUNCHER_FOCUSED_RE = re.compile( 254 r'\s*mCurrentFocus.*(Launcher|launcher).*') 255 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') 256 257 LOCAL_PROPERTIES_PATH = posixpath.join('/', 'data', 'local.prop') 258 259 # Property in /data/local.prop that controls Java assertions. 260 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' 261 262 def __init__(self, device, enable_device_files_cache=False, 263 default_timeout=_DEFAULT_TIMEOUT, 264 default_retries=_DEFAULT_RETRIES): 265 """DeviceUtils constructor. 266 267 Args: 268 device: Either a device serial, an existing AdbWrapper instance, or an 269 an existing AndroidCommands instance. 270 enable_device_files_cache: For PushChangedFiles(), cache checksums of 271 pushed files rather than recomputing them on a subsequent call. 272 default_timeout: An integer containing the default number of seconds to 273 wait for an operation to complete if no explicit value is provided. 274 default_retries: An integer containing the default number or times an 275 operation should be retried on failure if no explicit value is provided. 276 """ 277 self.adb = None 278 if isinstance(device, basestring): 279 self.adb = _CreateAdbWrapper(device) 280 elif isinstance(device, adb_wrapper.AdbWrapper): 281 self.adb = device 282 else: 283 raise ValueError('Unsupported device value: %r' % device) 284 self._commands_installed = None 285 self._default_timeout = default_timeout 286 self._default_retries = default_retries 287 self._enable_device_files_cache = enable_device_files_cache 288 self._cache = {} 289 self._client_caches = {} 290 self._cache_lock = threading.RLock() 291 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) 292 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) 293 294 self._ClearCache() 295 296 def __eq__(self, other): 297 """Checks whether |other| refers to the same device as |self|. 298 299 Args: 300 other: The object to compare to. This can be a basestring, an instance 301 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. 302 Returns: 303 Whether |other| refers to the same device as |self|. 304 """ 305 return self.adb.GetDeviceSerial() == str(other) 306 307 def __lt__(self, other): 308 """Compares two instances of DeviceUtils. 309 310 This merely compares their serial numbers. 311 312 Args: 313 other: The instance of DeviceUtils to compare to. 314 Returns: 315 Whether |self| is less than |other|. 316 """ 317 return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial() 318 319 def __str__(self): 320 """Returns the device serial.""" 321 return self.adb.GetDeviceSerial() 322 323 @decorators.WithTimeoutAndRetriesFromInstance() 324 def IsOnline(self, timeout=None, retries=None): 325 """Checks whether the device is online. 326 327 Args: 328 timeout: timeout in seconds 329 retries: number of retries 330 331 Returns: 332 True if the device is online, False otherwise. 333 334 Raises: 335 CommandTimeoutError on timeout. 336 """ 337 try: 338 return self.adb.GetState() == 'device' 339 except base_error.BaseError as exc: 340 logging.info('Failed to get state: %s', exc) 341 return False 342 343 @decorators.WithTimeoutAndRetriesFromInstance() 344 def HasRoot(self, timeout=None, retries=None): 345 """Checks whether or not adbd has root privileges. 346 347 Args: 348 timeout: timeout in seconds 349 retries: number of retries 350 351 Returns: 352 True if adbd has root privileges, False otherwise. 353 354 Raises: 355 CommandTimeoutError on timeout. 356 DeviceUnreachableError on missing device. 357 """ 358 try: 359 self.RunShellCommand(['ls', '/root'], check_return=True) 360 return True 361 except device_errors.AdbCommandFailedError: 362 return False 363 364 def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT): 365 """Checks whether 'su' is needed to access protected resources. 366 367 Args: 368 timeout: timeout in seconds 369 retries: number of retries 370 371 Returns: 372 True if 'su' is available on the device and is needed to to access 373 protected resources; False otherwise if either 'su' is not available 374 (e.g. because the device has a user build), or not needed (because adbd 375 already has root privileges). 376 377 Raises: 378 CommandTimeoutError on timeout. 379 DeviceUnreachableError on missing device. 380 """ 381 if 'needs_su' not in self._cache: 382 try: 383 self.RunShellCommand( 384 '%s && ! ls /root' % self._Su('ls /root'), check_return=True, 385 timeout=self._default_timeout if timeout is DEFAULT else timeout, 386 retries=self._default_retries if retries is DEFAULT else retries) 387 self._cache['needs_su'] = True 388 except device_errors.AdbCommandFailedError: 389 self._cache['needs_su'] = False 390 return self._cache['needs_su'] 391 392 def _Su(self, command): 393 if self.build_version_sdk >= version_codes.MARSHMALLOW: 394 return 'su 0 %s' % command 395 return 'su -c %s' % command 396 397 @decorators.WithTimeoutAndRetriesFromInstance() 398 def EnableRoot(self, timeout=None, retries=None): 399 """Restarts adbd with root privileges. 400 401 Args: 402 timeout: timeout in seconds 403 retries: number of retries 404 405 Raises: 406 CommandFailedError if root could not be enabled. 407 CommandTimeoutError on timeout. 408 """ 409 if self.IsUserBuild(): 410 raise device_errors.CommandFailedError( 411 'Cannot enable root in user builds.', str(self)) 412 if 'needs_su' in self._cache: 413 del self._cache['needs_su'] 414 self.adb.Root() 415 self.WaitUntilFullyBooted() 416 417 @decorators.WithTimeoutAndRetriesFromInstance() 418 def IsUserBuild(self, timeout=None, retries=None): 419 """Checks whether or not the device is running a user build. 420 421 Args: 422 timeout: timeout in seconds 423 retries: number of retries 424 425 Returns: 426 True if the device is running a user build, False otherwise (i.e. if 427 it's running a userdebug build). 428 429 Raises: 430 CommandTimeoutError on timeout. 431 DeviceUnreachableError on missing device. 432 """ 433 return self.build_type == 'user' 434 435 @decorators.WithTimeoutAndRetriesFromInstance() 436 def GetExternalStoragePath(self, timeout=None, retries=None): 437 """Get the device's path to its SD card. 438 439 Args: 440 timeout: timeout in seconds 441 retries: number of retries 442 443 Returns: 444 The device's path to its SD card. 445 446 Raises: 447 CommandFailedError if the external storage path could not be determined. 448 CommandTimeoutError on timeout. 449 DeviceUnreachableError on missing device. 450 """ 451 self._EnsureCacheInitialized() 452 if not self._cache['external_storage']: 453 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', 454 str(self)) 455 return self._cache['external_storage'] 456 457 @decorators.WithTimeoutAndRetriesFromInstance() 458 def GetApplicationPaths(self, package, timeout=None, retries=None): 459 """Get the paths of the installed apks on the device for the given package. 460 461 Args: 462 package: Name of the package. 463 464 Returns: 465 List of paths to the apks on the device for the given package. 466 """ 467 return self._GetApplicationPathsInternal(package) 468 469 def _GetApplicationPathsInternal(self, package, skip_cache=False): 470 cached_result = self._cache['package_apk_paths'].get(package) 471 if cached_result is not None and not skip_cache: 472 if package in self._cache['package_apk_paths_to_verify']: 473 self._cache['package_apk_paths_to_verify'].remove(package) 474 # Don't verify an app that is not thought to be installed. We are 475 # concerned only with apps we think are installed having been 476 # uninstalled manually. 477 if cached_result and not self.PathExists(cached_result): 478 cached_result = None 479 self._cache['package_apk_checksums'].pop(package, 0) 480 if cached_result is not None: 481 return list(cached_result) 482 # 'pm path' is liable to incorrectly exit with a nonzero number starting 483 # in Lollipop. 484 # TODO(jbudorick): Check if this is fixed as new Android versions are 485 # released to put an upper bound on this. 486 should_check_return = (self.build_version_sdk < version_codes.LOLLIPOP) 487 output = self.RunShellCommand( 488 ['pm', 'path', package], check_return=should_check_return) 489 apks = [] 490 for line in output: 491 if not line.startswith('package:'): 492 # KitKat on x86 may show following warnings that is safe to ignore. 493 if (line.startswith('WARNING: linker: libdvm.so has text relocations.') 494 and version_codes.KITKAT <= self.build_version_sdk 495 and self.build_version_sdk <= version_codes.KITKAT_WATCH 496 and self.product_cpu_abi == 'x86'): 497 continue 498 raise device_errors.CommandFailedError( 499 'pm path returned: %r' % '\n'.join(output), str(self)) 500 apks.append(line[len('package:'):]) 501 self._cache['package_apk_paths'][package] = list(apks) 502 return apks 503 504 @decorators.WithTimeoutAndRetriesFromInstance() 505 def GetApplicationVersion(self, package, timeout=None, retries=None): 506 """Get the version name of a package installed on the device. 507 508 Args: 509 package: Name of the package. 510 511 Returns: 512 A string with the version name or None if the package is not found 513 on the device. 514 """ 515 output = self.RunShellCommand( 516 ['dumpsys', 'package', package], check_return=True) 517 if not output: 518 return None 519 for line in output: 520 line = line.strip() 521 if line.startswith('versionName='): 522 return line[len('versionName='):] 523 raise device_errors.CommandFailedError( 524 'Version name for %s not found on dumpsys output' % package, str(self)) 525 526 @decorators.WithTimeoutAndRetriesFromInstance() 527 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): 528 """Get the data directory on the device for the given package. 529 530 Args: 531 package: Name of the package. 532 533 Returns: 534 The package's data directory, or None if the package doesn't exist on the 535 device. 536 """ 537 try: 538 output = self._RunPipedShellCommand( 539 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package)) 540 for line in output: 541 _, _, dataDir = line.partition('dataDir=') 542 if dataDir: 543 return dataDir 544 except device_errors.CommandFailedError: 545 logging.exception('Could not find data directory for %s', package) 546 return None 547 548 @decorators.WithTimeoutAndRetriesFromInstance() 549 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): 550 """Wait for the device to fully boot. 551 552 This means waiting for the device to boot, the package manager to be 553 available, and the SD card to be ready. It can optionally mean waiting 554 for wifi to come up, too. 555 556 Args: 557 wifi: A boolean indicating if we should wait for wifi to come up or not. 558 timeout: timeout in seconds 559 retries: number of retries 560 561 Raises: 562 CommandFailedError on failure. 563 CommandTimeoutError if one of the component waits times out. 564 DeviceUnreachableError if the device becomes unresponsive. 565 """ 566 def sd_card_ready(): 567 try: 568 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], 569 check_return=True) 570 return True 571 except device_errors.AdbCommandFailedError: 572 return False 573 574 def pm_ready(): 575 try: 576 return self._GetApplicationPathsInternal('android', skip_cache=True) 577 except device_errors.CommandFailedError: 578 return False 579 580 def boot_completed(): 581 try: 582 return self.GetProp('sys.boot_completed', cache=False) == '1' 583 except device_errors.CommandFailedError: 584 return False 585 586 def wifi_enabled(): 587 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], 588 check_return=False) 589 590 self.adb.WaitForDevice() 591 timeout_retry.WaitFor(sd_card_ready) 592 timeout_retry.WaitFor(pm_ready) 593 timeout_retry.WaitFor(boot_completed) 594 if wifi: 595 timeout_retry.WaitFor(wifi_enabled) 596 597 REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT 598 599 @decorators.WithTimeoutAndRetriesFromInstance( 600 min_default_timeout=REBOOT_DEFAULT_TIMEOUT) 601 def Reboot(self, block=True, wifi=False, timeout=None, retries=None): 602 """Reboot the device. 603 604 Args: 605 block: A boolean indicating if we should wait for the reboot to complete. 606 wifi: A boolean indicating if we should wait for wifi to be enabled after 607 the reboot. The option has no effect unless |block| is also True. 608 timeout: timeout in seconds 609 retries: number of retries 610 611 Raises: 612 CommandTimeoutError on timeout. 613 DeviceUnreachableError on missing device. 614 """ 615 def device_offline(): 616 return not self.IsOnline() 617 618 self.adb.Reboot() 619 self._ClearCache() 620 timeout_retry.WaitFor(device_offline, wait_period=1) 621 if block: 622 self.WaitUntilFullyBooted(wifi=wifi) 623 624 INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT 625 626 @decorators.WithTimeoutAndRetriesFromInstance( 627 min_default_timeout=INSTALL_DEFAULT_TIMEOUT) 628 def Install(self, apk, allow_downgrade=False, reinstall=False, 629 permissions=None, timeout=None, retries=None): 630 """Install an APK. 631 632 Noop if an identical APK is already installed. 633 634 Args: 635 apk: An ApkHelper instance or string containing the path to the APK. 636 allow_downgrade: A boolean indicating if we should allow downgrades. 637 reinstall: A boolean indicating if we should keep any existing app data. 638 permissions: Set of permissions to set. If not set, finds permissions with 639 apk helper. To set no permissions, pass []. 640 timeout: timeout in seconds 641 retries: number of retries 642 643 Raises: 644 CommandFailedError if the installation fails. 645 CommandTimeoutError if the installation times out. 646 DeviceUnreachableError on missing device. 647 """ 648 self._InstallInternal(apk, None, allow_downgrade=allow_downgrade, 649 reinstall=reinstall, permissions=permissions) 650 651 @decorators.WithTimeoutAndRetriesFromInstance( 652 min_default_timeout=INSTALL_DEFAULT_TIMEOUT) 653 def InstallSplitApk(self, base_apk, split_apks, allow_downgrade=False, 654 reinstall=False, allow_cached_props=False, 655 permissions=None, timeout=None, retries=None): 656 """Install a split APK. 657 658 Noop if all of the APK splits are already installed. 659 660 Args: 661 base_apk: An ApkHelper instance or string containing the path to the base 662 APK. 663 split_apks: A list of strings of paths of all of the APK splits. 664 allow_downgrade: A boolean indicating if we should allow downgrades. 665 reinstall: A boolean indicating if we should keep any existing app data. 666 allow_cached_props: Whether to use cached values for device properties. 667 permissions: Set of permissions to set. If not set, finds permissions with 668 apk helper. To set no permissions, pass []. 669 timeout: timeout in seconds 670 retries: number of retries 671 672 Raises: 673 CommandFailedError if the installation fails. 674 CommandTimeoutError if the installation times out. 675 DeviceUnreachableError on missing device. 676 DeviceVersionError if device SDK is less than Android L. 677 """ 678 self._InstallInternal(base_apk, split_apks, reinstall=reinstall, 679 allow_cached_props=allow_cached_props, 680 permissions=permissions, 681 allow_downgrade=allow_downgrade) 682 683 def _InstallInternal(self, base_apk, split_apks, allow_downgrade=False, 684 reinstall=False, allow_cached_props=False, 685 permissions=None): 686 if split_apks: 687 self._CheckSdkLevel(version_codes.LOLLIPOP) 688 689 base_apk = apk_helper.ToHelper(base_apk) 690 691 all_apks = [base_apk.path] 692 if split_apks: 693 all_apks += split_select.SelectSplits( 694 self, base_apk.path, split_apks, allow_cached_props=allow_cached_props) 695 if len(all_apks) == 1: 696 logging.warning('split-select did not select any from %s', split_apks) 697 698 package_name = base_apk.GetPackageName() 699 device_apk_paths = self._GetApplicationPathsInternal(package_name) 700 701 apks_to_install = None 702 host_checksums = None 703 if not device_apk_paths: 704 apks_to_install = all_apks 705 elif len(device_apk_paths) > 1 and not split_apks: 706 logging.warning( 707 'Installing non-split APK when split APK was previously installed') 708 apks_to_install = all_apks 709 elif len(device_apk_paths) == 1 and split_apks: 710 logging.warning( 711 'Installing split APK when non-split APK was previously installed') 712 apks_to_install = all_apks 713 else: 714 try: 715 apks_to_install, host_checksums = ( 716 self._ComputeStaleApks(package_name, all_apks)) 717 except EnvironmentError as e: 718 logging.warning('Error calculating md5: %s', e) 719 apks_to_install, host_checksums = all_apks, None 720 if apks_to_install and not reinstall: 721 self.Uninstall(package_name) 722 apks_to_install = all_apks 723 724 if apks_to_install: 725 # Assume that we won't know the resulting device state. 726 self._cache['package_apk_paths'].pop(package_name, 0) 727 self._cache['package_apk_checksums'].pop(package_name, 0) 728 if split_apks: 729 partial = package_name if len(apks_to_install) < len(all_apks) else None 730 self.adb.InstallMultiple( 731 apks_to_install, partial=partial, reinstall=reinstall, 732 allow_downgrade=allow_downgrade) 733 else: 734 self.adb.Install( 735 base_apk.path, reinstall=reinstall, allow_downgrade=allow_downgrade) 736 if (permissions is None 737 and self.build_version_sdk >= version_codes.MARSHMALLOW): 738 permissions = base_apk.GetPermissions() 739 self.GrantPermissions(package_name, permissions) 740 # Upon success, we know the device checksums, but not their paths. 741 if host_checksums is not None: 742 self._cache['package_apk_checksums'][package_name] = host_checksums 743 else: 744 # Running adb install terminates running instances of the app, so to be 745 # consistent, we explicitly terminate it when skipping the install. 746 self.ForceStop(package_name) 747 748 @decorators.WithTimeoutAndRetriesFromInstance() 749 def Uninstall(self, package_name, keep_data=False, timeout=None, 750 retries=None): 751 """Remove the app |package_name| from the device. 752 753 This is a no-op if the app is not already installed. 754 755 Args: 756 package_name: The package to uninstall. 757 keep_data: (optional) Whether to keep the data and cache directories. 758 timeout: Timeout in seconds. 759 retries: Number of retries. 760 761 Raises: 762 CommandFailedError if the uninstallation fails. 763 CommandTimeoutError if the uninstallation times out. 764 DeviceUnreachableError on missing device. 765 """ 766 installed = self._GetApplicationPathsInternal(package_name) 767 if not installed: 768 return 769 try: 770 self.adb.Uninstall(package_name, keep_data) 771 self._cache['package_apk_paths'][package_name] = [] 772 self._cache['package_apk_checksums'][package_name] = set() 773 except: 774 # Clear cache since we can't be sure of the state. 775 self._cache['package_apk_paths'].pop(package_name, 0) 776 self._cache['package_apk_checksums'].pop(package_name, 0) 777 raise 778 779 def _CheckSdkLevel(self, required_sdk_level): 780 """Raises an exception if the device does not have the required SDK level. 781 """ 782 if self.build_version_sdk < required_sdk_level: 783 raise device_errors.DeviceVersionError( 784 ('Requires SDK level %s, device is SDK level %s' % 785 (required_sdk_level, self.build_version_sdk)), 786 device_serial=self.adb.GetDeviceSerial()) 787 788 @decorators.WithTimeoutAndRetriesFromInstance() 789 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, 790 as_root=False, single_line=False, large_output=False, 791 raw_output=False, timeout=None, retries=None): 792 """Run an ADB shell command. 793 794 The command to run |cmd| should be a sequence of program arguments or else 795 a single string. 796 797 When |cmd| is a sequence, it is assumed to contain the name of the command 798 to run followed by its arguments. In this case, arguments are passed to the 799 command exactly as given, without any further processing by the shell. This 800 allows to easily pass arguments containing spaces or special characters 801 without having to worry about getting quoting right. Whenever possible, it 802 is recomended to pass |cmd| as a sequence. 803 804 When |cmd| is given as a string, it will be interpreted and run by the 805 shell on the device. 806 807 This behaviour is consistent with that of command runners in cmd_helper as 808 well as Python's own subprocess.Popen. 809 810 TODO(perezju) Change the default of |check_return| to True when callers 811 have switched to the new behaviour. 812 813 Args: 814 cmd: A string with the full command to run on the device, or a sequence 815 containing the command and its arguments. 816 check_return: A boolean indicating whether or not the return code should 817 be checked. 818 cwd: The device directory in which the command should be run. 819 env: The environment variables with which the command should be run. 820 as_root: A boolean indicating whether the shell command should be run 821 with root privileges. 822 single_line: A boolean indicating if only a single line of output is 823 expected. 824 large_output: Uses a work-around for large shell command output. Without 825 this large output will be truncated. 826 raw_output: Whether to only return the raw output 827 (no splitting into lines). 828 timeout: timeout in seconds 829 retries: number of retries 830 831 Returns: 832 If single_line is False, the output of the command as a list of lines, 833 otherwise, a string with the unique line of output emmited by the command 834 (with the optional newline at the end stripped). 835 836 Raises: 837 AdbCommandFailedError if check_return is True and the exit code of 838 the command run on the device is non-zero. 839 CommandFailedError if single_line is True but the output contains two or 840 more lines. 841 CommandTimeoutError on timeout. 842 DeviceUnreachableError on missing device. 843 """ 844 def env_quote(key, value): 845 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): 846 raise KeyError('Invalid shell variable name %r' % key) 847 # using double quotes here to allow interpolation of shell variables 848 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) 849 850 def run(cmd): 851 return self.adb.Shell(cmd) 852 853 def handle_check_return(cmd): 854 try: 855 return run(cmd) 856 except device_errors.AdbCommandFailedError as exc: 857 if check_return: 858 raise 859 else: 860 return exc.output 861 862 def handle_large_command(cmd): 863 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH: 864 return handle_check_return(cmd) 865 else: 866 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: 867 self._WriteFileWithPush(script.name, cmd) 868 logging.info('Large shell command will be run from file: %s ...', 869 cmd[:self._MAX_ADB_COMMAND_LENGTH]) 870 return handle_check_return('sh %s' % script.name_quoted) 871 872 def handle_large_output(cmd, large_output_mode): 873 if large_output_mode: 874 with device_temp_file.DeviceTempFile(self.adb) as large_output_file: 875 cmd = '( %s )>%s' % (cmd, large_output_file.name) 876 logging.debug('Large output mode enabled. Will write output to ' 877 'device and read results from file.') 878 handle_large_command(cmd) 879 return self.ReadFile(large_output_file.name, force_pull=True) 880 else: 881 try: 882 return handle_large_command(cmd) 883 except device_errors.AdbCommandFailedError as exc: 884 if exc.status is None: 885 logging.error(_FormatPartialOutputError(exc.output)) 886 logging.warning('Attempting to run in large_output mode.') 887 logging.warning('Use RunShellCommand(..., large_output=True) for ' 888 'shell commands that expect a lot of output.') 889 return handle_large_output(cmd, True) 890 else: 891 raise 892 893 if not isinstance(cmd, basestring): 894 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) 895 if env: 896 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) 897 cmd = '%s %s' % (env, cmd) 898 if cwd: 899 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) 900 if as_root and self.NeedsSU(): 901 # "su -c sh -c" allows using shell features in |cmd| 902 cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd)) 903 904 output = handle_large_output(cmd, large_output) 905 906 if raw_output: 907 return output 908 909 output = output.splitlines() 910 if single_line: 911 if not output: 912 return '' 913 elif len(output) == 1: 914 return output[0] 915 else: 916 msg = 'one line of output was expected, but got: %s' 917 raise device_errors.CommandFailedError(msg % output, str(self)) 918 else: 919 return output 920 921 def _RunPipedShellCommand(self, script, **kwargs): 922 PIPESTATUS_LEADER = 'PIPESTATUS: ' 923 924 script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER 925 kwargs['check_return'] = True 926 output = self.RunShellCommand(script, **kwargs) 927 pipestatus_line = output[-1] 928 929 if not pipestatus_line.startswith(PIPESTATUS_LEADER): 930 logging.error('Pipe exit statuses of shell script missing.') 931 raise device_errors.AdbShellCommandFailedError( 932 script, output, status=None, 933 device_serial=self.adb.GetDeviceSerial()) 934 935 output = output[:-1] 936 statuses = [ 937 int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()] 938 if any(statuses): 939 raise device_errors.AdbShellCommandFailedError( 940 script, output, status=statuses, 941 device_serial=self.adb.GetDeviceSerial()) 942 return output 943 944 @decorators.WithTimeoutAndRetriesFromInstance() 945 def KillAll(self, process_name, exact=False, signum=device_signal.SIGKILL, 946 as_root=False, blocking=False, quiet=False, 947 timeout=None, retries=None): 948 """Kill all processes with the given name on the device. 949 950 Args: 951 process_name: A string containing the name of the process to kill. 952 exact: A boolean indicating whether to kill all processes matching 953 the string |process_name| exactly, or all of those which contain 954 |process_name| as a substring. Defaults to False. 955 signum: An integer containing the signal number to send to kill. Defaults 956 to SIGKILL (9). 957 as_root: A boolean indicating whether the kill should be executed with 958 root privileges. 959 blocking: A boolean indicating whether we should wait until all processes 960 with the given |process_name| are dead. 961 quiet: A boolean indicating whether to ignore the fact that no processes 962 to kill were found. 963 timeout: timeout in seconds 964 retries: number of retries 965 966 Returns: 967 The number of processes attempted to kill. 968 969 Raises: 970 CommandFailedError if no process was killed and |quiet| is False. 971 CommandTimeoutError on timeout. 972 DeviceUnreachableError on missing device. 973 """ 974 procs_pids = self.GetPids(process_name) 975 if exact: 976 procs_pids = {process_name: procs_pids.get(process_name, [])} 977 pids = set(itertools.chain(*procs_pids.values())) 978 if not pids: 979 if quiet: 980 return 0 981 else: 982 raise device_errors.CommandFailedError( 983 'No process "%s"' % process_name, str(self)) 984 985 logging.info( 986 'KillAll(%r, ...) attempting to kill the following:', process_name) 987 for name, ids in procs_pids.iteritems(): 988 for i in ids: 989 logging.info(' %05s %s', str(i), name) 990 991 cmd = ['kill', '-%d' % signum] + sorted(pids) 992 self.RunShellCommand(cmd, as_root=as_root, check_return=True) 993 994 def all_pids_killed(): 995 procs_pids_remain = self.GetPids(process_name) 996 return not pids.intersection(itertools.chain(*procs_pids_remain.values())) 997 998 if blocking: 999 timeout_retry.WaitFor(all_pids_killed, wait_period=0.1) 1000 1001 return len(pids) 1002 1003 @decorators.WithTimeoutAndRetriesFromInstance() 1004 def StartActivity(self, intent_obj, blocking=False, trace_file_name=None, 1005 force_stop=False, timeout=None, retries=None): 1006 """Start package's activity on the device. 1007 1008 Args: 1009 intent_obj: An Intent object to send. 1010 blocking: A boolean indicating whether we should wait for the activity to 1011 finish launching. 1012 trace_file_name: If present, a string that both indicates that we want to 1013 profile the activity and contains the path to which the 1014 trace should be saved. 1015 force_stop: A boolean indicating whether we should stop the activity 1016 before starting it. 1017 timeout: timeout in seconds 1018 retries: number of retries 1019 1020 Raises: 1021 CommandFailedError if the activity could not be started. 1022 CommandTimeoutError on timeout. 1023 DeviceUnreachableError on missing device. 1024 """ 1025 cmd = ['am', 'start'] 1026 if blocking: 1027 cmd.append('-W') 1028 if trace_file_name: 1029 cmd.extend(['--start-profiler', trace_file_name]) 1030 if force_stop: 1031 cmd.append('-S') 1032 cmd.extend(intent_obj.am_args) 1033 for line in self.RunShellCommand(cmd, check_return=True): 1034 if line.startswith('Error:'): 1035 raise device_errors.CommandFailedError(line, str(self)) 1036 1037 @decorators.WithTimeoutAndRetriesFromInstance() 1038 def StartInstrumentation(self, component, finish=True, raw=False, 1039 extras=None, timeout=None, retries=None): 1040 if extras is None: 1041 extras = {} 1042 1043 cmd = ['am', 'instrument'] 1044 if finish: 1045 cmd.append('-w') 1046 if raw: 1047 cmd.append('-r') 1048 for k, v in extras.iteritems(): 1049 cmd.extend(['-e', str(k), str(v)]) 1050 cmd.append(component) 1051 1052 # Store the package name in a shell variable to help the command stay under 1053 # the _MAX_ADB_COMMAND_LENGTH limit. 1054 package = component.split('/')[0] 1055 shell_snippet = 'p=%s;%s' % (package, 1056 cmd_helper.ShrinkToSnippet(cmd, 'p', package)) 1057 return self.RunShellCommand(shell_snippet, check_return=True, 1058 large_output=True) 1059 1060 @decorators.WithTimeoutAndRetriesFromInstance() 1061 def BroadcastIntent(self, intent_obj, timeout=None, retries=None): 1062 """Send a broadcast intent. 1063 1064 Args: 1065 intent: An Intent to broadcast. 1066 timeout: timeout in seconds 1067 retries: number of retries 1068 1069 Raises: 1070 CommandTimeoutError on timeout. 1071 DeviceUnreachableError on missing device. 1072 """ 1073 cmd = ['am', 'broadcast'] + intent_obj.am_args 1074 self.RunShellCommand(cmd, check_return=True) 1075 1076 @decorators.WithTimeoutAndRetriesFromInstance() 1077 def GoHome(self, timeout=None, retries=None): 1078 """Return to the home screen and obtain launcher focus. 1079 1080 This command launches the home screen and attempts to obtain 1081 launcher focus until the timeout is reached. 1082 1083 Args: 1084 timeout: timeout in seconds 1085 retries: number of retries 1086 1087 Raises: 1088 CommandTimeoutError on timeout. 1089 DeviceUnreachableError on missing device. 1090 """ 1091 def is_launcher_focused(): 1092 output = self.RunShellCommand(['dumpsys', 'window', 'windows'], 1093 check_return=True, large_output=True) 1094 return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output) 1095 1096 def dismiss_popups(): 1097 # There is a dialog present; attempt to get rid of it. 1098 # Not all dialogs can be dismissed with back. 1099 self.SendKeyEvent(keyevent.KEYCODE_ENTER) 1100 self.SendKeyEvent(keyevent.KEYCODE_BACK) 1101 return is_launcher_focused() 1102 1103 # If Home is already focused, return early to avoid unnecessary work. 1104 if is_launcher_focused(): 1105 return 1106 1107 self.StartActivity( 1108 intent.Intent(action='android.intent.action.MAIN', 1109 category='android.intent.category.HOME'), 1110 blocking=True) 1111 1112 if not is_launcher_focused(): 1113 timeout_retry.WaitFor(dismiss_popups, wait_period=1) 1114 1115 @decorators.WithTimeoutAndRetriesFromInstance() 1116 def ForceStop(self, package, timeout=None, retries=None): 1117 """Close the application. 1118 1119 Args: 1120 package: A string containing the name of the package to stop. 1121 timeout: timeout in seconds 1122 retries: number of retries 1123 1124 Raises: 1125 CommandTimeoutError on timeout. 1126 DeviceUnreachableError on missing device. 1127 """ 1128 cmd = 'p=%s;if [[ "$(ps)" = *$p* ]]; then am force-stop $p; fi' 1129 self.RunShellCommand(cmd % package, check_return=True) 1130 1131 @decorators.WithTimeoutAndRetriesFromInstance() 1132 def ClearApplicationState( 1133 self, package, permissions=None, timeout=None, retries=None): 1134 """Clear all state for the given package. 1135 1136 Args: 1137 package: A string containing the name of the package to stop. 1138 permissions: List of permissions to set after clearing data. 1139 timeout: timeout in seconds 1140 retries: number of retries 1141 1142 Raises: 1143 CommandTimeoutError on timeout. 1144 DeviceUnreachableError on missing device. 1145 """ 1146 # Check that the package exists before clearing it for android builds below 1147 # JB MR2. Necessary because calling pm clear on a package that doesn't exist 1148 # may never return. 1149 if ((self.build_version_sdk >= version_codes.JELLY_BEAN_MR2) 1150 or self._GetApplicationPathsInternal(package)): 1151 self.RunShellCommand(['pm', 'clear', package], check_return=True) 1152 self.GrantPermissions(package, permissions) 1153 1154 @decorators.WithTimeoutAndRetriesFromInstance() 1155 def SendKeyEvent(self, keycode, timeout=None, retries=None): 1156 """Sends a keycode to the device. 1157 1158 See the devil.android.sdk.keyevent module for suitable keycode values. 1159 1160 Args: 1161 keycode: A integer keycode to send to the device. 1162 timeout: timeout in seconds 1163 retries: number of retries 1164 1165 Raises: 1166 CommandTimeoutError on timeout. 1167 DeviceUnreachableError on missing device. 1168 """ 1169 self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')], 1170 check_return=True) 1171 1172 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT 1173 1174 @decorators.WithTimeoutAndRetriesFromInstance( 1175 min_default_timeout=PUSH_CHANGED_FILES_DEFAULT_TIMEOUT) 1176 def PushChangedFiles(self, host_device_tuples, timeout=None, 1177 retries=None, delete_device_stale=False): 1178 """Push files to the device, skipping files that don't need updating. 1179 1180 When a directory is pushed, it is traversed recursively on the host and 1181 all files in it are pushed to the device as needed. 1182 Additionally, if delete_device_stale option is True, 1183 files that exist on the device but don't exist on the host are deleted. 1184 1185 Args: 1186 host_device_tuples: A list of (host_path, device_path) tuples, where 1187 |host_path| is an absolute path of a file or directory on the host 1188 that should be minimially pushed to the device, and |device_path| is 1189 an absolute path of the destination on the device. 1190 timeout: timeout in seconds 1191 retries: number of retries 1192 delete_device_stale: option to delete stale files on device 1193 1194 Raises: 1195 CommandFailedError on failure. 1196 CommandTimeoutError on timeout. 1197 DeviceUnreachableError on missing device. 1198 """ 1199 1200 all_changed_files = [] 1201 all_stale_files = [] 1202 missing_dirs = [] 1203 cache_commit_funcs = [] 1204 for h, d in host_device_tuples: 1205 assert os.path.isabs(h) and posixpath.isabs(d) 1206 changed_files, up_to_date_files, stale_files, cache_commit_func = ( 1207 self._GetChangedAndStaleFiles(h, d, delete_device_stale)) 1208 all_changed_files += changed_files 1209 all_stale_files += stale_files 1210 cache_commit_funcs.append(cache_commit_func) 1211 if changed_files and not up_to_date_files and not stale_files: 1212 if os.path.isdir(h): 1213 missing_dirs.append(d) 1214 else: 1215 missing_dirs.append(posixpath.dirname(d)) 1216 1217 if delete_device_stale and all_stale_files: 1218 self.RunShellCommand(['rm', '-f'] + all_stale_files, check_return=True) 1219 1220 if all_changed_files: 1221 if missing_dirs: 1222 self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True) 1223 self._PushFilesImpl(host_device_tuples, all_changed_files) 1224 for func in cache_commit_funcs: 1225 func() 1226 1227 def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False): 1228 """Get files to push and delete 1229 1230 Args: 1231 host_path: an absolute path of a file or directory on the host 1232 device_path: an absolute path of a file or directory on the device 1233 track_stale: whether to bother looking for stale files (slower) 1234 1235 Returns: 1236 a four-element tuple 1237 1st element: a list of (host_files_path, device_files_path) tuples to push 1238 2nd element: a list of host_files_path that are up-to-date 1239 3rd element: a list of stale files under device_path, or [] when 1240 track_stale == False 1241 4th element: a cache commit function. 1242 """ 1243 try: 1244 # Length calculations below assume no trailing /. 1245 host_path = host_path.rstrip('/') 1246 device_path = device_path.rstrip('/') 1247 1248 specific_device_paths = [device_path] 1249 ignore_other_files = not track_stale and os.path.isdir(host_path) 1250 if ignore_other_files: 1251 specific_device_paths = [] 1252 for root, _, filenames in os.walk(host_path): 1253 relative_dir = root[len(host_path) + 1:] 1254 specific_device_paths.extend( 1255 posixpath.join(device_path, relative_dir, f) for f in filenames) 1256 1257 def calculate_host_checksums(): 1258 return md5sum.CalculateHostMd5Sums([host_path]) 1259 1260 def calculate_device_checksums(): 1261 if self._enable_device_files_cache: 1262 cache_entry = self._cache['device_path_checksums'].get(device_path) 1263 if cache_entry and cache_entry[0] == ignore_other_files: 1264 return dict(cache_entry[1]) 1265 1266 sums = md5sum.CalculateDeviceMd5Sums(specific_device_paths, self) 1267 1268 cache_entry = [ignore_other_files, sums] 1269 self._cache['device_path_checksums'][device_path] = cache_entry 1270 return dict(sums) 1271 1272 host_checksums, device_checksums = reraiser_thread.RunAsync(( 1273 calculate_host_checksums, 1274 calculate_device_checksums)) 1275 except EnvironmentError as e: 1276 logging.warning('Error calculating md5: %s', e) 1277 return ([(host_path, device_path)], [], [], lambda: 0) 1278 1279 to_push = [] 1280 up_to_date = [] 1281 to_delete = [] 1282 if os.path.isfile(host_path): 1283 host_checksum = host_checksums.get(host_path) 1284 device_checksum = device_checksums.get(device_path) 1285 if host_checksum == device_checksum: 1286 up_to_date.append(host_path) 1287 else: 1288 to_push.append((host_path, device_path)) 1289 else: 1290 for host_abs_path, host_checksum in host_checksums.iteritems(): 1291 device_abs_path = posixpath.join( 1292 device_path, os.path.relpath(host_abs_path, host_path)) 1293 device_checksum = device_checksums.pop(device_abs_path, None) 1294 if device_checksum == host_checksum: 1295 up_to_date.append(host_abs_path) 1296 else: 1297 to_push.append((host_abs_path, device_abs_path)) 1298 to_delete = device_checksums.keys() 1299 1300 def cache_commit_func(): 1301 new_sums = {posixpath.join(device_path, path[len(host_path) + 1:]): val 1302 for path, val in host_checksums.iteritems()} 1303 cache_entry = [ignore_other_files, new_sums] 1304 self._cache['device_path_checksums'][device_path] = cache_entry 1305 1306 return (to_push, up_to_date, to_delete, cache_commit_func) 1307 1308 def _ComputeDeviceChecksumsForApks(self, package_name): 1309 ret = self._cache['package_apk_checksums'].get(package_name) 1310 if ret is None: 1311 device_paths = self._GetApplicationPathsInternal(package_name) 1312 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self) 1313 ret = set(file_to_checksums.values()) 1314 self._cache['package_apk_checksums'][package_name] = ret 1315 return ret 1316 1317 def _ComputeStaleApks(self, package_name, host_apk_paths): 1318 def calculate_host_checksums(): 1319 return md5sum.CalculateHostMd5Sums(host_apk_paths) 1320 1321 def calculate_device_checksums(): 1322 return self._ComputeDeviceChecksumsForApks(package_name) 1323 1324 host_checksums, device_checksums = reraiser_thread.RunAsync(( 1325 calculate_host_checksums, calculate_device_checksums)) 1326 stale_apks = [k for (k, v) in host_checksums.iteritems() 1327 if v not in device_checksums] 1328 return stale_apks, set(host_checksums.values()) 1329 1330 def _PushFilesImpl(self, host_device_tuples, files): 1331 if not files: 1332 return 1333 1334 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) 1335 file_count = len(files) 1336 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) 1337 for h, _ in host_device_tuples) 1338 dir_file_count = 0 1339 for h, _ in host_device_tuples: 1340 if os.path.isdir(h): 1341 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) 1342 else: 1343 dir_file_count += 1 1344 1345 push_duration = self._ApproximateDuration( 1346 file_count, file_count, size, False) 1347 dir_push_duration = self._ApproximateDuration( 1348 len(host_device_tuples), dir_file_count, dir_size, False) 1349 zip_duration = self._ApproximateDuration(1, 1, size, True) 1350 1351 if (dir_push_duration < push_duration and dir_push_duration < zip_duration 1352 # TODO(jbudorick): Resume directory pushing once clients have switched 1353 # to 1.0.36-compatible syntax. 1354 and False): 1355 self._PushChangedFilesIndividually(host_device_tuples) 1356 elif push_duration < zip_duration: 1357 self._PushChangedFilesIndividually(files) 1358 elif self._commands_installed is False: 1359 # Already tried and failed to install unzip command. 1360 self._PushChangedFilesIndividually(files) 1361 elif not self._PushChangedFilesZipped( 1362 files, [d for _, d in host_device_tuples]): 1363 self._PushChangedFilesIndividually(files) 1364 1365 def _MaybeInstallCommands(self): 1366 if self._commands_installed is None: 1367 try: 1368 if not install_commands.Installed(self): 1369 install_commands.InstallCommands(self) 1370 self._commands_installed = True 1371 except device_errors.CommandFailedError as e: 1372 logging.warning('unzip not available: %s', str(e)) 1373 self._commands_installed = False 1374 return self._commands_installed 1375 1376 @staticmethod 1377 def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping): 1378 # We approximate the time to push a set of files to a device as: 1379 # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where 1380 # t: total time (sec) 1381 # c1: adb call time delay (sec) 1382 # a: number of times adb is called (unitless) 1383 # c2: push time delay (sec) 1384 # f: number of files pushed via adb (unitless) 1385 # c3: zip time delay (sec) 1386 # c4: zip rate (bytes/sec) 1387 # b: total number of bytes (bytes) 1388 # c5: transfer rate (bytes/sec) 1389 # c6: compression ratio (unitless) 1390 1391 # All of these are approximations. 1392 ADB_CALL_PENALTY = 0.1 # seconds 1393 ADB_PUSH_PENALTY = 0.01 # seconds 1394 ZIP_PENALTY = 2.0 # seconds 1395 ZIP_RATE = 10000000.0 # bytes / second 1396 TRANSFER_RATE = 2000000.0 # bytes / second 1397 COMPRESSION_RATIO = 2.0 # unitless 1398 1399 adb_call_time = ADB_CALL_PENALTY * adb_calls 1400 adb_push_setup_time = ADB_PUSH_PENALTY * file_count 1401 if is_zipping: 1402 zip_time = ZIP_PENALTY + byte_count / ZIP_RATE 1403 transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO) 1404 else: 1405 zip_time = 0 1406 transfer_time = byte_count / TRANSFER_RATE 1407 return adb_call_time + adb_push_setup_time + zip_time + transfer_time 1408 1409 def _PushChangedFilesIndividually(self, files): 1410 for h, d in files: 1411 self.adb.Push(h, d) 1412 1413 def _PushChangedFilesZipped(self, files, dirs): 1414 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: 1415 zip_proc = multiprocessing.Process( 1416 target=DeviceUtils._CreateDeviceZip, 1417 args=(zip_file.name, files)) 1418 zip_proc.start() 1419 try: 1420 # While it's zipping, ensure the unzip command exists on the device. 1421 if not self._MaybeInstallCommands(): 1422 zip_proc.terminate() 1423 return False 1424 1425 # Warm up NeedsSU cache while we're still zipping. 1426 self.NeedsSU() 1427 with device_temp_file.DeviceTempFile( 1428 self.adb, suffix='.zip') as device_temp: 1429 zip_proc.join() 1430 self.adb.Push(zip_file.name, device_temp.name) 1431 quoted_dirs = ' '.join(cmd_helper.SingleQuote(d) for d in dirs) 1432 self.RunShellCommand( 1433 'unzip %s&&chmod -R 777 %s' % (device_temp.name, quoted_dirs), 1434 as_root=True, 1435 env={'PATH': '%s:$PATH' % install_commands.BIN_DIR}, 1436 check_return=True) 1437 finally: 1438 if zip_proc.is_alive(): 1439 zip_proc.terminate() 1440 return True 1441 1442 @staticmethod 1443 def _CreateDeviceZip(zip_path, host_device_tuples): 1444 with zipfile.ZipFile(zip_path, 'w') as zip_file: 1445 for host_path, device_path in host_device_tuples: 1446 zip_utils.WriteToZipFile(zip_file, host_path, device_path) 1447 1448 # TODO(nednguyen): remove this and migrate the callsite to PathExists(). 1449 @decorators.WithTimeoutAndRetriesFromInstance() 1450 def FileExists(self, device_path, timeout=None, retries=None): 1451 """Checks whether the given file exists on the device. 1452 1453 Arguments are the same as PathExists. 1454 """ 1455 return self.PathExists(device_path, timeout=timeout, retries=retries) 1456 1457 @decorators.WithTimeoutAndRetriesFromInstance() 1458 def PathExists(self, device_paths, as_root=False, timeout=None, retries=None): 1459 """Checks whether the given path(s) exists on the device. 1460 1461 Args: 1462 device_path: A string containing the absolute path to the file on the 1463 device, or an iterable of paths to check. 1464 as_root: Whether root permissions should be use to check for the existence 1465 of the given path(s). 1466 timeout: timeout in seconds 1467 retries: number of retries 1468 1469 Returns: 1470 True if the all given paths exist on the device, False otherwise. 1471 1472 Raises: 1473 CommandTimeoutError on timeout. 1474 DeviceUnreachableError on missing device. 1475 """ 1476 paths = device_paths 1477 if isinstance(paths, basestring): 1478 paths = (paths,) 1479 condition = ' -a '.join('-e %s' % cmd_helper.SingleQuote(p) for p in paths) 1480 cmd = 'test %s' % condition 1481 try: 1482 self.RunShellCommand(cmd, as_root=as_root, check_return=True, 1483 timeout=timeout, retries=retries) 1484 return True 1485 except device_errors.CommandFailedError: 1486 return False 1487 1488 @decorators.WithTimeoutAndRetriesFromInstance() 1489 def PullFile(self, device_path, host_path, timeout=None, retries=None): 1490 """Pull a file from the device. 1491 1492 Args: 1493 device_path: A string containing the absolute path of the file to pull 1494 from the device. 1495 host_path: A string containing the absolute path of the destination on 1496 the host. 1497 timeout: timeout in seconds 1498 retries: number of retries 1499 1500 Raises: 1501 CommandFailedError on failure. 1502 CommandTimeoutError on timeout. 1503 """ 1504 # Create the base dir if it doesn't exist already 1505 dirname = os.path.dirname(host_path) 1506 if dirname and not os.path.exists(dirname): 1507 os.makedirs(dirname) 1508 self.adb.Pull(device_path, host_path) 1509 1510 def _ReadFileWithPull(self, device_path): 1511 try: 1512 d = tempfile.mkdtemp() 1513 host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull') 1514 self.adb.Pull(device_path, host_temp_path) 1515 with open(host_temp_path, 'r') as host_temp: 1516 return host_temp.read() 1517 finally: 1518 if os.path.exists(d): 1519 shutil.rmtree(d) 1520 1521 @decorators.WithTimeoutAndRetriesFromInstance() 1522 def ReadFile(self, device_path, as_root=False, force_pull=False, 1523 timeout=None, retries=None): 1524 """Reads the contents of a file from the device. 1525 1526 Args: 1527 device_path: A string containing the absolute path of the file to read 1528 from the device. 1529 as_root: A boolean indicating whether the read should be executed with 1530 root privileges. 1531 force_pull: A boolean indicating whether to force the operation to be 1532 performed by pulling a file from the device. The default is, when the 1533 contents are short, to retrieve the contents using cat instead. 1534 timeout: timeout in seconds 1535 retries: number of retries 1536 1537 Returns: 1538 The contents of |device_path| as a string. Contents are intepreted using 1539 universal newlines, so the caller will see them encoded as '\n'. Also, 1540 all lines will be terminated. 1541 1542 Raises: 1543 AdbCommandFailedError if the file can't be read. 1544 CommandTimeoutError on timeout. 1545 DeviceUnreachableError on missing device. 1546 """ 1547 def get_size(path): 1548 return self.FileSize(path, as_root=as_root) 1549 1550 if (not force_pull 1551 and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH): 1552 return _JoinLines(self.RunShellCommand( 1553 ['cat', device_path], as_root=as_root, check_return=True)) 1554 elif as_root and self.NeedsSU(): 1555 with device_temp_file.DeviceTempFile(self.adb) as device_temp: 1556 cmd = 'SRC=%s DEST=%s;cp "$SRC" "$DEST" && chmod 666 "$DEST"' % ( 1557 cmd_helper.SingleQuote(device_path), 1558 cmd_helper.SingleQuote(device_temp.name)) 1559 self.RunShellCommand(cmd, as_root=True, check_return=True) 1560 return self._ReadFileWithPull(device_temp.name) 1561 else: 1562 return self._ReadFileWithPull(device_path) 1563 1564 def _WriteFileWithPush(self, device_path, contents): 1565 with tempfile.NamedTemporaryFile() as host_temp: 1566 host_temp.write(contents) 1567 host_temp.flush() 1568 self.adb.Push(host_temp.name, device_path) 1569 1570 @decorators.WithTimeoutAndRetriesFromInstance() 1571 def WriteFile(self, device_path, contents, as_root=False, force_push=False, 1572 timeout=None, retries=None): 1573 """Writes |contents| to a file on the device. 1574 1575 Args: 1576 device_path: A string containing the absolute path to the file to write 1577 on the device. 1578 contents: A string containing the data to write to the device. 1579 as_root: A boolean indicating whether the write should be executed with 1580 root privileges (if available). 1581 force_push: A boolean indicating whether to force the operation to be 1582 performed by pushing a file to the device. The default is, when the 1583 contents are short, to pass the contents using a shell script instead. 1584 timeout: timeout in seconds 1585 retries: number of retries 1586 1587 Raises: 1588 CommandFailedError if the file could not be written on the device. 1589 CommandTimeoutError on timeout. 1590 DeviceUnreachableError on missing device. 1591 """ 1592 if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH: 1593 # If the contents are small, for efficieny we write the contents with 1594 # a shell command rather than pushing a file. 1595 cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), 1596 cmd_helper.SingleQuote(device_path)) 1597 self.RunShellCommand(cmd, as_root=as_root, check_return=True) 1598 elif as_root and self.NeedsSU(): 1599 # Adb does not allow to "push with su", so we first push to a temp file 1600 # on a safe location, and then copy it to the desired location with su. 1601 with device_temp_file.DeviceTempFile(self.adb) as device_temp: 1602 self._WriteFileWithPush(device_temp.name, contents) 1603 # Here we need 'cp' rather than 'mv' because the temp and 1604 # destination files might be on different file systems (e.g. 1605 # on internal storage and an external sd card). 1606 self.RunShellCommand(['cp', device_temp.name, device_path], 1607 as_root=True, check_return=True) 1608 else: 1609 # If root is not needed, we can push directly to the desired location. 1610 self._WriteFileWithPush(device_path, contents) 1611 1612 def _ParseLongLsOutput(self, device_path, as_root=False, **kwargs): 1613 """Run and scrape the output of 'ls -a -l' on a device directory.""" 1614 device_path = posixpath.join(device_path, '') # Force trailing '/'. 1615 output = self.RunShellCommand( 1616 ['ls', '-a', '-l', device_path], as_root=as_root, 1617 check_return=True, env={'TZ': 'utc'}, **kwargs) 1618 if output and output[0].startswith('total '): 1619 output.pop(0) # pylint: disable=maybe-no-member 1620 1621 entries = [] 1622 for line in output: 1623 m = _LONG_LS_OUTPUT_RE.match(line) 1624 if m: 1625 if m.group('filename') not in ['.', '..']: 1626 entries.append(m.groupdict()) 1627 else: 1628 logging.info('Skipping: %s', line) 1629 1630 return entries 1631 1632 def ListDirectory(self, device_path, as_root=False, **kwargs): 1633 """List all files on a device directory. 1634 1635 Mirroring os.listdir (and most client expectations) the resulting list 1636 does not include the special entries '.' and '..' even if they are present 1637 in the directory. 1638 1639 Args: 1640 device_path: A string containing the path of the directory on the device 1641 to list. 1642 as_root: A boolean indicating whether the to use root privileges to list 1643 the directory contents. 1644 timeout: timeout in seconds 1645 retries: number of retries 1646 1647 Returns: 1648 A list of filenames for all entries contained in the directory. 1649 1650 Raises: 1651 AdbCommandFailedError if |device_path| does not specify a valid and 1652 accessible directory in the device. 1653 CommandTimeoutError on timeout. 1654 DeviceUnreachableError on missing device. 1655 """ 1656 entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs) 1657 return [d['filename'] for d in entries] 1658 1659 def StatDirectory(self, device_path, as_root=False, **kwargs): 1660 """List file and stat info for all entries on a device directory. 1661 1662 Implementation notes: this is currently implemented by parsing the output 1663 of 'ls -a -l' on the device. Whether possible and convenient, we attempt to 1664 make parsing strict and return values mirroring those of the standard |os| 1665 and |stat| Python modules. 1666 1667 Mirroring os.listdir (and most client expectations) the resulting list 1668 does not include the special entries '.' and '..' even if they are present 1669 in the directory. 1670 1671 Args: 1672 device_path: A string containing the path of the directory on the device 1673 to list. 1674 as_root: A boolean indicating whether the to use root privileges to list 1675 the directory contents. 1676 timeout: timeout in seconds 1677 retries: number of retries 1678 1679 Returns: 1680 A list of dictionaries, each containing the following keys: 1681 filename: A string with the file name. 1682 st_mode: File permissions, use the stat module to interpret these. 1683 st_nlink: Number of hard links (may be missing). 1684 st_owner: A string with the user name of the owner. 1685 st_group: A string with the group name of the owner. 1686 st_rdev_pair: Device type as (major, minior) (only if inode device). 1687 st_size: Size of file, in bytes (may be missing for non-regular files). 1688 st_mtime: Time of most recent modification, in seconds since epoch 1689 (although resolution is in minutes). 1690 symbolic_link_to: If entry is a symbolic link, path where it points to; 1691 missing otherwise. 1692 1693 Raises: 1694 AdbCommandFailedError if |device_path| does not specify a valid and 1695 accessible directory in the device. 1696 CommandTimeoutError on timeout. 1697 DeviceUnreachableError on missing device. 1698 """ 1699 entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs) 1700 for d in entries: 1701 for key, value in d.items(): 1702 if value is None: 1703 del d[key] # Remove missing fields. 1704 d['st_mode'] = _ParseModeString(d['st_mode']) 1705 d['st_mtime'] = calendar.timegm( 1706 time.strptime(d['st_mtime'], _LS_DATE_FORMAT)) 1707 for key in ['st_nlink', 'st_size', 'st_rdev_major', 'st_rdev_minor']: 1708 if key in d: 1709 d[key] = int(d[key]) 1710 if 'st_rdev_major' in d and 'st_rdev_minor' in d: 1711 d['st_rdev_pair'] = (d.pop('st_rdev_major'), d.pop('st_rdev_minor')) 1712 return entries 1713 1714 def StatPath(self, device_path, as_root=False, **kwargs): 1715 """Get the stat attributes of a file or directory on the device. 1716 1717 Args: 1718 device_path: A string containing the path of a file or directory from 1719 which to get attributes. 1720 as_root: A boolean indicating whether the to use root privileges to 1721 access the file information. 1722 timeout: timeout in seconds 1723 retries: number of retries 1724 1725 Returns: 1726 A dictionary with the stat info collected; see StatDirectory for details. 1727 1728 Raises: 1729 CommandFailedError if device_path cannot be found on the device. 1730 CommandTimeoutError on timeout. 1731 DeviceUnreachableError on missing device. 1732 """ 1733 dirname, filename = posixpath.split(posixpath.normpath(device_path)) 1734 for entry in self.StatDirectory(dirname, as_root=as_root, **kwargs): 1735 if entry['filename'] == filename: 1736 return entry 1737 raise device_errors.CommandFailedError( 1738 'Cannot find file or directory: %r' % device_path, str(self)) 1739 1740 def FileSize(self, device_path, as_root=False, **kwargs): 1741 """Get the size of a file on the device. 1742 1743 Note: This is implemented by parsing the output of the 'ls' command on 1744 the device. On some Android versions, when passing a directory or special 1745 file, the size is *not* reported and this function will throw an exception. 1746 1747 Args: 1748 device_path: A string containing the path of a file on the device. 1749 as_root: A boolean indicating whether the to use root privileges to 1750 access the file information. 1751 timeout: timeout in seconds 1752 retries: number of retries 1753 1754 Returns: 1755 The size of the file in bytes. 1756 1757 Raises: 1758 CommandFailedError if device_path cannot be found on the device, or 1759 its size cannot be determited for some reason. 1760 CommandTimeoutError on timeout. 1761 DeviceUnreachableError on missing device. 1762 """ 1763 entry = self.StatPath(device_path, as_root=as_root, **kwargs) 1764 try: 1765 return entry['st_size'] 1766 except KeyError: 1767 raise device_errors.CommandFailedError( 1768 'Could not determine the size of: %s' % device_path, str(self)) 1769 1770 @decorators.WithTimeoutAndRetriesFromInstance() 1771 def SetJavaAsserts(self, enabled, timeout=None, retries=None): 1772 """Enables or disables Java asserts. 1773 1774 Args: 1775 enabled: A boolean indicating whether Java asserts should be enabled 1776 or disabled. 1777 timeout: timeout in seconds 1778 retries: number of retries 1779 1780 Returns: 1781 True if the device-side property changed and a restart is required as a 1782 result, False otherwise. 1783 1784 Raises: 1785 CommandTimeoutError on timeout. 1786 """ 1787 def find_property(lines, property_name): 1788 for index, line in enumerate(lines): 1789 if line.strip() == '': 1790 continue 1791 key_value = tuple(s.strip() for s in line.split('=', 1)) 1792 if len(key_value) != 2: 1793 continue 1794 key, value = key_value 1795 if key == property_name: 1796 return index, value 1797 return None, '' 1798 1799 new_value = 'all' if enabled else '' 1800 1801 # First ensure the desired property is persisted. 1802 try: 1803 properties = self.ReadFile(self.LOCAL_PROPERTIES_PATH).splitlines() 1804 except device_errors.CommandFailedError: 1805 properties = [] 1806 index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY) 1807 if new_value != value: 1808 if new_value: 1809 new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value) 1810 if index is None: 1811 properties.append(new_line) 1812 else: 1813 properties[index] = new_line 1814 else: 1815 assert index is not None # since new_value == '' and new_value != value 1816 properties.pop(index) 1817 self.WriteFile(self.LOCAL_PROPERTIES_PATH, _JoinLines(properties)) 1818 1819 # Next, check the current runtime value is what we need, and 1820 # if not, set it and report that a reboot is required. 1821 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) 1822 if new_value != value: 1823 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) 1824 return True 1825 else: 1826 return False 1827 1828 def GetLanguage(self, cache=False): 1829 """Returns the language setting on the device. 1830 Args: 1831 cache: Whether to use cached properties when available. 1832 """ 1833 return self.GetProp('persist.sys.language', cache=cache) 1834 1835 def GetCountry(self, cache=False): 1836 """Returns the country setting on the device. 1837 1838 Args: 1839 cache: Whether to use cached properties when available. 1840 """ 1841 return self.GetProp('persist.sys.country', cache=cache) 1842 1843 @property 1844 def screen_density(self): 1845 """Returns the screen density of the device.""" 1846 DPI_TO_DENSITY = { 1847 120: 'ldpi', 1848 160: 'mdpi', 1849 240: 'hdpi', 1850 320: 'xhdpi', 1851 480: 'xxhdpi', 1852 640: 'xxxhdpi', 1853 } 1854 return DPI_TO_DENSITY.get(self.pixel_density, 'tvdpi') 1855 1856 @property 1857 def pixel_density(self): 1858 return int(self.GetProp('ro.sf.lcd_density', cache=True)) 1859 1860 @property 1861 def build_description(self): 1862 """Returns the build description of the system. 1863 1864 For example: 1865 nakasi-user 4.4.4 KTU84P 1227136 release-keys 1866 """ 1867 return self.GetProp('ro.build.description', cache=True) 1868 1869 @property 1870 def build_fingerprint(self): 1871 """Returns the build fingerprint of the system. 1872 1873 For example: 1874 google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys 1875 """ 1876 return self.GetProp('ro.build.fingerprint', cache=True) 1877 1878 @property 1879 def build_id(self): 1880 """Returns the build ID of the system (e.g. 'KTU84P').""" 1881 return self.GetProp('ro.build.id', cache=True) 1882 1883 @property 1884 def build_product(self): 1885 """Returns the build product of the system (e.g. 'grouper').""" 1886 return self.GetProp('ro.build.product', cache=True) 1887 1888 @property 1889 def build_type(self): 1890 """Returns the build type of the system (e.g. 'user').""" 1891 return self.GetProp('ro.build.type', cache=True) 1892 1893 @property 1894 def build_version_sdk(self): 1895 """Returns the build version sdk of the system as a number (e.g. 19). 1896 1897 For version code numbers see: 1898 http://developer.android.com/reference/android/os/Build.VERSION_CODES.html 1899 1900 For named constants see devil.android.sdk.version_codes 1901 1902 Raises: 1903 CommandFailedError if the build version sdk is not a number. 1904 """ 1905 value = self.GetProp('ro.build.version.sdk', cache=True) 1906 try: 1907 return int(value) 1908 except ValueError: 1909 raise device_errors.CommandFailedError( 1910 'Invalid build version sdk: %r' % value) 1911 1912 @property 1913 def product_cpu_abi(self): 1914 """Returns the product cpu abi of the device (e.g. 'armeabi-v7a').""" 1915 return self.GetProp('ro.product.cpu.abi', cache=True) 1916 1917 @property 1918 def product_model(self): 1919 """Returns the name of the product model (e.g. 'Nexus 7').""" 1920 return self.GetProp('ro.product.model', cache=True) 1921 1922 @property 1923 def product_name(self): 1924 """Returns the product name of the device (e.g. 'nakasi').""" 1925 return self.GetProp('ro.product.name', cache=True) 1926 1927 @property 1928 def product_board(self): 1929 """Returns the product board name of the device (e.g. 'shamu').""" 1930 return self.GetProp('ro.product.board', cache=True) 1931 1932 def _EnsureCacheInitialized(self): 1933 """Populates cache token, runs getprop and fetches $EXTERNAL_STORAGE.""" 1934 if self._cache['token']: 1935 return 1936 with self._cache_lock: 1937 if self._cache['token']: 1938 return 1939 # Change the token every time to ensure that it will match only the 1940 # previously dumped cache. 1941 token = str(uuid.uuid1()) 1942 cmd = ( 1943 'c=/data/local/tmp/cache_token;' 1944 'echo $EXTERNAL_STORAGE;' 1945 'cat $c 2>/dev/null||echo;' 1946 'echo "%s">$c &&' % token + 1947 'getprop' 1948 ) 1949 output = self.RunShellCommand(cmd, check_return=True, large_output=True) 1950 # Error-checking for this existing is done in GetExternalStoragePath(). 1951 self._cache['external_storage'] = output[0] 1952 self._cache['prev_token'] = output[1] 1953 output = output[2:] 1954 1955 prop_cache = self._cache['getprop'] 1956 prop_cache.clear() 1957 for key, value in _GETPROP_RE.findall(''.join(output)): 1958 prop_cache[key] = value 1959 self._cache['token'] = token 1960 1961 @decorators.WithTimeoutAndRetriesFromInstance() 1962 def GetProp(self, property_name, cache=False, timeout=None, retries=None): 1963 """Gets a property from the device. 1964 1965 Args: 1966 property_name: A string containing the name of the property to get from 1967 the device. 1968 cache: Whether to use cached properties when available. 1969 timeout: timeout in seconds 1970 retries: number of retries 1971 1972 Returns: 1973 The value of the device's |property_name| property. 1974 1975 Raises: 1976 CommandTimeoutError on timeout. 1977 """ 1978 assert isinstance(property_name, basestring), ( 1979 "property_name is not a string: %r" % property_name) 1980 1981 if cache: 1982 # It takes ~120ms to query a single property, and ~130ms to query all 1983 # properties. So, when caching we always query all properties. 1984 self._EnsureCacheInitialized() 1985 else: 1986 # timeout and retries are handled down at run shell, because we don't 1987 # want to apply them in the other branch when reading from the cache 1988 value = self.RunShellCommand( 1989 ['getprop', property_name], single_line=True, check_return=True, 1990 timeout=timeout, retries=retries) 1991 self._cache['getprop'][property_name] = value 1992 # Non-existent properties are treated as empty strings by getprop. 1993 return self._cache['getprop'].get(property_name, '') 1994 1995 @decorators.WithTimeoutAndRetriesFromInstance() 1996 def SetProp(self, property_name, value, check=False, timeout=None, 1997 retries=None): 1998 """Sets a property on the device. 1999 2000 Args: 2001 property_name: A string containing the name of the property to set on 2002 the device. 2003 value: A string containing the value to set to the property on the 2004 device. 2005 check: A boolean indicating whether to check that the property was 2006 successfully set on the device. 2007 timeout: timeout in seconds 2008 retries: number of retries 2009 2010 Raises: 2011 CommandFailedError if check is true and the property was not correctly 2012 set on the device (e.g. because it is not rooted). 2013 CommandTimeoutError on timeout. 2014 """ 2015 assert isinstance(property_name, basestring), ( 2016 "property_name is not a string: %r" % property_name) 2017 assert isinstance(value, basestring), "value is not a string: %r" % value 2018 2019 self.RunShellCommand(['setprop', property_name, value], check_return=True) 2020 prop_cache = self._cache['getprop'] 2021 if property_name in prop_cache: 2022 del prop_cache[property_name] 2023 # TODO(perezju) remove the option and make the check mandatory, but using a 2024 # single shell script to both set- and getprop. 2025 if check and value != self.GetProp(property_name, cache=False): 2026 raise device_errors.CommandFailedError( 2027 'Unable to set property %r on the device to %r' 2028 % (property_name, value), str(self)) 2029 2030 @decorators.WithTimeoutAndRetriesFromInstance() 2031 def GetABI(self, timeout=None, retries=None): 2032 """Gets the device main ABI. 2033 2034 Args: 2035 timeout: timeout in seconds 2036 retries: number of retries 2037 2038 Returns: 2039 The device's main ABI name. 2040 2041 Raises: 2042 CommandTimeoutError on timeout. 2043 """ 2044 return self.GetProp('ro.product.cpu.abi', cache=True) 2045 2046 @decorators.WithTimeoutAndRetriesFromInstance() 2047 def GetPids(self, process_name, timeout=None, retries=None): 2048 """Returns the PIDs of processes with the given name. 2049 2050 Note that the |process_name| is often the package name. 2051 2052 Args: 2053 process_name: A string containing the process name to get the PIDs for. 2054 timeout: timeout in seconds 2055 retries: number of retries 2056 2057 Returns: 2058 A dict mapping process name to a list of PIDs for each process that 2059 contained the provided |process_name|. 2060 2061 Raises: 2062 CommandTimeoutError on timeout. 2063 DeviceUnreachableError on missing device. 2064 """ 2065 procs_pids = collections.defaultdict(list) 2066 try: 2067 ps_output = self._RunPipedShellCommand( 2068 'ps | grep -F %s' % cmd_helper.SingleQuote(process_name)) 2069 except device_errors.AdbShellCommandFailedError as e: 2070 if e.status and isinstance(e.status, list) and not e.status[0]: 2071 # If ps succeeded but grep failed, there were no processes with the 2072 # given name. 2073 return procs_pids 2074 else: 2075 raise 2076 2077 for line in ps_output: 2078 try: 2079 ps_data = line.split() 2080 if process_name in ps_data[-1]: 2081 pid, process = ps_data[1], ps_data[-1] 2082 procs_pids[process].append(pid) 2083 except IndexError: 2084 pass 2085 return procs_pids 2086 2087 @decorators.WithTimeoutAndRetriesFromInstance() 2088 def TakeScreenshot(self, host_path=None, timeout=None, retries=None): 2089 """Takes a screenshot of the device. 2090 2091 Args: 2092 host_path: A string containing the path on the host to save the 2093 screenshot to. If None, a file name in the current 2094 directory will be generated. 2095 timeout: timeout in seconds 2096 retries: number of retries 2097 2098 Returns: 2099 The name of the file on the host to which the screenshot was saved. 2100 2101 Raises: 2102 CommandFailedError on failure. 2103 CommandTimeoutError on timeout. 2104 DeviceUnreachableError on missing device. 2105 """ 2106 if not host_path: 2107 host_path = os.path.abspath('screenshot-%s-%s.png' % ( 2108 self.adb.GetDeviceSerial(), _GetTimeStamp())) 2109 with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp: 2110 self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name], 2111 check_return=True) 2112 self.PullFile(device_tmp.name, host_path) 2113 return host_path 2114 2115 @decorators.WithTimeoutAndRetriesFromInstance() 2116 def GetMemoryUsageForPid(self, pid, timeout=None, retries=None): 2117 """Gets the memory usage for the given PID. 2118 2119 Args: 2120 pid: PID of the process. 2121 timeout: timeout in seconds 2122 retries: number of retries 2123 2124 Returns: 2125 A dict containing memory usage statistics for the PID. May include: 2126 Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean, 2127 Private_Dirty, VmHWM 2128 2129 Raises: 2130 CommandTimeoutError on timeout. 2131 """ 2132 result = collections.defaultdict(int) 2133 2134 try: 2135 result.update(self._GetMemoryUsageForPidFromSmaps(pid)) 2136 except device_errors.CommandFailedError: 2137 logging.exception('Error getting memory usage from smaps') 2138 2139 try: 2140 result.update(self._GetMemoryUsageForPidFromStatus(pid)) 2141 except device_errors.CommandFailedError: 2142 logging.exception('Error getting memory usage from status') 2143 2144 return result 2145 2146 @decorators.WithTimeoutAndRetriesFromInstance() 2147 def DismissCrashDialogIfNeeded(self, timeout=None, retries=None): 2148 """Dismiss the error/ANR dialog if present. 2149 2150 Returns: Name of the crashed package if a dialog is focused, 2151 None otherwise. 2152 """ 2153 def _FindFocusedWindow(): 2154 match = None 2155 # TODO(jbudorick): Try to grep the output on the device instead of using 2156 # large_output if/when DeviceUtils exposes a public interface for piped 2157 # shell command handling. 2158 for line in self.RunShellCommand(['dumpsys', 'window', 'windows'], 2159 check_return=True, large_output=True): 2160 match = re.match(_CURRENT_FOCUS_CRASH_RE, line) 2161 if match: 2162 break 2163 return match 2164 2165 match = _FindFocusedWindow() 2166 if not match: 2167 return None 2168 package = match.group(2) 2169 logging.warning('Trying to dismiss %s dialog for %s', *match.groups()) 2170 self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) 2171 self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) 2172 self.SendKeyEvent(keyevent.KEYCODE_ENTER) 2173 match = _FindFocusedWindow() 2174 if match: 2175 logging.error('Still showing a %s dialog for %s', *match.groups()) 2176 return package 2177 2178 def _GetMemoryUsageForPidFromSmaps(self, pid): 2179 SMAPS_COLUMNS = ( 2180 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean', 2181 'Private_Dirty') 2182 2183 showmap_out = self._RunPipedShellCommand( 2184 'showmap %d | grep TOTAL' % int(pid), as_root=True) 2185 2186 split_totals = showmap_out[-1].split() 2187 if (not split_totals 2188 or len(split_totals) != 9 2189 or split_totals[-1] != 'TOTAL'): 2190 raise device_errors.CommandFailedError( 2191 'Invalid output from showmap: %s' % '\n'.join(showmap_out)) 2192 2193 return dict(itertools.izip(SMAPS_COLUMNS, (int(n) for n in split_totals))) 2194 2195 def _GetMemoryUsageForPidFromStatus(self, pid): 2196 for line in self.ReadFile( 2197 '/proc/%s/status' % str(pid), as_root=True).splitlines(): 2198 if line.startswith('VmHWM:'): 2199 return {'VmHWM': int(line.split()[1])} 2200 raise device_errors.CommandFailedError( 2201 'Could not find memory peak value for pid %s', str(pid)) 2202 2203 def GetLogcatMonitor(self, *args, **kwargs): 2204 """Returns a new LogcatMonitor associated with this device. 2205 2206 Parameters passed to this function are passed directly to 2207 |logcat_monitor.LogcatMonitor| and are documented there. 2208 """ 2209 return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs) 2210 2211 def GetClientCache(self, client_name): 2212 """Returns client cache.""" 2213 if client_name not in self._client_caches: 2214 self._client_caches[client_name] = {} 2215 return self._client_caches[client_name] 2216 2217 def _ClearCache(self): 2218 """Clears all caches.""" 2219 for client in self._client_caches: 2220 self._client_caches[client].clear() 2221 self._cache = { 2222 # Map of packageId -> list of on-device .apk paths 2223 'package_apk_paths': {}, 2224 # Set of packageId that were loaded from LoadCacheData and not yet 2225 # verified. 2226 'package_apk_paths_to_verify': set(), 2227 # Map of packageId -> set of on-device .apk checksums 2228 'package_apk_checksums': {}, 2229 # Map of property_name -> value 2230 'getprop': {}, 2231 # Map of device_path -> [ignore_other_files, map of path->checksum] 2232 'device_path_checksums': {}, 2233 # Location of sdcard ($EXTERNAL_STORAGE). 2234 'external_storage': None, 2235 # Token used to detect when LoadCacheData is stale. 2236 'token': None, 2237 'prev_token': None, 2238 } 2239 2240 @decorators.WithTimeoutAndRetriesFromInstance() 2241 def LoadCacheData(self, data, timeout=None, retries=None): 2242 """Initializes the cache from data created using DumpCacheData. 2243 2244 The cache is used only if its token matches the one found on the device. 2245 This prevents a stale cache from being used (which can happen when sharing 2246 devices). 2247 2248 Args: 2249 data: A previously serialized cache (string). 2250 timeout: timeout in seconds 2251 retries: number of retries 2252 2253 Returns: 2254 Whether the cache was loaded. 2255 """ 2256 obj = json.loads(data) 2257 self._EnsureCacheInitialized() 2258 given_token = obj.get('token') 2259 if not given_token or self._cache['prev_token'] != given_token: 2260 logging.warning('Stale cache detected. Not using it.') 2261 return False 2262 2263 self._cache['package_apk_paths'] = obj.get('package_apk_paths', {}) 2264 # When using a cache across script invokations, verify that apps have 2265 # not been uninstalled. 2266 self._cache['package_apk_paths_to_verify'] = set( 2267 self._cache['package_apk_paths'].iterkeys()) 2268 2269 package_apk_checksums = obj.get('package_apk_checksums', {}) 2270 for k, v in package_apk_checksums.iteritems(): 2271 package_apk_checksums[k] = set(v) 2272 self._cache['package_apk_checksums'] = package_apk_checksums 2273 device_path_checksums = obj.get('device_path_checksums', {}) 2274 self._cache['device_path_checksums'] = device_path_checksums 2275 return True 2276 2277 @decorators.WithTimeoutAndRetriesFromInstance() 2278 def DumpCacheData(self, timeout=None, retries=None): 2279 """Dumps the current cache state to a string. 2280 2281 Args: 2282 timeout: timeout in seconds 2283 retries: number of retries 2284 2285 Returns: 2286 A serialized cache as a string. 2287 """ 2288 self._EnsureCacheInitialized() 2289 obj = {} 2290 obj['token'] = self._cache['token'] 2291 obj['package_apk_paths'] = self._cache['package_apk_paths'] 2292 obj['package_apk_checksums'] = self._cache['package_apk_checksums'] 2293 # JSON can't handle sets. 2294 for k, v in obj['package_apk_checksums'].iteritems(): 2295 obj['package_apk_checksums'][k] = list(v) 2296 obj['device_path_checksums'] = self._cache['device_path_checksums'] 2297 return json.dumps(obj, separators=(',', ':')) 2298 2299 @classmethod 2300 def parallel(cls, devices, async=False): 2301 """Creates a Parallelizer to operate over the provided list of devices. 2302 2303 Args: 2304 devices: A list of either DeviceUtils instances or objects from 2305 from which DeviceUtils instances can be constructed. If None, 2306 all attached devices will be used. 2307 async: If true, returns a Parallelizer that runs operations 2308 asynchronously. 2309 2310 Returns: 2311 A Parallelizer operating over |devices|. 2312 """ 2313 devices = [d if isinstance(d, cls) else cls(d) for d in devices] 2314 if async: 2315 return parallelizer.Parallelizer(devices) 2316 else: 2317 return parallelizer.SyncParallelizer(devices) 2318 2319 @classmethod 2320 def HealthyDevices(cls, blacklist=None, device_arg='default', **kwargs): 2321 """Returns a list of DeviceUtils instances. 2322 2323 Returns a list of DeviceUtils instances that are attached, not blacklisted, 2324 and optionally filtered by --device flags or ANDROID_SERIAL environment 2325 variable. 2326 2327 Args: 2328 blacklist: A DeviceBlacklist instance (optional). Device serials in this 2329 blacklist will never be returned, but a warning will be logged if they 2330 otherwise would have been. 2331 device_arg: The value of the --device flag. This can be: 2332 'default' -> Same as [], but returns an empty list rather than raise a 2333 NoDevicesError. 2334 [] -> Returns all devices, unless $ANDROID_SERIAL is set. 2335 None -> Use $ANDROID_SERIAL if set, otherwise looks for a single 2336 attached device. Raises an exception if multiple devices are 2337 attached. 2338 'serial' -> Returns an instance for the given serial, if not 2339 blacklisted. 2340 ['A', 'B', ...] -> Returns instances for the subset that is not 2341 blacklisted. 2342 A device serial, or a list of device serials (optional). 2343 2344 Returns: 2345 A list of one or more DeviceUtils instances. 2346 2347 Raises: 2348 NoDevicesError: Raised when no non-blacklisted devices exist and 2349 device_arg is passed. 2350 MultipleDevicesError: Raise when multiple devices exist, but |device_arg| 2351 is None. 2352 """ 2353 allow_no_devices = False 2354 if device_arg == 'default': 2355 allow_no_devices = True 2356 device_arg = () 2357 2358 select_multiple = True 2359 if not (isinstance(device_arg, tuple) or isinstance(device_arg, list)): 2360 select_multiple = False 2361 if device_arg: 2362 device_arg = (device_arg,) 2363 2364 blacklisted_devices = blacklist.Read() if blacklist else [] 2365 2366 # adb looks for ANDROID_SERIAL, so support it as well. 2367 android_serial = os.environ.get('ANDROID_SERIAL') 2368 if not device_arg and android_serial: 2369 device_arg = (android_serial,) 2370 2371 def blacklisted(serial): 2372 if serial in blacklisted_devices: 2373 logging.warning('Device %s is blacklisted.', serial) 2374 return True 2375 return False 2376 2377 if device_arg: 2378 devices = [cls(x, **kwargs) for x in device_arg if not blacklisted(x)] 2379 else: 2380 devices = [] 2381 for adb in adb_wrapper.AdbWrapper.Devices(): 2382 if not blacklisted(adb.GetDeviceSerial()): 2383 devices.append(cls(_CreateAdbWrapper(adb), **kwargs)) 2384 2385 if len(devices) == 0 and not allow_no_devices: 2386 raise device_errors.NoDevicesError() 2387 if len(devices) > 1 and not select_multiple: 2388 raise device_errors.MultipleDevicesError(devices) 2389 return sorted(devices) 2390 2391 @decorators.WithTimeoutAndRetriesFromInstance() 2392 def RestartAdbd(self, timeout=None, retries=None): 2393 logging.info('Restarting adbd on device.') 2394 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: 2395 self.WriteFile(script.name, _RESTART_ADBD_SCRIPT) 2396 self.RunShellCommand(['source', script.name], as_root=True) 2397 self.adb.WaitForDevice() 2398 2399 @decorators.WithTimeoutAndRetriesFromInstance() 2400 def GrantPermissions(self, package, permissions, timeout=None, retries=None): 2401 # Permissions only need to be set on M and above because of the changes to 2402 # the permission model. 2403 if not permissions or self.build_version_sdk < version_codes.MARSHMALLOW: 2404 return 2405 logging.info('Setting permissions for %s.', package) 2406 permissions = [p for p in permissions if p not in _PERMISSIONS_BLACKLIST] 2407 if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions 2408 and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions): 2409 permissions.append('android.permission.READ_EXTERNAL_STORAGE') 2410 cmd = '&&'.join('pm grant %s %s' % (package, p) for p in permissions) 2411 if cmd: 2412 output = self.RunShellCommand(cmd, check_return=True) 2413 if output: 2414 logging.warning('Possible problem when granting permissions. Blacklist ' 2415 'may need to be updated.') 2416 for line in output: 2417 logging.warning(' %s', line) 2418 2419 @decorators.WithTimeoutAndRetriesFromInstance() 2420 def IsScreenOn(self, timeout=None, retries=None): 2421 """Determines if screen is on. 2422 2423 Dumpsys input_method exposes screen on/off state. Below is an explination of 2424 the states. 2425 2426 Pre-L: 2427 On: mScreenOn=true 2428 Off: mScreenOn=false 2429 L+: 2430 On: mInteractive=true 2431 Off: mInteractive=false 2432 2433 Returns: 2434 True if screen is on, false if it is off. 2435 2436 Raises: 2437 device_errors.CommandFailedError: If screen state cannot be found. 2438 """ 2439 if self.build_version_sdk < version_codes.LOLLIPOP: 2440 input_check = 'mScreenOn' 2441 check_value = 'mScreenOn=true' 2442 else: 2443 input_check = 'mInteractive' 2444 check_value = 'mInteractive=true' 2445 dumpsys_out = self._RunPipedShellCommand( 2446 'dumpsys input_method | grep %s' % input_check) 2447 if not dumpsys_out: 2448 raise device_errors.CommandFailedError( 2449 'Unable to detect screen state', str(self)) 2450 return check_value in dumpsys_out[0] 2451 2452 @decorators.WithTimeoutAndRetriesFromInstance() 2453 def SetScreen(self, on, timeout=None, retries=None): 2454 """Turns screen on and off. 2455 2456 Args: 2457 on: bool to decide state to switch to. True = on False = off. 2458 """ 2459 def screen_test(): 2460 return self.IsScreenOn() == on 2461 2462 if screen_test(): 2463 logging.info('Screen already in expected state.') 2464 return 2465 self.RunShellCommand('input keyevent 26') 2466 timeout_retry.WaitFor(screen_test, wait_period=1) 2467