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