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"""Provides a variety of device interactions based on adb.""" 5# pylint: disable=unused-argument 6 7import calendar 8import collections 9import contextlib 10import fnmatch 11import json 12import logging 13import math 14import os 15import posixpath 16import pprint 17import random 18import re 19import shutil 20import stat 21import sys 22import tempfile 23import time 24import threading 25import uuid 26 27from devil import base_error 28from devil import devil_env 29from devil.utils import cmd_helper 30from devil.android import apk_helper 31from devil.android import device_signal 32from devil.android import decorators 33from devil.android import device_errors 34from devil.android import device_temp_file 35from devil.android import install_commands 36from devil.android import logcat_monitor 37from devil.android import md5sum 38from devil.android.sdk import adb_wrapper 39from devil.android.sdk import intent 40from devil.android.sdk import keyevent 41from devil.android.sdk import version_codes 42from devil.utils import host_utils 43from devil.utils import parallelizer 44from devil.utils import reraiser_thread 45from devil.utils import timeout_retry 46from devil.utils import zip_utils 47 48from py_utils import tempfile_ext 49 50try: 51 from devil.utils import reset_usb 52except ImportError: 53 # Fail silently if we can't import reset_usb. We're likely on windows. 54 reset_usb = None 55 56logger = logging.getLogger(__name__) 57 58_DEFAULT_TIMEOUT = 30 59_DEFAULT_RETRIES = 3 60 61# A sentinel object for default values 62# TODO(jbudorick): revisit how default values are handled by 63# the timeout_retry decorators. 64DEFAULT = object() 65 66# A sentinel object to require that calls to RunShellCommand force running the 67# command with su even if the device has been rooted. To use, pass into the 68# as_root param. 69_FORCE_SU = object() 70 71# Lists all files for the specified directories. 72# In order to minimize data transfer, prints directories as absolute paths 73# followed by files within that directory without their path. 74_FILE_LIST_SCRIPT = """ 75 function list_files() { 76 for f in "$1"/{.,}* 77 do 78 if [ "$f" == "." ] || [ "$f" == ".." ] || [ "$f" == "${1}/.*" ] \ 79 || [ "$f" == "${1}/*" ] 80 then 81 continue 82 fi 83 base=${f##*/} # Get the basename for the file, dropping the path. 84 echo "$base" 85 done 86 } 87 for dir in %s 88 do 89 if [ -d "$dir" ]; then 90 echo "$dir" 91 list_files "$dir" 92 fi 93 done 94""" 95 96_RESTART_ADBD_SCRIPT = """ 97 trap '' HUP 98 trap '' TERM 99 trap '' PIPE 100 function restart() { 101 stop adbd 102 start adbd 103 } 104 restart & 105""" 106 107# Not all permissions can be set. 108_PERMISSIONS_DENYLIST_RE = re.compile('|'.join( 109 fnmatch.translate(p) for p in [ 110 'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS', 111 'android.permission.ACCESS_MOCK_LOCATION', 112 'android.permission.ACCESS_NETWORK_STATE', 113 'android.permission.ACCESS_NOTIFICATION_POLICY', 114 'android.permission.ACCESS_VR_STATE', 115 'android.permission.ACCESS_WIFI_STATE', 116 'android.permission.AUTHENTICATE_ACCOUNTS', 117 'android.permission.BLUETOOTH', 118 'android.permission.BLUETOOTH_ADMIN', 119 'android.permission.BROADCAST_STICKY', 120 'android.permission.CHANGE_NETWORK_STATE', 121 'android.permission.CHANGE_WIFI_MULTICAST_STATE', 122 'android.permission.CHANGE_WIFI_STATE', 123 'android.permission.DISABLE_KEYGUARD', 124 'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION', 125 'android.permission.EXPAND_STATUS_BAR', 126 'android.permission.FOREGROUND_SERVICE', 127 'android.permission.GET_PACKAGE_SIZE', 128 'android.permission.INSTALL_SHORTCUT', 129 'android.permission.INJECT_EVENTS', 130 'android.permission.INTERNET', 131 'android.permission.KILL_BACKGROUND_PROCESSES', 132 'android.permission.MANAGE_ACCOUNTS', 133 'android.permission.MODIFY_AUDIO_SETTINGS', 134 'android.permission.NFC', 135 'android.permission.QUERY_ALL_PACKAGES', 136 'android.permission.READ_SYNC_SETTINGS', 137 'android.permission.READ_SYNC_STATS', 138 'android.permission.RECEIVE_BOOT_COMPLETED', 139 'android.permission.RECORD_VIDEO', 140 'android.permission.REORDER_TASKS', 141 'android.permission.REQUEST_INSTALL_PACKAGES', 142 'android.permission.RESTRICTED_VR_ACCESS', 143 'android.permission.RUN_INSTRUMENTATION', 144 'android.permission.SET_ALARM', 145 'android.permission.SET_TIME_ZONE', 146 'android.permission.SET_WALLPAPER', 147 'android.permission.SET_WALLPAPER_HINTS', 148 'android.permission.TRANSMIT_IR', 149 'android.permission.USE_CREDENTIALS', 150 'android.permission.USE_FINGERPRINT', 151 'android.permission.VIBRATE', 152 'android.permission.WAKE_LOCK', 153 'android.permission.WRITE_SYNC_SETTINGS', 154 'com.android.browser.permission.READ_HISTORY_BOOKMARKS', 155 'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS', 156 'com.android.launcher.permission.INSTALL_SHORTCUT', 157 'com.chrome.permission.DEVICE_EXTRAS', 158 'com.google.android.apps.now.CURRENT_ACCOUNT_ACCESS', 159 'com.google.android.c2dm.permission.RECEIVE', 160 'com.google.android.providers.gsf.permission.READ_GSERVICES', 161 'com.google.vr.vrcore.permission.VRCORE_INTERNAL', 162 'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER', 163 '*.permission.C2D_MESSAGE', 164 '*.permission.READ_WRITE_BOOKMARK_FOLDERS', 165 '*.TOS_ACKED', 166 ])) 167_SHELL_OUTPUT_SEPARATOR = '~X~' 168_PERMISSIONS_EXCEPTION_RE = re.compile(r'java\.lang\.\w+Exception: .*$', 169 re.MULTILINE) 170 171_CURRENT_FOCUS_CRASH_RE = re.compile( 172 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') 173 174_GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]') 175_VERSION_CODE_SDK_RE = re.compile( 176 r'\s*versionCode=(\d+).*minSdk=(\d+).*targetSdk=(.*)\s*') 177 178# Regex to parse the long (-l) output of 'ls' command, c.f. 179# https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446 180# yapf: disable 181_LONG_LS_OUTPUT_RE = re.compile( 182 r'(?P<st_mode>[\w-]{10})\s+' # File permissions 183 r'(?:(?P<st_nlink>\d+)\s+)?' # Number of links (optional) 184 r'(?P<st_owner>\w+)\s+' # Name of owner 185 r'(?P<st_group>\w+)\s+' # Group of owner 186 r'(?:' # Either ... 187 r'(?P<st_rdev_major>\d+),\s+' # Device major, and 188 r'(?P<st_rdev_minor>\d+)\s+' # Device minor 189 r'|' # .. or 190 r'(?P<st_size>\d+)\s+' # Size in bytes 191 r')?' # .. or nothing 192 r'(?P<st_mtime>\d{4}-\d\d-\d\d \d\d:\d\d)\s+' # Modification date/time 193 r'(?P<filename>.+?)' # File name 194 r'(?: -> (?P<symbolic_link_to>.+))?' # Symbolic link (optional) 195 r'$' # End of string 196) 197# yapf: enable 198 199_LS_DATE_FORMAT = '%Y-%m-%d %H:%M' 200_FILE_MODE_RE = re.compile(r'[dbclps-](?:[r-][w-][xSs-]){2}[r-][w-][xTt-]$') 201_FILE_MODE_KIND = { 202 'd': stat.S_IFDIR, 203 'b': stat.S_IFBLK, 204 'c': stat.S_IFCHR, 205 'l': stat.S_IFLNK, 206 'p': stat.S_IFIFO, 207 's': stat.S_IFSOCK, 208 '-': stat.S_IFREG 209} 210_FILE_MODE_PERMS = [ 211 stat.S_IRUSR, 212 stat.S_IWUSR, 213 stat.S_IXUSR, 214 stat.S_IRGRP, 215 stat.S_IWGRP, 216 stat.S_IXGRP, 217 stat.S_IROTH, 218 stat.S_IWOTH, 219 stat.S_IXOTH, 220] 221_FILE_MODE_SPECIAL = [ 222 ('s', stat.S_ISUID), 223 ('s', stat.S_ISGID), 224 ('t', stat.S_ISVTX), 225] 226_PS_COLUMNS = {'pid': 1, 'ppid': 2, 'name': -1} 227_SELINUX_MODE = {'enforcing': True, 'permissive': False, 'disabled': None} 228# Some devices require different logic for checking if root is necessary 229_SPECIAL_ROOT_DEVICE_LIST = [ 230 'marlin', # Pixel XL 231 'sailfish', # Pixel 232 'taimen', # Pixel 2 XL 233 'vega', # Lenovo Mirage Solo 234 'walleye', # Pixel 2 235 'crosshatch', # Pixel 3 XL 236 'blueline', # Pixel 3 237 'sargo', # Pixel 3a 238 'bonito', # Pixel 3a XL 239 'sdk_goog3_x86', # Crow emulator 240] 241_SPECIAL_ROOT_DEVICE_LIST += [ 242 'aosp_%s' % _d for _d in _SPECIAL_ROOT_DEVICE_LIST 243] 244 245# Somce devices are slow/timeout when using default install. 246# Devices listed here will perform no_streaming app installation. 247_NO_STREAMING_DEVICE_LIST = [ 248 'flounder', # Nexus 9 249 'volantis', # Another product name for Nexus 9 250] 251 252_IMEI_RE = re.compile(r' Device ID = (.+)$') 253# The following regex is used to match result parcels like: 254""" 255Result: Parcel( 256 0x00000000: 00000000 0000000f 00350033 00360033 '........3.5.3.6.' 257 0x00000010: 00360032 00370030 00300032 00300039 '2.6.0.7.2.0.9.0.' 258 0x00000020: 00380033 00000039 '3.8.9... ') 259""" 260_PARCEL_RESULT_RE = re.compile( 261 r'0x[0-9a-f]{8}\: (?:[0-9a-f]{8}\s+){1,4}\'(.{16})\'') 262 263# http://bit.ly/2WLZhUF added a timeout to adb wait-for-device. We sometimes 264# want to wait longer than the implicit call within adb root allows. 265_WAIT_FOR_DEVICE_TIMEOUT_STR = 'timeout expired while waiting for device' 266 267_WEBVIEW_SYSUPDATE_CURRENT_PKG_RE = re.compile( 268 r'Current WebView package.*:.*\(([a-z.]*),') 269_WEBVIEW_SYSUPDATE_NULL_PKG_RE = re.compile(r'Current WebView package is null') 270_WEBVIEW_SYSUPDATE_FALLBACK_LOGIC_RE = re.compile( 271 r'Fallback logic enabled: (true|false)') 272_WEBVIEW_SYSUPDATE_PACKAGE_INSTALLED_RE = re.compile( 273 r'(?:Valid|Invalid) package\s+(\S+)\s+\(.*\),?\s+(.*)$') 274_WEBVIEW_SYSUPDATE_PACKAGE_NOT_INSTALLED_RE = re.compile( 275 r'(\S+)\s+(is NOT installed\.)') 276_WEBVIEW_SYSUPDATE_MIN_VERSION_CODE = re.compile( 277 r'Minimum WebView version code: (\d+)') 278 279_GOOGLE_FEATURES_RE = re.compile(r'^\s*com\.google\.') 280 281_EMULATOR_RE = re.compile(r'^generic_.*$') 282 283# Regular expressions for determining if a package is installed using the 284# output of `dumpsys package`. 285# Matches lines like "Package [com.google.android.youtube] (c491050):". 286# or "Package [org.chromium.trichromelibrary_425300033] (e476383):" 287_DUMPSYS_PACKAGE_RE_STR =\ 288 r'^\s*Package\s*\[%s(_(?P<version_code>\d*))?\]\s*\(\w*\):$' 289 290PS_COLUMNS = ('name', 'pid', 'ppid') 291ProcessInfo = collections.namedtuple('ProcessInfo', PS_COLUMNS) 292 293 294@decorators.WithExplicitTimeoutAndRetries(_DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 295def GetAVDs(): 296 """Returns a list of Android Virtual Devices. 297 298 Returns: 299 A list containing the configured AVDs. 300 """ 301 lines = cmd_helper.GetCmdOutput([ 302 os.path.join( 303 devil_env.config.LocalPath('android_sdk'), 'tools', 'android'), 304 'list', 'avd' 305 ]).splitlines() 306 avds = [] 307 for line in lines: 308 if 'Name:' not in line: 309 continue 310 key, value = (s.strip() for s in line.split(':', 1)) 311 if key == 'Name': 312 avds.append(value) 313 return avds 314 315 316@decorators.WithExplicitTimeoutAndRetries(_DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 317def RestartServer(): 318 """Restarts the adb server. 319 320 Raises: 321 CommandFailedError if we fail to kill or restart the server. 322 """ 323 324 def adb_killed(): 325 return not adb_wrapper.AdbWrapper.IsServerOnline() 326 327 def adb_started(): 328 return adb_wrapper.AdbWrapper.IsServerOnline() 329 330 adb_wrapper.AdbWrapper.KillServer() 331 if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5): 332 # TODO(crbug.com/442319): Switch this to raise an exception if we 333 # figure out why sometimes not all adb servers on bots get killed. 334 logger.warning('Failed to kill adb server') 335 adb_wrapper.AdbWrapper.StartServer() 336 if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5): 337 raise device_errors.CommandFailedError('Failed to start adb server') 338 339 340def _ParseModeString(mode_str): 341 """Parse a mode string, e.g. 'drwxrwxrwx', into a st_mode value. 342 343 Effectively the reverse of |mode_to_string| in, e.g.: 344 https://github.com/landley/toybox/blob/master/lib/lib.c#L896 345 """ 346 if not _FILE_MODE_RE.match(mode_str): 347 raise ValueError('Unexpected file mode %r', mode_str) 348 mode = _FILE_MODE_KIND[mode_str[0]] 349 for c, flag in zip(mode_str[1:], _FILE_MODE_PERMS): 350 if c != '-' and c.islower(): 351 mode |= flag 352 for c, (t, flag) in zip(mode_str[3::3], _FILE_MODE_SPECIAL): 353 if c.lower() == t: 354 mode |= flag 355 return mode 356 357 358def _GetTimeStamp(): 359 """Return a basic ISO 8601 time stamp with the current local time.""" 360 return time.strftime('%Y%m%dT%H%M%S', time.localtime()) 361 362 363def _JoinLines(lines): 364 # makes sure that the last line is also terminated, and is more memory 365 # efficient than first appending an end-line to each line and then joining 366 # all of them together. 367 return ''.join(s for line in lines for s in (line, '\n')) 368 369 370def _CreateAdbWrapper(device): 371 if isinstance(device, adb_wrapper.AdbWrapper): 372 return device 373 else: 374 return adb_wrapper.AdbWrapper(device) 375 376 377def _FormatPartialOutputError(output): 378 lines = output.splitlines() if isinstance(output, basestring) else output 379 message = ['Partial output found:'] 380 if len(lines) > 11: 381 message.extend('- %s' % line for line in lines[:5]) 382 message.extend('<snip>') 383 message.extend('- %s' % line for line in lines[-5:]) 384 else: 385 message.extend('- %s' % line for line in lines) 386 return '\n'.join(message) 387 388 389_PushableComponents = collections.namedtuple('_PushableComponents', 390 ('host', 'device', 'collapse')) 391 392 393def _IterPushableComponents(host_path, device_path): 394 """Yields a sequence of paths that can be pushed directly via adb push. 395 396 `adb push` doesn't currently handle pushing directories that contain 397 symlinks: https://bit.ly/2pMBlW5 398 399 To circumvent this issue, we get the smallest set of files and/or 400 directories that can be pushed without attempting to push a directory 401 that contains a symlink. 402 403 This function does so by recursing through |host_path|. Each call 404 yields 3-tuples that include the smallest set of (host, device) path pairs 405 that can be passed to adb push and a bool indicating whether the parent 406 directory can be pushed -- i.e., if True, the host path is neither a 407 symlink nor a directory that contains a symlink. 408 409 Args: 410 host_path: an absolute path of a file or directory on the host 411 device_path: an absolute path of a file or directory on the device 412 Yields: 413 3-tuples containing 414 host (str): the host path, with symlinks dereferenced 415 device (str): the device path 416 collapse (bool): whether this entity permits its parent to be pushed 417 in its entirety. (Parents need permission from all child entities 418 in order to be pushed in their entirety.) 419 """ 420 if os.path.isfile(host_path): 421 yield _PushableComponents( 422 os.path.realpath(host_path), device_path, not os.path.islink(host_path)) 423 else: 424 components = [] 425 for child in os.listdir(host_path): 426 components.extend( 427 _IterPushableComponents( 428 os.path.join(host_path, child), posixpath.join( 429 device_path, child))) 430 431 if all(c.collapse for c in components): 432 yield _PushableComponents( 433 os.path.realpath(host_path), device_path, 434 not os.path.islink(host_path)) 435 else: 436 for c in components: 437 yield c 438 439 440class DeviceUtils(object): 441 442 _MAX_ADB_COMMAND_LENGTH = 512 443 _MAX_ADB_OUTPUT_LENGTH = 32768 444 _LAUNCHER_FOCUSED_RE = re.compile(r'\s*mCurrentFocus.*(Launcher|launcher).*') 445 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') 446 447 LOCAL_PROPERTIES_PATH = posixpath.join('/', 'data', 'local.prop') 448 449 # Property in /data/local.prop that controls Java assertions. 450 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' 451 452 def __init__(self, 453 device, 454 enable_device_files_cache=False, 455 default_timeout=_DEFAULT_TIMEOUT, 456 default_retries=_DEFAULT_RETRIES): 457 """DeviceUtils constructor. 458 459 Args: 460 device: Either a device serial, an existing AdbWrapper instance, or an 461 an existing AndroidCommands instance. 462 enable_device_files_cache: For PushChangedFiles(), cache checksums of 463 pushed files rather than recomputing them on a subsequent call. 464 default_timeout: An integer containing the default number of seconds to 465 wait for an operation to complete if no explicit value is provided. 466 default_retries: An integer containing the default number or times an 467 operation should be retried on failure if no explicit value is provided. 468 """ 469 self.adb = None 470 if isinstance(device, basestring): 471 self.adb = _CreateAdbWrapper(device) 472 elif isinstance(device, adb_wrapper.AdbWrapper): 473 self.adb = device 474 else: 475 raise ValueError('Unsupported device value: %r' % device) 476 self._commands_installed = None 477 self._default_timeout = default_timeout 478 self._default_retries = default_retries 479 self._enable_device_files_cache = enable_device_files_cache 480 self._cache = {} 481 self._client_caches = {} 482 self._cache_lock = threading.RLock() 483 self._skip_root_user_build = None 484 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) 485 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) 486 487 self.ClearCache() 488 489 @property 490 def serial(self): 491 """Returns the device serial.""" 492 return self.adb.GetDeviceSerial() 493 494 def __eq__(self, other): 495 """Checks whether |other| refers to the same device as |self|. 496 497 Args: 498 other: The object to compare to. This can be a basestring, an instance 499 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. 500 Returns: 501 Whether |other| refers to the same device as |self|. 502 """ 503 return self.serial == str(other) 504 505 def __lt__(self, other): 506 """Compares two instances of DeviceUtils. 507 508 This merely compares their serial numbers. 509 510 Args: 511 other: The instance of DeviceUtils to compare to. 512 Returns: 513 Whether |self| is less than |other|. 514 """ 515 return self.serial < other.serial 516 517 def __str__(self): 518 """Returns the device serial.""" 519 return self.serial 520 521 @decorators.WithTimeoutAndRetriesFromInstance() 522 def IsOnline(self, timeout=None, retries=None): 523 """Checks whether the device is online. 524 525 Args: 526 timeout: timeout in seconds 527 retries: number of retries 528 529 Returns: 530 True if the device is online, False otherwise. 531 532 Raises: 533 CommandTimeoutError on timeout. 534 """ 535 try: 536 return self.adb.GetState() == 'device' 537 except base_error.BaseError as exc: 538 logger.info('Failed to get state: %s', exc) 539 return False 540 541 @decorators.WithTimeoutAndRetriesFromInstance() 542 def HasRoot(self, timeout=None, retries=None): 543 """Checks whether or not adbd has root privileges. 544 545 A device is considered to have root if all commands are implicitly run 546 with elevated privileges, i.e. without having to use "su" to run them. 547 548 Note that some devices do not allow this implicit privilige elevation, 549 but _can_ run commands as root just fine when done explicitly with "su". 550 To check if your device can run commands with elevated privileges at all 551 use: 552 553 device.HasRoot() or device.NeedsSU() 554 555 Luckily, for the most part you don't need to worry about this and using 556 RunShellCommand(cmd, as_root=True) will figure out for you the right 557 command incantation to run with elevated privileges. 558 559 Args: 560 timeout: timeout in seconds 561 retries: number of retries 562 563 Returns: 564 True if adbd has root privileges, False otherwise. 565 566 Raises: 567 CommandTimeoutError on timeout. 568 DeviceUnreachableError on missing device. 569 """ 570 if self.build_type == 'eng': 571 # 'eng' builds have root enabled by default and the adb session cannot 572 # be unrooted. 573 return True 574 # Check if uid is 0. Such behavior has remained unchanged since 575 # android 2.2.3 (https://bit.ly/2QQzg67) 576 output = self.RunShellCommand(['id'], single_line=True) 577 return output.startswith('uid=0(root)') 578 579 def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT): 580 """Checks whether 'su' is needed to access protected resources. 581 582 Args: 583 timeout: timeout in seconds 584 retries: number of retries 585 586 Returns: 587 True if 'su' is available on the device and is needed to to access 588 protected resources; False otherwise if either 'su' is not available 589 (e.g. because the device has a user build), or not needed (because adbd 590 already has root privileges). 591 592 Raises: 593 CommandTimeoutError on timeout. 594 DeviceUnreachableError on missing device. 595 """ 596 if 'needs_su' not in self._cache: 597 cmd = '%s && ! ls /root' % self._Su('ls /root') 598 # Devices using the system-as-root partition layout appear to not have 599 # a /root directory. See http://bit.ly/37F34sx for more context. 600 if (self.build_system_root_image == 'true' 601 or self.build_version_sdk >= version_codes.Q 602 # This may be redundant with the checks above. 603 or self.product_name in _SPECIAL_ROOT_DEVICE_LIST): 604 if self.HasRoot(): 605 self._cache['needs_su'] = False 606 return False 607 cmd = 'which which && which su' 608 try: 609 self.RunShellCommand( 610 cmd, 611 shell=True, 612 check_return=True, 613 timeout=self._default_timeout if timeout is DEFAULT else timeout, 614 retries=self._default_retries if retries is DEFAULT else retries) 615 self._cache['needs_su'] = True 616 except device_errors.AdbCommandFailedError: 617 self._cache['needs_su'] = False 618 return self._cache['needs_su'] 619 620 def _Su(self, command): 621 if self.build_version_sdk >= version_codes.MARSHMALLOW: 622 return 'su 0 %s' % command 623 return 'su -c %s' % command 624 625 @decorators.WithTimeoutAndRetriesFromInstance() 626 def EnableRoot(self, timeout=None, retries=None): 627 """Restarts adbd with root privileges. 628 629 Args: 630 timeout: timeout in seconds 631 retries: number of retries 632 633 Raises: 634 CommandFailedError if root could not be enabled. 635 CommandTimeoutError on timeout. 636 """ 637 if 'needs_su' in self._cache: 638 del self._cache['needs_su'] 639 640 try: 641 self.adb.Root() 642 except device_errors.AdbCommandFailedError as e: 643 if self.IsUserBuild(): 644 raise device_errors.RootUserBuildError(device_serial=str(self)) 645 elif e.output and _WAIT_FOR_DEVICE_TIMEOUT_STR in e.output: 646 # adb 1.0.41 added a call to wait-for-device *inside* root 647 # with a timeout that can be too short in some cases. 648 # If we hit that timeout, ignore it & do our own wait below. 649 pass 650 else: 651 raise # Failed probably due to some other reason. 652 653 def device_online_with_root(): 654 try: 655 self.adb.WaitForDevice() 656 return self.HasRoot() 657 except (device_errors.AdbCommandFailedError, 658 device_errors.DeviceUnreachableError): 659 return False 660 661 timeout_retry.WaitFor(device_online_with_root, wait_period=1) 662 663 @decorators.WithTimeoutAndRetriesFromInstance() 664 def IsUserBuild(self, timeout=None, retries=None): 665 """Checks whether or not the device is running a user build. 666 667 Args: 668 timeout: timeout in seconds 669 retries: number of retries 670 671 Returns: 672 True if the device is running a user build, False otherwise (i.e. if 673 it's running a userdebug build). 674 675 Raises: 676 CommandTimeoutError on timeout. 677 DeviceUnreachableError on missing device. 678 """ 679 return self.build_type == 'user' 680 681 @decorators.WithTimeoutAndRetriesFromInstance() 682 def GetExternalStoragePath(self, timeout=None, retries=None): 683 """Get the device's path to its SD card. 684 685 Note: this path is read-only by apps in R+. Use GetAppWritablePath() to 686 obtain a path writable by apps. 687 688 Args: 689 timeout: timeout in seconds 690 retries: number of retries 691 692 Returns: 693 The device's path to its SD card. 694 695 Raises: 696 CommandFailedError if the external storage path could not be determined. 697 CommandTimeoutError on timeout. 698 DeviceUnreachableError on missing device. 699 """ 700 self._EnsureCacheInitialized() 701 if not self._cache['external_storage']: 702 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', 703 str(self)) 704 return self._cache['external_storage'] 705 706 def GetAppWritablePath(self, timeout=None, retries=None): 707 """Get a path that on the device's SD card that apps can write. 708 709 Args: 710 timeout: timeout in seconds 711 retries: number of retries 712 713 Returns: 714 A app-writeable path on the device's SD card. 715 716 Raises: 717 CommandFailedError if the external storage path could not be determined. 718 CommandTimeoutError on timeout. 719 DeviceUnreachableError on missing device. 720 """ 721 if self.build_version_sdk >= version_codes.Q: 722 # On Q+ apps don't require permissions to access well-defined media 723 # locations like /sdcard/Download. On R+ the WRITE_EXTERNAL_STORAGE 724 # permission no longer provides access to the external storage root. See 725 # https://developer.android.com/preview/privacy/storage#permissions-target-11 726 # So use /sdcard/Download for the app-writable path on those versions. 727 return posixpath.join(self.GetExternalStoragePath(), 'Download') 728 return self.GetExternalStoragePath() 729 730 @decorators.WithTimeoutAndRetriesFromInstance() 731 def GetIMEI(self, timeout=None, retries=None): 732 """Get the device's IMEI. 733 734 Args: 735 timeout: timeout in seconds 736 retries: number of retries 737 738 Returns: 739 The device's IMEI. 740 741 Raises: 742 AdbCommandFailedError on error 743 """ 744 if self._cache.get('imei') is not None: 745 return self._cache.get('imei') 746 747 if self.build_version_sdk < 21: 748 out = self.RunShellCommand(['dumpsys', 'iphonesubinfo'], 749 raw_output=True, 750 check_return=True) 751 if out: 752 match = re.search(_IMEI_RE, out) 753 if match: 754 self._cache['imei'] = match.group(1) 755 return self._cache['imei'] 756 else: 757 out = self.RunShellCommand(['service', 'call', 'iphonesubinfo', '1'], 758 check_return=True) 759 if out: 760 imei = '' 761 for line in out: 762 match = re.search(_PARCEL_RESULT_RE, line) 763 if match: 764 imei = imei + match.group(1) 765 imei = imei.replace('.', '').strip() 766 if imei: 767 self._cache['imei'] = imei 768 return self._cache['imei'] 769 770 raise device_errors.CommandFailedError('Unable to fetch IMEI.') 771 772 @decorators.WithTimeoutAndRetriesFromInstance() 773 def IsApplicationInstalled( 774 self, package, version_code=None, timeout=None, retries=None): 775 """Determines whether a particular package is installed on the device. 776 777 Args: 778 package: Name of the package. 779 version_code: The version of the package to check for as an int, if 780 applicable. Only used for static shared libraries, otherwise ignored. 781 782 Returns: 783 True if the application is installed, False otherwise. 784 """ 785 # `pm list packages` doesn't include the version code, so if it was 786 # provided, skip this since we can't guarantee that the installed 787 # version is the requested version. 788 if version_code is None: 789 # `pm list packages` allows matching substrings, but we want exact matches 790 # only. 791 matching_packages = self.RunShellCommand( 792 ['pm', 'list', 'packages', package], check_return=True) 793 desired_line = 'package:' + package 794 found_package = desired_line in matching_packages 795 if found_package: 796 return True 797 798 # Some packages do not properly show up via `pm list packages`, so fall back 799 # to checking via `dumpsys package`. 800 matcher = re.compile(_DUMPSYS_PACKAGE_RE_STR % package) 801 dumpsys_output = self.RunShellCommand( 802 ['dumpsys', 'package'], check_return=True, large_output=True) 803 for line in dumpsys_output: 804 match = matcher.match(line) 805 # We should have one of these cases: 806 # 1. The package is a regular app, in which case it will show up without 807 # its version code in the line we're filtering for. 808 # 2. The package is a static shared library, in which case one or more 809 # entries with the version code can show up, but not one without the 810 # version code. 811 if match: 812 installed_version_code = match.groupdict().get('version_code') 813 if (installed_version_code is None 814 or installed_version_code == str(version_code)): 815 return True 816 return False 817 818 @decorators.WithTimeoutAndRetriesFromInstance() 819 def GetApplicationPaths(self, package, timeout=None, retries=None): 820 """Get the paths of the installed apks on the device for the given package. 821 822 Args: 823 package: Name of the package. 824 825 Returns: 826 List of paths to the apks on the device for the given package. 827 """ 828 return self._GetApplicationPathsInternal(package) 829 830 def _GetApplicationPathsInternal(self, package, skip_cache=False): 831 cached_result = self._cache['package_apk_paths'].get(package) 832 if cached_result is not None and not skip_cache: 833 if package in self._cache['package_apk_paths_to_verify']: 834 self._cache['package_apk_paths_to_verify'].remove(package) 835 # Don't verify an app that is not thought to be installed. We are 836 # concerned only with apps we think are installed having been 837 # uninstalled manually. 838 if cached_result and not self.PathExists(cached_result): 839 cached_result = None 840 self._cache['package_apk_checksums'].pop(package, 0) 841 if cached_result is not None: 842 return list(cached_result) 843 # 'pm path' is liable to incorrectly exit with a nonzero number starting 844 # in Lollipop. 845 # TODO(jbudorick): Check if this is fixed as new Android versions are 846 # released to put an upper bound on this. 847 should_check_return = (self.build_version_sdk < version_codes.LOLLIPOP) 848 output = self.RunShellCommand(['pm', 'path', package], 849 check_return=should_check_return) 850 apks = [] 851 bad_output = False 852 for line in output: 853 if line.startswith('package:'): 854 apks.append(line[len('package:'):]) 855 elif line.startswith('WARNING:'): 856 continue 857 else: 858 bad_output = True # Unexpected line in output. 859 if not apks and output: 860 if bad_output: 861 raise device_errors.CommandFailedError( 862 'Unexpected pm path output: %r' % '\n'.join(output), str(self)) 863 else: 864 logger.warning('pm returned no paths but the following warnings:') 865 for line in output: 866 logger.warning('- %s', line) 867 self._cache['package_apk_paths'][package] = list(apks) 868 return apks 869 870 @decorators.WithTimeoutAndRetriesFromInstance() 871 def GetApplicationVersion(self, package, timeout=None, retries=None): 872 """Get the version name of a package installed on the device. 873 874 Args: 875 package: Name of the package. 876 877 Returns: 878 A string with the version name or None if the package is not found 879 on the device. 880 """ 881 output = self.RunShellCommand(['dumpsys', 'package', package], 882 check_return=True) 883 if not output: 884 return None 885 for line in output: 886 line = line.strip() 887 if line.startswith('versionName='): 888 return line[len('versionName='):] 889 raise device_errors.CommandFailedError( 890 'Version name for %s not found on dumpsys output' % package, str(self)) 891 892 @decorators.WithTimeoutAndRetriesFromInstance() 893 def GetApplicationTargetSdk(self, package, timeout=None, retries=None): 894 """Get the targetSdkVersion of a package installed on the device. 895 896 Args: 897 package: Name of the package. 898 899 Returns: 900 A string with the targetSdkVersion or None if the package is not found on 901 the device. Note: this cannot always be cast to an integer. If this 902 application targets a pre-release SDK, this returns the version codename 903 instead (ex. "R"). 904 """ 905 if not self.IsApplicationInstalled(package): 906 return None 907 lines = self._GetDumpsysOutput(['package', package], 'targetSdk=') 908 for line in lines: 909 m = _VERSION_CODE_SDK_RE.match(line) 910 if m: 911 value = m.group(3) 912 # 10000 is the code used by Android for a pre-finalized SDK. 913 if value == '10000': 914 return self.GetProp('ro.build.version.codename', cache=True) 915 else: 916 return value 917 raise device_errors.CommandFailedError( 918 'targetSdkVersion for %s not found on dumpsys output' % package, 919 str(self)) 920 921 @decorators.WithTimeoutAndRetriesFromInstance() 922 def GetPackageArchitecture(self, package, timeout=None, retries=None): 923 """Get the architecture of a package installed on the device. 924 925 Args: 926 package: Name of the package. 927 928 Returns: 929 A string with the architecture, or None if the package is missing. 930 """ 931 lines = self._GetDumpsysOutput(['package', package], 'primaryCpuAbi') 932 if lines: 933 _, _, package_arch = lines[-1].partition('=') 934 return package_arch.strip() 935 return None 936 937 @decorators.WithTimeoutAndRetriesFromInstance() 938 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): 939 """Get the data directory on the device for the given package. 940 941 Args: 942 package: Name of the package. 943 944 Returns: 945 The package's data directory. 946 Raises: 947 CommandFailedError if the package's data directory can't be found, 948 whether because it's not installed or otherwise. 949 """ 950 if not self.IsApplicationInstalled(package): 951 raise device_errors.CommandFailedError('%s is not installed' % package, 952 str(self)) 953 output = self._RunPipedShellCommand( 954 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package)) 955 for line in output: 956 _, _, dataDir = line.partition('dataDir=') 957 if dataDir: 958 return dataDir 959 raise device_errors.CommandFailedError( 960 'Could not find data directory for %s' % package, str(self)) 961 962 @decorators.WithTimeoutAndRetriesFromInstance() 963 def GetSecurityContextForPackage(self, 964 package, 965 encrypted=False, 966 timeout=None, 967 retries=None): 968 """Gets the SELinux security context for the given package. 969 970 Args: 971 package: Name of the package. 972 encrypted: Whether to check in the encrypted data directory 973 (/data/user_de/0/) or the unencrypted data directory (/data/data/). 974 975 Returns: 976 The package's security context as a string, or None if not found. 977 """ 978 directory = '/data/user_de/0/' if encrypted else '/data/data/' 979 for line in self.RunShellCommand(['ls', '-Z', directory], 980 as_root=True, 981 check_return=True): 982 split_line = line.split() 983 # ls -Z output differs between Android versions, but the package is 984 # always last and the context always starts with "u:object" 985 if split_line[-1] == package: 986 for column in split_line: 987 if column.startswith('u:object'): 988 return column 989 return None 990 991 def TakeBugReport(self, path, timeout=60 * 5, retries=None): 992 """Takes a bug report and dumps it to the specified path. 993 994 This doesn't use adb's bugreport option since its behavior is dependent on 995 both adb version and device OS version. To make it simpler, this directly 996 runs the bugreport command on the device itself and dumps the stdout to a 997 file. 998 999 Args: 1000 path: Path on the host to drop the bug report. 1001 timeout: (optional) Timeout per try in seconds. 1002 retries: (optional) Number of retries to attempt. 1003 """ 1004 with device_temp_file.DeviceTempFile(self.adb) as device_tmp_file: 1005 cmd = '( bugreport )>%s 2>&1' % device_tmp_file.name 1006 self.RunShellCommand( 1007 cmd, check_return=True, shell=True, timeout=timeout, retries=retries) 1008 self.PullFile(device_tmp_file.name, path) 1009 1010 @decorators.WithTimeoutAndRetriesFromInstance() 1011 def WaitUntilFullyBooted(self, 1012 wifi=False, 1013 decrypt=False, 1014 timeout=None, 1015 retries=None): 1016 """Wait for the device to fully boot. 1017 1018 This means waiting for the device to boot, the package manager to be 1019 available, and the SD card to be ready. 1020 It can optionally wait the following: 1021 - Wait for wifi to come up. 1022 - Wait for full-disk decryption to complete. 1023 1024 Args: 1025 wifi: A boolean indicating if we should wait for wifi to come up or not. 1026 decrypt: A boolean indicating if we should wait for full-disk decryption 1027 to complete. 1028 timeout: timeout in seconds 1029 retries: number of retries 1030 1031 Raises: 1032 CommandFailedError on failure. 1033 CommandTimeoutError if one of the component waits times out. 1034 DeviceUnreachableError if the device becomes unresponsive. 1035 """ 1036 1037 def sd_card_ready(): 1038 try: 1039 self.RunShellCommand( 1040 ['test', '-d', self.GetExternalStoragePath()], check_return=True) 1041 return True 1042 except device_errors.AdbCommandFailedError: 1043 return False 1044 1045 def pm_ready(): 1046 try: 1047 return self._GetApplicationPathsInternal('android', skip_cache=True) 1048 except device_errors.CommandFailedError: 1049 return False 1050 1051 def boot_completed(): 1052 try: 1053 return self.GetProp('sys.boot_completed', cache=False) == '1' 1054 except device_errors.CommandFailedError: 1055 return False 1056 1057 def wifi_enabled(): 1058 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], 1059 check_return=False) 1060 1061 def decryption_completed(): 1062 try: 1063 decrypt = self.GetProp('vold.decrypt', cache=False) 1064 # The prop "void.decrypt" will only be set when the device uses 1065 # full-disk encryption (FDE). 1066 # Return true when: 1067 # - The prop is empty, which means the device is unencrypted or uses 1068 # file-based encryption (FBE). 1069 # - or the prop has value "trigger_restart_framework", which means 1070 # the decription is finished. 1071 return decrypt == '' or decrypt == 'trigger_restart_framework' 1072 except device_errors.CommandFailedError: 1073 return False 1074 1075 self.adb.WaitForDevice() 1076 timeout_retry.WaitFor(sd_card_ready) 1077 timeout_retry.WaitFor(pm_ready) 1078 timeout_retry.WaitFor(boot_completed) 1079 if wifi: 1080 timeout_retry.WaitFor(wifi_enabled) 1081 if decrypt: 1082 timeout_retry.WaitFor(decryption_completed) 1083 1084 REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT 1085 1086 @decorators.WithTimeoutAndRetriesFromInstance( 1087 min_default_timeout=REBOOT_DEFAULT_TIMEOUT) 1088 def Reboot(self, 1089 block=True, 1090 wifi=False, 1091 decrypt=False, 1092 timeout=None, 1093 retries=None): 1094 """Reboot the device. 1095 1096 Args: 1097 block: A boolean indicating if we should wait for the reboot to complete. 1098 wifi: A boolean indicating if we should wait for wifi to be enabled after 1099 the reboot. 1100 The option has no effect unless |block| is also True. 1101 decrypt: A boolean indicating if we should wait for full-disk decryption 1102 to complete after the reboot. 1103 The option has no effect unless |block| is also True. 1104 timeout: timeout in seconds 1105 retries: number of retries 1106 1107 Raises: 1108 CommandTimeoutError on timeout. 1109 DeviceUnreachableError on missing device. 1110 """ 1111 1112 def device_offline(): 1113 return not self.IsOnline() 1114 1115 self.adb.Reboot() 1116 self.ClearCache() 1117 timeout_retry.WaitFor(device_offline, wait_period=1) 1118 if block: 1119 self.WaitUntilFullyBooted(wifi=wifi, decrypt=decrypt) 1120 1121 INSTALL_DEFAULT_TIMEOUT = 8 * _DEFAULT_TIMEOUT 1122 MODULES_SRC_DIRECTORY_PATH = '/data/local/tmp/modules' 1123 1124 @decorators.WithTimeoutAndRetriesFromInstance( 1125 min_default_timeout=INSTALL_DEFAULT_TIMEOUT) 1126 def Install(self, 1127 apk, 1128 allow_downgrade=False, 1129 reinstall=False, 1130 permissions=None, 1131 timeout=None, 1132 retries=None, 1133 modules=None, 1134 fake_modules=None, 1135 additional_locales=None): 1136 """Install an APK or app bundle. 1137 1138 Noop if an identical APK is already installed. If installing a bundle, the 1139 bundletools helper script (bin/*_bundle) should be used rather than the .aab 1140 file. 1141 1142 Args: 1143 apk: An ApkHelper instance or string containing the path to the APK or 1144 bundle. 1145 allow_downgrade: A boolean indicating if we should allow downgrades. 1146 reinstall: A boolean indicating if we should keep any existing app data. 1147 Ignored if |apk| is a bundle. 1148 permissions: Set of permissions to set. If not set, finds permissions with 1149 apk helper. To set no permissions, pass []. 1150 timeout: timeout in seconds 1151 retries: number of retries 1152 modules: An iterable containing specific bundle modules to install. 1153 Error if set and |apk| points to an APK instead of a bundle. 1154 fake_modules: An iterable containing specific bundle modules that should 1155 have their apks copied to |MODULES_SRC_DIRECTORY_PATH| subdirectory 1156 rather than installed. Thus the app can emulate SplitCompat while 1157 running. This should not have any overlap with |modules|. 1158 additional_locales: An iterable with additional locales to install for a 1159 bundle. 1160 1161 Raises: 1162 CommandFailedError if the installation fails. 1163 CommandTimeoutError if the installation times out. 1164 DeviceUnreachableError on missing device. 1165 """ 1166 apk = apk_helper.ToHelper(apk) 1167 modules_set = set(modules or []) 1168 fake_modules_set = set(fake_modules or []) 1169 assert modules_set.isdisjoint(fake_modules_set), ( 1170 'These modules overlap: %s' % (modules_set & fake_modules_set)) 1171 all_modules = modules_set | fake_modules_set 1172 package_name = apk.GetPackageName() 1173 1174 with apk.GetApkPaths(self, 1175 modules=all_modules, 1176 additional_locales=additional_locales) as apk_paths: 1177 if apk.SupportsSplits(): 1178 fake_apk_paths = self._GetFakeInstallPaths(apk_paths, fake_modules) 1179 self._FakeInstall(fake_apk_paths, fake_modules, package_name) 1180 apk_paths_to_install = [p for p in apk_paths if p not in fake_apk_paths] 1181 else: 1182 apk_paths_to_install = apk_paths 1183 self._InstallInternal( 1184 apk, 1185 apk_paths_to_install, 1186 allow_downgrade=allow_downgrade, 1187 reinstall=reinstall, 1188 permissions=permissions) 1189 1190 @staticmethod 1191 def _GetFakeInstallPaths(apk_paths, fake_modules): 1192 def IsFakeModulePath(path): 1193 filename = os.path.basename(path) 1194 return any(filename.startswith(f + '-') for f in fake_modules) 1195 1196 if not fake_modules: 1197 return set() 1198 return set(p for p in apk_paths if IsFakeModulePath(p)) 1199 1200 def _FakeInstall(self, fake_apk_paths, fake_modules, package_name): 1201 with tempfile_ext.NamedTemporaryDirectory() as modules_dir: 1202 device_dir = posixpath.join(self.MODULES_SRC_DIRECTORY_PATH, package_name) 1203 if not fake_modules: 1204 # Push empty module dir to clear device dir and update the cache. 1205 self.PushChangedFiles([(modules_dir, device_dir)], 1206 delete_device_stale=True) 1207 return 1208 1209 still_need_master = set(fake_modules) 1210 for path in fake_apk_paths: 1211 filename = os.path.basename(path) 1212 # Example names: base-en.apk, test_dummy-master.apk. 1213 module_name, suffix = filename.split('-', 1) 1214 if 'master' in suffix: 1215 assert module_name in still_need_master, ( 1216 'Duplicate master apk file for %s' % module_name) 1217 still_need_master.remove(module_name) 1218 new_filename = '%s.apk' % module_name 1219 else: 1220 # |suffix| includes .apk extension. 1221 new_filename = '%s.config.%s' % (module_name, suffix) 1222 new_path = os.path.join(modules_dir, new_filename) 1223 os.rename(path, new_path) 1224 1225 assert not still_need_master, ( 1226 'Missing master apk file for %s' % still_need_master) 1227 self.PushChangedFiles([(modules_dir, device_dir)], 1228 delete_device_stale=True) 1229 1230 @decorators.WithTimeoutAndRetriesFromInstance( 1231 min_default_timeout=INSTALL_DEFAULT_TIMEOUT) 1232 def InstallSplitApk(self, 1233 base_apk, 1234 split_apks, 1235 allow_downgrade=False, 1236 reinstall=False, 1237 allow_cached_props=False, 1238 permissions=None, 1239 timeout=None, 1240 retries=None): 1241 """Install a split APK. 1242 1243 Noop if all of the APK splits are already installed. 1244 1245 Args: 1246 base_apk: An ApkHelper instance or string containing the path to the base 1247 APK. 1248 split_apks: A list of strings of paths of all of the APK splits. 1249 allow_downgrade: A boolean indicating if we should allow downgrades. 1250 reinstall: A boolean indicating if we should keep any existing app data. 1251 allow_cached_props: Whether to use cached values for device properties. 1252 permissions: Set of permissions to set. If not set, finds permissions with 1253 apk helper. To set no permissions, pass []. 1254 timeout: timeout in seconds 1255 retries: number of retries 1256 1257 Raises: 1258 CommandFailedError if the installation fails. 1259 CommandTimeoutError if the installation times out. 1260 DeviceUnreachableError on missing device. 1261 DeviceVersionError if device SDK is less than Android L. 1262 """ 1263 apk = apk_helper.ToSplitHelper(base_apk, split_apks) 1264 with apk.GetApkPaths( 1265 self, allow_cached_props=allow_cached_props) as apk_paths: 1266 self._InstallInternal( 1267 apk, 1268 apk_paths, 1269 reinstall=reinstall, 1270 permissions=permissions, 1271 allow_downgrade=allow_downgrade) 1272 1273 def _InstallInternal(self, 1274 apk, 1275 apk_paths, 1276 allow_downgrade=False, 1277 reinstall=False, 1278 permissions=None): 1279 if not apk_paths: 1280 raise device_errors.CommandFailedError('Did not get any APKs to install') 1281 1282 if len(apk_paths) > 1: 1283 self._CheckSdkLevel(version_codes.LOLLIPOP) 1284 1285 missing_apks = [a for a in apk_paths if not os.path.exists(a)] 1286 if missing_apks: 1287 raise device_errors.CommandFailedError( 1288 'Attempted to install non-existent apks: %s' % 1289 pprint.pformat(missing_apks)) 1290 1291 package_name = apk.GetPackageName() 1292 device_apk_paths = self._GetApplicationPathsInternal(package_name) 1293 1294 host_checksums = None 1295 if not device_apk_paths: 1296 apks_to_install = apk_paths 1297 elif len(device_apk_paths) > 1 and len(apk_paths) == 1: 1298 logger.warning( 1299 'Installing non-split APK when split APK was previously installed') 1300 apks_to_install = apk_paths 1301 elif len(device_apk_paths) == 1 and len(apk_paths) > 1: 1302 logger.warning( 1303 'Installing split APK when non-split APK was previously installed') 1304 apks_to_install = apk_paths 1305 else: 1306 try: 1307 apks_to_install, host_checksums = (self._ComputeStaleApks( 1308 package_name, apk_paths)) 1309 except device_errors.CommandFailedError as e: 1310 logger.warning('Error calculating md5: %s', e) 1311 apks_to_install, host_checksums = apk_paths, None 1312 if apks_to_install and not reinstall: 1313 apks_to_install = apk_paths 1314 1315 if device_apk_paths and apks_to_install and not reinstall: 1316 self.Uninstall(package_name) 1317 1318 if apks_to_install: 1319 # Assume that we won't know the resulting device state. 1320 self._cache['package_apk_paths'].pop(package_name, 0) 1321 self._cache['package_apk_checksums'].pop(package_name, 0) 1322 partial = package_name if len(apks_to_install) < len(apk_paths) else None 1323 streaming = None 1324 if self.product_name in _NO_STREAMING_DEVICE_LIST: 1325 streaming = False 1326 if len(apks_to_install) > 1 or partial: 1327 self.adb.InstallMultiple( 1328 apks_to_install, 1329 partial=partial, 1330 reinstall=reinstall, 1331 streaming=streaming, 1332 allow_downgrade=allow_downgrade) 1333 else: 1334 self.adb.Install( 1335 apks_to_install[0], 1336 reinstall=reinstall, 1337 streaming=streaming, 1338 allow_downgrade=allow_downgrade) 1339 else: 1340 # Running adb install terminates running instances of the app, so to be 1341 # consistent, we explicitly terminate it when skipping the install. 1342 self.ForceStop(package_name) 1343 1344 # There have been cases of APKs not being detected after being explicitly 1345 # installed, so perform a sanity check now and fail early if the 1346 # installation somehow failed. 1347 apk_version = apk.GetVersionCode() 1348 if not self.IsApplicationInstalled(package_name, apk_version): 1349 raise device_errors.CommandFailedError( 1350 'Package %s with version %s not installed on device after explicit ' 1351 'install attempt.' % (package_name, apk_version)) 1352 1353 if (permissions is None 1354 and self.build_version_sdk >= version_codes.MARSHMALLOW): 1355 permissions = apk.GetPermissions() 1356 self.GrantPermissions(package_name, permissions) 1357 # Upon success, we know the device checksums, but not their paths. 1358 if host_checksums is not None: 1359 self._cache['package_apk_checksums'][package_name] = host_checksums 1360 1361 @decorators.WithTimeoutAndRetriesFromInstance() 1362 def Uninstall(self, package_name, keep_data=False, timeout=None, 1363 retries=None): 1364 """Remove the app |package_name| from the device. 1365 1366 This is a no-op if the app is not already installed. 1367 1368 Args: 1369 package_name: The package to uninstall. 1370 keep_data: (optional) Whether to keep the data and cache directories. 1371 timeout: Timeout in seconds. 1372 retries: Number of retries. 1373 1374 Raises: 1375 CommandFailedError if the uninstallation fails. 1376 CommandTimeoutError if the uninstallation times out. 1377 DeviceUnreachableError on missing device. 1378 """ 1379 installed = self._GetApplicationPathsInternal(package_name) 1380 if not installed: 1381 return 1382 # cached package paths are indeterminate due to system apps taking over 1383 # user apps after uninstall, so clear it 1384 self._cache['package_apk_paths'].pop(package_name, 0) 1385 self._cache['package_apk_checksums'].pop(package_name, 0) 1386 self.adb.Uninstall(package_name, keep_data) 1387 1388 def _CheckSdkLevel(self, required_sdk_level): 1389 """Raises an exception if the device does not have the required SDK level. 1390 """ 1391 if self.build_version_sdk < required_sdk_level: 1392 raise device_errors.DeviceVersionError( 1393 ('Requires SDK level %s, device is SDK level %s' % 1394 (required_sdk_level, self.build_version_sdk)), 1395 device_serial=self.serial) 1396 1397 @decorators.WithTimeoutAndRetriesFromInstance() 1398 def RunShellCommand(self, 1399 cmd, 1400 shell=False, 1401 check_return=False, 1402 cwd=None, 1403 env=None, 1404 run_as=None, 1405 as_root=False, 1406 single_line=False, 1407 large_output=False, 1408 raw_output=False, 1409 timeout=None, 1410 retries=None): 1411 """Run an ADB shell command. 1412 1413 The command to run |cmd| should be a sequence of program arguments 1414 (preferred) or a single string with a shell script to run. 1415 1416 When |cmd| is a sequence, it is assumed to contain the name of the command 1417 to run followed by its arguments. In this case, arguments are passed to the 1418 command exactly as given, preventing any further processing by the shell. 1419 This allows callers to easily pass arguments with spaces or special 1420 characters without having to worry about quoting rules. Whenever possible, 1421 it is recomended to pass |cmd| as a sequence. 1422 1423 When |cmd| is passed as a single string, |shell| should be set to True. 1424 The command will be interpreted and run by the shell on the device, 1425 allowing the use of shell features such as pipes, wildcards, or variables. 1426 Failing to set shell=True will issue a warning, but this will be changed 1427 to a hard failure in the future (see: catapult:#3242). 1428 1429 This behaviour is consistent with that of command runners in cmd_helper as 1430 well as Python's own subprocess.Popen. 1431 1432 TODO(crbug.com/1029769) Change the default of |check_return| to True when 1433 callers have switched to the new behaviour. 1434 1435 Args: 1436 cmd: A sequence containing the command to run and its arguments, or a 1437 string with a shell script to run (should also set shell=True). 1438 shell: A boolean indicating whether shell features may be used in |cmd|. 1439 check_return: A boolean indicating whether or not the return code should 1440 be checked. 1441 cwd: The device directory in which the command should be run. 1442 env: The environment variables with which the command should be run. 1443 run_as: A string containing the package as which the command should be 1444 run. 1445 as_root: A boolean indicating whether the shell command should be run 1446 with root privileges. 1447 single_line: A boolean indicating if only a single line of output is 1448 expected. 1449 large_output: Uses a work-around for large shell command output. Without 1450 this large output will be truncated. 1451 raw_output: Whether to only return the raw output 1452 (no splitting into lines). 1453 timeout: timeout in seconds 1454 retries: number of retries 1455 1456 Returns: 1457 If single_line is False, the output of the command as a list of lines, 1458 otherwise, a string with the unique line of output emmited by the command 1459 (with the optional newline at the end stripped). 1460 1461 Raises: 1462 AdbCommandFailedError if check_return is True and the exit code of 1463 the command run on the device is non-zero. 1464 CommandFailedError if single_line is True but the output contains two or 1465 more lines. 1466 CommandTimeoutError on timeout. 1467 DeviceUnreachableError on missing device. 1468 """ 1469 1470 def env_quote(key, value): 1471 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): 1472 raise KeyError('Invalid shell variable name %r' % key) 1473 # using double quotes here to allow interpolation of shell variables 1474 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) 1475 1476 def run(cmd): 1477 return self.adb.Shell(cmd) 1478 1479 def handle_check_return(cmd): 1480 try: 1481 return run(cmd) 1482 except device_errors.AdbCommandFailedError as exc: 1483 if check_return: 1484 raise 1485 else: 1486 return exc.output 1487 1488 def handle_large_command(cmd): 1489 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH: 1490 return handle_check_return(cmd) 1491 else: 1492 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: 1493 self._WriteFileWithPush(script.name, cmd) 1494 logger.debug('Large shell command will be run from file: %s ...', 1495 cmd[:self._MAX_ADB_COMMAND_LENGTH]) 1496 return handle_check_return('sh %s' % script.name_quoted) 1497 1498 def handle_large_output(cmd, large_output_mode): 1499 if large_output_mode: 1500 with device_temp_file.DeviceTempFile(self.adb) as large_output_file: 1501 large_output_cmd = '( %s )>%s 2>&1' % (cmd, large_output_file.name) 1502 logger.debug('Large output mode enabled. Will write output to ' 1503 'device and read results from file.') 1504 try: 1505 handle_large_command(large_output_cmd) 1506 return self.ReadFile(large_output_file.name, force_pull=True) 1507 except device_errors.AdbShellCommandFailedError as exc: 1508 output = self.ReadFile(large_output_file.name, force_pull=True) 1509 raise device_errors.AdbShellCommandFailedError( 1510 cmd, output, exc.status, exc.device_serial) 1511 else: 1512 try: 1513 return handle_large_command(cmd) 1514 except device_errors.AdbCommandFailedError as exc: 1515 if exc.status is None: 1516 logger.error(_FormatPartialOutputError(exc.output)) 1517 logger.warning('Attempting to run in large_output mode.') 1518 logger.warning('Use RunShellCommand(..., large_output=True) for ' 1519 'shell commands that expect a lot of output.') 1520 return handle_large_output(cmd, True) 1521 else: 1522 raise 1523 1524 if isinstance(cmd, basestring): 1525 if not shell: 1526 # TODO(crbug.com/1029769): Make this an error instead. 1527 logger.warning( 1528 'The command to run should preferably be passed as a sequence of' 1529 ' args. If shell features are needed (pipes, wildcards, variables)' 1530 ' clients should explicitly set shell=True.') 1531 else: 1532 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) 1533 if env: 1534 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) 1535 cmd = '%s %s' % (env, cmd) 1536 if cwd: 1537 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) 1538 if run_as: 1539 cmd = 'run-as %s sh -c %s' % (cmd_helper.SingleQuote(run_as), 1540 cmd_helper.SingleQuote(cmd)) 1541 if as_root: 1542 # Explicitly check the root status as the device may have lost it after 1543 # reboot. 1544 # For devices with user build, if the first root attempt fails, a warning 1545 # will be issued and the following root attempts will be skipped because 1546 # some commands that set as_root as True may still work without the root 1547 # privilege. 1548 if not self.HasRoot() and not self._skip_root_user_build: 1549 try: 1550 self.EnableRoot() 1551 except device_errors.RootUserBuildError as e: 1552 logger.warning('%s The adb shell command to run may fail with ' 1553 'permission issues.', str(e)) 1554 self._skip_root_user_build = True 1555 1556 if (as_root is _FORCE_SU) or self.NeedsSU(): 1557 # "su -c sh -c" allows using shell features in |cmd| 1558 cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd)) 1559 1560 output = handle_large_output(cmd, large_output) 1561 1562 if raw_output: 1563 return output 1564 1565 output = output.splitlines() 1566 if single_line: 1567 if not output: 1568 return '' 1569 elif len(output) == 1: 1570 return output[0] 1571 else: 1572 msg = 'one line of output was expected, but got: %s' 1573 raise device_errors.CommandFailedError(msg % output, str(self)) 1574 else: 1575 return output 1576 1577 def _RunPipedShellCommand(self, script, **kwargs): 1578 PIPESTATUS_LEADER = 'PIPESTATUS: ' 1579 1580 script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER 1581 kwargs.update(shell=True, check_return=True) 1582 output = self.RunShellCommand(script, **kwargs) 1583 pipestatus_line = output[-1] 1584 1585 if not pipestatus_line.startswith(PIPESTATUS_LEADER): 1586 logger.error('Pipe exit statuses of shell script missing.') 1587 raise device_errors.AdbShellCommandFailedError( 1588 script, output, status=None, device_serial=self.serial) 1589 1590 output = output[:-1] 1591 statuses = [ 1592 int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split() 1593 ] 1594 if any(statuses): 1595 raise device_errors.AdbShellCommandFailedError( 1596 script, output, status=statuses, device_serial=self.serial) 1597 return output 1598 1599 @decorators.WithTimeoutAndRetriesFromInstance() 1600 def KillAll(self, 1601 process_name, 1602 exact=False, 1603 signum=device_signal.SIGKILL, 1604 as_root=False, 1605 blocking=False, 1606 quiet=False, 1607 timeout=None, 1608 retries=None): 1609 """Kill all processes with the given name on the device. 1610 1611 Args: 1612 process_name: A string containing the name of the process to kill. 1613 exact: A boolean indicating whether to kill all processes matching 1614 the string |process_name| exactly, or all of those which contain 1615 |process_name| as a substring. Defaults to False. 1616 signum: An integer containing the signal number to send to kill. Defaults 1617 to SIGKILL (9). 1618 as_root: A boolean indicating whether the kill should be executed with 1619 root privileges. 1620 blocking: A boolean indicating whether we should wait until all processes 1621 with the given |process_name| are dead. 1622 quiet: A boolean indicating whether to ignore the fact that no processes 1623 to kill were found. 1624 timeout: timeout in seconds 1625 retries: number of retries 1626 1627 Returns: 1628 The number of processes attempted to kill. 1629 1630 Raises: 1631 CommandFailedError if no process was killed and |quiet| is False. 1632 CommandTimeoutError on timeout. 1633 DeviceUnreachableError on missing device. 1634 """ 1635 processes = self.ListProcesses(process_name) 1636 if exact: 1637 processes = [p for p in processes if p.name == process_name] 1638 if not processes: 1639 if quiet: 1640 return 0 1641 else: 1642 raise device_errors.CommandFailedError( 1643 'No processes matching %r (exact=%r)' % (process_name, exact), 1644 str(self)) 1645 1646 logger.info('KillAll(%r, ...) attempting to kill the following:', 1647 process_name) 1648 for p in processes: 1649 logger.info(' %05d %s', p.pid, p.name) 1650 1651 pids = set(p.pid for p in processes) 1652 cmd = ['kill', '-%d' % signum] + sorted(str(p) for p in pids) 1653 self.RunShellCommand(cmd, as_root=as_root, check_return=True) 1654 1655 def all_pids_killed(): 1656 pids_left = (p.pid for p in self.ListProcesses(process_name)) 1657 return not pids.intersection(pids_left) 1658 1659 if blocking: 1660 timeout_retry.WaitFor(all_pids_killed, wait_period=0.1) 1661 1662 return len(pids) 1663 1664 @decorators.WithTimeoutAndRetriesFromInstance() 1665 def StartActivity(self, 1666 intent_obj, 1667 blocking=False, 1668 trace_file_name=None, 1669 force_stop=False, 1670 timeout=None, 1671 retries=None): 1672 """Start package's activity on the device. 1673 1674 Args: 1675 intent_obj: An Intent object to send. 1676 blocking: A boolean indicating whether we should wait for the activity to 1677 finish launching. 1678 trace_file_name: If present, a string that both indicates that we want to 1679 profile the activity and contains the path to which the 1680 trace should be saved. 1681 force_stop: A boolean indicating whether we should stop the activity 1682 before starting it. 1683 timeout: timeout in seconds 1684 retries: number of retries 1685 1686 Raises: 1687 CommandFailedError if the activity could not be started. 1688 CommandTimeoutError on timeout. 1689 DeviceUnreachableError on missing device. 1690 """ 1691 cmd = ['am', 'start'] 1692 if blocking: 1693 cmd.append('-W') 1694 if trace_file_name: 1695 cmd.extend(['--start-profiler', trace_file_name]) 1696 if force_stop: 1697 cmd.append('-S') 1698 cmd.extend(intent_obj.am_args) 1699 for line in self.RunShellCommand(cmd, check_return=True): 1700 if line.startswith('Error:'): 1701 raise device_errors.CommandFailedError(line, str(self)) 1702 1703 @decorators.WithTimeoutAndRetriesFromInstance() 1704 def StartService(self, intent_obj, user_id=None, timeout=None, retries=None): 1705 """Start a service on the device. 1706 1707 Args: 1708 intent_obj: An Intent object to send describing the service to start. 1709 user_id: A specific user to start the service as, defaults to current. 1710 timeout: Timeout in seconds. 1711 retries: Number of retries 1712 1713 Raises: 1714 CommandFailedError if the service could not be started. 1715 CommandTimeoutError on timeout. 1716 DeviceUnreachableError on missing device. 1717 """ 1718 # For whatever reason, startservice was changed to start-service on O and 1719 # above. 1720 cmd = ['am', 'startservice'] 1721 if self.build_version_sdk >= version_codes.OREO: 1722 cmd[1] = 'start-service' 1723 if user_id: 1724 cmd.extend(['--user', str(user_id)]) 1725 cmd.extend(intent_obj.am_args) 1726 for line in self.RunShellCommand(cmd, check_return=True): 1727 if line.startswith('Error:'): 1728 raise device_errors.CommandFailedError(line, str(self)) 1729 1730 @decorators.WithTimeoutAndRetriesFromInstance() 1731 def StartInstrumentation(self, 1732 component, 1733 finish=True, 1734 raw=False, 1735 extras=None, 1736 timeout=None, 1737 retries=None): 1738 if extras is None: 1739 extras = {} 1740 1741 cmd = ['am', 'instrument'] 1742 if finish: 1743 cmd.append('-w') 1744 if raw: 1745 cmd.append('-r') 1746 for k, v in extras.iteritems(): 1747 cmd.extend(['-e', str(k), str(v)]) 1748 cmd.append(component) 1749 1750 # Store the package name in a shell variable to help the command stay under 1751 # the _MAX_ADB_COMMAND_LENGTH limit. 1752 package = component.split('/')[0] 1753 shell_snippet = 'p=%s;%s' % (package, 1754 cmd_helper.ShrinkToSnippet(cmd, 'p', package)) 1755 return self.RunShellCommand( 1756 shell_snippet, shell=True, check_return=True, large_output=True) 1757 1758 @decorators.WithTimeoutAndRetriesFromInstance() 1759 def BroadcastIntent(self, intent_obj, timeout=None, retries=None): 1760 """Send a broadcast intent. 1761 1762 Args: 1763 intent: An Intent to broadcast. 1764 timeout: timeout in seconds 1765 retries: number of retries 1766 1767 Raises: 1768 CommandTimeoutError on timeout. 1769 DeviceUnreachableError on missing device. 1770 """ 1771 cmd = ['am', 'broadcast'] + intent_obj.am_args 1772 self.RunShellCommand(cmd, check_return=True) 1773 1774 @decorators.WithTimeoutAndRetriesFromInstance() 1775 def GoHome(self, timeout=None, retries=None): 1776 """Return to the home screen and obtain launcher focus. 1777 1778 This command launches the home screen and attempts to obtain 1779 launcher focus until the timeout is reached. 1780 1781 Args: 1782 timeout: timeout in seconds 1783 retries: number of retries 1784 1785 Raises: 1786 CommandTimeoutError on timeout. 1787 DeviceUnreachableError on missing device. 1788 """ 1789 1790 def is_launcher_focused(): 1791 output = self.RunShellCommand(['dumpsys', 'window', 'windows'], 1792 check_return=True, 1793 large_output=True) 1794 return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output) 1795 1796 def dismiss_popups(): 1797 # There is a dialog present; attempt to get rid of it. 1798 # Not all dialogs can be dismissed with back. 1799 self.SendKeyEvent(keyevent.KEYCODE_ENTER) 1800 self.SendKeyEvent(keyevent.KEYCODE_BACK) 1801 return is_launcher_focused() 1802 1803 # If Home is already focused, return early to avoid unnecessary work. 1804 if is_launcher_focused(): 1805 return 1806 1807 self.StartActivity( 1808 intent.Intent( 1809 action='android.intent.action.MAIN', 1810 category='android.intent.category.HOME'), 1811 blocking=True) 1812 1813 if not is_launcher_focused(): 1814 timeout_retry.WaitFor(dismiss_popups, wait_period=1) 1815 1816 @decorators.WithTimeoutAndRetriesFromInstance() 1817 def ForceStop(self, package, timeout=None, retries=None): 1818 """Close the application. 1819 1820 Args: 1821 package: A string containing the name of the package to stop. 1822 timeout: timeout in seconds 1823 retries: number of retries 1824 1825 Raises: 1826 CommandTimeoutError on timeout. 1827 DeviceUnreachableError on missing device. 1828 """ 1829 if self.GetApplicationPids(package): 1830 self.RunShellCommand(['am', 'force-stop', package], check_return=True) 1831 1832 @decorators.WithTimeoutAndRetriesFromInstance() 1833 def ClearApplicationState(self, 1834 package, 1835 permissions=None, 1836 timeout=None, 1837 retries=None): 1838 """Clear all state for the given package. 1839 1840 Args: 1841 package: A string containing the name of the package to stop. 1842 permissions: List of permissions to set after clearing data. 1843 timeout: timeout in seconds 1844 retries: number of retries 1845 1846 Raises: 1847 CommandTimeoutError on timeout. 1848 DeviceUnreachableError on missing device. 1849 """ 1850 # Check that the package exists before clearing it for android builds below 1851 # JB MR2. Necessary because calling pm clear on a package that doesn't exist 1852 # may never return. 1853 if ((self.build_version_sdk >= version_codes.JELLY_BEAN_MR2) 1854 or self._GetApplicationPathsInternal(package)): 1855 self.RunShellCommand(['pm', 'clear', package], check_return=True) 1856 self.GrantPermissions(package, permissions) 1857 1858 @decorators.WithTimeoutAndRetriesFromInstance() 1859 def SendKeyEvent(self, keycode, timeout=None, retries=None): 1860 """Sends a keycode to the device. 1861 1862 See the devil.android.sdk.keyevent module for suitable keycode values. 1863 1864 Args: 1865 keycode: A integer keycode to send to the device. 1866 timeout: timeout in seconds 1867 retries: number of retries 1868 1869 Raises: 1870 CommandTimeoutError on timeout. 1871 DeviceUnreachableError on missing device. 1872 """ 1873 self.RunShellCommand( 1874 ['input', 'keyevent', format(keycode, 'd')], check_return=True) 1875 1876 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT 1877 1878 @decorators.WithTimeoutAndRetriesFromInstance( 1879 min_default_timeout=PUSH_CHANGED_FILES_DEFAULT_TIMEOUT) 1880 def PushChangedFiles(self, 1881 host_device_tuples, 1882 delete_device_stale=False, 1883 timeout=None, 1884 retries=None): 1885 """Push files to the device, skipping files that don't need updating. 1886 1887 When a directory is pushed, it is traversed recursively on the host and 1888 all files in it are pushed to the device as needed. 1889 Additionally, if delete_device_stale option is True, 1890 files that exist on the device but don't exist on the host are deleted. 1891 1892 Args: 1893 host_device_tuples: A list of (host_path, device_path) tuples, where 1894 |host_path| is an absolute path of a file or directory on the host 1895 that should be minimially pushed to the device, and |device_path| is 1896 an absolute path of the destination on the device. 1897 delete_device_stale: option to delete stale files on device 1898 timeout: timeout in seconds 1899 retries: number of retries 1900 1901 Raises: 1902 CommandFailedError on failure. 1903 CommandTimeoutError on timeout. 1904 DeviceUnreachableError on missing device. 1905 """ 1906 # TODO(crbug.com/1005504): Experiment with this on physical devices after 1907 # upgrading devil's default adb beyond 1.0.39. 1908 # TODO(crbug.com/1020716): disabled as can result in extra directory. 1909 enable_push_sync = False 1910 1911 if enable_push_sync: 1912 try: 1913 self._PushChangedFilesSync(host_device_tuples) 1914 return 1915 except device_errors.AdbVersionError as e: 1916 # If we don't meet the adb requirements, fall back to the previous 1917 # sync-unaware implementation. 1918 logging.warning(str(e)) 1919 1920 changed_files, missing_dirs, cache_commit_func = (self._GetChangedFiles( 1921 host_device_tuples, delete_device_stale)) 1922 1923 if changed_files: 1924 if missing_dirs: 1925 self.RunShellCommand(['mkdir', '-p'] + list(missing_dirs), 1926 check_return=True) 1927 self._PushFilesImpl(host_device_tuples, changed_files) 1928 cache_commit_func() 1929 1930 def _PushChangedFilesSync(self, host_device_tuples): 1931 """Push changed files via `adb sync`. 1932 1933 Args: 1934 host_device_tuples: Same as PushChangedFiles. 1935 """ 1936 for h, d in host_device_tuples: 1937 for ph, pd, _ in _IterPushableComponents(h, d): 1938 self.adb.Push(ph, pd, sync=True) 1939 1940 1941 def _GetDeviceNodes(self, paths): 1942 """Get the set of all files and directories on the device contained within 1943 the provided list of paths, without recursively expanding directories. 1944 1945 Args: 1946 paths: The list of paths for which to list files and directories. 1947 1948 Returns: 1949 a set containing all files and directories contained within |paths| on the 1950 device. 1951 """ 1952 nodes = set() 1953 paths = [p.replace(' ', r'\ ') for p in paths] 1954 command = _FILE_LIST_SCRIPT % ' '.join(paths) 1955 current_path = "" 1956 # We use shell=True to evaluate the command as a script through the shell, 1957 # otherwise RunShellCommand tries to interpret it as the name of a (non 1958 # existent) command to run. 1959 for line in self.RunShellCommand(command, shell=True, check_return=True): 1960 # If the line is an absolute path it's a directory, otherwise it's a file 1961 # within the most recent directory. 1962 if posixpath.isabs(line): 1963 current_path = line + '/' 1964 else: 1965 line = current_path + line 1966 nodes.add(line) 1967 1968 return nodes 1969 1970 def _GetChangedFiles(self, host_device_tuples, delete_stale=False): 1971 """Get files to push and delete. 1972 1973 Args: 1974 host_device_tuples: a list of (host_files_path, device_files_path) tuples 1975 to find changed files from 1976 delete_stale: Whether to delete stale files 1977 1978 Returns: 1979 a three-element tuple 1980 1st element: a list of (host_files_path, device_files_path) tuples to push 1981 2nd element: a list of missing device directories to mkdir 1982 3rd element: a cache commit function 1983 """ 1984 # The fully expanded list of host/device tuples of files to push. 1985 file_tuples = [] 1986 # All directories we're pushing files to. 1987 device_dirs_to_push_to = set() 1988 # All files and directories we expect to have on the device after pushing 1989 # files. 1990 expected_device_nodes = set() 1991 1992 for h, d in host_device_tuples: 1993 assert os.path.isabs(h) and posixpath.isabs(d) 1994 h = os.path.realpath(h) 1995 host_path = h.rstrip('/') 1996 device_dir = d.rstrip('/') 1997 1998 expected_device_nodes.add(device_dir) 1999 2000 # Add all parent directories to the directories we expect to have so we 2001 # don't delete empty nested directories. 2002 parent = posixpath.dirname(device_dir) 2003 while parent and parent != '/': 2004 expected_device_nodes.add(parent) 2005 parent = posixpath.dirname(parent) 2006 2007 if os.path.isdir(host_path): 2008 device_dirs_to_push_to.add(device_dir) 2009 for root, _, filenames in os.walk(host_path): 2010 # ignore hidden directories 2011 if os.path.sep + '.' in root: 2012 continue 2013 relative_dir = os.path.relpath(root, host_path).rstrip('.') 2014 device_path = posixpath.join(device_dir, relative_dir).rstrip('/') 2015 expected_device_nodes.add(device_path) 2016 device_dirs_to_push_to.add(device_path) 2017 files = ( 2018 [posixpath.join(device_dir, relative_dir, f) for f in filenames]) 2019 expected_device_nodes.update(files) 2020 file_tuples.extend(zip( 2021 (os.path.join(root, f) for f in filenames), files)) 2022 else: 2023 device_dirs_to_push_to.add(posixpath.dirname(device_dir)) 2024 file_tuples.append((host_path, device_dir)) 2025 2026 if file_tuples or delete_stale: 2027 current_device_nodes = self._GetDeviceNodes(device_dirs_to_push_to) 2028 nodes_to_delete = current_device_nodes - expected_device_nodes 2029 2030 missing_dirs = device_dirs_to_push_to - current_device_nodes 2031 2032 if not file_tuples: 2033 if delete_stale and nodes_to_delete: 2034 self.RemovePath(nodes_to_delete, force=True, recursive=True) 2035 return (host_device_tuples, missing_dirs, lambda: 0) 2036 2037 possibly_stale_device_nodes = current_device_nodes - nodes_to_delete 2038 possibly_stale_tuples = ( 2039 [t for t in file_tuples if t[1] in possibly_stale_device_nodes]) 2040 2041 def calculate_host_checksums(): 2042 # Need to compute all checksums when caching. 2043 if self._enable_device_files_cache: 2044 return md5sum.CalculateHostMd5Sums([t[0] for t in file_tuples]) 2045 else: 2046 return md5sum.CalculateHostMd5Sums( 2047 [t[0] for t in possibly_stale_tuples]) 2048 2049 def calculate_device_checksums(): 2050 paths = set([t[1] for t in possibly_stale_tuples]) 2051 if not paths: 2052 return dict() 2053 sums = dict() 2054 if self._enable_device_files_cache: 2055 paths_not_in_cache = set() 2056 for path in paths: 2057 cache_entry = self._cache['device_path_checksums'].get(path) 2058 if cache_entry: 2059 sums[path] = cache_entry 2060 else: 2061 paths_not_in_cache.add(path) 2062 paths = paths_not_in_cache 2063 sums.update(dict(md5sum.CalculateDeviceMd5Sums(paths, self))) 2064 if self._enable_device_files_cache: 2065 for path, checksum in sums.iteritems(): 2066 self._cache['device_path_checksums'][path] = checksum 2067 return sums 2068 try: 2069 host_checksums, device_checksums = reraiser_thread.RunAsync( 2070 (calculate_host_checksums, calculate_device_checksums)) 2071 except device_errors.CommandFailedError as e: 2072 logger.warning('Error calculating md5: %s', e) 2073 return (host_device_tuples, set(), lambda: 0) 2074 2075 up_to_date = set() 2076 2077 for host_path, device_path in possibly_stale_tuples: 2078 device_checksum = device_checksums.get(device_path, None) 2079 host_checksum = host_checksums.get(host_path, None) 2080 if device_checksum == host_checksum and device_checksum is not None: 2081 up_to_date.add(device_path) 2082 else: 2083 nodes_to_delete.add(device_path) 2084 2085 if delete_stale and nodes_to_delete: 2086 self.RemovePath(nodes_to_delete, force=True, recursive=True) 2087 2088 to_push = ( 2089 [t for t in file_tuples if t[1] not in up_to_date]) 2090 2091 def cache_commit_func(): 2092 if not self._enable_device_files_cache: 2093 return 2094 for host_path, device_path in file_tuples: 2095 host_checksum = host_checksums.get(host_path, None) 2096 self._cache['device_path_checksums'][device_path] = host_checksum 2097 2098 return (to_push, missing_dirs, cache_commit_func) 2099 2100 def _ComputeDeviceChecksumsForApks(self, package_name): 2101 ret = self._cache['package_apk_checksums'].get(package_name) 2102 if ret is None: 2103 if self.PathExists('/data/data/' + package_name, as_root=True): 2104 device_paths = self._GetApplicationPathsInternal(package_name) 2105 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self) 2106 ret = set(file_to_checksums.values()) 2107 else: 2108 logger.info('Cannot reuse package %s (data directory missing)', 2109 package_name) 2110 ret = set() 2111 self._cache['package_apk_checksums'][package_name] = ret 2112 return ret 2113 2114 def _ComputeStaleApks(self, package_name, host_apk_paths): 2115 def calculate_host_checksums(): 2116 return md5sum.CalculateHostMd5Sums(host_apk_paths) 2117 2118 def calculate_device_checksums(): 2119 return self._ComputeDeviceChecksumsForApks(package_name) 2120 2121 host_checksums, device_checksums = reraiser_thread.RunAsync( 2122 (calculate_host_checksums, calculate_device_checksums)) 2123 stale_apks = [ 2124 k for (k, v) in host_checksums.iteritems() if v not in device_checksums 2125 ] 2126 return stale_apks, set(host_checksums.values()) 2127 2128 def _PushFilesImpl(self, host_device_tuples, files): 2129 if not files: 2130 return 2131 2132 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) 2133 file_count = len(files) 2134 dir_size = sum( 2135 host_utils.GetRecursiveDiskUsage(h) for h, _ in host_device_tuples) 2136 dir_file_count = 0 2137 for h, _ in host_device_tuples: 2138 if os.path.isdir(h): 2139 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) 2140 else: 2141 dir_file_count += 1 2142 2143 push_duration = self._ApproximateDuration(file_count, file_count, size, 2144 False) 2145 dir_push_duration = self._ApproximateDuration( 2146 len(host_device_tuples), dir_file_count, dir_size, False) 2147 zip_duration = self._ApproximateDuration(1, 1, size, True) 2148 2149 if (dir_push_duration < push_duration and dir_push_duration < zip_duration 2150 # TODO(jbudorick): Resume directory pushing once clients have switched 2151 # to 1.0.36-compatible syntax. 2152 and False): 2153 self._PushChangedFilesIndividually(host_device_tuples) 2154 elif push_duration < zip_duration: 2155 self._PushChangedFilesIndividually(files) 2156 elif self._commands_installed is False: 2157 # Already tried and failed to install unzip command. 2158 self._PushChangedFilesIndividually(files) 2159 elif not self._PushChangedFilesZipped(files, 2160 [d for _, d in host_device_tuples]): 2161 self._PushChangedFilesIndividually(files) 2162 2163 def _MaybeInstallCommands(self): 2164 if self._commands_installed is None: 2165 try: 2166 if not install_commands.Installed(self): 2167 install_commands.InstallCommands(self) 2168 self._commands_installed = True 2169 except device_errors.CommandFailedError as e: 2170 logger.warning('unzip not available: %s', str(e)) 2171 self._commands_installed = False 2172 return self._commands_installed 2173 2174 @staticmethod 2175 def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping): 2176 # We approximate the time to push a set of files to a device as: 2177 # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where 2178 # t: total time (sec) 2179 # c1: adb call time delay (sec) 2180 # a: number of times adb is called (unitless) 2181 # c2: push time delay (sec) 2182 # f: number of files pushed via adb (unitless) 2183 # c3: zip time delay (sec) 2184 # c4: zip rate (bytes/sec) 2185 # b: total number of bytes (bytes) 2186 # c5: transfer rate (bytes/sec) 2187 # c6: compression ratio (unitless) 2188 2189 # All of these are approximations. 2190 ADB_CALL_PENALTY = 0.1 # seconds 2191 ADB_PUSH_PENALTY = 0.01 # seconds 2192 ZIP_PENALTY = 2.0 # seconds 2193 ZIP_RATE = 10000000.0 # bytes / second 2194 TRANSFER_RATE = 2000000.0 # bytes / second 2195 COMPRESSION_RATIO = 2.0 # unitless 2196 2197 adb_call_time = ADB_CALL_PENALTY * adb_calls 2198 adb_push_setup_time = ADB_PUSH_PENALTY * file_count 2199 if is_zipping: 2200 zip_time = ZIP_PENALTY + byte_count / ZIP_RATE 2201 transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO) 2202 else: 2203 zip_time = 0 2204 transfer_time = byte_count / TRANSFER_RATE 2205 return adb_call_time + adb_push_setup_time + zip_time + transfer_time 2206 2207 def _PushChangedFilesIndividually(self, files): 2208 for h, d in files: 2209 self.adb.Push(h, d) 2210 2211 def _PushChangedFilesZipped(self, files, dirs): 2212 if not self._MaybeInstallCommands(): 2213 return False 2214 2215 with tempfile_ext.NamedTemporaryDirectory() as working_dir: 2216 zip_path = os.path.join(working_dir, 'tmp.zip') 2217 try: 2218 zip_utils.WriteZipFile(zip_path, files) 2219 except zip_utils.ZipFailedError: 2220 return False 2221 2222 logger.info('Pushing %d files via .zip of size %d', len(files), 2223 os.path.getsize(zip_path)) 2224 self.NeedsSU() 2225 with device_temp_file.DeviceTempFile( 2226 self.adb, suffix='.zip') as device_temp: 2227 self.adb.Push(zip_path, device_temp.name) 2228 2229 quoted_dirs = ' '.join(cmd_helper.SingleQuote(d) for d in dirs) 2230 self.RunShellCommand( 2231 'unzip %s&&chmod -R 777 %s' % (device_temp.name, quoted_dirs), 2232 shell=True, 2233 as_root=True, 2234 env={'PATH': '%s:$PATH' % install_commands.BIN_DIR}, 2235 check_return=True) 2236 2237 return True 2238 2239 # TODO(crbug.com/1111556): remove this and migrate the callsite to 2240 # PathExists(). 2241 @decorators.WithTimeoutAndRetriesFromInstance() 2242 def FileExists(self, device_path, timeout=None, retries=None): 2243 """Checks whether the given file exists on the device. 2244 2245 Arguments are the same as PathExists. 2246 """ 2247 return self.PathExists(device_path, timeout=timeout, retries=retries) 2248 2249 @decorators.WithTimeoutAndRetriesFromInstance() 2250 def PathExists(self, device_paths, as_root=False, timeout=None, retries=None): 2251 """Checks whether the given path(s) exists on the device. 2252 2253 Args: 2254 device_path: A string containing the absolute path to the file on the 2255 device, or an iterable of paths to check. 2256 as_root: Whether root permissions should be use to check for the existence 2257 of the given path(s). 2258 timeout: timeout in seconds 2259 retries: number of retries 2260 2261 Returns: 2262 True if the all given paths exist on the device, False otherwise. 2263 2264 Raises: 2265 CommandTimeoutError on timeout. 2266 DeviceUnreachableError on missing device. 2267 """ 2268 paths = device_paths 2269 if isinstance(paths, basestring): 2270 paths = (paths, ) 2271 if not paths: 2272 return True 2273 cmd = ['test', '-e', paths[0]] 2274 for p in paths[1:]: 2275 cmd.extend(['-a', '-e', p]) 2276 try: 2277 self.RunShellCommand( 2278 cmd, 2279 as_root=as_root, 2280 check_return=True, 2281 timeout=timeout, 2282 retries=retries) 2283 return True 2284 except device_errors.CommandFailedError: 2285 return False 2286 2287 @decorators.WithTimeoutAndRetriesFromInstance() 2288 def RemovePath(self, 2289 device_path, 2290 force=False, 2291 recursive=False, 2292 as_root=False, 2293 rename=False, 2294 timeout=None, 2295 retries=None): 2296 """Removes the given path(s) from the device. 2297 2298 Args: 2299 device_path: A string containing the absolute path to the file on the 2300 device, or an iterable of paths to check. 2301 force: Whether to remove the path(s) with force (-f). 2302 recursive: Whether to remove any directories in the path(s) recursively. 2303 as_root: Whether root permissions should be use to remove the given 2304 path(s). 2305 rename: Whether to rename the path(s) before removing to help avoid 2306 filesystem errors. See https://stackoverflow.com/questions/11539657 2307 timeout: timeout in seconds 2308 retries: number of retries 2309 """ 2310 2311 def _RenamePath(path): 2312 random_suffix = hex(random.randint(2**12, 2**16 - 1))[2:] 2313 dest = '%s-%s' % (path, random_suffix) 2314 try: 2315 self.RunShellCommand(['mv', path, dest], 2316 as_root=as_root, 2317 check_return=True) 2318 return dest 2319 except device_errors.AdbShellCommandFailedError: 2320 # If it couldn't be moved, just try rm'ing the original path instead. 2321 return path 2322 2323 args = ['rm'] 2324 if force: 2325 args.append('-f') 2326 if recursive: 2327 args.append('-r') 2328 if isinstance(device_path, basestring): 2329 args.append(device_path if not rename else _RenamePath(device_path)) 2330 else: 2331 args.extend( 2332 device_path if not rename else [_RenamePath(p) for p in device_path]) 2333 self.RunShellCommand(args, as_root=as_root, check_return=True) 2334 2335 @contextlib.contextmanager 2336 def _CopyToReadableLocation(self, device_path): 2337 """Context manager to copy a file to a globally readable temp file. 2338 2339 This uses root permission to copy a file to a globally readable named 2340 temporary file. The temp file is removed when this contextmanager is closed. 2341 2342 Args: 2343 device_path: A string containing the absolute path of the file (on the 2344 device) to copy. 2345 Yields: 2346 The globally readable file object. 2347 """ 2348 with device_temp_file.DeviceTempFile(self.adb) as device_temp: 2349 cmd = 'SRC=%s DEST=%s;cp "$SRC" "$DEST" && chmod 666 "$DEST"' % ( 2350 cmd_helper.SingleQuote(device_path), 2351 cmd_helper.SingleQuote(device_temp.name)) 2352 self.RunShellCommand(cmd, shell=True, as_root=True, check_return=True) 2353 yield device_temp 2354 2355 @decorators.WithTimeoutAndRetriesFromInstance() 2356 def PullFile(self, 2357 device_path, 2358 host_path, 2359 as_root=False, 2360 timeout=None, 2361 retries=None): 2362 """Pull a file from the device. 2363 2364 Args: 2365 device_path: A string containing the absolute path of the file to pull 2366 from the device. 2367 host_path: A string containing the absolute path of the destination on 2368 the host. 2369 as_root: Whether root permissions should be used to pull the file. 2370 timeout: timeout in seconds 2371 retries: number of retries 2372 2373 Raises: 2374 CommandFailedError on failure. 2375 CommandTimeoutError on timeout. 2376 """ 2377 # Create the base dir if it doesn't exist already 2378 dirname = os.path.dirname(host_path) 2379 if dirname and not os.path.exists(dirname): 2380 os.makedirs(dirname) 2381 if as_root and self.NeedsSU(): 2382 if not self.PathExists(device_path, as_root=True): 2383 raise device_errors.CommandFailedError( 2384 '%r: No such file or directory' % device_path, str(self)) 2385 with self._CopyToReadableLocation(device_path) as readable_temp_file: 2386 self.adb.Pull(readable_temp_file.name, host_path) 2387 else: 2388 self.adb.Pull(device_path, host_path) 2389 2390 def _ReadFileWithPull(self, device_path): 2391 try: 2392 d = tempfile.mkdtemp() 2393 host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull') 2394 self.adb.Pull(device_path, host_temp_path) 2395 with open(host_temp_path, 'r') as host_temp: 2396 return host_temp.read() 2397 finally: 2398 if os.path.exists(d): 2399 shutil.rmtree(d) 2400 2401 @decorators.WithTimeoutAndRetriesFromInstance() 2402 def ReadFile(self, 2403 device_path, 2404 as_root=False, 2405 force_pull=False, 2406 timeout=None, 2407 retries=None): 2408 """Reads the contents of a file from the device. 2409 2410 Args: 2411 device_path: A string containing the absolute path of the file to read 2412 from the device. 2413 as_root: A boolean indicating whether the read should be executed with 2414 root privileges. 2415 force_pull: A boolean indicating whether to force the operation to be 2416 performed by pulling a file from the device. The default is, when the 2417 contents are short, to retrieve the contents using cat instead. 2418 timeout: timeout in seconds 2419 retries: number of retries 2420 2421 Returns: 2422 The contents of |device_path| as a string. Contents are intepreted using 2423 universal newlines, so the caller will see them encoded as '\n'. Also, 2424 all lines will be terminated. 2425 2426 Raises: 2427 AdbCommandFailedError if the file can't be read. 2428 CommandTimeoutError on timeout. 2429 DeviceUnreachableError on missing device. 2430 """ 2431 2432 def get_size(path): 2433 return self.FileSize(path, as_root=as_root) 2434 2435 # Reading by pulling is faster than first getting the file size and cat-ing, 2436 # so only read by cat when we need root. 2437 if as_root and self.NeedsSU(): 2438 if (not force_pull 2439 and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH): 2440 return _JoinLines( 2441 self.RunShellCommand(['cat', device_path], 2442 as_root=as_root, 2443 check_return=True)) 2444 else: 2445 with self._CopyToReadableLocation(device_path) as readable_temp_file: 2446 return self._ReadFileWithPull(readable_temp_file.name) 2447 else: 2448 return self._ReadFileWithPull(device_path) 2449 2450 def _WriteFileWithPush(self, device_path, contents): 2451 with tempfile.NamedTemporaryFile() as host_temp: 2452 host_temp.write(contents) 2453 host_temp.flush() 2454 self.adb.Push(host_temp.name, device_path) 2455 2456 @decorators.WithTimeoutAndRetriesFromInstance() 2457 def WriteFile(self, 2458 device_path, 2459 contents, 2460 as_root=False, 2461 force_push=False, 2462 timeout=None, 2463 retries=None): 2464 """Writes |contents| to a file on the device. 2465 2466 Args: 2467 device_path: A string containing the absolute path to the file to write 2468 on the device. 2469 contents: A string containing the data to write to the device. 2470 as_root: A boolean indicating whether the write should be executed with 2471 root privileges (if available). 2472 force_push: A boolean indicating whether to force the operation to be 2473 performed by pushing a file to the device. The default is, when the 2474 contents are short, to pass the contents using a shell script instead. 2475 timeout: timeout in seconds 2476 retries: number of retries 2477 2478 Raises: 2479 CommandFailedError if the file could not be written on the device. 2480 CommandTimeoutError on timeout. 2481 DeviceUnreachableError on missing device. 2482 """ 2483 if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH: 2484 # If the contents are small, for efficieny we write the contents with 2485 # a shell command rather than pushing a file. 2486 cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), 2487 cmd_helper.SingleQuote(device_path)) 2488 self.RunShellCommand(cmd, shell=True, as_root=as_root, check_return=True) 2489 elif as_root and self.NeedsSU(): 2490 # Adb does not allow to "push with su", so we first push to a temp file 2491 # on a safe location, and then copy it to the desired location with su. 2492 with device_temp_file.DeviceTempFile(self.adb) as device_temp: 2493 self._WriteFileWithPush(device_temp.name, contents) 2494 # Here we need 'cp' rather than 'mv' because the temp and 2495 # destination files might be on different file systems (e.g. 2496 # on internal storage and an external sd card). 2497 self.RunShellCommand(['cp', device_temp.name, device_path], 2498 as_root=True, 2499 check_return=True) 2500 else: 2501 # If root is not needed, we can push directly to the desired location. 2502 self._WriteFileWithPush(device_path, contents) 2503 2504 def _ParseLongLsOutput(self, device_path, as_root=False, **kwargs): 2505 """Run and scrape the output of 'ls -a -l' on a device directory.""" 2506 device_path = posixpath.join(device_path, '') # Force trailing '/'. 2507 output = self.RunShellCommand(['ls', '-a', '-l', device_path], 2508 as_root=as_root, 2509 check_return=True, 2510 env={'TZ': 'utc'}, 2511 **kwargs) 2512 if output and output[0].startswith('total '): 2513 output.pop(0) # pylint: disable=maybe-no-member 2514 2515 entries = [] 2516 for line in output: 2517 m = _LONG_LS_OUTPUT_RE.match(line) 2518 if m: 2519 if m.group('filename') not in ['.', '..']: 2520 item = m.groupdict() 2521 # A change in toybox is causing recent Android versions to escape 2522 # spaces in file names. Here we just unquote those spaces. If we 2523 # later find more essoteric characters in file names, a more careful 2524 # unquoting mechanism may be needed. But hopefully not. 2525 # See: https://goo.gl/JAebZj 2526 item['filename'] = item['filename'].replace('\\ ', ' ') 2527 entries.append(item) 2528 else: 2529 logger.info('Skipping: %s', line) 2530 2531 return entries 2532 2533 def ListDirectory(self, device_path, as_root=False, **kwargs): 2534 """List all files on a device directory. 2535 2536 Mirroring os.listdir (and most client expectations) the resulting list 2537 does not include the special entries '.' and '..' even if they are present 2538 in the directory. 2539 2540 Args: 2541 device_path: A string containing the path of the directory on the device 2542 to list. 2543 as_root: A boolean indicating whether the to use root privileges to list 2544 the directory contents. 2545 timeout: timeout in seconds 2546 retries: number of retries 2547 2548 Returns: 2549 A list of filenames for all entries contained in the directory. 2550 2551 Raises: 2552 AdbCommandFailedError if |device_path| does not specify a valid and 2553 accessible directory in the device. 2554 CommandTimeoutError on timeout. 2555 DeviceUnreachableError on missing device. 2556 """ 2557 entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs) 2558 return [d['filename'] for d in entries] 2559 2560 def StatDirectory(self, device_path, as_root=False, **kwargs): 2561 """List file and stat info for all entries on a device directory. 2562 2563 Implementation notes: this is currently implemented by parsing the output 2564 of 'ls -a -l' on the device. Whether possible and convenient, we attempt to 2565 make parsing strict and return values mirroring those of the standard |os| 2566 and |stat| Python modules. 2567 2568 Mirroring os.listdir (and most client expectations) the resulting list 2569 does not include the special entries '.' and '..' even if they are present 2570 in the directory. 2571 2572 Args: 2573 device_path: A string containing the path of the directory on the device 2574 to list. 2575 as_root: A boolean indicating whether the to use root privileges to list 2576 the directory contents. 2577 timeout: timeout in seconds 2578 retries: number of retries 2579 2580 Returns: 2581 A list of dictionaries, each containing the following keys: 2582 filename: A string with the file name. 2583 st_mode: File permissions, use the stat module to interpret these. 2584 st_nlink: Number of hard links (may be missing). 2585 st_owner: A string with the user name of the owner. 2586 st_group: A string with the group name of the owner. 2587 st_rdev_pair: Device type as (major, minior) (only if inode device). 2588 st_size: Size of file, in bytes (may be missing for non-regular files). 2589 st_mtime: Time of most recent modification, in seconds since epoch 2590 (although resolution is in minutes). 2591 symbolic_link_to: If entry is a symbolic link, path where it points to; 2592 missing otherwise. 2593 2594 Raises: 2595 AdbCommandFailedError if |device_path| does not specify a valid and 2596 accessible directory in the device. 2597 CommandTimeoutError on timeout. 2598 DeviceUnreachableError on missing device. 2599 """ 2600 entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs) 2601 for d in entries: 2602 for key, value in d.items(): 2603 if value is None: 2604 del d[key] # Remove missing fields. 2605 d['st_mode'] = _ParseModeString(d['st_mode']) 2606 d['st_mtime'] = calendar.timegm( 2607 time.strptime(d['st_mtime'], _LS_DATE_FORMAT)) 2608 for key in ['st_nlink', 'st_size', 'st_rdev_major', 'st_rdev_minor']: 2609 if key in d: 2610 d[key] = int(d[key]) 2611 if 'st_rdev_major' in d and 'st_rdev_minor' in d: 2612 d['st_rdev_pair'] = (d.pop('st_rdev_major'), d.pop('st_rdev_minor')) 2613 return entries 2614 2615 def StatPath(self, device_path, as_root=False, **kwargs): 2616 """Get the stat attributes of a file or directory on the device. 2617 2618 Args: 2619 device_path: A string containing the path of a file or directory from 2620 which to get attributes. 2621 as_root: A boolean indicating whether the to use root privileges to 2622 access the file information. 2623 timeout: timeout in seconds 2624 retries: number of retries 2625 2626 Returns: 2627 A dictionary with the stat info collected; see StatDirectory for details. 2628 2629 Raises: 2630 CommandFailedError if device_path cannot be found on the device. 2631 CommandTimeoutError on timeout. 2632 DeviceUnreachableError on missing device. 2633 """ 2634 dirname, filename = posixpath.split(posixpath.normpath(device_path)) 2635 for entry in self.StatDirectory(dirname, as_root=as_root, **kwargs): 2636 if entry['filename'] == filename: 2637 return entry 2638 raise device_errors.CommandFailedError( 2639 'Cannot find file or directory: %r' % device_path, str(self)) 2640 2641 def FileSize(self, device_path, as_root=False, **kwargs): 2642 """Get the size of a file on the device. 2643 2644 Note: This is implemented by parsing the output of the 'ls' command on 2645 the device. On some Android versions, when passing a directory or special 2646 file, the size is *not* reported and this function will throw an exception. 2647 2648 Args: 2649 device_path: A string containing the path of a file on the device. 2650 as_root: A boolean indicating whether the to use root privileges to 2651 access the file information. 2652 timeout: timeout in seconds 2653 retries: number of retries 2654 2655 Returns: 2656 The size of the file in bytes. 2657 2658 Raises: 2659 CommandFailedError if device_path cannot be found on the device, or 2660 its size cannot be determited for some reason. 2661 CommandTimeoutError on timeout. 2662 DeviceUnreachableError on missing device. 2663 """ 2664 entry = self.StatPath(device_path, as_root=as_root, **kwargs) 2665 try: 2666 return entry['st_size'] 2667 except KeyError: 2668 raise device_errors.CommandFailedError( 2669 'Could not determine the size of: %s' % device_path, str(self)) 2670 2671 @decorators.WithTimeoutAndRetriesFromInstance() 2672 def SetJavaAsserts(self, enabled, timeout=None, retries=None): 2673 """Enables or disables Java asserts. 2674 2675 Args: 2676 enabled: A boolean indicating whether Java asserts should be enabled 2677 or disabled. 2678 timeout: timeout in seconds 2679 retries: number of retries 2680 2681 Returns: 2682 True if the device-side property changed and a restart is required as a 2683 result, False otherwise. 2684 2685 Raises: 2686 CommandTimeoutError on timeout. 2687 """ 2688 2689 def find_property(lines, property_name): 2690 for index, line in enumerate(lines): 2691 if line.strip() == '': 2692 continue 2693 key_value = tuple(s.strip() for s in line.split('=', 1)) 2694 if len(key_value) != 2: 2695 continue 2696 key, value = key_value 2697 if key == property_name: 2698 return index, value 2699 return None, '' 2700 2701 new_value = 'all' if enabled else '' 2702 2703 # First ensure the desired property is persisted. 2704 try: 2705 properties = self.ReadFile(self.LOCAL_PROPERTIES_PATH).splitlines() 2706 except device_errors.CommandFailedError: 2707 properties = [] 2708 index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY) 2709 if new_value != value: 2710 if new_value: 2711 new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value) 2712 if index is None: 2713 properties.append(new_line) 2714 else: 2715 properties[index] = new_line 2716 else: 2717 assert index is not None # since new_value == '' and new_value != value 2718 properties.pop(index) 2719 self.WriteFile(self.LOCAL_PROPERTIES_PATH, _JoinLines(properties)) 2720 2721 # Next, check the current runtime value is what we need, and 2722 # if not, set it and report that a reboot is required. 2723 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) 2724 if new_value != value: 2725 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) 2726 return True 2727 else: 2728 return False 2729 2730 def GetLocale(self, cache=False): 2731 """Returns the locale setting on the device. 2732 2733 Args: 2734 cache: Whether to use cached properties when available. 2735 Returns: 2736 A pair (language, country). 2737 """ 2738 locale = self.GetProp('persist.sys.locale', cache=cache) 2739 if locale: 2740 if '-' not in locale: 2741 logging.error('Unparsable locale: %s', locale) 2742 return ('', '') # Behave as if persist.sys.locale is undefined. 2743 return tuple(locale.split('-', 1)) 2744 return (self.GetProp('persist.sys.language', cache=cache), 2745 self.GetProp('persist.sys.country', cache=cache)) 2746 2747 def GetLanguage(self, cache=False): 2748 """Returns the language setting on the device. 2749 2750 DEPRECATED: Prefer GetLocale() instead. 2751 2752 Args: 2753 cache: Whether to use cached properties when available. 2754 """ 2755 return self.GetLocale(cache=cache)[0] 2756 2757 def GetCountry(self, cache=False): 2758 """Returns the country setting on the device. 2759 2760 DEPRECATED: Prefer GetLocale() instead. 2761 2762 Args: 2763 cache: Whether to use cached properties when available. 2764 """ 2765 return self.GetLocale(cache=cache)[1] 2766 2767 @property 2768 def screen_density(self): 2769 """Returns the screen density of the device.""" 2770 DPI_TO_DENSITY = { 2771 120: 'ldpi', 2772 160: 'mdpi', 2773 240: 'hdpi', 2774 320: 'xhdpi', 2775 480: 'xxhdpi', 2776 640: 'xxxhdpi', 2777 } 2778 return DPI_TO_DENSITY.get(self.pixel_density, 'tvdpi') 2779 2780 @property 2781 def pixel_density(self): 2782 density = self.GetProp('ro.sf.lcd_density', cache=True) 2783 if not density: 2784 # It might be an emulator, try the qemu prop. 2785 density = self.GetProp('qemu.sf.lcd_density', cache=True) 2786 return int(density) 2787 2788 @property 2789 def is_emulator(self): 2790 return _EMULATOR_RE.match(self.GetProp('ro.product.device', cache=True)) 2791 2792 @property 2793 def build_description(self): 2794 """Returns the build description of the system. 2795 2796 For example: 2797 nakasi-user 4.4.4 KTU84P 1227136 release-keys 2798 """ 2799 return self.GetProp('ro.build.description', cache=True) 2800 2801 @property 2802 def build_fingerprint(self): 2803 """Returns the build fingerprint of the system. 2804 2805 For example: 2806 google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys 2807 """ 2808 return self.GetProp('ro.build.fingerprint', cache=True) 2809 2810 @property 2811 def build_id(self): 2812 """Returns the build ID of the system (e.g. 'KTU84P').""" 2813 return self.GetProp('ro.build.id', cache=True) 2814 2815 @property 2816 def build_product(self): 2817 """Returns the build product of the system (e.g. 'grouper').""" 2818 return self.GetProp('ro.build.product', cache=True) 2819 2820 @property 2821 def build_system_root_image(self): 2822 """Returns the system_root_image property. 2823 2824 This seems to indicate whether the device is using a system-as-root 2825 partition layout. See http://bit.ly/37F34sx for more info. 2826 """ 2827 return self.GetProp('ro.build.system_root_image', cache=True) 2828 2829 @property 2830 def build_type(self): 2831 """Returns the build type of the system (e.g. 'user').""" 2832 return self.GetProp('ro.build.type', cache=True) 2833 2834 @property 2835 def build_version_sdk(self): 2836 """Returns the build version sdk of the system as a number (e.g. 19). 2837 2838 For version code numbers see: 2839 http://developer.android.com/reference/android/os/Build.VERSION_CODES.html 2840 2841 For named constants see devil.android.sdk.version_codes 2842 2843 Raises: 2844 CommandFailedError if the build version sdk is not a number. 2845 """ 2846 value = self.GetProp('ro.build.version.sdk', cache=True) 2847 try: 2848 return int(value) 2849 except ValueError: 2850 raise device_errors.CommandFailedError( 2851 'Invalid build version sdk: %r' % value) 2852 2853 @property 2854 def tracing_path(self): 2855 """Returns the tracing path of the device for atrace.""" 2856 return self.GetTracingPath() 2857 2858 @property 2859 def product_cpu_abi(self): 2860 """Returns the product cpu abi of the device (e.g. 'armeabi-v7a'). 2861 2862 For supported ABIs, the return value will be one of the values defined in 2863 devil.android.ndk.abis. 2864 """ 2865 return self.GetProp('ro.product.cpu.abi', cache=True) 2866 2867 @property 2868 def product_cpu_abis(self): 2869 """Returns all product cpu abi of the device.""" 2870 return self.GetProp('ro.product.cpu.abilist', cache=True).split(',') 2871 2872 @property 2873 def product_model(self): 2874 """Returns the name of the product model (e.g. 'Nexus 7').""" 2875 return self.GetProp('ro.product.model', cache=True) 2876 2877 @property 2878 def product_name(self): 2879 """Returns the product name of the device (e.g. 'nakasi').""" 2880 return self.GetProp('ro.product.name', cache=True) 2881 2882 @property 2883 def product_board(self): 2884 """Returns the product board name of the device (e.g. 'shamu').""" 2885 return self.GetProp('ro.product.board', cache=True) 2886 2887 def _EnsureCacheInitialized(self): 2888 """Populates cache token, runs getprop and fetches $EXTERNAL_STORAGE.""" 2889 if self._cache['token']: 2890 return 2891 with self._cache_lock: 2892 if self._cache['token']: 2893 return 2894 # Change the token every time to ensure that it will match only the 2895 # previously dumped cache. 2896 token = str(uuid.uuid1()) 2897 cmd = ('c=/data/local/tmp/cache_token;' 2898 'echo $EXTERNAL_STORAGE;' 2899 'cat $c 2>/dev/null||echo;' 2900 'echo "%s">$c &&' % token + 'getprop') 2901 output = self.RunShellCommand( 2902 cmd, shell=True, check_return=True, large_output=True) 2903 # Error-checking for this existing is done in GetExternalStoragePath(). 2904 self._cache['external_storage'] = output[0] 2905 self._cache['prev_token'] = output[1] 2906 output = output[2:] 2907 2908 prop_cache = self._cache['getprop'] 2909 prop_cache.clear() 2910 for key, value in _GETPROP_RE.findall(''.join(output)): 2911 prop_cache[key] = value 2912 self._cache['token'] = token 2913 2914 @decorators.WithTimeoutAndRetriesFromInstance() 2915 def GetTracingPath(self, timeout=None, retries=None): 2916 """Gets tracing path from the device. 2917 2918 Args: 2919 timeout: timeout in seconds 2920 retries: number of retries 2921 2922 Returns: 2923 /sys/kernel/debug/tracing for device with debugfs mount support; 2924 /sys/kernel/tracing for device with tracefs support; 2925 /sys/kernel/debug/tracing if support can't be determined. 2926 2927 Raises: 2928 CommandTimeoutError on timeout. 2929 """ 2930 tracing_path = self._cache['tracing_path'] 2931 if tracing_path: 2932 return tracing_path 2933 with self._cache_lock: 2934 tracing_path = '/sys/kernel/debug/tracing' 2935 try: 2936 lines = self.RunShellCommand(['mount'], 2937 check_return=True, 2938 timeout=timeout, 2939 retries=retries) 2940 if not any('debugfs' in line for line in lines): 2941 tracing_path = '/sys/kernel/tracing' 2942 except device_errors.AdbCommandFailedError: 2943 pass 2944 self._cache['tracing_path'] = tracing_path 2945 return tracing_path 2946 2947 @decorators.WithTimeoutAndRetriesFromInstance() 2948 def GetProp(self, property_name, cache=False, timeout=None, retries=None): 2949 """Gets a property from the device. 2950 2951 Args: 2952 property_name: A string containing the name of the property to get from 2953 the device. 2954 cache: Whether to use cached properties when available. 2955 timeout: timeout in seconds 2956 retries: number of retries 2957 2958 Returns: 2959 The value of the device's |property_name| property. 2960 2961 Raises: 2962 CommandTimeoutError on timeout. 2963 """ 2964 assert isinstance( 2965 property_name, 2966 basestring), ("property_name is not a string: %r" % property_name) 2967 2968 if cache: 2969 # It takes ~120ms to query a single property, and ~130ms to query all 2970 # properties. So, when caching we always query all properties. 2971 self._EnsureCacheInitialized() 2972 else: 2973 # timeout and retries are handled down at run shell, because we don't 2974 # want to apply them in the other branch when reading from the cache 2975 value = self.RunShellCommand(['getprop', property_name], 2976 single_line=True, 2977 check_return=True, 2978 timeout=timeout, 2979 retries=retries) 2980 self._cache['getprop'][property_name] = value 2981 # Non-existent properties are treated as empty strings by getprop. 2982 return self._cache['getprop'].get(property_name, '') 2983 2984 @decorators.WithTimeoutAndRetriesFromInstance() 2985 def SetProp(self, 2986 property_name, 2987 value, 2988 check=False, 2989 timeout=None, 2990 retries=None): 2991 """Sets a property on the device. 2992 2993 Args: 2994 property_name: A string containing the name of the property to set on 2995 the device. 2996 value: A string containing the value to set to the property on the 2997 device. 2998 check: A boolean indicating whether to check that the property was 2999 successfully set on the device. 3000 timeout: timeout in seconds 3001 retries: number of retries 3002 3003 Raises: 3004 CommandFailedError if check is true and the property was not correctly 3005 set on the device (e.g. because it is not rooted). 3006 CommandTimeoutError on timeout. 3007 """ 3008 assert isinstance( 3009 property_name, 3010 basestring), ("property_name is not a string: %r" % property_name) 3011 assert isinstance(value, basestring), "value is not a string: %r" % value 3012 3013 self.RunShellCommand(['setprop', property_name, value], check_return=True) 3014 prop_cache = self._cache['getprop'] 3015 if property_name in prop_cache: 3016 del prop_cache[property_name] 3017 # TODO(crbug.com/1029772) remove the option and make the check mandatory, 3018 # but using a single shell script to both set- and getprop. 3019 if check and value != self.GetProp(property_name, cache=False): 3020 raise device_errors.CommandFailedError( 3021 'Unable to set property %r on the device to %r' % (property_name, 3022 value), str(self)) 3023 3024 @decorators.WithTimeoutAndRetriesFromInstance() 3025 def GetABI(self, timeout=None, retries=None): 3026 """Gets the device main ABI. 3027 3028 Args: 3029 timeout: timeout in seconds 3030 retries: number of retries 3031 3032 Returns: 3033 The device's main ABI name. For supported ABIs, the return value will be 3034 one of the values defined in devil.android.ndk.abis. 3035 3036 Raises: 3037 CommandTimeoutError on timeout. 3038 """ 3039 return self.GetProp('ro.product.cpu.abi', cache=True) 3040 3041 @decorators.WithTimeoutAndRetriesFromInstance() 3042 def GetFeatures(self, timeout=None, retries=None): 3043 """Returns the features supported on the device.""" 3044 lines = self.RunShellCommand(['pm', 'list', 'features'], check_return=True) 3045 return [f[8:] for f in lines if f.startswith('feature:')] 3046 3047 def _GetPsOutput(self, pattern): 3048 """Runs |ps| command on the device and returns its output, 3049 3050 This private method abstracts away differences between Android verions for 3051 calling |ps|, and implements support for filtering the output by a given 3052 |pattern|, but does not do any output parsing. 3053 """ 3054 try: 3055 ps_cmd = 'ps' 3056 # ps behavior was changed in Android O and above, http://crbug.com/686716 3057 if self.build_version_sdk >= version_codes.OREO: 3058 ps_cmd = 'ps -e' 3059 if pattern: 3060 return self._RunPipedShellCommand( 3061 '%s | grep -F %s' % (ps_cmd, cmd_helper.SingleQuote(pattern))) 3062 else: 3063 return self.RunShellCommand( 3064 ps_cmd.split(), check_return=True, large_output=True) 3065 except device_errors.AdbShellCommandFailedError as e: 3066 if e.status and isinstance(e.status, list) and not e.status[0]: 3067 # If ps succeeded but grep failed, there were no processes with the 3068 # given name. 3069 return [] 3070 else: 3071 raise 3072 3073 @decorators.WithTimeoutAndRetriesFromInstance() 3074 def ListProcesses(self, process_name=None, timeout=None, retries=None): 3075 """Returns a list of tuples with info about processes on the device. 3076 3077 This essentially parses the output of the |ps| command into convenient 3078 ProcessInfo tuples. 3079 3080 Args: 3081 process_name: A string used to filter the returned processes. If given, 3082 only processes whose name have this value as a substring 3083 will be returned. 3084 timeout: timeout in seconds 3085 retries: number of retries 3086 3087 Returns: 3088 A list of ProcessInfo tuples with |name|, |pid|, and |ppid| fields. 3089 """ 3090 process_name = process_name or '' 3091 processes = [] 3092 for line in self._GetPsOutput(process_name): 3093 row = line.split() 3094 try: 3095 row = {k: row[i] for k, i in _PS_COLUMNS.iteritems()} 3096 if row['pid'] == 'PID' or process_name not in row['name']: 3097 # Skip over header and non-matching processes. 3098 continue 3099 row['pid'] = int(row['pid']) 3100 row['ppid'] = int(row['ppid']) 3101 except StandardError: # e.g. IndexError, TypeError, ValueError. 3102 logging.warning('failed to parse ps line: %r', line) 3103 continue 3104 processes.append(ProcessInfo(**row)) 3105 return processes 3106 3107 def _GetDumpsysOutput(self, extra_args, pattern=None): 3108 """Runs |dumpsys| command on the device and returns its output. 3109 3110 This private method implements support for filtering the output by a given 3111 |pattern|, but does not do any output parsing. 3112 """ 3113 try: 3114 cmd = ['dumpsys'] + extra_args 3115 if pattern: 3116 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) 3117 return self._RunPipedShellCommand( 3118 '%s | grep -F %s' % (cmd, cmd_helper.SingleQuote(pattern))) 3119 else: 3120 cmd = ['dumpsys'] + extra_args 3121 return self.RunShellCommand(cmd, check_return=True, large_output=True) 3122 except device_errors.AdbShellCommandFailedError as e: 3123 if e.status and isinstance(e.status, list) and not e.status[0]: 3124 # If dumpsys succeeded but grep failed, there were no lines matching 3125 # the given pattern. 3126 return [] 3127 else: 3128 raise 3129 3130 # TODO(#4103): Remove after migrating clients to ListProcesses. 3131 @decorators.WithTimeoutAndRetriesFromInstance() 3132 def GetPids(self, process_name=None, timeout=None, retries=None): 3133 """Returns the PIDs of processes containing the given name as substring. 3134 3135 DEPRECATED 3136 3137 Note that the |process_name| is often the package name. 3138 3139 Args: 3140 process_name: A string containing the process name to get the PIDs for. 3141 If missing returns PIDs for all processes. 3142 timeout: timeout in seconds 3143 retries: number of retries 3144 3145 Returns: 3146 A dict mapping process name to a list of PIDs for each process that 3147 contained the provided |process_name|. 3148 3149 Raises: 3150 CommandTimeoutError on timeout. 3151 DeviceUnreachableError on missing device. 3152 """ 3153 procs_pids = collections.defaultdict(list) 3154 for p in self.ListProcesses(process_name): 3155 procs_pids[p.name].append(str(p.pid)) 3156 return procs_pids 3157 3158 @decorators.WithTimeoutAndRetriesFromInstance() 3159 def GetApplicationPids(self, 3160 process_name, 3161 at_most_one=False, 3162 timeout=None, 3163 retries=None): 3164 """Returns the PID or PIDs of a given process name. 3165 3166 Note that the |process_name|, often the package name, must match exactly. 3167 3168 Args: 3169 process_name: A string containing the process name to get the PIDs for. 3170 at_most_one: A boolean indicating that at most one PID is expected to 3171 be found. 3172 timeout: timeout in seconds 3173 retries: number of retries 3174 3175 Returns: 3176 A list of the PIDs for the named process. If at_most_one=True returns 3177 the single PID found or None otherwise. 3178 3179 Raises: 3180 CommandFailedError if at_most_one=True and more than one PID is found 3181 for the named process. 3182 CommandTimeoutError on timeout. 3183 DeviceUnreachableError on missing device. 3184 """ 3185 pids = [ 3186 p.pid for p in self.ListProcesses(process_name) 3187 if p.name == process_name 3188 ] 3189 if at_most_one: 3190 if len(pids) > 1: 3191 raise device_errors.CommandFailedError( 3192 'Expected a single PID for %r but found: %r.' % (process_name, 3193 pids), 3194 device_serial=str(self)) 3195 return pids[0] if pids else None 3196 else: 3197 return pids 3198 3199 @decorators.WithTimeoutAndRetriesFromInstance() 3200 def GetEnforce(self, timeout=None, retries=None): 3201 """Get the current mode of SELinux. 3202 3203 Args: 3204 timeout: timeout in seconds 3205 retries: number of retries 3206 3207 Returns: 3208 True (enforcing), False (permissive), or None (disabled). 3209 3210 Raises: 3211 CommandFailedError on failure. 3212 CommandTimeoutError on timeout. 3213 DeviceUnreachableError on missing device. 3214 """ 3215 output = self.RunShellCommand(['getenforce'], 3216 check_return=True, 3217 single_line=True).lower() 3218 if output not in _SELINUX_MODE: 3219 raise device_errors.CommandFailedError( 3220 'Unexpected getenforce output: %s' % output) 3221 return _SELINUX_MODE[output] 3222 3223 @decorators.WithTimeoutAndRetriesFromInstance() 3224 def SetEnforce(self, enabled, timeout=None, retries=None): 3225 """Modify the mode SELinux is running in. 3226 3227 Args: 3228 enabled: a boolean indicating whether to put SELinux in encorcing mode 3229 (if True), or permissive mode (otherwise). 3230 timeout: timeout in seconds 3231 retries: number of retries 3232 3233 Raises: 3234 CommandFailedError on failure. 3235 CommandTimeoutError on timeout. 3236 DeviceUnreachableError on missing device. 3237 """ 3238 self.RunShellCommand(['setenforce', '1' if int(enabled) else '0'], 3239 as_root=True, 3240 check_return=True) 3241 3242 @decorators.WithTimeoutAndRetriesFromInstance() 3243 def GetWebViewUpdateServiceDump(self, timeout=None, retries=None): 3244 """Get the WebView update command sysdump on the device. 3245 3246 Returns: 3247 A dictionary with these possible entries: 3248 FallbackLogicEnabled: True|False 3249 CurrentWebViewPackage: "package name" or None 3250 MinimumWebViewVersionCode: int 3251 WebViewPackages: Dict of installed WebView providers, mapping "package 3252 name" to "reason it's valid/invalid." 3253 3254 It may return an empty dictionary if device does not 3255 support the "dumpsys webviewupdate" command. 3256 3257 Raises: 3258 CommandFailedError on failure. 3259 CommandTimeoutError on timeout. 3260 DeviceUnreachableError on missing device. 3261 """ 3262 result = {} 3263 3264 # Command was implemented starting in Oreo 3265 if self.build_version_sdk < version_codes.OREO: 3266 return result 3267 3268 output = self.RunShellCommand(['dumpsys', 'webviewupdate'], 3269 check_return=True) 3270 webview_packages = {} 3271 for line in output: 3272 match = re.search(_WEBVIEW_SYSUPDATE_CURRENT_PKG_RE, line) 3273 if match: 3274 result['CurrentWebViewPackage'] = match.group(1) 3275 match = re.search(_WEBVIEW_SYSUPDATE_NULL_PKG_RE, line) 3276 if match: 3277 result['CurrentWebViewPackage'] = None 3278 match = re.search(_WEBVIEW_SYSUPDATE_FALLBACK_LOGIC_RE, line) 3279 if match: 3280 result['FallbackLogicEnabled'] = \ 3281 True if match.group(1) == 'true' else False 3282 match = re.search(_WEBVIEW_SYSUPDATE_PACKAGE_INSTALLED_RE, line) 3283 if match: 3284 package_name = match.group(1) 3285 reason = match.group(2) 3286 webview_packages[package_name] = reason 3287 match = re.search(_WEBVIEW_SYSUPDATE_PACKAGE_NOT_INSTALLED_RE, line) 3288 if match: 3289 package_name = match.group(1) 3290 reason = match.group(2) 3291 webview_packages[package_name] = reason 3292 match = re.search(_WEBVIEW_SYSUPDATE_MIN_VERSION_CODE, line) 3293 if match: 3294 result['MinimumWebViewVersionCode'] = int(match.group(1)) 3295 if webview_packages: 3296 result['WebViewPackages'] = webview_packages 3297 3298 missing_fields = set(['CurrentWebViewPackage', 'FallbackLogicEnabled']) - \ 3299 set(result.keys()) 3300 if len(missing_fields) > 0: 3301 raise device_errors.CommandFailedError( 3302 '%s not found in dumpsys webviewupdate' % str(list(missing_fields))) 3303 return result 3304 3305 @decorators.WithTimeoutAndRetriesFromInstance() 3306 def SetWebViewImplementation(self, package_name, timeout=None, retries=None): 3307 """Select the WebView implementation to the specified package. 3308 3309 Args: 3310 package_name: The package name of a WebView implementation. The package 3311 must be already installed on the device. 3312 timeout: timeout in seconds 3313 retries: number of retries 3314 3315 Raises: 3316 CommandFailedError on failure. 3317 CommandTimeoutError on timeout. 3318 DeviceUnreachableError on missing device. 3319 """ 3320 if not self.IsApplicationInstalled(package_name): 3321 raise device_errors.CommandFailedError( 3322 '%s is not installed' % package_name, str(self)) 3323 output = self.RunShellCommand( 3324 ['cmd', 'webviewupdate', 'set-webview-implementation', package_name], 3325 single_line=True, 3326 check_return=False) 3327 if output == 'Success': 3328 logging.info('WebView provider set to: %s', package_name) 3329 else: 3330 dumpsys_output = self.GetWebViewUpdateServiceDump() 3331 webview_packages = dumpsys_output.get('WebViewPackages') 3332 if webview_packages: 3333 reason = webview_packages.get(package_name) 3334 if not reason: 3335 all_provider_package_names = webview_packages.keys() 3336 raise device_errors.CommandFailedError( 3337 '%s is not in the system WebView provider list. Must choose one ' 3338 'of %r.' % (package_name, all_provider_package_names), str(self)) 3339 if re.search(r'is\s+NOT\s+installed/enabled for all users', reason): 3340 raise device_errors.CommandFailedError( 3341 '%s is disabled, make sure to disable WebView fallback logic' % 3342 package_name, str(self)) 3343 if re.search(r'No WebView-library manifest flag', reason): 3344 raise device_errors.CommandFailedError( 3345 '%s does not declare a WebView native library, so it cannot ' 3346 'be a WebView provider' % package_name, str(self)) 3347 if re.search(r'SDK version too low', reason): 3348 app_target_sdk_version = self.GetApplicationTargetSdk(package_name) 3349 is_preview_sdk = self.GetProp('ro.build.version.preview_sdk') == '1' 3350 if is_preview_sdk: 3351 codename = self.GetProp('ro.build.version.codename') 3352 raise device_errors.CommandFailedError( 3353 '%s targets a finalized SDK (%r), but valid WebView providers ' 3354 'must target a pre-finalized SDK (%r) on this device' % 3355 (package_name, app_target_sdk_version, codename), str(self)) 3356 else: 3357 raise device_errors.CommandFailedError( 3358 '%s has targetSdkVersion %r, but valid WebView providers must ' 3359 'target >= %r on this device' % 3360 (package_name, app_target_sdk_version, self.build_version_sdk), 3361 str(self)) 3362 if re.search(r'Version code too low', reason): 3363 raise device_errors.CommandFailedError( 3364 '%s needs a higher versionCode (must be >= %d)' % 3365 (package_name, dumpsys_output.get('MinimumWebViewVersionCode')), 3366 str(self)) 3367 if re.search(r'Incorrect signature', reason): 3368 raise device_errors.CommandFailedError( 3369 '%s is not signed with release keys (but user builds require ' 3370 'this for WebView providers)' % package_name, str(self)) 3371 raise device_errors.CommandFailedError( 3372 'Error setting WebView provider: %s' % output, str(self)) 3373 3374 @decorators.WithTimeoutAndRetriesFromInstance() 3375 def SetWebViewFallbackLogic(self, enabled, timeout=None, retries=None): 3376 """Set whether WebViewUpdateService's "fallback logic" should be enabled. 3377 3378 WebViewUpdateService has nonintuitive "fallback logic" for devices where 3379 Monochrome (Chrome Stable) is preinstalled as the WebView provider, with a 3380 "stub" (little-to-no code) implementation of standalone WebView. 3381 3382 "Fallback logic" (enabled by default) is designed, in the case where the 3383 user has disabled Chrome, to fall back to the stub standalone WebView by 3384 enabling the package. The implementation plumbs through the Chrome APK until 3385 Play Store installs an update with the full implementation. 3386 3387 A surprising side-effect of "fallback logic" is that, immediately after 3388 sideloading WebView, WebViewUpdateService re-disables the package and 3389 uninstalls the update. This can prevent successfully using standalone 3390 WebView for development, although "fallback logic" can be disabled on 3391 userdebug/eng devices. 3392 3393 Because this is only relevant for devices with the standalone WebView stub, 3394 this command is only relevant on N-P (inclusive). 3395 3396 You can determine if "fallback logic" is currently enabled by checking 3397 FallbackLogicEnabled in the dictionary returned by 3398 GetWebViewUpdateServiceDump. 3399 3400 Args: 3401 enabled: bool - True for enabled, False for disabled 3402 timeout: timeout in seconds 3403 retries: number of retries 3404 3405 Raises: 3406 CommandFailedError on failure. 3407 CommandTimeoutError on timeout. 3408 DeviceUnreachableError on missing device. 3409 """ 3410 3411 # Command is only available on devices which preinstall stub WebView. 3412 if not version_codes.NOUGAT <= self.build_version_sdk <= version_codes.PIE: 3413 return 3414 3415 # redundant-packages is the opposite of fallback logic 3416 enable_string = 'disable' if enabled else 'enable' 3417 output = self.RunShellCommand( 3418 ['cmd', 'webviewupdate', 3419 '%s-redundant-packages' % enable_string], 3420 single_line=True, 3421 check_return=True) 3422 if output == 'Success': 3423 logging.info('WebView Fallback Logic is %s', 3424 'enabled' if enabled else 'disabled') 3425 else: 3426 raise device_errors.CommandFailedError( 3427 'Error setting WebView Fallback Logic: %s' % output, str(self)) 3428 3429 @decorators.WithTimeoutAndRetriesFromInstance() 3430 def TakeScreenshot(self, host_path=None, timeout=None, retries=None): 3431 """Takes a screenshot of the device. 3432 3433 Args: 3434 host_path: A string containing the path on the host to save the 3435 screenshot to. If None, a file name in the current 3436 directory will be generated. 3437 timeout: timeout in seconds 3438 retries: number of retries 3439 3440 Returns: 3441 The name of the file on the host to which the screenshot was saved. 3442 3443 Raises: 3444 CommandFailedError on failure. 3445 CommandTimeoutError on timeout. 3446 DeviceUnreachableError on missing device. 3447 """ 3448 if not host_path: 3449 host_path = os.path.abspath( 3450 'screenshot-%s-%s.png' % (self.serial, _GetTimeStamp())) 3451 with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp: 3452 self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name], 3453 check_return=True) 3454 self.PullFile(device_tmp.name, host_path) 3455 return host_path 3456 3457 @decorators.WithTimeoutAndRetriesFromInstance() 3458 def DismissCrashDialogIfNeeded(self, timeout=None, retries=None): 3459 """Dismiss the error/ANR dialog if present. 3460 3461 Returns: Name of the crashed package if a dialog is focused, 3462 None otherwise. 3463 """ 3464 3465 def _FindFocusedWindow(): 3466 match = None 3467 # TODO(jbudorick): Try to grep the output on the device instead of using 3468 # large_output if/when DeviceUtils exposes a public interface for piped 3469 # shell command handling. 3470 for line in self.RunShellCommand(['dumpsys', 'window', 'windows'], 3471 check_return=True, 3472 large_output=True): 3473 match = re.match(_CURRENT_FOCUS_CRASH_RE, line) 3474 if match: 3475 break 3476 return match 3477 3478 match = _FindFocusedWindow() 3479 if not match: 3480 return None 3481 package = match.group(2) 3482 logger.warning('Trying to dismiss %s dialog for %s', *match.groups()) 3483 self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) 3484 self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) 3485 self.SendKeyEvent(keyevent.KEYCODE_ENTER) 3486 match = _FindFocusedWindow() 3487 if match: 3488 logger.error('Still showing a %s dialog for %s', *match.groups()) 3489 return package 3490 3491 def GetLogcatMonitor(self, *args, **kwargs): 3492 """Returns a new LogcatMonitor associated with this device. 3493 3494 Parameters passed to this function are passed directly to 3495 |logcat_monitor.LogcatMonitor| and are documented there. 3496 """ 3497 return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs) 3498 3499 def GetClientCache(self, client_name): 3500 """Returns client cache.""" 3501 if client_name not in self._client_caches: 3502 self._client_caches[client_name] = {} 3503 return self._client_caches[client_name] 3504 3505 def ClearCache(self): 3506 """Clears all caches.""" 3507 for client in self._client_caches: 3508 self._client_caches[client].clear() 3509 self._cache = { 3510 # Map of packageId -> list of on-device .apk paths 3511 'package_apk_paths': {}, 3512 # Set of packageId that were loaded from LoadCacheData and not yet 3513 # verified. 3514 'package_apk_paths_to_verify': set(), 3515 # Map of packageId -> set of on-device .apk checksums 3516 'package_apk_checksums': {}, 3517 # Map of property_name -> value 3518 'getprop': {}, 3519 # Map of device path -> checksum] 3520 'device_path_checksums': {}, 3521 # Location of sdcard ($EXTERNAL_STORAGE). 3522 'external_storage': None, 3523 # Token used to detect when LoadCacheData is stale. 3524 'token': None, 3525 'prev_token': None, 3526 # Path for tracing. 3527 'tracing_path': None, 3528 } 3529 3530 @decorators.WithTimeoutAndRetriesFromInstance() 3531 def LoadCacheData(self, data, timeout=None, retries=None): 3532 """Initializes the cache from data created using DumpCacheData. 3533 3534 The cache is used only if its token matches the one found on the device. 3535 This prevents a stale cache from being used (which can happen when sharing 3536 devices). 3537 3538 Args: 3539 data: A previously serialized cache (string). 3540 timeout: timeout in seconds 3541 retries: number of retries 3542 3543 Returns: 3544 Whether the cache was loaded. 3545 """ 3546 try: 3547 obj = json.loads(data) 3548 except ValueError: 3549 logger.error('Unable to parse cache file. Not using it.') 3550 return False 3551 self._EnsureCacheInitialized() 3552 given_token = obj.get('token') 3553 if not given_token or self._cache['prev_token'] != given_token: 3554 logger.warning('Stale cache detected. Not using it.') 3555 return False 3556 3557 self._cache['package_apk_paths'] = obj.get('package_apk_paths', {}) 3558 # When using a cache across script invokations, verify that apps have 3559 # not been uninstalled. 3560 self._cache['package_apk_paths_to_verify'] = set( 3561 self._cache['package_apk_paths'].iterkeys()) 3562 3563 package_apk_checksums = obj.get('package_apk_checksums', {}) 3564 for k, v in package_apk_checksums.iteritems(): 3565 package_apk_checksums[k] = set(v) 3566 self._cache['package_apk_checksums'] = package_apk_checksums 3567 device_path_checksums = obj.get('device_path_checksums', {}) 3568 self._cache['device_path_checksums'] = device_path_checksums 3569 return True 3570 3571 @decorators.WithTimeoutAndRetriesFromInstance() 3572 def DumpCacheData(self, timeout=None, retries=None): 3573 """Dumps the current cache state to a string. 3574 3575 Args: 3576 timeout: timeout in seconds 3577 retries: number of retries 3578 3579 Returns: 3580 A serialized cache as a string. 3581 """ 3582 self._EnsureCacheInitialized() 3583 obj = {} 3584 obj['token'] = self._cache['token'] 3585 obj['package_apk_paths'] = self._cache['package_apk_paths'] 3586 obj['package_apk_checksums'] = self._cache['package_apk_checksums'] 3587 # JSON can't handle sets. 3588 for k, v in obj['package_apk_checksums'].iteritems(): 3589 obj['package_apk_checksums'][k] = list(v) 3590 obj['device_path_checksums'] = self._cache['device_path_checksums'] 3591 return json.dumps(obj, separators=(',', ':')) 3592 3593 @classmethod 3594 def parallel(cls, devices, async=False): 3595 """Creates a Parallelizer to operate over the provided list of devices. 3596 3597 Args: 3598 devices: A list of either DeviceUtils instances or objects from 3599 from which DeviceUtils instances can be constructed. If None, 3600 all attached devices will be used. 3601 async: If true, returns a Parallelizer that runs operations 3602 asynchronously. 3603 3604 Returns: 3605 A Parallelizer operating over |devices|. 3606 """ 3607 devices = [d if isinstance(d, cls) else cls(d) for d in devices] 3608 if async: 3609 return parallelizer.Parallelizer(devices) 3610 else: 3611 return parallelizer.SyncParallelizer(devices) 3612 3613 @classmethod 3614 def HealthyDevices(cls, 3615 denylist=None, 3616 device_arg='default', 3617 retries=1, 3618 enable_usb_resets=False, 3619 abis=None, 3620 **kwargs): 3621 """Returns a list of DeviceUtils instances. 3622 3623 Returns a list of DeviceUtils instances that are attached, not denylisted, 3624 and optionally filtered by --device flags or ANDROID_SERIAL environment 3625 variable. 3626 3627 Args: 3628 denylist: A DeviceDenylist instance (optional). Device serials in this 3629 denylist will never be returned, but a warning will be logged if they 3630 otherwise would have been. 3631 device_arg: The value of the --device flag. This can be: 3632 'default' -> Same as [], but returns an empty list rather than raise a 3633 NoDevicesError. 3634 [] -> Returns all devices, unless $ANDROID_SERIAL is set. 3635 None -> Use $ANDROID_SERIAL if set, otherwise looks for a single 3636 attached device. Raises an exception if multiple devices are 3637 attached. 3638 'serial' -> Returns an instance for the given serial, if not 3639 denylisted. 3640 ['A', 'B', ...] -> Returns instances for the subset that is not 3641 denylisted. 3642 retries: Number of times to restart adb server and query it again if no 3643 devices are found on the previous attempts, with exponential backoffs 3644 up to 60s between each retry. 3645 enable_usb_resets: If true, will attempt to trigger a USB reset prior to 3646 the last attempt if there are no available devices. It will only reset 3647 those that appear to be android devices. 3648 abis: A list of ABIs for which the device needs to support at least one of 3649 (optional). See devil.android.ndk.abis for valid values. 3650 A device serial, or a list of device serials (optional). 3651 3652 Returns: 3653 A list of DeviceUtils instances. 3654 3655 Raises: 3656 NoDevicesError: Raised when no non-denylisted devices exist and 3657 device_arg is passed. 3658 MultipleDevicesError: Raise when multiple devices exist, but |device_arg| 3659 is None. 3660 """ 3661 allow_no_devices = False 3662 if device_arg == 'default': 3663 allow_no_devices = True 3664 device_arg = () 3665 3666 select_multiple = True 3667 if not (isinstance(device_arg, tuple) or isinstance(device_arg, list)): 3668 select_multiple = False 3669 if device_arg: 3670 device_arg = (device_arg, ) 3671 3672 denylisted_devices = denylist.Read() if denylist else [] 3673 3674 # adb looks for ANDROID_SERIAL, so support it as well. 3675 android_serial = os.environ.get('ANDROID_SERIAL') 3676 if not device_arg and android_serial: 3677 device_arg = (android_serial, ) 3678 3679 def denylisted(serial): 3680 if serial in denylisted_devices: 3681 logger.warning('Device %s is denylisted.', serial) 3682 return True 3683 return False 3684 3685 def supports_abi(abi, serial): 3686 if abis and abi not in abis: 3687 logger.warning("Device %s doesn't support required ABIs.", serial) 3688 return False 3689 return True 3690 3691 def _get_devices(): 3692 if device_arg: 3693 devices = [cls(x, **kwargs) for x in device_arg if not denylisted(x)] 3694 else: 3695 devices = [] 3696 for adb in adb_wrapper.AdbWrapper.Devices(): 3697 serial = adb.GetDeviceSerial() 3698 if not denylisted(serial): 3699 device = cls(_CreateAdbWrapper(adb), **kwargs) 3700 if supports_abi(device.GetABI(), serial): 3701 devices.append(device) 3702 3703 if len(devices) == 0 and not allow_no_devices: 3704 raise device_errors.NoDevicesError() 3705 if len(devices) > 1 and not select_multiple: 3706 raise device_errors.MultipleDevicesError(devices) 3707 return sorted(devices) 3708 3709 def _reset_devices(): 3710 if not reset_usb: 3711 logging.error( 3712 'reset_usb.py not supported on this platform (%s). Skipping usb ' 3713 'resets.', sys.platform) 3714 return 3715 if device_arg: 3716 for serial in device_arg: 3717 reset_usb.reset_android_usb(serial) 3718 else: 3719 reset_usb.reset_all_android_devices() 3720 3721 for attempt in xrange(retries + 1): 3722 try: 3723 return _get_devices() 3724 except device_errors.NoDevicesError: 3725 if attempt == retries: 3726 logging.error('No devices found after exhausting all retries.') 3727 raise 3728 elif attempt == retries - 1 and enable_usb_resets: 3729 logging.warning( 3730 'Attempting to reset relevant USB devices prior to the last ' 3731 'attempt.') 3732 _reset_devices() 3733 # math.pow returns floats, so cast to int for easier testing 3734 sleep_s = min(int(math.pow(2, attempt + 1)), 60) 3735 logger.warning( 3736 'No devices found. Will try again after restarting adb server ' 3737 'and a short nap of %d s.', sleep_s) 3738 time.sleep(sleep_s) 3739 RestartServer() 3740 3741 @decorators.WithTimeoutAndRetriesFromInstance() 3742 def RestartAdbd(self, timeout=None, retries=None): 3743 logger.info('Restarting adbd on device.') 3744 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: 3745 self.WriteFile(script.name, _RESTART_ADBD_SCRIPT) 3746 self.RunShellCommand(['source', script.name], 3747 check_return=True, 3748 as_root=True) 3749 self.adb.WaitForDevice() 3750 3751 @decorators.WithTimeoutAndRetriesFromInstance() 3752 def GrantPermissions(self, package, permissions, timeout=None, retries=None): 3753 if not permissions: 3754 return 3755 3756 permissions = set(p for p in permissions 3757 if not _PERMISSIONS_DENYLIST_RE.match(p)) 3758 3759 if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions 3760 and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions): 3761 permissions.add('android.permission.READ_EXTERNAL_STORAGE') 3762 3763 script = ';'.join([ 3764 'p={package}', 'for q in {permissions}', 'do pm grant "$p" "$q"', 3765 'echo "{sep}$q{sep}$?{sep}"', 'done' 3766 ]).format( 3767 package=cmd_helper.SingleQuote(package), 3768 permissions=' '.join( 3769 cmd_helper.SingleQuote(p) for p in sorted(permissions)), 3770 sep=_SHELL_OUTPUT_SEPARATOR) 3771 3772 logger.info('Setting permissions for %s.', package) 3773 res = self.RunShellCommand( 3774 script, 3775 shell=True, 3776 raw_output=True, 3777 large_output=True, 3778 check_return=True) 3779 res = res.split(_SHELL_OUTPUT_SEPARATOR) 3780 failures = [ 3781 (permission, output.strip()) 3782 for permission, status, output in zip(res[1::3], res[2::3], res[0::3]) 3783 if int(status) 3784 ] 3785 3786 if failures: 3787 logger.warning( 3788 'Failed to grant some permissions. Denylist may need to be updated?') 3789 for permission, output in failures: 3790 # Try to grab the relevant error message from the output. 3791 m = _PERMISSIONS_EXCEPTION_RE.search(output) 3792 if m: 3793 error_msg = m.group(0) 3794 elif len(output) > 200: 3795 error_msg = repr(output[:200]) + ' (truncated)' 3796 else: 3797 error_msg = repr(output) 3798 logger.warning('- %s: %s', permission, error_msg) 3799 3800 @decorators.WithTimeoutAndRetriesFromInstance() 3801 def IsScreenOn(self, timeout=None, retries=None): 3802 """Determines if screen is on. 3803 3804 Dumpsys input_method exposes screen on/off state. Below is an explination of 3805 the states. 3806 3807 Pre-L: 3808 On: mScreenOn=true 3809 Off: mScreenOn=false 3810 L+: 3811 On: mInteractive=true 3812 Off: mInteractive=false 3813 3814 Returns: 3815 True if screen is on, false if it is off. 3816 3817 Raises: 3818 device_errors.CommandFailedError: If screen state cannot be found. 3819 """ 3820 if self.build_version_sdk < version_codes.LOLLIPOP: 3821 input_check = 'mScreenOn' 3822 check_value = 'mScreenOn=true' 3823 else: 3824 input_check = 'mInteractive' 3825 check_value = 'mInteractive=true' 3826 dumpsys_out = self._RunPipedShellCommand( 3827 'dumpsys input_method | grep %s' % input_check) 3828 if not dumpsys_out: 3829 raise device_errors.CommandFailedError('Unable to detect screen state', 3830 str(self)) 3831 return check_value in dumpsys_out[0] 3832 3833 @decorators.WithTimeoutAndRetriesFromInstance() 3834 def SetScreen(self, on, timeout=None, retries=None): 3835 """Turns screen on and off. 3836 3837 Args: 3838 on: bool to decide state to switch to. True = on False = off. 3839 """ 3840 3841 def screen_test(): 3842 return self.IsScreenOn() == on 3843 3844 if screen_test(): 3845 logger.info('Screen already in expected state.') 3846 return 3847 self.SendKeyEvent(keyevent.KEYCODE_POWER) 3848 timeout_retry.WaitFor(screen_test, wait_period=1) 3849 3850 @decorators.WithTimeoutAndRetriesFromInstance() 3851 def ChangeOwner(self, owner_group, paths, timeout=None, retries=None): 3852 """Changes file system ownership for permissions. 3853 3854 Args: 3855 owner_group: New owner and group to assign. Note that this should be a 3856 string in the form user[.group] where the group is option. 3857 paths: Paths to change ownership of. 3858 3859 Note that the -R recursive option is not supported by all Android 3860 versions. 3861 """ 3862 if not paths: 3863 return 3864 self.RunShellCommand(['chown', owner_group] + paths, check_return=True) 3865 3866 @decorators.WithTimeoutAndRetriesFromInstance() 3867 def ChangeSecurityContext(self, 3868 security_context, 3869 paths, 3870 timeout=None, 3871 retries=None): 3872 """Changes the SELinux security context for files. 3873 3874 Args: 3875 security_context: The new security context as a string 3876 paths: Paths to change the security context of. 3877 3878 Note that the -R recursive option is not supported by all Android 3879 versions. 3880 """ 3881 if not paths: 3882 return 3883 command = ['chcon', security_context] + paths 3884 3885 # Note, need to force su because chcon can fail with permission errors even 3886 # if the device is rooted. 3887 self.RunShellCommand(command, as_root=_FORCE_SU, check_return=True) 3888