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 import settings 22from devil.android.sdk import adb_wrapper 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, 560 wifi=True, 561 timeout=180, 562 retries=0) 563 564 if additional_apks: 565 for apk in additional_apks: 566 instance.device.Install(apk, allow_downgrade=True, reinstall=True) 567 package_name = apk_helper.GetPackageName(apk) 568 package_version = instance.device.GetApplicationVersion(package_name) 569 logging.info('The version for package %r on the device is %r', 570 package_name, package_version) 571 572 if privileged_apk_tuples: 573 system_app.InstallPrivilegedApps(instance.device, privileged_apk_tuples) 574 for apk, _ in privileged_apk_tuples: 575 package_name = apk_helper.GetPackageName(apk) 576 package_version = instance.device.GetApplicationVersion(package_name) 577 logging.info('The version for package %r on the device is %r', 578 package_name, package_version) 579 580 # Always disable the network to prevent built-in system apps from 581 # updating themselves, which could take over package manager and 582 # cause shell command timeout. 583 logging.info('Disabling the network.') 584 settings.ConfigureContentSettings(instance.device, 585 settings.NETWORK_DISABLED_SETTINGS) 586 587 if snapshot: 588 # Reboot so that changes like disabling network can take effect. 589 instance.device.Reboot() 590 instance.SaveSnapshot() 591 592 instance.Stop() 593 594 # The multiinstance lock file seems to interfere with the emulator's 595 # operation in some circumstances (beyond the obvious -read-only ones), 596 # and there seems to be no mechanism by which it gets closed or deleted. 597 # See https://bit.ly/2pWQTH7 for context. 598 multiInstanceLockFile = os.path.join(self._avd_dir, 'multiinstance.lock') 599 if os.path.exists(multiInstanceLockFile): 600 os.unlink(multiInstanceLockFile) 601 602 package_def_content = { 603 'package': 604 self._config.avd_package.package_name, 605 'root': 606 self.emulator_home, 607 'install_mode': 608 'copy', 609 'data': [{ 610 'dir': os.path.relpath(self._avd_dir, self.emulator_home) 611 }, { 612 'file': 613 os.path.relpath(self._root_ini_path, self.emulator_home) 614 }, { 615 'file': 616 os.path.relpath(self._features_ini_path, self.emulator_home) 617 }], 618 } 619 620 logging.info('Creating AVD CIPD package.') 621 logging.info('ensure file content: %s', 622 json.dumps(package_def_content, indent=2)) 623 624 with tempfile_ext.TemporaryFileName(suffix='.json') as package_def_path: 625 with open(package_def_path, 'w') as package_def_file: 626 json.dump(package_def_content, package_def_file) 627 628 logging.info(' %s', self._config.avd_package.package_name) 629 cipd_create_cmd = [ 630 'cipd', 631 'create', 632 '-pkg-def', 633 package_def_path, 634 '-tag', 635 'emulator_version:%s' % self._config.emulator_package.version, 636 '-tag', 637 'system_image_version:%s' % 638 self._config.system_image_package.version, 639 ] 640 if cipd_json_output: 641 cipd_create_cmd.extend([ 642 '-json-output', 643 cipd_json_output, 644 ]) 645 logging.info('running %r%s', cipd_create_cmd, 646 ' (dry_run)' if dry_run else '') 647 if not dry_run: 648 try: 649 for line in cmd_helper.IterCmdOutputLines(cipd_create_cmd): 650 logging.info(' %s', line) 651 except subprocess.CalledProcessError as e: 652 # avd.py is executed with python2. 653 # pylint: disable=W0707 654 raise AvdException('CIPD package creation failed: %s' % str(e), 655 command=cipd_create_cmd) 656 657 finally: 658 if not keep: 659 logging.info('Deleting AVD.') 660 avd_manager.Delete(avd_name=self.avd_name) 661 662 def IsAvailable(self): 663 """Returns whether emulator is up-to-date.""" 664 if not os.path.exists(self._config_ini_path): 665 return False 666 667 # Skip when no version exists to prevent "IsAvailable()" returning False 668 # for emualtors set up using Create() (rather than Install()). 669 for cipd_root, pkgs in self._IterCipdPackages(_PACKAGES_RUNTIME, 670 check_version=False): 671 stdout = subprocess.run(['cipd', 'installed', '--root', cipd_root], 672 capture_output=True, 673 check=False, 674 encoding='utf8').stdout 675 # Output looks like: 676 # Packages: 677 # name1:version1 678 # name2:version2 679 installed = [l.strip().split(':', 1) for l in stdout.splitlines()[1:]] 680 681 if any([p.package_name, p.version] not in installed for p in pkgs): 682 return False 683 return True 684 685 def Uninstall(self): 686 """Uninstall all the artifacts associated with the given config. 687 688 Artifacts includes: 689 - CIPD packages specified in the avd config. 690 - The local AVD created by `Create`, if present. 691 692 """ 693 # Delete any existing local AVD. This must occur before deleting CIPD 694 # packages because a AVD needs system image to be recognized by avdmanager. 695 avd_manager = _AvdManagerAgent(avd_home=self.avd_home, 696 sdk_root=self.emulator_sdk_root) 697 if avd_manager.IsAvailable(self.avd_name): 698 logging.info('Deleting local AVD %s', self.avd_name) 699 avd_manager.Delete(self.avd_name) 700 701 # Delete installed CIPD packages. 702 for cipd_root, _ in self._IterCipdPackages(_PACKAGES_ALL, 703 check_version=False): 704 logging.info('Uninstalling packages in %s', cipd_root) 705 if not os.path.exists(cipd_root): 706 continue 707 # Create an empty ensure file to removed any installed CIPD packages. 708 ensure_path = os.path.join(cipd_root, '.ensure') 709 with open(ensure_path, 'w') as ensure_file: 710 ensure_file.write('$ParanoidMode CheckIntegrity\n\n') 711 ensure_cmd = [ 712 'cipd', 713 'ensure', 714 '-ensure-file', 715 ensure_path, 716 '-root', 717 cipd_root, 718 ] 719 try: 720 for line in cmd_helper.IterCmdOutputLines(ensure_cmd): 721 logging.info(' %s', line) 722 except subprocess.CalledProcessError as e: 723 # avd.py is executed with python2. 724 # pylint: disable=W0707 725 raise AvdException('Failed to uninstall CIPD packages: %s' % str(e), 726 command=ensure_cmd) 727 728 def Install(self): 729 """Installs the requested CIPD packages and prepares them for use. 730 731 This includes making files writeable and revising some of the 732 emulator's internal config files. 733 734 Returns: None 735 Raises: AvdException on failure to install. 736 """ 737 self._InstallCipdPackages(_PACKAGES_RUNTIME) 738 self._MakeWriteable() 739 self._UpdateConfigs() 740 self._RebaseQcow2Images() 741 742 def _RebaseQcow2Images(self): 743 """Rebase the paths in qcow2 images. 744 745 qcow2 files may exists in avd directory which have hard-coded paths to the 746 backing files, e.g., system.img, vendor.img. Such paths need to be rebased 747 if the avd is moved to a different directory in order to boot successfully. 748 """ 749 for f in _BACKING_FILES: 750 qcow2_image_path = os.path.join(self._avd_dir, '%s.qcow2' % f) 751 if not os.path.exists(qcow2_image_path): 752 continue 753 backing_file_path = os.path.join(self._system_image_dir, f) 754 logging.info('Rebasing the qcow2 image %r with the backing file %r', 755 qcow2_image_path, backing_file_path) 756 cmd_helper.RunCmd([ 757 self.qemu_img_path, 758 'rebase', 759 '-u', 760 '-f', 761 'qcow2', 762 '-b', 763 # The path to backing file must be relative to the qcow2 image. 764 os.path.relpath(backing_file_path, os.path.dirname(qcow2_image_path)), 765 qcow2_image_path, 766 ]) 767 768 def _ListPackages(self, packages): 769 if packages is _PACKAGES_RUNTIME: 770 packages = [ 771 self._config.avd_package, 772 self._config.emulator_package, 773 self._config.system_image_package, 774 ] 775 elif packages is _PACKAGES_CREATION: 776 packages = [ 777 self._config.emulator_package, 778 self._config.system_image_package, 779 *self._config.privileged_apk, 780 *self._config.additional_apk, 781 ] 782 elif packages is _PACKAGES_ALL: 783 packages = [ 784 self._config.avd_package, 785 self._config.emulator_package, 786 self._config.system_image_package, 787 *self._config.privileged_apk, 788 *self._config.additional_apk, 789 ] 790 return packages 791 792 def _IterCipdPackages(self, packages, check_version=True): 793 """Iterate a list of CIPD packages by their CIPD roots. 794 795 Args: 796 packages: a list of packages from an AVD config. 797 check_version: If set, raise Exception when a package has no version. 798 """ 799 pkgs_by_dir = collections.defaultdict(list) 800 for pkg in self._ListPackages(packages): 801 if pkg.version: 802 pkgs_by_dir[pkg.dest_path].append(pkg) 803 elif check_version: 804 raise AvdException('Expecting a version for the package %s' % 805 pkg.package_name) 806 807 for pkg_dir, pkgs in pkgs_by_dir.items(): 808 cipd_root = os.path.join(COMMON_CIPD_ROOT, pkg_dir) 809 yield cipd_root, pkgs 810 811 def _InstallCipdPackages(self, packages, check_version=True): 812 for cipd_root, pkgs in self._IterCipdPackages(packages, 813 check_version=check_version): 814 logging.info('Installing packages in %s', cipd_root) 815 if not os.path.exists(cipd_root): 816 os.makedirs(cipd_root) 817 ensure_path = os.path.join(cipd_root, '.ensure') 818 with open(ensure_path, 'w') as ensure_file: 819 # Make CIPD ensure that all files are present and correct, 820 # even if it thinks the package is installed. 821 ensure_file.write('$ParanoidMode CheckIntegrity\n\n') 822 for pkg in pkgs: 823 ensure_file.write('%s %s\n' % (pkg.package_name, pkg.version)) 824 logging.info(' %s %s', pkg.package_name, pkg.version) 825 ensure_cmd = [ 826 'cipd', 827 'ensure', 828 '-ensure-file', 829 ensure_path, 830 '-root', 831 cipd_root, 832 ] 833 try: 834 for line in cmd_helper.IterCmdOutputLines(ensure_cmd): 835 logging.info(' %s', line) 836 except subprocess.CalledProcessError as e: 837 # avd.py is executed with python2. 838 # pylint: disable=W0707 839 raise AvdException('Failed to install CIPD packages: %s' % str(e), 840 command=ensure_cmd) 841 842 def _MakeWriteable(self): 843 # The emulator requires that some files are writable. 844 for dirname, _, filenames in os.walk(self.emulator_home): 845 for f in filenames: 846 path = os.path.join(dirname, f) 847 mode = os.lstat(path).st_mode 848 if mode & stat.S_IRUSR: 849 mode = mode | stat.S_IWUSR 850 os.chmod(path, mode) 851 852 def _UpdateConfigs(self): 853 """Update various properties in config files after installation. 854 855 AVD config files contain some properties which can be different between AVD 856 creation and installation, e.g. hw.sdCard.path, which is an absolute path. 857 Update their values so that: 858 * Emulator instance can be booted correctly. 859 * The snapshot can be loaded successfully. 860 """ 861 logging.info('Updating AVD configurations.') 862 # Update the absolute avd path in root_ini file 863 with ini.update_ini_file(self._root_ini_path) as r_ini_contents: 864 r_ini_contents['path'] = self._avd_dir 865 866 # Update hardware settings. 867 config_paths = [self._config_ini_path] 868 # The file hardware.ini within each snapshot need to be updated as well. 869 hw_ini_glob_pattern = os.path.join(self._avd_dir, 'snapshots', '*', 870 'hardware.ini') 871 config_paths.extend(glob.glob(hw_ini_glob_pattern)) 872 873 properties = {} 874 # Update hw.sdCard.path if applicable 875 sdcard_path = os.path.join(self._avd_dir, _SDCARD_NAME) 876 if os.path.exists(sdcard_path): 877 properties['hw.sdCard.path'] = sdcard_path 878 879 for config_path in config_paths: 880 with ini.update_ini_file(config_path) as config_contents: 881 config_contents.update(properties) 882 883 # Create qt config file to disable adb warning when launched in window mode. 884 with ini.update_ini_file(self._qt_config_path) as config_contents: 885 config_contents['set'] = {'autoFindAdb': 'false'} 886 887 def _Initialize(self): 888 if self._initialized: 889 return 890 891 with self._initializer_lock: 892 if self._initialized: 893 return 894 895 # Emulator start-up looks for the adb daemon. Make sure it's running. 896 adb_wrapper.AdbWrapper.StartServer() 897 898 # Emulator start-up requires a valid sdk root. 899 assert self.emulator_sdk_root 900 901 def CreateInstance(self, output_manager=None): 902 """Creates an AVD instance without starting it. 903 904 Returns: 905 An _AvdInstance. 906 """ 907 self._Initialize() 908 return _AvdInstance(self, output_manager=output_manager) 909 910 def StartInstance(self): 911 """Starts an AVD instance. 912 913 Returns: 914 An _AvdInstance. 915 """ 916 instance = self.CreateInstance() 917 instance.Start() 918 return instance 919 920 921class _AvdInstance: 922 """Represents a single running instance of an AVD. 923 924 This class should only be created directly by AvdConfig.StartInstance, 925 but its other methods can be freely called. 926 """ 927 928 def __init__(self, avd_config, output_manager=None): 929 """Create an _AvdInstance object. 930 931 Args: 932 avd_config: an AvdConfig instance. 933 output_manager: a pylib.base.output_manager.OutputManager instance. 934 """ 935 self._avd_config = avd_config 936 self._avd_name = avd_config.avd_name 937 self._emulator_home = avd_config.emulator_home 938 self._emulator_path = avd_config.emulator_path 939 self._emulator_proc = None 940 self._emulator_serial = None 941 self._emulator_device = None 942 943 self._output_manager = output_manager 944 self._output_file = None 945 946 self._writable_system = False 947 self._debug_tags = None 948 949 def __str__(self): 950 return '%s|%s' % (self._avd_name, (self._emulator_serial or id(self))) 951 952 def Start(self, 953 ensure_system_settings=True, 954 read_only=True, 955 window=False, 956 writable_system=False, 957 gpu_mode=None, 958 wipe_data=False, 959 debug_tags=None, 960 disk_size=None, 961 require_fast_start=False): 962 """Starts the emulator running an instance of the given AVD. 963 964 Note when ensure_system_settings is True, the program will wait until the 965 emulator is fully booted, and then update system settings. 966 """ 967 is_slow_start = not require_fast_start 968 # Force to load system snapshot if detected. 969 if self.HasSystemSnapshot(): 970 if not writable_system: 971 logging.info('System snapshot found. Set "writable_system=True" ' 972 'to load it properly.') 973 writable_system = True 974 if read_only: 975 logging.info('System snapshot found. Set "read_only=False" ' 976 'to load it properly.') 977 read_only = False 978 elif writable_system: 979 is_slow_start = True 980 logging.warning('Emulator will be slow to start, as ' 981 '"writable_system=True" but system snapshot not found.') 982 983 self._writable_system = writable_system 984 985 with tempfile_ext.TemporaryFileName() as socket_path, (contextlib.closing( 986 socket.socket(socket.AF_UNIX))) as sock: 987 sock.bind(socket_path) 988 emulator_cmd = [ 989 self._emulator_path, 990 '-avd', 991 self._avd_name, 992 '-report-console', 993 'unix:%s' % socket_path, 994 '-no-boot-anim', 995 # Explicitly prevent emulator from auto-saving to snapshot on exit. 996 '-no-snapshot-save', 997 # Explicitly set the snapshot name for auto-load 998 '-snapshot', 999 self.GetSnapshotName(), 1000 ] 1001 1002 if wipe_data: 1003 emulator_cmd.append('-wipe-data') 1004 if disk_size: 1005 emulator_cmd.extend(['-partition-size', str(disk_size)]) 1006 if read_only: 1007 emulator_cmd.append('-read-only') 1008 if writable_system: 1009 emulator_cmd.append('-writable-system') 1010 # Note when "--gpu-mode" is set to "host": 1011 # * It needs a valid DISPLAY env, even if "--emulator-window" is false. 1012 # Otherwise it may throw errors like "Failed to initialize backend 1013 # EGL display". See the code in https://bit.ly/3ruiMlB as an example 1014 # to setup the DISPLAY env with xvfb. 1015 # * It will not work under remote sessions like chrome remote desktop. 1016 if not gpu_mode: 1017 gpu_mode = (self._avd_config.avd_launch_settings.gpu_mode 1018 or _DEFAULT_GPU_MODE) 1019 emulator_cmd.extend(['-gpu', gpu_mode]) 1020 if debug_tags: 1021 self._debug_tags = set(debug_tags.split(',')) 1022 # Always print timestamp when debug tags are set. 1023 self._debug_tags.add('time') 1024 emulator_cmd.extend(['-debug', ','.join(self._debug_tags)]) 1025 if 'kernel' in self._debug_tags: 1026 # TODO(crbug.com/1404176): newer API levels need "-virtio-console" 1027 # as well to print kernel log. 1028 emulator_cmd.append('-show-kernel') 1029 1030 emulator_env = { 1031 # kill immediately when emulator hang. 1032 'ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL': '0', 1033 # Sets the emulator configuration directory 1034 'ANDROID_EMULATOR_HOME': self._emulator_home, 1035 } 1036 if 'DISPLAY' in os.environ: 1037 emulator_env['DISPLAY'] = os.environ.get('DISPLAY') 1038 if window: 1039 if 'DISPLAY' not in emulator_env: 1040 raise AvdException('Emulator failed to start: DISPLAY not defined') 1041 else: 1042 emulator_cmd.append('-no-window') 1043 1044 # Need this for the qt config file to take effect. 1045 xdg_config_dirs = [self._avd_config.xdg_config_dir] 1046 if 'XDG_CONFIG_DIRS' in os.environ: 1047 xdg_config_dirs.append(os.environ.get('XDG_CONFIG_DIRS')) 1048 emulator_env['XDG_CONFIG_DIRS'] = ':'.join(xdg_config_dirs) 1049 1050 sock.listen(1) 1051 1052 logging.info('Starting emulator...') 1053 logging.info( 1054 ' With environments: %s', 1055 ' '.join(['%s=%s' % (k, v) for k, v in emulator_env.items()])) 1056 logging.info(' With commands: %s', ' '.join(emulator_cmd)) 1057 1058 # Enable the emulator log when debug_tags is set. 1059 if self._debug_tags: 1060 # Write to an ArchivedFile if output manager is set, otherwise stdout. 1061 if self._output_manager: 1062 self._output_file = self._output_manager.CreateArchivedFile( 1063 'emulator_%s' % time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), 1064 'emulator') 1065 else: 1066 self._output_file = open('/dev/null', 'w') 1067 self._emulator_proc = cmd_helper.Popen(emulator_cmd, 1068 stdout=self._output_file, 1069 stderr=self._output_file, 1070 env=emulator_env) 1071 1072 # Waits for the emulator to report its serial as requested via 1073 # -report-console. See http://bit.ly/2lK3L18 for more. 1074 def listen_for_serial(s): 1075 logging.info('Waiting for connection from emulator.') 1076 with contextlib.closing(s.accept()[0]) as conn: 1077 val = conn.recv(1024) 1078 return 'emulator-%d' % int(val) 1079 1080 try: 1081 self._emulator_serial = timeout_retry.Run( 1082 listen_for_serial, 1083 timeout=120 if is_slow_start else 30, 1084 retries=0, 1085 args=[sock]) 1086 logging.info('%s started', self._emulator_serial) 1087 except Exception: 1088 self.Stop(force=True) 1089 raise 1090 1091 # Set the system settings in "Start" here instead of setting in "Create" 1092 # because "Create" is used during AVD creation, and we want to avoid extra 1093 # turn-around on rolling AVD. 1094 if ensure_system_settings: 1095 assert self.device is not None, '`instance.device` not initialized.' 1096 logging.info('Waiting for device to be fully booted.') 1097 self.device.WaitUntilFullyBooted(timeout=360 if is_slow_start else 90, 1098 retries=0) 1099 logging.info('Device fully booted, verifying system settings.') 1100 _EnsureSystemSettings(self.device) 1101 1102 def Stop(self, force=False): 1103 """Stops the emulator process. 1104 1105 When "force" is True, we will call "terminate" on the emulator process, 1106 which is recommended when emulator is not responding to adb commands. 1107 """ 1108 # Close output file first in case emulator process killing goes wrong. 1109 if self._output_file: 1110 if self._debug_tags: 1111 if self._output_manager: 1112 self._output_manager.ArchiveArchivedFile(self._output_file, 1113 delete=True) 1114 link = self._output_file.Link() 1115 if link: 1116 logging.critical('Emulator logs saved to %s', link) 1117 else: 1118 self._output_file.close() 1119 self._output_file = None 1120 1121 if self._emulator_proc: 1122 if self._emulator_proc.poll() is None: 1123 if force or not self.device: 1124 self._emulator_proc.terminate() 1125 else: 1126 self.device.adb.Emu('kill') 1127 self._emulator_proc.wait() 1128 self._emulator_proc = None 1129 self._emulator_serial = None 1130 self._emulator_device = None 1131 1132 def GetSnapshotName(self): 1133 """Return the snapshot name to load/save. 1134 1135 Emulator has a different snapshot process when '-writable-system' flag is 1136 set (See https://issuetracker.google.com/issues/135857816#comment8). 1137 1138 """ 1139 if self._writable_system: 1140 return _SYSTEM_SNAPSHOT_NAME 1141 1142 return _DEFAULT_SNAPSHOT_NAME 1143 1144 def HasSystemSnapshot(self): 1145 """Check if the instance has the snapshot named _SYSTEM_SNAPSHOT_NAME.""" 1146 return self._avd_config.HasSnapshot(_SYSTEM_SNAPSHOT_NAME) 1147 1148 def SaveSnapshot(self): 1149 snapshot_name = self.GetSnapshotName() 1150 if self.device: 1151 logging.info('Saving snapshot to %r.', snapshot_name) 1152 self.device.adb.Emu(['avd', 'snapshot', 'save', snapshot_name]) 1153 1154 @property 1155 def serial(self): 1156 return self._emulator_serial 1157 1158 @property 1159 def device(self): 1160 if not self._emulator_device and self._emulator_serial: 1161 self._emulator_device = device_utils.DeviceUtils(self._emulator_serial) 1162 return self._emulator_device 1163 1164 1165# TODO(crbug.com/1275767): Refactor it to a dict-based approach. 1166def _EnsureSystemSettings(device): 1167 set_long_press_timeout_cmd = [ 1168 'settings', 'put', 'secure', 'long_press_timeout', _LONG_PRESS_TIMEOUT 1169 ] 1170 device.RunShellCommand(set_long_press_timeout_cmd, check_return=True) 1171 1172 # Verify if long_press_timeout is set correctly. 1173 get_long_press_timeout_cmd = [ 1174 'settings', 'get', 'secure', 'long_press_timeout' 1175 ] 1176 adb_output = device.RunShellCommand(get_long_press_timeout_cmd, 1177 check_return=True) 1178 if _LONG_PRESS_TIMEOUT in adb_output: 1179 logging.info('long_press_timeout set to %r', _LONG_PRESS_TIMEOUT) 1180 else: 1181 logging.warning('long_press_timeout is not set correctly') 1182