• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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