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