1# Copyright 2019 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import collections 6import contextlib 7import glob 8import json 9import logging 10import os 11import socket 12import stat 13import subprocess 14import threading 15import time 16 17from google.protobuf import text_format # pylint: disable=import-error 18 19from devil.android import apk_helper 20from devil.android import device_utils 21from devil.android.sdk import adb_wrapper 22from devil.android.sdk import version_codes 23from devil.android.tools import system_app 24from devil.utils import cmd_helper 25from devil.utils import timeout_retry 26from py_utils import tempfile_ext 27from pylib import constants 28from pylib.local.emulator import ini 29from pylib.local.emulator.proto import avd_pb2 30 31# A common root directory to store the CIPD packages for creating or starting 32# the emulator instance, e.g. emulator binary, system images, AVDs. 33COMMON_CIPD_ROOT = os.path.join(constants.DIR_SOURCE_ROOT, '.android_emulator') 34 35# Packages that are needed for runtime. 36_PACKAGES_RUNTIME = object() 37# Packages that are needed during AVD creation. 38_PACKAGES_CREATION = object() 39# All the packages that could exist in the AVD config file. 40_PACKAGES_ALL = object() 41 42# These files are used as backing files for corresponding qcow2 images. 43_BACKING_FILES = ('system.img', 'vendor.img') 44 45_DEFAULT_AVDMANAGER_PATH = os.path.join(constants.ANDROID_SDK_ROOT, 46 'cmdline-tools', 'latest', 'bin', 47 'avdmanager') 48# Default to a 480dp mdpi screen (a relatively large phone). 49# See https://developer.android.com/training/multiscreen/screensizes 50# and https://developer.android.com/training/multiscreen/screendensities 51# for more information. 52_DEFAULT_SCREEN_DENSITY = 160 53_DEFAULT_SCREEN_HEIGHT = 960 54_DEFAULT_SCREEN_WIDTH = 480 55 56# Default to swiftshader_indirect since it works for most cases. 57_DEFAULT_GPU_MODE = 'swiftshader_indirect' 58 59# The snapshot name to load/save when writable_system=False. 60# This is the default name used by the emulator binary. 61_DEFAULT_SNAPSHOT_NAME = 'default_boot' 62 63# crbug.com/1275767: Set long press timeout to 1000ms to reduce the flakiness 64# caused by click being incorrectly interpreted as longclick. 65_LONG_PRESS_TIMEOUT = '1000' 66 67# The snapshot name to load/save when writable_system=True 68_SYSTEM_SNAPSHOT_NAME = 'boot_with_system' 69 70_SDCARD_NAME = 'cr-sdcard.img' 71 72 73class AvdException(Exception): 74 """Raised when this module has a problem interacting with an AVD.""" 75 76 def __init__(self, summary, command=None, stdout=None, stderr=None): 77 message_parts = [summary] 78 if command: 79 message_parts.append(' command: %s' % ' '.join(command)) 80 if stdout: 81 message_parts.append(' stdout:') 82 message_parts.extend(' %s' % line for line in stdout.splitlines()) 83 if stderr: 84 message_parts.append(' stderr:') 85 message_parts.extend(' %s' % line for line in stderr.splitlines()) 86 87 # avd.py is executed with python2. 88 # pylint: disable=R1725 89 super(AvdException, self).__init__('\n'.join(message_parts)) 90 91 92def _Load(avd_proto_path): 93 """Loads an Avd proto from a textpb file at the given path. 94 95 Should not be called outside of this module. 96 97 Args: 98 avd_proto_path: path to a textpb file containing an Avd message. 99 """ 100 with open(avd_proto_path) as avd_proto_file: 101 # python generated codes are simplified since Protobuf v3.20.0 and cause 102 # pylint error. See https://github.com/protocolbuffers/protobuf/issues/9730 103 # pylint: disable=no-member 104 return text_format.Merge(avd_proto_file.read(), avd_pb2.Avd()) 105 106 107def _FindMinSdkFile(apk_dir, min_sdk): 108 """Finds the apk file associated with the min_sdk file. 109 110 This reads a version.json file located in the apk_dir to find an apk file 111 that is closest without going over the min_sdk. 112 113 Args: 114 apk_dir: The directory to look for apk files. 115 min_sdk: The minimum sdk version supported by the device. 116 117 Returns: 118 The path to the file that suits the minSdkFile or None 119 """ 120 json_file = os.path.join(apk_dir, 'version.json') 121 if not os.path.exists(json_file): 122 logging.error('Json version file not found: %s', json_file) 123 return None 124 125 min_sdk_found = None 126 curr_min_sdk_version = 0 127 with open(json_file) as f: 128 data = json.loads(f.read()) 129 # Finds the entry that is closest to min_sdk without going over. 130 for entry in data: 131 if (entry['min_sdk'] > curr_min_sdk_version 132 and entry['min_sdk'] <= min_sdk): 133 min_sdk_found = entry 134 curr_min_sdk_version = entry['min_sdk'] 135 136 if not min_sdk_found: 137 logging.error('No suitable apk file found that suits the minimum sdk %d.', 138 min_sdk) 139 return None 140 141 logging.info('Found apk file for mininum sdk %d: %r with version %r', 142 min_sdk, min_sdk_found['file_name'], 143 min_sdk_found['version_name']) 144 return os.path.join(apk_dir, min_sdk_found['file_name']) 145 146 147class _AvdManagerAgent: 148 """Private utility for interacting with avdmanager.""" 149 150 def __init__(self, avd_home, sdk_root): 151 """Create an _AvdManagerAgent. 152 153 Args: 154 avd_home: path to ANDROID_AVD_HOME directory. 155 Typically something like /path/to/dir/.android/avd 156 sdk_root: path to SDK root directory. 157 """ 158 self._avd_home = avd_home 159 self._sdk_root = sdk_root 160 161 self._env = dict(os.environ) 162 163 # The avdmanager from cmdline-tools would look two levels 164 # up from toolsdir to find the SDK root. 165 # Pass avdmanager a fake directory under the directory in which 166 # we install the system images s.t. avdmanager can find the 167 # system images. 168 fake_tools_dir = os.path.join(self._sdk_root, 'non-existent-tools', 169 'non-existent-version') 170 self._env.update({ 171 'ANDROID_AVD_HOME': 172 self._avd_home, 173 'AVDMANAGER_OPTS': 174 '-Dcom.android.sdkmanager.toolsdir=%s' % fake_tools_dir, 175 'JAVA_HOME': 176 constants.JAVA_HOME, 177 }) 178 179 def Create(self, avd_name, system_image, force=False): 180 """Call `avdmanager create`. 181 182 Args: 183 avd_name: name of the AVD to create. 184 system_image: system image to use for the AVD. 185 force: whether to force creation, overwriting any existing 186 AVD with the same name. 187 """ 188 create_cmd = [ 189 _DEFAULT_AVDMANAGER_PATH, 190 '-v', 191 'create', 192 'avd', 193 '-n', 194 avd_name, 195 '-k', 196 system_image, 197 ] 198 if force: 199 create_cmd += ['--force'] 200 201 create_proc = cmd_helper.Popen(create_cmd, 202 stdin=subprocess.PIPE, 203 stdout=subprocess.PIPE, 204 stderr=subprocess.PIPE, 205 env=self._env) 206 output, error = create_proc.communicate(input='\n') 207 if create_proc.returncode != 0: 208 raise AvdException('AVD creation failed', 209 command=create_cmd, 210 stdout=output, 211 stderr=error) 212 213 for line in output.splitlines(): 214 logging.info(' %s', line) 215 216 def Delete(self, avd_name): 217 """Call `avdmanager delete`. 218 219 Args: 220 avd_name: name of the AVD to delete. 221 """ 222 delete_cmd = [ 223 _DEFAULT_AVDMANAGER_PATH, 224 '-v', 225 'delete', 226 'avd', 227 '-n', 228 avd_name, 229 ] 230 try: 231 for line in cmd_helper.IterCmdOutputLines(delete_cmd, env=self._env): 232 logging.info(' %s', line) 233 except subprocess.CalledProcessError as e: 234 # avd.py is executed with python2. 235 # pylint: disable=W0707 236 raise AvdException('AVD deletion failed: %s' % str(e), command=delete_cmd) 237 238 def List(self): 239 """List existing AVDs by the name.""" 240 list_cmd = [ 241 _DEFAULT_AVDMANAGER_PATH, 242 '-v', 243 'list', 244 'avd', 245 '-c', 246 ] 247 output = cmd_helper.GetCmdOutput(list_cmd, env=self._env) 248 return output.splitlines() 249 250 def IsAvailable(self, avd_name): 251 """Check if an AVD exists or not.""" 252 return avd_name in self.List() 253 254 255class AvdConfig: 256 """Represents a particular AVD configuration. 257 258 This class supports creation, installation, and execution of an AVD 259 from a given Avd proto message, as defined in 260 //build/android/pylib/local/emulator/proto/avd.proto. 261 """ 262 263 def __init__(self, avd_proto_path): 264 """Create an AvdConfig object. 265 266 Args: 267 avd_proto_path: path to a textpb file containing an Avd message. 268 """ 269 self.avd_proto_path = avd_proto_path 270 self._config = _Load(avd_proto_path) 271 272 self._initialized = False 273 self._initializer_lock = threading.Lock() 274 275 @property 276 def emulator_home(self): 277 """User-specific emulator configuration directory. 278 279 It corresponds to the environment variable $ANDROID_EMULATOR_HOME. 280 Configs like advancedFeatures.ini are expected to be under this dir. 281 """ 282 return os.path.join(COMMON_CIPD_ROOT, self._config.avd_package.dest_path) 283 284 @property 285 def emulator_sdk_root(self): 286 """The path to the SDK installation directory. 287 288 It corresponds to the environment variable $ANDROID_HOME. 289 290 To be a valid sdk root, it requires to have the subdirecotries "platforms" 291 and "platform-tools". See http://bit.ly/2YAkyFE for context. 292 293 Also, it is expected to have subdirecotries "emulator" and "system-images". 294 """ 295 emulator_sdk_root = os.path.join(COMMON_CIPD_ROOT, 296 self._config.emulator_package.dest_path) 297 # Ensure this is a valid sdk root. 298 required_dirs = [ 299 os.path.join(emulator_sdk_root, 'platforms'), 300 os.path.join(emulator_sdk_root, 'platform-tools'), 301 ] 302 for d in required_dirs: 303 if not os.path.exists(d): 304 os.makedirs(d) 305 306 return emulator_sdk_root 307 308 @property 309 def emulator_path(self): 310 """The path to the emulator binary.""" 311 return os.path.join(self.emulator_sdk_root, 'emulator', 'emulator') 312 313 @property 314 def qemu_img_path(self): 315 """The path to the qemu-img binary. 316 317 This is used to rebase the paths in qcow2 images. 318 """ 319 return os.path.join(self.emulator_sdk_root, 'emulator', 'qemu-img') 320 321 @property 322 def mksdcard_path(self): 323 """The path to the mksdcard binary. 324 325 This is used to create a sdcard image. 326 """ 327 return os.path.join(self.emulator_sdk_root, 'emulator', 'mksdcard') 328 329 @property 330 def avd_settings(self): 331 """The AvdSettings in the avd proto file. 332 333 This defines how to configure the AVD at creation. 334 """ 335 return self._config.avd_settings 336 337 @property 338 def avd_launch_settings(self): 339 """The AvdLaunchSettings in the avd proto file. 340 341 This defines AVD setting during launch time. 342 """ 343 return self._config.avd_launch_settings 344 345 @property 346 def avd_name(self): 347 """The name of the AVD to create or use.""" 348 return self._config.avd_name 349 350 @property 351 def avd_home(self): 352 """The path that contains the files of one or multiple AVDs.""" 353 avd_home = os.path.join(self.emulator_home, 'avd') 354 if not os.path.exists(avd_home): 355 os.makedirs(avd_home) 356 357 return avd_home 358 359 @property 360 def _avd_dir(self): 361 """The path that contains the files of the given AVD.""" 362 return os.path.join(self.avd_home, '%s.avd' % self.avd_name) 363 364 @property 365 def _system_image_dir(self): 366 """The path of the directory that directly contains the system images. 367 368 For example, if the system_image_name is 369 "system-images;android-33;google_apis;x86_64" 370 371 The _system_image_dir will be: 372 <COMMON_CIPD_ROOT>/<dest_path>/system-images/android-33/google_apis/x86_64 373 374 This is used to rebase the paths in qcow2 images. 375 """ 376 return os.path.join(COMMON_CIPD_ROOT, 377 self._config.system_image_package.dest_path, 378 *self._config.system_image_name.split(';')) 379 380 @property 381 def _root_ini_path(self): 382 """The <avd_name>.ini file of the given AVD.""" 383 return os.path.join(self.avd_home, '%s.ini' % self.avd_name) 384 385 @property 386 def _config_ini_path(self): 387 """The config.ini file under _avd_dir.""" 388 return os.path.join(self._avd_dir, 'config.ini') 389 390 @property 391 def _features_ini_path(self): 392 return os.path.join(self.emulator_home, 'advancedFeatures.ini') 393 394 @property 395 def xdg_config_dir(self): 396 """The base directory to store qt config file. 397 398 This dir should be added to the env variable $XDG_CONFIG_DIRS so that 399 _qt_config_path can take effect. See https://bit.ly/3HIQRZ3 for context. 400 """ 401 config_dir = os.path.join(self.emulator_home, '.config') 402 if not os.path.exists(config_dir): 403 os.makedirs(config_dir) 404 405 return config_dir 406 407 @property 408 def _qt_config_path(self): 409 """The qt config file for emulator.""" 410 qt_config_dir = os.path.join(self.xdg_config_dir, 411 'Android Open Source Project') 412 if not os.path.exists(qt_config_dir): 413 os.makedirs(qt_config_dir) 414 415 return os.path.join(qt_config_dir, 'Emulator.conf') 416 417 def HasSnapshot(self, snapshot_name): 418 """Check if a given snapshot exists or not.""" 419 snapshot_path = os.path.join(self._avd_dir, 'snapshots', snapshot_name) 420 return os.path.exists(snapshot_path) 421 422 def Create(self, 423 force=False, 424 snapshot=False, 425 keep=False, 426 additional_apks=None, 427 privileged_apk_tuples=None, 428 cipd_json_output=None, 429 dry_run=False): 430 """Create an instance of the AVD CIPD package. 431 432 This method: 433 - installs the requisite system image 434 - creates the AVD 435 - modifies the AVD's ini files to support running chromium tests 436 in chromium infrastructure 437 - optionally starts, installs additional apks and/or privileged apks, and 438 stops the AVD for snapshotting (default no) 439 - By default creates and uploads an instance of the AVD CIPD package 440 (can be turned off by dry_run flag). 441 - optionally deletes the AVD (default yes) 442 443 Args: 444 force: bool indicating whether to force create the AVD. 445 snapshot: bool indicating whether to snapshot the AVD before creating 446 the CIPD package. 447 keep: bool indicating whether to keep the AVD after creating 448 the CIPD package. 449 additional_apks: a list of strings contains the paths to the APKs. These 450 APKs will be installed after AVD is started. 451 privileged_apk_tuples: a list of (apk_path, device_partition) tuples where 452 |apk_path| is a string containing the path to the APK, and 453 |device_partition| is a string indicating the system image partition on 454 device that contains "priv-app" directory, e.g. "/system", "/product". 455 cipd_json_output: string path to pass to `cipd create` via -json-output. 456 dry_run: When set to True, it will skip the CIPD package creation 457 after creating the AVD. 458 """ 459 logging.info('Installing required packages.') 460 self._InstallCipdPackages(_PACKAGES_CREATION) 461 462 avd_manager = _AvdManagerAgent(avd_home=self.avd_home, 463 sdk_root=self.emulator_sdk_root) 464 465 logging.info('Creating AVD.') 466 avd_manager.Create(avd_name=self.avd_name, 467 system_image=self._config.system_image_name, 468 force=force) 469 470 try: 471 logging.info('Modifying AVD configuration.') 472 473 # Clear out any previous configuration or state from this AVD. 474 with ini.update_ini_file(self._root_ini_path) as r_ini_contents: 475 r_ini_contents['path.rel'] = 'avd/%s.avd' % self.avd_name 476 477 with ini.update_ini_file(self._features_ini_path) as f_ini_contents: 478 # features_ini file will not be refreshed by avdmanager during 479 # creation. So explicitly clear its content to exclude any leftover 480 # from previous creation. 481 f_ini_contents.clear() 482 f_ini_contents.update(self.avd_settings.advanced_features) 483 484 with ini.update_ini_file(self._config_ini_path) as config_ini_contents: 485 # Update avd_properties first so that they won't override settings 486 # like screen and ram_size 487 config_ini_contents.update(self.avd_settings.avd_properties) 488 489 height = self.avd_settings.screen.height or _DEFAULT_SCREEN_HEIGHT 490 width = self.avd_settings.screen.width or _DEFAULT_SCREEN_WIDTH 491 density = self.avd_settings.screen.density or _DEFAULT_SCREEN_DENSITY 492 493 config_ini_contents.update({ 494 'disk.dataPartition.size': '4G', 495 'hw.keyboard': 'yes', 496 'hw.lcd.density': density, 497 'hw.lcd.height': height, 498 'hw.lcd.width': width, 499 'hw.mainKeys': 'no', # Show nav buttons on screen 500 }) 501 502 if self.avd_settings.ram_size: 503 config_ini_contents['hw.ramSize'] = self.avd_settings.ram_size 504 505 config_ini_contents['hw.sdCard'] = 'yes' 506 if self.avd_settings.sdcard.size: 507 sdcard_path = os.path.join(self._avd_dir, _SDCARD_NAME) 508 cmd_helper.RunCmd([ 509 self.mksdcard_path, 510 self.avd_settings.sdcard.size, 511 sdcard_path, 512 ]) 513 config_ini_contents['hw.sdCard.path'] = sdcard_path 514 515 if not additional_apks: 516 additional_apks = [] 517 for pkg in self._config.additional_apk: 518 apk_dir = os.path.join(COMMON_CIPD_ROOT, pkg.dest_path) 519 apk_file = _FindMinSdkFile(apk_dir, self._config.min_sdk) 520 # Some of these files come from chrome internal, so may not be 521 # available to non-internal permissioned users. 522 if os.path.exists(apk_file): 523 logging.info('Adding additional apk for install: %s', apk_file) 524 additional_apks.append(apk_file) 525 526 if not privileged_apk_tuples: 527 privileged_apk_tuples = [] 528 for pkg in self._config.privileged_apk: 529 apk_dir = os.path.join(COMMON_CIPD_ROOT, pkg.dest_path) 530 apk_file = _FindMinSdkFile(apk_dir, self._config.min_sdk) 531 # Some of these files come from chrome internal, so may not be 532 # available to non-internal permissioned users. 533 if os.path.exists(apk_file): 534 logging.info('Adding privilege apk for install: %s', apk_file) 535 privileged_apk_tuples.append( 536 (apk_file, self._config.install_privileged_apk_partition)) 537 538 # Start & stop the AVD. 539 self._Initialize() 540 instance = _AvdInstance(self) 541 # Enable debug for snapshot when it is set to True 542 debug_tags = 'time,init,snapshot' if snapshot else None 543 # Installing privileged apks requires modifying the system 544 # image. 545 writable_system = bool(privileged_apk_tuples) 546 gpu_mode = self.avd_launch_settings.gpu_mode or _DEFAULT_GPU_MODE 547 instance.Start(ensure_system_settings=False, 548 read_only=False, 549 writable_system=writable_system, 550 gpu_mode=gpu_mode, 551 debug_tags=debug_tags) 552 553 assert instance.device is not None, '`instance.device` not initialized.' 554 # Android devices with full-disk encryption are encrypted on first boot, 555 # and then get decrypted to continue the boot process (See details in 556 # https://bit.ly/3agmjcM). 557 # Wait for this step to complete since it can take a while for old OSs 558 # like M, otherwise the avd may have "Encryption Unsuccessful" error. 559 instance.device.WaitUntilFullyBooted(decrypt=True, timeout=360, retries=0) 560 logging.info('The build fingerprint of the system is %r', 561 instance.device.build_fingerprint) 562 563 if additional_apks: 564 for apk in additional_apks: 565 instance.device.Install(apk, allow_downgrade=True, reinstall=True) 566 package_name = apk_helper.GetPackageName(apk) 567 package_version = instance.device.GetApplicationVersion(package_name) 568 logging.info('The version for package %r on the device is %r', 569 package_name, package_version) 570 571 if privileged_apk_tuples: 572 system_app.InstallPrivilegedApps(instance.device, privileged_apk_tuples) 573 for apk, _ in privileged_apk_tuples: 574 package_name = apk_helper.GetPackageName(apk) 575 package_version = instance.device.GetApplicationVersion(package_name) 576 logging.info('The version for package %r on the device is %r', 577 package_name, package_version) 578 579 # Skip Marshmallow as svc commands fail on this version. 580 if instance.device.build_version_sdk != 23: 581 # Always disable the network to prevent built-in system apps from 582 # updating themselves, which could take over package manager and 583 # cause shell command timeout. 584 # Use svc as this also works on the images with build type "user", and 585 # does not require a reboot or broadcast compared to setting the 586 # airplane_mode_on in "settings/global". 587 logging.info('Disabling the network.') 588 instance.device.RunShellCommand(['svc', 'wifi', 'disable'], 589 as_root=True, 590 check_return=True) 591 instance.device.RunShellCommand(['svc', 'data', 'disable'], 592 as_root=True, 593 check_return=True) 594 595 if snapshot: 596 logging.info('Wait additional 60 secs before saving snapshot for AVD') 597 time.sleep(60) 598 instance.SaveSnapshot() 599 600 instance.Stop() 601 602 # The multiinstance lock file seems to interfere with the emulator's 603 # operation in some circumstances (beyond the obvious -read-only ones), 604 # and there seems to be no mechanism by which it gets closed or deleted. 605 # See https://bit.ly/2pWQTH7 for context. 606 multiInstanceLockFile = os.path.join(self._avd_dir, 'multiinstance.lock') 607 if os.path.exists(multiInstanceLockFile): 608 os.unlink(multiInstanceLockFile) 609 610 package_def_content = { 611 'package': 612 self._config.avd_package.package_name, 613 'root': 614 self.emulator_home, 615 'install_mode': 616 'copy', 617 'data': [{ 618 'dir': os.path.relpath(self._avd_dir, self.emulator_home) 619 }, { 620 'file': 621 os.path.relpath(self._root_ini_path, self.emulator_home) 622 }, { 623 'file': 624 os.path.relpath(self._features_ini_path, self.emulator_home) 625 }], 626 } 627 628 logging.info('Creating AVD CIPD package.') 629 logging.info('ensure file content: %s', 630 json.dumps(package_def_content, indent=2)) 631 632 with tempfile_ext.TemporaryFileName(suffix='.json') as package_def_path: 633 with open(package_def_path, 'w') as package_def_file: 634 json.dump(package_def_content, package_def_file) 635 636 logging.info(' %s', self._config.avd_package.package_name) 637 cipd_create_cmd = [ 638 'cipd', 639 'create', 640 '-pkg-def', 641 package_def_path, 642 '-tag', 643 'emulator_version:%s' % self._config.emulator_package.version, 644 '-tag', 645 'system_image_version:%s' % 646 self._config.system_image_package.version, 647 ] 648 if cipd_json_output: 649 cipd_create_cmd.extend([ 650 '-json-output', 651 cipd_json_output, 652 ]) 653 logging.info('running %r%s', cipd_create_cmd, 654 ' (dry_run)' if dry_run else '') 655 if not dry_run: 656 try: 657 for line in cmd_helper.IterCmdOutputLines(cipd_create_cmd): 658 logging.info(' %s', line) 659 except subprocess.CalledProcessError as e: 660 # avd.py is executed with python2. 661 # pylint: disable=W0707 662 raise AvdException('CIPD package creation failed: %s' % str(e), 663 command=cipd_create_cmd) 664 665 finally: 666 if not keep: 667 logging.info('Deleting AVD.') 668 avd_manager.Delete(avd_name=self.avd_name) 669 670 def IsAvailable(self): 671 """Returns whether emulator is up-to-date.""" 672 if not os.path.exists(self._config_ini_path): 673 return False 674 675 # Skip when no version exists to prevent "IsAvailable()" returning False 676 # for emualtors set up using Create() (rather than Install()). 677 for cipd_root, pkgs in self._IterCipdPackages(_PACKAGES_RUNTIME, 678 check_version=False): 679 stdout = subprocess.run(['cipd', 'installed', '--root', cipd_root], 680 capture_output=True, 681 check=False, 682 encoding='utf8').stdout 683 # Output looks like: 684 # Packages: 685 # name1:version1 686 # name2:version2 687 installed = [l.strip().split(':', 1) for l in stdout.splitlines()[1:]] 688 689 if any([p.package_name, p.version] not in installed for p in pkgs): 690 return False 691 return True 692 693 def Uninstall(self): 694 """Uninstall all the artifacts associated with the given config. 695 696 Artifacts includes: 697 - CIPD packages specified in the avd config. 698 - The local AVD created by `Create`, if present. 699 700 """ 701 # Delete any existing local AVD. This must occur before deleting CIPD 702 # packages because a AVD needs system image to be recognized by avdmanager. 703 avd_manager = _AvdManagerAgent(avd_home=self.avd_home, 704 sdk_root=self.emulator_sdk_root) 705 if avd_manager.IsAvailable(self.avd_name): 706 logging.info('Deleting local AVD %s', self.avd_name) 707 avd_manager.Delete(self.avd_name) 708 709 # Delete installed CIPD packages. 710 for cipd_root, _ in self._IterCipdPackages(_PACKAGES_ALL, 711 check_version=False): 712 logging.info('Uninstalling packages in %s', cipd_root) 713 if not os.path.exists(cipd_root): 714 continue 715 # Create an empty ensure file to removed any installed CIPD packages. 716 ensure_path = os.path.join(cipd_root, '.ensure') 717 with open(ensure_path, 'w') as ensure_file: 718 ensure_file.write('$ParanoidMode CheckIntegrity\n\n') 719 ensure_cmd = [ 720 'cipd', 721 'ensure', 722 '-ensure-file', 723 ensure_path, 724 '-root', 725 cipd_root, 726 ] 727 try: 728 for line in cmd_helper.IterCmdOutputLines(ensure_cmd): 729 logging.info(' %s', line) 730 except subprocess.CalledProcessError as e: 731 # avd.py is executed with python2. 732 # pylint: disable=W0707 733 raise AvdException('Failed to uninstall CIPD packages: %s' % str(e), 734 command=ensure_cmd) 735 736 def Install(self): 737 """Installs the requested CIPD packages and prepares them for use. 738 739 This includes making files writeable and revising some of the 740 emulator's internal config files. 741 742 Returns: None 743 Raises: AvdException on failure to install. 744 """ 745 self._InstallCipdPackages(_PACKAGES_RUNTIME) 746 self._MakeWriteable() 747 self._UpdateConfigs() 748 self._RebaseQcow2Images() 749 750 def _RebaseQcow2Images(self): 751 """Rebase the paths in qcow2 images. 752 753 qcow2 files may exists in avd directory which have hard-coded paths to the 754 backing files, e.g., system.img, vendor.img. Such paths need to be rebased 755 if the avd is moved to a different directory in order to boot successfully. 756 """ 757 for f in _BACKING_FILES: 758 qcow2_image_path = os.path.join(self._avd_dir, '%s.qcow2' % f) 759 if not os.path.exists(qcow2_image_path): 760 continue 761 backing_file_path = os.path.join(self._system_image_dir, f) 762 logging.info('Rebasing the qcow2 image %r with the backing file %r', 763 qcow2_image_path, backing_file_path) 764 cmd_helper.RunCmd([ 765 self.qemu_img_path, 766 'rebase', 767 '-u', 768 '-f', 769 'qcow2', 770 '-b', 771 # The path to backing file must be relative to the qcow2 image. 772 os.path.relpath(backing_file_path, os.path.dirname(qcow2_image_path)), 773 qcow2_image_path, 774 ]) 775 776 def _ListPackages(self, packages): 777 if packages is _PACKAGES_RUNTIME: 778 packages = [ 779 self._config.avd_package, 780 self._config.emulator_package, 781 self._config.system_image_package, 782 ] 783 elif packages is _PACKAGES_CREATION: 784 packages = [ 785 self._config.emulator_package, 786 self._config.system_image_package, 787 *self._config.privileged_apk, 788 *self._config.additional_apk, 789 ] 790 elif packages is _PACKAGES_ALL: 791 packages = [ 792 self._config.avd_package, 793 self._config.emulator_package, 794 self._config.system_image_package, 795 *self._config.privileged_apk, 796 *self._config.additional_apk, 797 ] 798 return packages 799 800 def _IterCipdPackages(self, packages, check_version=True): 801 """Iterate a list of CIPD packages by their CIPD roots. 802 803 Args: 804 packages: a list of packages from an AVD config. 805 check_version: If set, raise Exception when a package has no version. 806 """ 807 pkgs_by_dir = collections.defaultdict(list) 808 for pkg in self._ListPackages(packages): 809 if pkg.version: 810 pkgs_by_dir[pkg.dest_path].append(pkg) 811 elif check_version: 812 raise AvdException('Expecting a version for the package %s' % 813 pkg.package_name) 814 815 for pkg_dir, pkgs in pkgs_by_dir.items(): 816 cipd_root = os.path.join(COMMON_CIPD_ROOT, pkg_dir) 817 yield cipd_root, pkgs 818 819 def _InstallCipdPackages(self, packages, check_version=True): 820 for cipd_root, pkgs in self._IterCipdPackages(packages, 821 check_version=check_version): 822 logging.info('Installing packages in %s', cipd_root) 823 if not os.path.exists(cipd_root): 824 os.makedirs(cipd_root) 825 ensure_path = os.path.join(cipd_root, '.ensure') 826 with open(ensure_path, 'w') as ensure_file: 827 # Make CIPD ensure that all files are present and correct, 828 # even if it thinks the package is installed. 829 ensure_file.write('$ParanoidMode CheckIntegrity\n\n') 830 for pkg in pkgs: 831 ensure_file.write('%s %s\n' % (pkg.package_name, pkg.version)) 832 logging.info(' %s %s', pkg.package_name, pkg.version) 833 ensure_cmd = [ 834 'cipd', 835 'ensure', 836 '-ensure-file', 837 ensure_path, 838 '-root', 839 cipd_root, 840 ] 841 try: 842 for line in cmd_helper.IterCmdOutputLines(ensure_cmd): 843 logging.info(' %s', line) 844 except subprocess.CalledProcessError as e: 845 # avd.py is executed with python2. 846 # pylint: disable=W0707 847 raise AvdException('Failed to install CIPD packages: %s' % str(e), 848 command=ensure_cmd) 849 850 def _MakeWriteable(self): 851 # The emulator requires that some files are writable. 852 for dirname, _, filenames in os.walk(self.emulator_home): 853 for f in filenames: 854 path = os.path.join(dirname, f) 855 mode = os.lstat(path).st_mode 856 if mode & stat.S_IRUSR: 857 mode = mode | stat.S_IWUSR 858 os.chmod(path, mode) 859 860 def _UpdateConfigs(self): 861 """Update various properties in config files after installation. 862 863 AVD config files contain some properties which can be different between AVD 864 creation and installation, e.g. hw.sdCard.path, which is an absolute path. 865 Update their values so that: 866 * Emulator instance can be booted correctly. 867 * The snapshot can be loaded successfully. 868 """ 869 logging.info('Updating AVD configurations.') 870 # Update the absolute avd path in root_ini file 871 with ini.update_ini_file(self._root_ini_path) as r_ini_contents: 872 r_ini_contents['path'] = self._avd_dir 873 874 # Update hardware settings. 875 config_paths = [self._config_ini_path] 876 # The file hardware.ini within each snapshot need to be updated as well. 877 hw_ini_glob_pattern = os.path.join(self._avd_dir, 'snapshots', '*', 878 'hardware.ini') 879 config_paths.extend(glob.glob(hw_ini_glob_pattern)) 880 881 properties = {} 882 # Update hw.sdCard.path if applicable 883 sdcard_path = os.path.join(self._avd_dir, _SDCARD_NAME) 884 if os.path.exists(sdcard_path): 885 properties['hw.sdCard.path'] = sdcard_path 886 887 for config_path in config_paths: 888 with ini.update_ini_file(config_path) as config_contents: 889 config_contents.update(properties) 890 891 # Create qt config file to disable certain warnings when launched in window. 892 with ini.update_ini_file(self._qt_config_path) as config_contents: 893 # Disable nested virtualization warning. 894 config_contents['General'] = {'showNestedWarning': 'false'} 895 # Disable adb warning. 896 config_contents['set'] = {'autoFindAdb': 'false'} 897 898 def _Initialize(self): 899 if self._initialized: 900 return 901 902 with self._initializer_lock: 903 if self._initialized: 904 return 905 906 # Emulator start-up looks for the adb daemon. Make sure it's running. 907 adb_wrapper.AdbWrapper.StartServer() 908 909 # Emulator start-up requires a valid sdk root. 910 assert self.emulator_sdk_root 911 912 def CreateInstance(self, output_manager=None): 913 """Creates an AVD instance without starting it. 914 915 Returns: 916 An _AvdInstance. 917 """ 918 self._Initialize() 919 return _AvdInstance(self, output_manager=output_manager) 920 921 def StartInstance(self): 922 """Starts an AVD instance. 923 924 Returns: 925 An _AvdInstance. 926 """ 927 instance = self.CreateInstance() 928 instance.Start() 929 return instance 930 931 932class _AvdInstance: 933 """Represents a single running instance of an AVD. 934 935 This class should only be created directly by AvdConfig.StartInstance, 936 but its other methods can be freely called. 937 """ 938 939 def __init__(self, avd_config, output_manager=None): 940 """Create an _AvdInstance object. 941 942 Args: 943 avd_config: an AvdConfig instance. 944 output_manager: a pylib.base.output_manager.OutputManager instance. 945 """ 946 self._avd_config = avd_config 947 self._avd_name = avd_config.avd_name 948 self._emulator_home = avd_config.emulator_home 949 self._emulator_path = avd_config.emulator_path 950 self._emulator_proc = None 951 self._emulator_serial = None 952 self._emulator_device = None 953 954 self._output_manager = output_manager 955 self._output_file = None 956 957 self._writable_system = False 958 self._debug_tags = None 959 960 def __str__(self): 961 return '%s|%s' % (self._avd_name, (self._emulator_serial or id(self))) 962 963 def Start(self, 964 ensure_system_settings=True, 965 read_only=True, 966 window=False, 967 writable_system=False, 968 gpu_mode=None, 969 wipe_data=False, 970 debug_tags=None, 971 disk_size=None, 972 enable_network=False, 973 require_fast_start=False): 974 """Starts the emulator running an instance of the given AVD. 975 976 Note when ensure_system_settings is True, the program will wait until the 977 emulator is fully booted, and then update system settings. 978 """ 979 is_slow_start = not require_fast_start 980 # Force to load system snapshot if detected. 981 if self.HasSystemSnapshot(): 982 if not writable_system: 983 logging.info('System snapshot found. Set "writable_system=True" ' 984 'to load it properly.') 985 writable_system = True 986 if read_only: 987 logging.info('System snapshot found. Set "read_only=False" ' 988 'to load it properly.') 989 read_only = False 990 elif writable_system: 991 is_slow_start = True 992 logging.warning('Emulator will be slow to start, as ' 993 '"writable_system=True" but system snapshot not found.') 994 995 self._writable_system = writable_system 996 997 with tempfile_ext.TemporaryFileName() as socket_path, (contextlib.closing( 998 socket.socket(socket.AF_UNIX))) as sock: 999 sock.bind(socket_path) 1000 emulator_cmd = [ 1001 self._emulator_path, 1002 '-avd', 1003 self._avd_name, 1004 '-report-console', 1005 'unix:%s' % socket_path, 1006 '-no-boot-anim', 1007 # Explicitly prevent emulator from auto-saving to snapshot on exit. 1008 '-no-snapshot-save', 1009 # Explicitly set the snapshot name for auto-load 1010 '-snapshot', 1011 self.GetSnapshotName(), 1012 ] 1013 1014 avd_type = self._avd_name.split('_')[1] 1015 logging.info('Emulator Type: %s', avd_type) 1016 1017 if avd_type == 'car': 1018 logging.info('Auto emulator will start slow') 1019 is_slow_start = True 1020 1021 if wipe_data: 1022 emulator_cmd.append('-wipe-data') 1023 if disk_size: 1024 emulator_cmd.extend(['-partition-size', str(disk_size)]) 1025 if read_only: 1026 emulator_cmd.append('-read-only') 1027 if writable_system: 1028 emulator_cmd.append('-writable-system') 1029 # Note when "--gpu-mode" is set to "host": 1030 # * It needs a valid DISPLAY env, even if "--emulator-window" is false. 1031 # Otherwise it may throw errors like "Failed to initialize backend 1032 # EGL display". See the code in https://bit.ly/3ruiMlB as an example 1033 # to setup the DISPLAY env with xvfb. 1034 # * It will not work under remote sessions like chrome remote desktop. 1035 if not gpu_mode: 1036 gpu_mode = (self._avd_config.avd_launch_settings.gpu_mode 1037 or _DEFAULT_GPU_MODE) 1038 emulator_cmd.extend(['-gpu', gpu_mode]) 1039 if debug_tags: 1040 self._debug_tags = set(debug_tags.split(',')) 1041 # Always print timestamp when debug tags are set. 1042 self._debug_tags.add('time') 1043 emulator_cmd.extend(['-debug', ','.join(self._debug_tags)]) 1044 if 'kernel' in self._debug_tags or 'all' in self._debug_tags: 1045 # TODO(crbug.com/1404176): newer API levels need "-virtio-console" 1046 # as well to print kernel log. 1047 emulator_cmd.append('-show-kernel') 1048 1049 emulator_env = { 1050 # kill immediately when emulator hang. 1051 'ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL': '0', 1052 # Sets the emulator configuration directory 1053 'ANDROID_EMULATOR_HOME': self._emulator_home, 1054 } 1055 if 'DISPLAY' in os.environ: 1056 emulator_env['DISPLAY'] = os.environ.get('DISPLAY') 1057 if window: 1058 if 'DISPLAY' not in emulator_env: 1059 raise AvdException('Emulator failed to start: DISPLAY not defined') 1060 else: 1061 emulator_cmd.append('-no-window') 1062 1063 # Need this for the qt config file to take effect. 1064 xdg_config_dirs = [self._avd_config.xdg_config_dir] 1065 if 'XDG_CONFIG_DIRS' in os.environ: 1066 xdg_config_dirs.append(os.environ.get('XDG_CONFIG_DIRS')) 1067 emulator_env['XDG_CONFIG_DIRS'] = ':'.join(xdg_config_dirs) 1068 1069 sock.listen(1) 1070 1071 logging.info('Starting emulator...') 1072 logging.info( 1073 ' With environments: %s', 1074 ' '.join(['%s=%s' % (k, v) for k, v in emulator_env.items()])) 1075 logging.info(' With commands: %s', ' '.join(emulator_cmd)) 1076 1077 # Enable the emulator log when debug_tags is set. 1078 if self._debug_tags: 1079 # Write to an ArchivedFile if output manager is set, otherwise stdout. 1080 if self._output_manager: 1081 self._output_file = self._output_manager.CreateArchivedFile( 1082 'emulator_%s' % time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), 1083 'emulator') 1084 else: 1085 self._output_file = open('/dev/null', 'w') 1086 self._emulator_proc = cmd_helper.Popen(emulator_cmd, 1087 stdout=self._output_file, 1088 stderr=self._output_file, 1089 env=emulator_env) 1090 1091 # Waits for the emulator to report its serial as requested via 1092 # -report-console. See http://bit.ly/2lK3L18 for more. 1093 def listen_for_serial(s): 1094 logging.info('Waiting for connection from emulator.') 1095 with contextlib.closing(s.accept()[0]) as conn: 1096 val = conn.recv(1024) 1097 return 'emulator-%d' % int(val) 1098 1099 try: 1100 self._emulator_serial = timeout_retry.Run( 1101 listen_for_serial, 1102 timeout=120 if is_slow_start else 30, 1103 retries=0, 1104 args=[sock]) 1105 logging.info('%s started', self._emulator_serial) 1106 except Exception: 1107 self.Stop(force=True) 1108 raise 1109 1110 # Set the system settings in "Start" here instead of setting in "Create" 1111 # because "Create" is used during AVD creation, and we want to avoid extra 1112 # turn-around on rolling AVD. 1113 if ensure_system_settings: 1114 assert self.device is not None, '`instance.device` not initialized.' 1115 logging.info('Waiting for device to be fully booted.') 1116 self.device.WaitUntilFullyBooted(timeout=360 if is_slow_start else 90, 1117 retries=0) 1118 logging.info('Device fully booted, verifying system settings.') 1119 _EnsureSystemSettings(self.device) 1120 1121 if enable_network: 1122 _EnableNetwork(self.device) 1123 1124 def Stop(self, force=False): 1125 """Stops the emulator process. 1126 1127 When "force" is True, we will call "terminate" on the emulator process, 1128 which is recommended when emulator is not responding to adb commands. 1129 """ 1130 # Close output file first in case emulator process killing goes wrong. 1131 if self._output_file: 1132 if self._debug_tags: 1133 if self._output_manager: 1134 self._output_manager.ArchiveArchivedFile(self._output_file, 1135 delete=True) 1136 link = self._output_file.Link() 1137 if link: 1138 logging.critical('Emulator logs saved to %s', link) 1139 else: 1140 self._output_file.close() 1141 self._output_file = None 1142 1143 if self._emulator_proc: 1144 if self._emulator_proc.poll() is None: 1145 if force or not self.device: 1146 self._emulator_proc.terminate() 1147 else: 1148 self.device.adb.Emu('kill') 1149 self._emulator_proc.wait() 1150 self._emulator_proc = None 1151 self._emulator_serial = None 1152 self._emulator_device = None 1153 1154 def GetSnapshotName(self): 1155 """Return the snapshot name to load/save. 1156 1157 Emulator has a different snapshot process when '-writable-system' flag is 1158 set (See https://issuetracker.google.com/issues/135857816#comment8). 1159 1160 """ 1161 if self._writable_system: 1162 return _SYSTEM_SNAPSHOT_NAME 1163 1164 return _DEFAULT_SNAPSHOT_NAME 1165 1166 def HasSystemSnapshot(self): 1167 """Check if the instance has the snapshot named _SYSTEM_SNAPSHOT_NAME.""" 1168 return self._avd_config.HasSnapshot(_SYSTEM_SNAPSHOT_NAME) 1169 1170 def SaveSnapshot(self): 1171 snapshot_name = self.GetSnapshotName() 1172 if self.device: 1173 logging.info('Saving snapshot to %r.', snapshot_name) 1174 self.device.adb.Emu(['avd', 'snapshot', 'save', snapshot_name]) 1175 1176 @property 1177 def serial(self): 1178 return self._emulator_serial 1179 1180 @property 1181 def device(self): 1182 if not self._emulator_device and self._emulator_serial: 1183 self._emulator_device = device_utils.DeviceUtils(self._emulator_serial) 1184 return self._emulator_device 1185 1186 1187# TODO(crbug.com/1275767): Refactor it to a dict-based approach. 1188def _EnsureSystemSettings(device): 1189 set_long_press_timeout_cmd = [ 1190 'settings', 'put', 'secure', 'long_press_timeout', _LONG_PRESS_TIMEOUT 1191 ] 1192 device.RunShellCommand(set_long_press_timeout_cmd, check_return=True) 1193 1194 # Verify if long_press_timeout is set correctly. 1195 get_long_press_timeout_cmd = [ 1196 'settings', 'get', 'secure', 'long_press_timeout' 1197 ] 1198 adb_output = device.RunShellCommand(get_long_press_timeout_cmd, 1199 check_return=True) 1200 if _LONG_PRESS_TIMEOUT in adb_output: 1201 logging.info('long_press_timeout set to %r', _LONG_PRESS_TIMEOUT) 1202 else: 1203 logging.warning('long_press_timeout is not set correctly') 1204 1205 # TODO(crbug.com/1488458): Move the date sync function to device_utils.py 1206 if device.IsUserBuild(): 1207 logging.warning('Cannot sync the device date on "user" build') 1208 return 1209 1210 logging.info('Sync the device date.') 1211 timezone = device.RunShellCommand(['date', '+"%Z"'], 1212 single_line=True, 1213 check_return=True) 1214 if timezone != 'UTC': 1215 device.RunShellCommand(['setprop', 'persist.sys.timezone', '"Etc/UTC"'], 1216 check_return=True, 1217 as_root=True) 1218 set_date_format = '%Y%m%d.%H%M%S' 1219 set_date_command = ['date', '-s'] 1220 if device.build_version_sdk >= version_codes.MARSHMALLOW: 1221 set_date_format = '%m%d%H%M%Y.%S' 1222 set_date_command = ['date'] 1223 strgmtime = time.strftime(set_date_format, time.gmtime()) 1224 set_date_command.append(strgmtime) 1225 device.RunShellCommand(set_date_command, check_return=True, as_root=True) 1226 1227 logging.info('Hide system error dialogs such as crash and ANR dialogs.') 1228 device.RunShellCommand( 1229 ['settings', 'put', 'global', 'hide_error_dialogs', '1']) 1230 1231 1232def _EnableNetwork(device): 1233 logging.info('Enable the network on the emulator.') 1234 # TODO(https://crbug.com/1486376): Remove airplane_mode once all AVD 1235 # are rolled to svc-based version. 1236 device.RunShellCommand( 1237 ['settings', 'put', 'global', 'airplane_mode_on', '0'], as_root=True) 1238 device.RunShellCommand( 1239 ['am', 'broadcast', '-a', 'android.intent.action.AIRPLANE_MODE'], 1240 as_root=True) 1241 device.RunShellCommand(['svc', 'wifi', 'enable'], as_root=True) 1242 device.RunShellCommand(['svc', 'data', 'enable'], as_root=True) 1243