def grant_runtime_permissions(build_obj, app): """ Grant required runtime permissions to the specified app (typically org.mozilla.fennec_$USER). """ adb_path = _find_sdk_exe(build_obj.substs, 'adb', False) if not adb_path: adb_path = 'adb' dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) dm.default_timeout = 10 try: sdk_level = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk']) if sdk_level and int(sdk_level) >= 23: _log_info("Granting important runtime permissions to %s" % app) dm.shellCheckOutput([ 'pm', 'grant', app, 'android.permission.WRITE_EXTERNAL_STORAGE' ]) dm.shellCheckOutput([ 'pm', 'grant', app, 'android.permission.ACCESS_FINE_LOCATION' ]) dm.shellCheckOutput( ['pm', 'grant', app, 'android.permission.CAMERA']) dm.shellCheckOutput( ['pm', 'grant', app, 'android.permission.WRITE_CONTACTS']) except DMError: _log_warning("Unable to grant runtime permissions to %s" % app)
def run_firefox_for_android(build_obj, params): """ Launch Firefox for Android on the connected device. Optional 'params' allow parameters to be passed to Firefox. """ adb_path = _find_sdk_exe(build_obj.substs, 'adb', False) if not adb_path: adb_path = 'adb' dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) try: # # Construct an adb command similar to: # # adb shell am start -a android.activity.MAIN -n org.mozilla.fennec_$USER -d <url param> --es args "<params>" # app = "%s/.App" % build_obj.substs['ANDROID_PACKAGE_NAME'] cmd = ['am', 'start', '-a', 'android.activity.MAIN', '-n', app] if params: for p in params: if urlparse.urlparse(p).scheme != "": cmd.extend(['-d', p]) params.remove(p) break if params: cmd.extend(['--es', 'args', '"%s"' % ' '.join(params)]) _log_debug(cmd) output = dm.shellCheckOutput(cmd, timeout=10) _log_info(output) except DMError: _log_warning("unable to launch Firefox for Android") return 1 return 0
def run_firefox_for_android(build_obj, params): """ Launch Firefox for Android on the connected device. Optional 'params' allow parameters to be passed to Firefox. """ adb_path = _find_sdk_exe(build_obj.substs, 'adb', False) if not adb_path: adb_path = 'adb' dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) try: # # Construct an adb command similar to: # # $ adb shell am start -a android.activity.MAIN \ # -n org.mozilla.fennec_$USER \ # -d <url param> \ # --es args "<params>" # app = "%s/org.mozilla.gecko.BrowserApp" % build_obj.substs['ANDROID_PACKAGE_NAME'] cmd = ['am', 'start', '-a', 'android.activity.MAIN', '-n', app] if params: for p in params: if urlparse.urlparse(p).scheme != "": cmd.extend(['-d', p]) params.remove(p) break if params: cmd.extend(['--es', 'args', '"%s"' % ' '.join(params)]) _log_debug(cmd) output = dm.shellCheckOutput(cmd, timeout=10) _log_info(output) except DMError: _log_warning("unable to launch Firefox for Android") return 1 return 0
def _get_device_platform(substs): # PIE executables are required when SDK level >= 21 - important for gdbserver adb_path = _find_sdk_exe(substs, 'adb', False) if not adb_path: adb_path = 'adb' dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) sdk_level = None try: cmd = ['getprop', 'ro.build.version.sdk'] _log_debug(cmd) output = dm.shellCheckOutput(cmd, timeout=10) if output: sdk_level = int(output) except: _log_warning("unable to determine Android sdk level") pie = '' if sdk_level and sdk_level >= 21: pie = '-pie' if substs['TARGET_CPU'].startswith('arm'): return 'arm%s' % pie if sdk_level and sdk_level >= 21: _log_warning( "PIE gdbserver is not yet available for x86: you may not be able to debug on this platform" ) return 'x86'
def grant_runtime_permissions(build_obj, app): """ Grant required runtime permissions to the specified app (typically org.mozilla.fennec_$USER). """ adb_path = _find_sdk_exe(build_obj.substs, 'adb', False) if not adb_path: adb_path = 'adb' dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) dm.default_timeout = 10 try: sdk_level = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk']) if sdk_level and int(sdk_level) >= 23: _log_info("Granting important runtime permissions to %s" % app) dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.WRITE_EXTERNAL_STORAGE']) dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.ACCESS_FINE_LOCATION']) dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.CAMERA']) dm.shellCheckOutput(['pm', 'grant', app, 'android.permission.WRITE_CONTACTS']) except DMError: _log_warning("Unable to grant runtime permissions to %s" % app)
def _get_device_platform(substs): # PIE executables are required when SDK level >= 21 - important for gdbserver adb_path = _find_sdk_exe(substs, 'adb', False) if not adb_path: adb_path = 'adb' dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) sdk_level = None try: cmd = ['getprop', 'ro.build.version.sdk'] _log_debug(cmd) output = dm.shellCheckOutput(cmd, timeout=10) if output: sdk_level = int(output) except: _log_warning("unable to determine Android sdk level") pie = '' if sdk_level and sdk_level >= 21: pie = '-pie' if substs['TARGET_CPU'].startswith('arm'): return 'arm%s' % pie return 'x86%s' % pie
def _get_device_platform(substs): # PIE executables are required when SDK level >= 21 - important for gdbserver adb_path = _find_sdk_exe(substs, "adb", False) if not adb_path: adb_path = "adb" dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) sdk_level = None try: cmd = ["getprop", "ro.build.version.sdk"] _log_debug(cmd) output = dm.shellCheckOutput(cmd, timeout=10) if output: sdk_level = int(output) except: _log_warning("unable to determine Android sdk level") pie = "" if sdk_level and sdk_level >= 21: pie = "-pie" if substs["TARGET_CPU"].startswith("arm"): return "arm%s" % pie return "x86%s" % pie
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type='4.3', verbose=False, substs=None, device_serial=None): global verbose_logging self.emulator_log = None self.emulator_path = 'emulator' verbose_logging = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] adb_path = _find_sdk_exe(substs, 'adb', False) if not adb_path: adb_path = 'adb' self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1, deviceSerial=device_serial) self.dm.default_timeout = 10 _log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith('emulator'): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = _find_sdk_exe(self.substs, 'emulator', True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join(EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): _log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join(EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR) url = '%s/%s' % (TRY_URL, self.avd_info.tooltool_manifest) _download_file(url, 'releng.manifest', EMULATOR_HOME_DIR) _tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ def outputHandler(line): self.emulator_log.write("<%s>\n" % line) env = os.environ env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [ self.emulator_path, "-avd", self.avd_info.name, "-port", "5554" ] if self.avd_info.extra_args: # -enable-kvm option is not valid on OSX if _get_host_platform( ) == 'macosx64' and '-enable-kvm' in self.avd_info.extra_args: self.avd_info.extra_args.remove('-enable-kvm') command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') self.emulator_log = open(log_path, 'w') _log_debug("Starting the emulator with this command: %s" % ' '.join(command)) _log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler(command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() _log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: _log_warning("Emulator not started!") return False if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Waiting for device status...") while (('emulator-5554', 'device') not in self.dm.devices()): time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Device status verified.") _log_debug("Checking that Android has booted...") complete = False while (not complete): output = '' try: output = self.dm.shellCheckOutput( ['getprop', 'sys.boot_completed'], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == '1': complete = True else: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.uses_sut: if not self._verify_sut(): return False return True def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _replace_ini_contents(self, path): with open(path, "r") as f: lines = f.readlines() with open(path, "w") as f: for line in lines: if line.startswith('path='): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") f.write('path=%s/%s.avd\n' % (avd_path, self.avd_info.name)) elif line.startswith('path.rel='): f.write('path.rel=avd/%s.avd\n' % self.avd_info.name) else: f.write(line) def _telnet_cmd(self, telnet, command): _log_debug(">>> " + command) telnet.write('%s\n' % command) result = telnet.read_until('OK', 10) _log_debug("<<< " + result) return result def _verify_emulator(self): telnet_ok = False tn = None while (not telnet_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.port, 10) if tn is not None: res = tn.read_until('OK', 10) self._telnet_cmd(tn, 'avd status') if self.avd_info.uses_sut: cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port), str(self.avd_info.sut_port)) self._telnet_cmd(tn, cmd) cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port2), str(self.avd_info.sut_port2)) self._telnet_cmd(tn, cmd) self._telnet_cmd(tn, 'redir list') self._telnet_cmd(tn, 'network status') tn.write('quit\n') tn.read_all() telnet_ok = True else: _log_warning("Unable to connect to port %d" % port) except: _log_warning("Trying again after unexpected exception") finally: if tn is not None: tn.close() if not telnet_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return telnet_ok def _verify_sut(self): sut_ok = False while (not sut_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.sut_port, 10) if tn is not None: _log_debug("Connected to port %d" % self.avd_info.sut_port) res = tn.read_until('$>', 10) if res.find('$>') == -1: _log_debug("Unexpected SUT response: %s" % res) else: _log_debug("SUT response: %s" % res) sut_ok = True tn.write('quit\n') tn.read_all() except: _log_debug("Caught exception while verifying sutagent") finally: if tn is not None: tn.close() if not sut_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return sut_ok def _get_avd_type(self, requested): if requested in AVD_DICT.keys(): return requested if self.substs: if not self.substs['TARGET_CPU'].startswith('arm'): return 'x86' if self.substs['MOZ_ANDROID_MIN_SDK_VERSION'] == '9': return '2.3' return '4.3'
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type='4.3', verbose=False, substs=None, device_serial=None): global verbose_logging self.emulator_log = None self.emulator_path = 'emulator' verbose_logging = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] self.gpu = True self.restarted = False adb_path = _find_sdk_exe(substs, 'adb', False) if not adb_path: adb_path = 'adb' self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1, deviceSerial=device_serial) self.dm.default_timeout = 10 _log_debug("Running on %s" % platform.platform()) _log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith('emulator'): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = _find_sdk_exe(self.substs, 'emulator', True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): _log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') ini_file = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.ini') if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): if os.path.exists(ini_file): os.remove(ini_file) path = self.avd_info.tooltool_manifest _get_tooltool_manifest(self.substs, path, EMULATOR_HOME_DIR, 'releng.manifest') _tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ if os.path.exists(EMULATOR_AUTH_FILE): os.remove(EMULATOR_AUTH_FILE) _log_debug("deleted %s" % EMULATOR_AUTH_FILE) # create an empty auth file to disable emulator authentication auth_file = open(EMULATOR_AUTH_FILE, 'w') auth_file.close() def outputHandler(line): self.emulator_log.write("<%s>\n" % line) if "Invalid value for -gpu" in line or "Invalid GPU mode" in line: self.gpu = False env = os.environ env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [self.emulator_path, "-avd", self.avd_info.name, "-port", "5554"] if self.gpu: command += ['-gpu', 'swiftshader'] if self.avd_info.extra_args: # -enable-kvm option is not valid on OSX if _get_host_platform() == 'macosx64' and '-enable-kvm' in self.avd_info.extra_args: self.avd_info.extra_args.remove('-enable-kvm') command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') self.emulator_log = open(log_path, 'w') _log_debug("Starting the emulator with this command: %s" % ' '.join(command)) _log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler( command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() _log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: _log_warning("Emulator not started!") return False if self.check_completed(): return False _log_debug("Waiting for device status...") while(('emulator-5554', 'device') not in self.dm.devices()): time.sleep(10) if self.check_completed(): return False _log_debug("Device status verified.") _log_debug("Checking that Android has booted...") complete = False while(not complete): output = '' try: output = self.dm.shellCheckOutput( ['getprop', 'sys.boot_completed'], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == '1': complete = True else: time.sleep(10) if self.check_completed(): return False _log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.x86: _log_info("Running the x86 emulator; be sure to install an x86 APK!") else: _log_info("Running the arm emulator; be sure to install an arm APK!") return True def check_completed(self): if self.proc.proc.poll() is not None: if not self.gpu and not self.restarted: _log_warning("Emulator failed to start. Your emulator may be out of date.") _log_warning("Trying to restart the emulator without -gpu argument.") self.restarted = True self.start() return False _log_warning("Emulator has already completed!") log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') _log_warning("See log at %s and/or use --verbose for more information." % log_path) return True return False def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _replace_ini_contents(self, path): with open(path, "r") as f: lines = f.readlines() with open(path, "w") as f: for line in lines: if line.startswith('path='): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") f.write('path=%s/%s.avd\n' % (avd_path, self.avd_info.name)) elif line.startswith('path.rel='): f.write('path.rel=avd/%s.avd\n' % self.avd_info.name) else: f.write(line) def _telnet_cmd(self, telnet, command): _log_debug(">>> " + command) telnet.write('%s\n' % command) result = telnet.read_until('OK', 10) _log_debug("<<< " + result) return result def _verify_emulator(self): telnet_ok = False tn = None while(not telnet_ok): try: tn = telnetlib.Telnet('localhost', 5554, 10) if tn is not None: tn.read_until('OK', 10) self._telnet_cmd(tn, 'avd status') self._telnet_cmd(tn, 'redir list') self._telnet_cmd(tn, 'network status') tn.write('quit\n') tn.read_all() telnet_ok = True else: _log_warning("Unable to connect to port 5554") except: _log_warning("Trying again after unexpected exception") finally: if tn is not None: tn.close() if not telnet_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return telnet_ok def _get_avd_type(self, requested): if requested in AVD_DICT.keys(): return requested if self.substs: if not self.substs['TARGET_CPU'].startswith('arm'): return 'x86' return '4.3'
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type='4.3', verbose=False, substs=None): global verbose_logging self.emulator_log = None self.emulator_path = 'emulator' verbose_logging = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] adb_path = self._find_sdk_exe('adb', False) if not adb_path: adb_path = 'adb' self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) self.dm.default_timeout = 10 _log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith('emulator'): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = self._find_sdk_exe('emulator', True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): _log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR) url = '%s/%s' % (TRY_URL, self.avd_info.tooltool_manifest) _download_file(url, 'releng.manifest', EMULATOR_HOME_DIR) _tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ def outputHandler(line): self.emulator_log.write("<%s>\n" % line) env = os.environ env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [self.emulator_path, "-avd", self.avd_info.name, "-port", "5554"] if self.avd_info.extra_args: command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') self.emulator_log = open(log_path, 'w') _log_debug("Starting the emulator with this command: %s" % ' '.join(command)) _log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler( command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() _log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: _log_warning("Emulator not started!") return False if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Waiting for device status...") while(('emulator-5554', 'device') not in self.dm.devices()): time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Device status verified.") _log_debug("Checking that Android has booted...") complete = False while(not complete): output = '' try: output = self.dm.shellCheckOutput( ['getprop', 'sys.boot_completed'], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == '1': complete = True else: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.uses_sut: if not self._verify_sut(): return False return True def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _replace_ini_contents(self, path): with open(path, "r") as f: lines = f.readlines() with open(path, "w") as f: for line in lines: if line.startswith('path='): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") f.write('path=%s/%s.avd\n' % (avd_path, self.avd_info.name)) elif line.startswith('path.rel='): f.write('path.rel=avd/%s.avd\n' % self.avd_info.name) else: f.write(line) def _telnet_cmd(self, telnet, command): _log_debug(">>> " + command) telnet.write('%s\n' % command) result = telnet.read_until('OK', 10) _log_debug("<<< " + result) return result def _verify_emulator(self): telnet_ok = False tn = None while(not telnet_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.port, 10) if tn is not None: res = tn.read_until('OK', 10) self._telnet_cmd(tn, 'avd status') if self.avd_info.uses_sut: cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port), str(self.avd_info.sut_port)) self._telnet_cmd(tn, cmd) cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port2), str(self.avd_info.sut_port2)) self._telnet_cmd(tn, cmd) self._telnet_cmd(tn, 'redir list') self._telnet_cmd(tn, 'network status') tn.write('quit\n') tn.read_all() telnet_ok = True else: _log_warning("Unable to connect to port %d" % port) except: _log_warning("Trying again after unexpected exception") finally: if tn is not None: tn.close() if not telnet_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return telnet_ok def _verify_sut(self): sut_ok = False while(not sut_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.sut_port, 10) if tn is not None: _log_debug( "Connected to port %d" % self.avd_info.sut_port) res = tn.read_until('$>', 10) if res.find('$>') == -1: _log_debug("Unexpected SUT response: %s" % res) else: _log_debug("SUT response: %s" % res) sut_ok = True tn.write('quit\n') tn.read_all() except: _log_debug("Caught exception while verifying sutagent") finally: if tn is not None: tn.close() if not sut_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return sut_ok def _get_avd_type(self, requested): if requested in AVD_DICT.keys(): return requested if self.substs: if not self.substs['TARGET_CPU'].startswith('arm'): return 'x86' if self.substs['MOZ_ANDROID_MIN_SDK_VERSION'] == '9': return '2.3' return '4.3' def _find_sdk_exe(self, exe, tools): if tools: subdir = 'tools' var = 'ANDROID_TOOLS' else: subdir = 'platform-tools' var = 'ANDROID_PLATFORM_TOOLS' found = False # Can exe be found in the Android SDK? try: android_sdk_root = os.environ['ANDROID_SDK_ROOT'] exe_path = os.path.join( android_sdk_root, subdir, exe) if os.path.exists(exe_path): found = True else: _log_debug( "Unable to find executable at %s" % exe_path) except KeyError: _log_debug("ANDROID_SDK_ROOT not set") if not found and self.substs: # Can exe be found in ANDROID_TOOLS/ANDROID_PLATFORM_TOOLS? try: exe_path = os.path.join( self.substs[var], exe) if os.path.exists(exe_path): found = True else: _log_debug( "Unable to find executable at %s" % exe_path) except KeyError: _log_debug("%s not set" % var) if not found: # Can exe be found in the default bootstrap location? mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH', os.path.expanduser(os.path.join('~', '.mozbuild'))) exe_path = os.path.join( mozbuild_path, 'android-sdk-linux', subdir, exe) if os.path.exists(exe_path): found = True else: _log_debug( "Unable to find executable at %s" % exe_path) if not found: # Is exe on PATH? exe_path = find_executable(exe) if exe_path: found = True else: _log_debug("Unable to find executable on PATH") if found: _log_debug("%s found at %s" % (exe, exe_path)) else: exe_path = None return exe_path
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type="4.3", verbose=False, substs=None): self.emulator_log = None self.emulator_path = "emulator" self.verbose = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] adb_path = self._find_sdk_exe("adb", False) if not adb_path: adb_path = "adb" self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) self.dm.default_timeout = 10 self._log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith("emulator"): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = self._find_sdk_exe("emulator", True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join(EMULATOR_HOME_DIR, "avd", self.avd_info.name + ".avd") if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): self._log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join(EMULATOR_HOME_DIR, "avd", self.avd_info.name + ".avd") if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): self._fetch_tooltool() self._fetch_tooltool_manifest() self._tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ def outputHandler(line): self.emulator_log.write("<%s>\n" % line) env = os.environ env["ANDROID_AVD_HOME"] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [self.emulator_path, "-avd", self.avd_info.name, "-port", "5554"] if self.avd_info.extra_args: command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, "emulator.log") self.emulator_log = open(log_path, "w") self._log_debug("Starting the emulator with this command: %s" % " ".join(command)) self._log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler(command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() self._log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: self._log_warning("Emulator not started!") return False if self.proc.proc.poll() is not None: self._log_warning("Emulator has already completed!") return False self._log_debug("Waiting for device status...") while ("emulator-5554", "device") not in self.dm.devices(): time.sleep(10) if self.proc.proc.poll() is not None: self._log_warning("Emulator has already completed!") return False self._log_debug("Device status verified.") self._log_debug("Checking that Android has booted...") complete = False while not complete: output = "" try: output = self.dm.shellCheckOutput(["getprop", "sys.boot_completed"], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == "1": complete = True else: time.sleep(10) if self.proc.proc.poll() is not None: self._log_warning("Emulator has already completed!") return False self._log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.uses_sut: if not self._verify_sut(): return False return True def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _log_debug(self, text): if self.verbose: print "DEBUG: %s" % text def _log_warning(self, text): print "WARNING: %s" % text def _fetch_tooltool(self): self._download_file(TOOLTOOL_URL, "tooltool.py", EMULATOR_HOME_DIR) def _fetch_tooltool_manifest(self): url = "https://hg.mozilla.org/%s/raw-file/%s/%s" % ("try", "default", self.avd_info.tooltool_manifest) self._download_file(url, "releng.manifest", EMULATOR_HOME_DIR) def _tooltool_fetch(self): def outputHandler(line): self._log_debug(line) command = ["python", "tooltool.py", "fetch", "-m", "releng.manifest"] proc = ProcessHandler(command, processOutputLine=outputHandler, storeOutput=False, cwd=EMULATOR_HOME_DIR) proc.run() try: proc.wait() except: if proc.poll() is None: proc.kill(signal.SIGTERM) def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _download_file(self, url, filename, path): f = urllib2.urlopen(url) if not os.path.isdir(path): try: os.makedirs(path) except Exception, e: self._log_warning(str(e)) return False local_file = open(os.path.join(path, filename), "wb") local_file.write(f.read()) local_file.close() self._log_debug("Downloaded %s to %s/%s" % (url, path, filename)) return True
def grant_runtime_permissions(build_obj): """ Grant required runtime permissions to the specified app (typically org.mozilla.fennec_$USER). """ app = build_obj.substs["ANDROID_PACKAGE_NAME"] adb_path = _find_sdk_exe(build_obj.substs, "adb", False) if not adb_path: adb_path = "adb" dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) dm.default_timeout = 10 try: sdk_level = dm.shellCheckOutput(["getprop", "ro.build.version.sdk"]) if sdk_level and int(sdk_level) >= 23: _log_info("Granting important runtime permissions to %s" % app) dm.shellCheckOutput(["pm", "grant", app, "android.permission.WRITE_EXTERNAL_STORAGE"]) dm.shellCheckOutput(["pm", "grant", app, "android.permission.READ_EXTERNAL_STORAGE"]) dm.shellCheckOutput(["pm", "grant", app, "android.permission.ACCESS_FINE_LOCATION"]) dm.shellCheckOutput(["pm", "grant", app, "android.permission.CAMERA"]) dm.shellCheckOutput(["pm", "grant", app, "android.permission.WRITE_CONTACTS"]) except DMError: _log_warning("Unable to grant runtime permissions to %s" % app)