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