Example #1
0
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)
Example #2
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/.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
Example #4
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'
Example #5
0
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
Example #7
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
    return "x86%s" % pie
Example #8
0
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'
Example #10
0
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
Example #11
0
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
Example #12
0
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)