• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Provides an interface to start and stop Android emulator.
6
7  Emulator: The class provides the methods to launch/shutdown the emulator with
8            the android virtual device named 'avd_armeabi' .
9"""
10
11import logging
12import os
13import signal
14import subprocess
15import time
16
17# TODO(craigdh): Move these pylib dependencies to pylib/utils/.
18from pylib import android_commands
19from pylib import cmd_helper
20from pylib import constants
21from pylib import pexpect
22from pylib.device import device_utils
23from pylib.utils import time_profile
24
25import errors
26import run_command
27
28# SD card size
29SDCARD_SIZE = '512M'
30
31# Template used to generate config.ini files for the emulator
32CONFIG_TEMPLATE = """avd.ini.encoding=ISO-8859-1
33hw.dPad=no
34hw.lcd.density=320
35sdcard.size=512M
36hw.cpu.arch={hw.cpu.arch}
37hw.device.hash=-708107041
38hw.camera.back=none
39disk.dataPartition.size=800M
40hw.gpu.enabled=yes
41skin.path=720x1280
42skin.dynamic=yes
43hw.keyboard=yes
44hw.ramSize=1024
45hw.device.manufacturer=Google
46hw.sdCard=yes
47hw.mainKeys=no
48hw.accelerometer=yes
49skin.name=720x1280
50abi.type={abi.type}
51hw.trackBall=no
52hw.device.name=Galaxy Nexus
53hw.battery=yes
54hw.sensors.proximity=yes
55image.sysdir.1=system-images/android-{api.level}/{abi.type}/
56hw.sensors.orientation=yes
57hw.audioInput=yes
58hw.camera.front=none
59hw.gps=yes
60vm.heapSize=128
61{extras}"""
62
63CONFIG_REPLACEMENTS = {
64  'x86': {
65    '{hw.cpu.arch}': 'x86',
66    '{abi.type}': 'x86',
67    '{extras}': ''
68  },
69  'arm': {
70    '{hw.cpu.arch}': 'arm',
71    '{abi.type}': 'armeabi-v7a',
72    '{extras}': 'hw.cpu.model=cortex-a8\n'
73  },
74  'mips': {
75    '{hw.cpu.arch}': 'mips',
76    '{abi.type}': 'mips',
77    '{extras}': ''
78  }
79}
80
81class EmulatorLaunchException(Exception):
82  """Emulator failed to launch."""
83  pass
84
85def _KillAllEmulators():
86  """Kill all running emulators that look like ones we started.
87
88  There are odd 'sticky' cases where there can be no emulator process
89  running but a device slot is taken.  A little bot trouble and and
90  we're out of room forever.
91  """
92  emulators = android_commands.GetAttachedDevices(hardware=False)
93  if not emulators:
94    return
95  for emu_name in emulators:
96    cmd_helper.RunCmd(['adb', '-s', emu_name, 'emu', 'kill'])
97  logging.info('Emulator killing is async; give a few seconds for all to die.')
98  for _ in range(5):
99    if not android_commands.GetAttachedDevices(hardware=False):
100      return
101    time.sleep(1)
102
103
104def DeleteAllTempAVDs():
105  """Delete all temporary AVDs which are created for tests.
106
107  If the test exits abnormally and some temporary AVDs created when testing may
108  be left in the system. Clean these AVDs.
109  """
110  avds = device_utils.GetAVDs()
111  if not avds:
112    return
113  for avd_name in avds:
114    if 'run_tests_avd' in avd_name:
115      cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name]
116      cmd_helper.RunCmd(cmd)
117      logging.info('Delete AVD %s' % avd_name)
118
119
120class PortPool(object):
121  """Pool for emulator port starting position that changes over time."""
122  _port_min = 5554
123  _port_max = 5585
124  _port_current_index = 0
125
126  @classmethod
127  def port_range(cls):
128    """Return a range of valid ports for emulator use.
129
130    The port must be an even number between 5554 and 5584.  Sometimes
131    a killed emulator "hangs on" to a port long enough to prevent
132    relaunch.  This is especially true on slow machines (like a bot).
133    Cycling through a port start position helps make us resilient."""
134    ports = range(cls._port_min, cls._port_max, 2)
135    n = cls._port_current_index
136    cls._port_current_index = (n + 1) % len(ports)
137    return ports[n:] + ports[:n]
138
139
140def _GetAvailablePort():
141  """Returns an available TCP port for the console."""
142  used_ports = []
143  emulators = android_commands.GetAttachedDevices(hardware=False)
144  for emulator in emulators:
145    used_ports.append(emulator.split('-')[1])
146  for port in PortPool.port_range():
147    if str(port) not in used_ports:
148      return port
149
150
151def LaunchTempEmulators(emulator_count, abi, api_level, wait_for_boot=True):
152  """Create and launch temporary emulators and wait for them to boot.
153
154  Args:
155    emulator_count: number of emulators to launch.
156    abi: the emulator target platform
157    api_level: the api level (e.g., 19 for Android v4.4 - KitKat release)
158    wait_for_boot: whether or not to wait for emulators to boot up
159
160  Returns:
161    List of emulators.
162  """
163  emulators = []
164  for n in xrange(emulator_count):
165    t = time_profile.TimeProfile('Emulator launch %d' % n)
166    # Creates a temporary AVD.
167    avd_name = 'run_tests_avd_%d' % n
168    logging.info('Emulator launch %d with avd_name=%s and api=%d',
169        n, avd_name, api_level)
170    emulator = Emulator(avd_name, abi)
171    emulator.CreateAVD(api_level)
172    emulator.Launch(kill_all_emulators=n == 0)
173    t.Stop()
174    emulators.append(emulator)
175  # Wait for all emulators to boot completed.
176  if wait_for_boot:
177    for emulator in emulators:
178      emulator.ConfirmLaunch(True)
179  return emulators
180
181
182def LaunchEmulator(avd_name, abi):
183  """Launch an existing emulator with name avd_name.
184
185  Args:
186    avd_name: name of existing emulator
187    abi: the emulator target platform
188
189  Returns:
190    emulator object.
191  """
192  logging.info('Specified emulator named avd_name=%s launched', avd_name)
193  emulator = Emulator(avd_name, abi)
194  emulator.Launch(kill_all_emulators=True)
195  emulator.ConfirmLaunch(True)
196  return emulator
197
198
199class Emulator(object):
200  """Provides the methods to launch/shutdown the emulator.
201
202  The emulator has the android virtual device named 'avd_armeabi'.
203
204  The emulator could use any even TCP port between 5554 and 5584 for the
205  console communication, and this port will be part of the device name like
206  'emulator-5554'. Assume it is always True, as the device name is the id of
207  emulator managed in this class.
208
209  Attributes:
210    emulator: Path of Android's emulator tool.
211    popen: Popen object of the running emulator process.
212    device: Device name of this emulator.
213  """
214
215  # Signals we listen for to kill the emulator on
216  _SIGNALS = (signal.SIGINT, signal.SIGHUP)
217
218  # Time to wait for an emulator launch, in seconds.  This includes
219  # the time to launch the emulator and a wait-for-device command.
220  _LAUNCH_TIMEOUT = 120
221
222  # Timeout interval of wait-for-device command before bouncing to a a
223  # process life check.
224  _WAITFORDEVICE_TIMEOUT = 5
225
226  # Time to wait for a "wait for boot complete" (property set on device).
227  _WAITFORBOOT_TIMEOUT = 300
228
229  def __init__(self, avd_name, abi):
230    """Init an Emulator.
231
232    Args:
233      avd_name: name of the AVD to create
234      abi: target platform for emulator being created, defaults to x86
235    """
236    android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk')
237    self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
238    self.android = os.path.join(android_sdk_root, 'tools', 'android')
239    self.popen = None
240    self.device_serial = None
241    self.abi = abi
242    self.avd_name = avd_name
243
244  @staticmethod
245  def _DeviceName():
246    """Return our device name."""
247    port = _GetAvailablePort()
248    return ('emulator-%d' % port, port)
249
250  def CreateAVD(self, api_level):
251    """Creates an AVD with the given name.
252
253    Args:
254      api_level: the api level of the image
255
256    Return avd_name.
257    """
258
259    if self.abi == 'arm':
260      abi_option = 'armeabi-v7a'
261    elif self.abi == 'mips':
262      abi_option = 'mips'
263    else:
264      abi_option = 'x86'
265
266    api_target = 'android-%s' % api_level
267
268    avd_command = [
269        self.android,
270        '--silent',
271        'create', 'avd',
272        '--name', self.avd_name,
273        '--abi', abi_option,
274        '--target', api_target,
275        '--sdcard', SDCARD_SIZE,
276        '--force',
277    ]
278    avd_cmd_str = ' '.join(avd_command)
279    logging.info('Create AVD command: %s', avd_cmd_str)
280    avd_process = pexpect.spawn(avd_cmd_str)
281
282    # Instead of creating a custom profile, we overwrite config files.
283    avd_process.expect('Do you wish to create a custom hardware profile')
284    avd_process.sendline('no\n')
285    avd_process.expect('Created AVD \'%s\'' % self.avd_name)
286
287    # Replace current configuration with default Galaxy Nexus config.
288    avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd')
289    ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name)
290    new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name,
291                                  'config.ini')
292
293    # Remove config files with defaults to replace with Google's GN settings.
294    os.unlink(ini_file)
295    os.unlink(new_config_ini)
296
297    # Create new configuration files with Galaxy Nexus by Google settings.
298    with open(ini_file, 'w') as new_ini:
299      new_ini.write('avd.ini.encoding=ISO-8859-1\n')
300      new_ini.write('target=%s\n' % api_target)
301      new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name))
302      new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name)
303
304    custom_config = CONFIG_TEMPLATE
305    replacements = CONFIG_REPLACEMENTS[self.abi]
306    for key in replacements:
307      custom_config = custom_config.replace(key, replacements[key])
308    custom_config = custom_config.replace('{api.level}', str(api_level))
309
310    with open(new_config_ini, 'w') as new_config_ini:
311      new_config_ini.write(custom_config)
312
313    return self.avd_name
314
315
316  def _DeleteAVD(self):
317    """Delete the AVD of this emulator."""
318    avd_command = [
319        self.android,
320        '--silent',
321        'delete',
322        'avd',
323        '--name', self.avd_name,
324    ]
325    logging.info('Delete AVD command: %s', ' '.join(avd_command))
326    cmd_helper.RunCmd(avd_command)
327
328
329  def Launch(self, kill_all_emulators):
330    """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
331    emulator is ready for use.
332
333    If fails, an exception will be raised.
334    """
335    if kill_all_emulators:
336      _KillAllEmulators()  # just to be sure
337    self._AggressiveImageCleanup()
338    (self.device_serial, port) = self._DeviceName()
339    emulator_command = [
340        self.emulator,
341        # Speed up emulator launch by 40%.  Really.
342        '-no-boot-anim',
343        # The default /data size is 64M.
344        # That's not enough for 8 unit test bundles and their data.
345        '-partition-size', '512',
346        # Use a familiar name and port.
347        '-avd', self.avd_name,
348        '-port', str(port),
349        # Wipe the data.  We've seen cases where an emulator gets 'stuck' if we
350        # don't do this (every thousand runs or so).
351        '-wipe-data',
352        # Enable GPU by default.
353        '-gpu', 'on',
354        '-qemu', '-m', '1024',
355        ]
356    if self.abi == 'x86':
357      emulator_command.extend([
358          # For x86 emulator --enable-kvm will fail early, avoiding accidental
359          # runs in a slow mode (i.e. without hardware virtualization support).
360          '--enable-kvm',
361          ])
362
363    logging.info('Emulator launch command: %s', ' '.join(emulator_command))
364    self.popen = subprocess.Popen(args=emulator_command,
365                                  stderr=subprocess.STDOUT)
366    self._InstallKillHandler()
367
368  @staticmethod
369  def _AggressiveImageCleanup():
370    """Aggressive cleanup of emulator images.
371
372    Experimentally it looks like our current emulator use on the bot
373    leaves image files around in /tmp/android-$USER.  If a "random"
374    name gets reused, we choke with a 'File exists' error.
375    TODO(jrg): is there a less hacky way to accomplish the same goal?
376    """
377    logging.info('Aggressive Image Cleanup')
378    emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
379    if not os.path.exists(emulator_imagedir):
380      return
381    for image in os.listdir(emulator_imagedir):
382      full_name = os.path.join(emulator_imagedir, image)
383      if 'emulator' in full_name:
384        logging.info('Deleting emulator image %s', full_name)
385        os.unlink(full_name)
386
387  def ConfirmLaunch(self, wait_for_boot=False):
388    """Confirm the emulator launched properly.
389
390    Loop on a wait-for-device with a very small timeout.  On each
391    timeout, check the emulator process is still alive.
392    After confirming a wait-for-device can be successful, make sure
393    it returns the right answer.
394    """
395    seconds_waited = 0
396    number_of_waits = 2  # Make sure we can wfd twice
397    # TODO(jbudorick) Un-handroll this in the implementation switch.
398    adb_cmd = "adb -s %s %s" % (self.device_serial, 'wait-for-device')
399    while seconds_waited < self._LAUNCH_TIMEOUT:
400      try:
401        run_command.RunCommand(adb_cmd,
402                               timeout_time=self._WAITFORDEVICE_TIMEOUT,
403                               retry_count=1)
404        number_of_waits -= 1
405        if not number_of_waits:
406          break
407      except errors.WaitForResponseTimedOutError:
408        seconds_waited += self._WAITFORDEVICE_TIMEOUT
409        adb_cmd = "adb -s %s %s" % (self.device_serial, 'kill-server')
410        run_command.RunCommand(adb_cmd)
411      self.popen.poll()
412      if self.popen.returncode != None:
413        raise EmulatorLaunchException('EMULATOR DIED')
414    if seconds_waited >= self._LAUNCH_TIMEOUT:
415      raise EmulatorLaunchException('TIMEOUT with wait-for-device')
416    logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
417    if wait_for_boot:
418      # Now that we checked for obvious problems, wait for a boot complete.
419      # Waiting for the package manager is sometimes problematic.
420      # TODO(jbudorick) Convert this once waiting for the package manager and
421      #                 the external storage is no longer problematic.
422      d = device_utils.DeviceUtils(self.device_serial)
423      d.old_interface.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT)
424
425  def Shutdown(self):
426    """Shuts down the process started by launch."""
427    self._DeleteAVD()
428    if self.popen:
429      self.popen.poll()
430      if self.popen.returncode == None:
431        self.popen.kill()
432      self.popen = None
433
434  def _ShutdownOnSignal(self, _signum, _frame):
435    logging.critical('emulator _ShutdownOnSignal')
436    for sig in self._SIGNALS:
437      signal.signal(sig, signal.SIG_DFL)
438    self.Shutdown()
439    raise KeyboardInterrupt  # print a stack
440
441  def _InstallKillHandler(self):
442    """Install a handler to kill the emulator when we exit unexpectedly."""
443    for sig in self._SIGNALS:
444      signal.signal(sig, self._ShutdownOnSignal)
445