xref: /aosp_15_r20/external/cronet/build/android/pylib/local/emulator/avd.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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