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