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