示例#1
0
class LinuxDevice(BaseLinuxDevice):

    platform = "linux"

    default_timeout = 30
    delay = 2
    long_delay = 3 * delay
    ready_timeout = 60

    parameters = [
        Parameter("host", mandatory=True, description="Host name or IP address for the device."),
        Parameter("username", mandatory=True, description="User name for the account on the device."),
        Parameter("password", description="Password for the account on the device (for password-based auth)."),
        Parameter("keyfile", description="Keyfile to be used for key-based authentication."),
        Parameter("port", kind=int, default=22, description="SSH port number on the device."),
        Parameter(
            "password_prompt",
            default="[sudo] password",
            description="Prompt presented by sudo when requesting the password.",
        ),
        Parameter(
            "use_telnet",
            kind=boolean,
            default=False,
            description="Optionally, telnet may be used instead of ssh, though this is discouraged.",
        ),
        Parameter(
            "boot_timeout",
            kind=int,
            default=120,
            description="How long to try to connect to the device after a reboot.",
        ),
        Parameter(
            "working_directory",
            default=None,
            description="""
                  Working directory to be used by WA. This must be in a location where the specified user
                  has write permissions. This will default to /home/<username>/wa (or to /root/wa, if
                  username is 'root').
                  """,
        ),
        Parameter(
            "binaries_directory",
            default="/usr/local/bin",
            description="Location of executable binaries on this device (must be in PATH).",
        ),
    ]

    @property
    def is_rooted(self):
        if self._is_rooted is None:
            # First check if the user is root
            try:
                self.execute("test $(id -u) = 0")
                self._is_root_user = True
                self._is_rooted = True
                return self._is_rooted
            except DeviceError:
                self._is_root_user = False

            # Otherwise, check if the user has sudo rights
            try:
                self.execute("ls /", as_root=True)
                self._is_rooted = True
            except DeviceError:
                self._is_rooted = False
        return self._is_rooted

    def __init__(self, *args, **kwargs):
        super(LinuxDevice, self).__init__(*args, **kwargs)
        self.shell = None
        self.local_binaries_directory = None
        self._is_rooted = None

    def validate(self):
        if self.working_directory is None:  # pylint: disable=access-member-before-definition
            if self.username == "root":
                self.working_directory = "/root/wa"  # pylint: disable=attribute-defined-outside-init
            else:
                self.working_directory = "/home/{}/wa".format(
                    self.username
                )  # pylint: disable=attribute-defined-outside-init
        self.local_binaries_directory = self.path.join(self.working_directory, "bin")

    def initialize(self, context, *args, **kwargs):
        self.execute("mkdir -p {}".format(self.local_binaries_directory))
        self.execute("mkdir -p {}".format(self.binaries_directory))
        self.execute("export PATH={}:$PATH".format(self.local_binaries_directory))
        self.execute("export PATH={}:$PATH".format(self.binaries_directory))
        super(LinuxDevice, self).initialize(context, *args, **kwargs)

    # Power control

    def reset(self):
        self.execute("reboot", as_root=True)
        self._is_ready = False

    def hard_reset(self):
        self._is_ready = False

    def boot(self, hard=False, **kwargs):
        if hard:
            self.hard_reset()
        else:
            self.reset()
        self.logger.debug("Waiting for device...")
        start_time = time.time()
        while (time.time() - start_time) < self.boot_timeout:
            try:
                s = socket.create_connection((self.host, self.port), timeout=5)
                s.close()
                break
            except socket.timeout:
                pass
            except socket.error:
                time.sleep(5)
        else:
            raise DeviceError("Could not connect to {} after reboot".format(self.host))

    def connect(self):  # NOQA pylint: disable=R0912
        self.shell = SshShell(
            password_prompt=self.password_prompt, timeout=self.default_timeout, telnet=self.use_telnet
        )
        self.shell.login(self.host, self.username, self.password, self.keyfile, self.port)
        self._is_ready = True

    def disconnect(self):  # NOQA pylint: disable=R0912
        self.shell.logout()
        self._is_ready = False

    # Execution

    def has_root(self):
        try:
            self.execute("ls /", as_root=True)
            return True
        except DeviceError as e:
            if "not in the sudoers file" not in e.message:
                raise e
            return False

    def execute(
        self,
        command,
        timeout=default_timeout,
        check_exit_code=True,
        background=False,
        as_root=False,
        strip_colors=True,
        **kwargs
    ):
        """
        Execute the specified command on the device using adb.

        Parameters:

            :param command: The command to be executed. It should appear exactly
                            as if you were typing it into a shell.
            :param timeout: Time, in seconds, to wait for adb to return before aborting
                            and raising an error. Defaults to ``AndroidDevice.default_timeout``.
            :param check_exit_code: If ``True``, the return code of the command on the Device will
                                    be check and exception will be raised if it is not 0.
                                    Defaults to ``True``.
            :param background: If ``True``, will execute create a new ssh shell rather than using
                               the default session and will return it immediately. If this is ``True``,
                               ``timeout``, ``strip_colors`` and (obvisously) ``check_exit_code`` will
                               be ignored; also, with this, ``as_root=True``  is only valid if ``username``
                               for the device was set to ``root``.
            :param as_root: If ``True``, will attempt to execute command in privileged mode. The device
                            must be rooted, otherwise an error will be raised. Defaults to ``False``.

                            Added in version 2.1.3

        :returns: If ``background`` parameter is set to ``True``, the subprocess object will
                  be returned; otherwise, the contents of STDOUT from the device will be returned.

        """
        self._check_ready()
        try:
            if background:
                if as_root and self.username != "root":
                    raise DeviceError("Cannot execute in background with as_root=True unless user is root.")
                return self.shell.background(command)
            else:
                # If we're already the root user, don't bother with sudo
                if self._is_root_user:
                    as_root = False
                return self.shell.execute(command, timeout, check_exit_code, as_root, strip_colors)
        except CalledProcessError as e:
            raise DeviceError(e)

    def kick_off(self, command):
        """
        Like execute but closes adb session and returns immediately, leaving the command running on the
        device (this is different from execute(background=True) which keeps adb connection open and returns
        a subprocess object).

        """
        self._check_ready()
        command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
        return self.shell.execute(command)

    def get_pids_of(self, process_name):
        """Returns a list of PIDs of all processes with the specified name."""
        # result should be a column of PIDs with the first row as "PID" header
        result = self.execute("ps -C {} -o pid".format(process_name), check_exit_code=False).strip().split()  # NOQA
        if len(result) >= 2:  # at least one row besides the header
            return map(int, result[1:])
        else:
            return []

    def ps(self, **kwargs):
        command = "ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname"
        lines = iter(convert_new_lines(self.execute(command)).split("\n"))
        lines.next()  # header

        result = []
        for line in lines:
            parts = re.split(r"\s+", line, maxsplit=8)
            if parts:
                result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))

        if not kwargs:
            return result
        else:
            filtered_result = []
            for entry in result:
                if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
                    filtered_result.append(entry)
            return filtered_result

    # File management

    def push_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221
        self._check_ready()
        try:
            if not as_root or self.username == "root":
                self.shell.push_file(source, dest, timeout=timeout)
            else:
                tempfile = self.path.join(self.working_directory, self.path.basename(dest))
                self.shell.push_file(source, tempfile, timeout=timeout)
                self.shell.execute("cp -r {} {}".format(tempfile, dest), timeout=timeout, as_root=True)
        except CalledProcessError as e:
            raise DeviceError(e)

    def pull_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221
        self._check_ready()
        try:
            if not as_root or self.username == "root":
                self.shell.pull_file(source, dest, timeout=timeout)
            else:
                tempfile = self.path.join(self.working_directory, self.path.basename(source))
                self.shell.execute("cp -r {} {}".format(source, tempfile), timeout=timeout, as_root=True)
                self.shell.execute("chown -R {} {}".format(self.username, tempfile), timeout=timeout, as_root=True)
                self.shell.pull_file(tempfile, dest, timeout=timeout)
        except CalledProcessError as e:
            raise DeviceError(e)

    def delete_file(self, filepath, as_root=False):  # pylint: disable=W0221
        self.execute("rm -rf {}".format(filepath), as_root=as_root)

    def file_exists(self, filepath):
        output = self.execute("if [ -e '{}' ]; then echo 1; else echo 0; fi".format(filepath))
        # output from ssh my contain part of the expression in the buffer,
        # split out everything except the last word.
        return boolean(output.split()[-1])  # pylint: disable=maybe-no-member

    def listdir(self, path, as_root=False, **kwargs):
        contents = self.execute("ls -1 {}".format(path), as_root=as_root).strip()
        if not contents:
            return []
        return [x.strip() for x in contents.split("\n")]  # pylint: disable=maybe-no-member

    def install(self, filepath, timeout=default_timeout, with_name=None):  # pylint: disable=W0221
        if self.is_rooted:
            destpath = self.path.join(self.binaries_directory, with_name and with_name or self.path.basename(filepath))
            self.push_file(filepath, destpath, as_root=True)
            self.execute("chmod a+x {}".format(destpath), timeout=timeout, as_root=True)
        else:
            destpath = self.path.join(
                self.local_binaries_directory, with_name and with_name or self.path.basename(filepath)
            )
            self.push_file(filepath, destpath)
            self.execute("chmod a+x {}".format(destpath), timeout=timeout)
        return destpath

    install_executable = install  # compatibility

    def uninstall(self, name):
        if self.is_rooted:
            path = self.path.join(self.binaries_directory, name)
            self.delete_file(path, as_root=True)
        else:
            path = self.path.join(self.local_binaries_directory, name)
            self.delete_file(path)

    uninstall_executable = uninstall  # compatibility

    def is_installed(self, name):
        try:
            self.execute("which {}".format(name))
            return True
        except DeviceError:
            return False

    # misc

    def ping(self):
        try:
            # May be triggered inside initialize()
            self.shell.execute("ls /", timeout=5)
        except (TimeoutError, CalledProcessError):
            raise DeviceNotRespondingError(self.host)

    def capture_screen(self, filepath):
        if not self.is_installed("scrot"):
            self.logger.debug("Could not take screenshot as scrot is not installed.")
            return
        try:
            tempfile = self.path.join(self.working_directory, os.path.basename(filepath))
            self.execute("DISPLAY=:0.0 scrot {}".format(tempfile))
            self.pull_file(tempfile, filepath)
            self.delete_file(tempfile)
        except DeviceError as e:
            if "Can't open X dispay." not in e.message:
                raise e
            message = e.message.split("OUTPUT:", 1)[1].strip()
            self.logger.debug("Could not take screenshot: {}".format(message))

    def is_screen_on(self):
        pass  # TODO

    def ensure_screen_is_on(self):
        pass  # TODO
class LinuxDevice(BaseLinuxDevice):

    platform = 'linux'

    default_timeout = 30
    delay = 2
    long_delay = 3 * delay
    ready_timeout = 60

    parameters = [
        Parameter('host', mandatory=True, description='Host name or IP address for the device.'),
        Parameter('username', mandatory=True, description='User name for the account on the device.'),
        Parameter('password', description='Password for the account on the device (for password-based auth).'),
        Parameter('keyfile', description='Keyfile to be used for key-based authentication.'),
        Parameter('port', kind=int, default=22, description='SSH port number on the device.'),
        Parameter('password_prompt', default='[sudo] password',
                  description='Prompt presented by sudo when requesting the password.'),

        Parameter('use_telnet', kind=boolean, default=False,
                  description='Optionally, telnet may be used instead of ssh, though this is discouraged.'),
        Parameter('boot_timeout', kind=int, default=120,
                  description='How long to try to connect to the device after a reboot.'),
    ]

    @property
    def is_rooted(self):
        self._check_ready()
        if self._is_rooted is None:
            # First check if the user is root
            try:
                self.execute('test $(id -u) = 0')
                self._is_root_user = True
                self._is_rooted = True
                return self._is_rooted
            except DeviceError:
                self._is_root_user = False

            # Otherwise, check if the user has sudo rights
            try:
                self.execute('ls /', as_root=True)
                self._is_rooted = True
            except DeviceError:
                self._is_rooted = False
        return self._is_rooted

    def __init__(self, *args, **kwargs):
        super(LinuxDevice, self).__init__(*args, **kwargs)
        self.shell = None
        self._is_rooted = None

    def validate(self):
        if self.working_directory is None:  # pylint: disable=access-member-before-definition
            if self.username == 'root':
                self.working_directory = '/root/wa'  # pylint: disable=attribute-defined-outside-init
            else:
                self.working_directory = '/home/{}/wa'.format(self.username)  # pylint: disable=attribute-defined-outside-init

    def initialize(self, context, *args, **kwargs):
        self.execute('mkdir -p {}'.format(self.binaries_directory))
        self.execute('export PATH={}:$PATH'.format(self.binaries_directory))
        super(LinuxDevice, self).initialize(context, *args, **kwargs)

    # Power control

    def reset(self):
        try:
            self.execute('reboot', as_root=True)
        except DeviceError as e:
            if 'Connection dropped' not in e.message:
                raise e
        self._is_ready = False

    def hard_reset(self):
        self._is_ready = False

    def boot(self, hard=False, **kwargs):
        if hard:
            self.hard_reset()
        else:
            self.reset()
        self.logger.debug('Waiting for device...')
        # Wait a fixed delay before starting polling to give the device time to
        # shut down, otherwise, might create the connection while it's still shutting
        # down resulting in subsequenct connection failing.
        initial_delay = 20
        time.sleep(initial_delay)
        boot_timeout = max(self.boot_timeout - initial_delay, 10)

        start_time = time.time()
        while (time.time() - start_time) < boot_timeout:
            try:
                s = socket.create_connection((self.host, self.port), timeout=5)
                s.close()
                break
            except socket.timeout:
                pass
            except socket.error:
                time.sleep(5)
        else:
            raise DeviceError('Could not connect to {} after reboot'.format(self.host))

    def connect(self):  # NOQA pylint: disable=R0912
        self.shell = SshShell(password_prompt=self.password_prompt,
                              timeout=self.default_timeout, telnet=self.use_telnet)
        self.shell.login(self.host, self.username, self.password, self.keyfile, self.port)
        self._is_ready = True

    def disconnect(self):  # NOQA pylint: disable=R0912
        self.shell.logout()
        self._is_ready = False

    # Execution

    def execute(self, command, timeout=default_timeout, check_exit_code=True, background=False,
                as_root=False, strip_colors=True, **kwargs):
        """
        Execute the specified command on the device using adb.

        Parameters:

            :param command: The command to be executed. It should appear exactly
                            as if you were typing it into a shell.
            :param timeout: Time, in seconds, to wait for adb to return before aborting
                            and raising an error. Defaults to ``AndroidDevice.default_timeout``.
            :param check_exit_code: If ``True``, the return code of the command on the Device will
                                    be check and exception will be raised if it is not 0.
                                    Defaults to ``True``.
            :param background: If ``True``, will execute create a new ssh shell rather than using
                               the default session and will return it immediately. If this is ``True``,
                               ``timeout``, ``strip_colors`` and (obvisously) ``check_exit_code`` will
                               be ignored; also, with this, ``as_root=True``  is only valid if ``username``
                               for the device was set to ``root``.
            :param as_root: If ``True``, will attempt to execute command in privileged mode. The device
                            must be rooted, otherwise an error will be raised. Defaults to ``False``.

                            Added in version 2.1.3

        :returns: If ``background`` parameter is set to ``True``, the subprocess object will
                  be returned; otherwise, the contents of STDOUT from the device will be returned.

        """
        self._check_ready()
        try:
            if background:
                if as_root and self.username != 'root':
                    raise DeviceError('Cannot execute in background with as_root=True unless user is root.')
                return self.shell.background(command)
            else:
                # If we're already the root user, don't bother with sudo
                if self._is_root_user:
                    as_root = False
                return self.shell.execute(command, timeout, check_exit_code, as_root, strip_colors)
        except CalledProcessError as e:
            raise DeviceError(e)

    def kick_off(self, command, as_root=None):
        """
        Like execute but closes ssh session and returns immediately, leaving the command running on the
        device (this is different from execute(background=True) which keeps ssh connection open and returns
        a subprocess object).

        """
        if as_root is None:
            as_root = self.is_rooted
        self._check_ready()
        command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
        return self.shell.execute(command, as_root=as_root)

    def get_pids_of(self, process_name):
        """Returns a list of PIDs of all processes with the specified name."""
        # result should be a column of PIDs with the first row as "PID" header
        result = self.execute('ps -C {} -o pid'.format(process_name),  # NOQA
                              check_exit_code=False).strip().split()
        if len(result) >= 2:  # at least one row besides the header
            return map(int, result[1:])
        else:
            return []

    def ps(self, **kwargs):
        command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname'
        lines = iter(convert_new_lines(self.execute(command)).split('\n'))
        lines.next()  # header

        result = []
        for line in lines:
            parts = re.split(r'\s+', line, maxsplit=8)
            if parts:
                result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))

        if not kwargs:
            return result
        else:
            filtered_result = []
            for entry in result:
                if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
                    filtered_result.append(entry)
            return filtered_result

    # File management

    def push_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221
        self._check_ready()
        try:
            if not as_root or self.username == 'root':
                self.shell.push_file(source, dest, timeout=timeout)
            else:
                tempfile = self.path.join(self.working_directory, self.path.basename(dest))
                self.shell.push_file(source, tempfile, timeout=timeout)
                self.shell.execute('cp -r {} {}'.format(tempfile, dest), timeout=timeout, as_root=True)
        except CalledProcessError as e:
            raise DeviceError(e)

    def pull_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221
        self._check_ready()
        try:
            if not as_root or self.username == 'root':
                self.shell.pull_file(source, dest, timeout=timeout)
            else:
                tempfile = self.path.join(self.working_directory, self.path.basename(source))
                self.shell.execute('cp -r {} {}'.format(source, tempfile), timeout=timeout, as_root=True)
                self.shell.execute('chown -R {} {}'.format(self.username, tempfile), timeout=timeout, as_root=True)
                self.shell.pull_file(tempfile, dest, timeout=timeout)
        except CalledProcessError as e:
            raise DeviceError(e)

    def delete_file(self, filepath, as_root=False):  # pylint: disable=W0221
        self.execute('rm -rf {}'.format(filepath), as_root=as_root)

    def file_exists(self, filepath):
        output = self.execute('if [ -e \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath))
        # output from ssh my contain part of the expression in the buffer,
        # split out everything except the last word.
        return boolean(output.split()[-1])  # pylint: disable=maybe-no-member

    def listdir(self, path, as_root=False, **kwargs):
        contents = self.execute('ls -1 {}'.format(path), as_root=as_root).strip()
        if not contents:
            return []
        return [x.strip() for x in contents.split('\n')]  # pylint: disable=maybe-no-member

    def install(self, filepath, timeout=default_timeout, with_name=None):  # pylint: disable=W0221
        destpath = self.path.join(self.binaries_directory,
                                  with_name or self.path.basename(filepath))
        self.push_file(filepath, destpath, as_root=True)
        self.execute('chmod a+x {}'.format(destpath), timeout=timeout, as_root=True)
        return destpath

    install_executable = install  # compatibility

    def uninstall(self, executable_name):
        on_device_executable = self.get_binary_path(executable_name, search_system_binaries=False)
        if not on_device_executable:
            raise DeviceError("Could not uninstall {}, binary not found".format(on_device_executable))
        self.delete_file(on_device_executable, as_root=self.is_rooted)

    uninstall_executable = uninstall  # compatibility

    # misc

    def lsmod(self):
        """List loaded kernel modules."""
        lines = self.execute('lsmod').splitlines()
        entries = []
        for line in lines[1:]:  # first line is the header
            if not line.strip():
                continue
            parts = line.split()
            name = parts[0]
            size = int(parts[1])
            use_count = int(parts[2])
            if len(parts) > 3:
                used_by = ''.join(parts[3:]).split(',')
            else:
                used_by = []
            entries.append(LsmodEntry(name, size, use_count, used_by))
        return entries

    def insmod(self, path):
        """Install a kernel module located on the host on the target device."""
        target_path = self.path.join(self.working_directory, os.path.basename(path))
        self.push_file(path, target_path)
        self.execute('insmod {}'.format(target_path), as_root=True)

    def ping(self):
        try:
            # May be triggered inside initialize()
            self.shell.execute('ls /', timeout=5)
        except (TimeoutError, CalledProcessError):
            raise DeviceNotRespondingError(self.host)

    def capture_screen(self, filepath):
        if not self.get_binary_path('scrot'):
            self.logger.debug('Could not take screenshot as scrot is not installed.')
            return
        try:
            tempfile = self.path.join(self.working_directory, os.path.basename(filepath))
            self.execute('DISPLAY=:0.0 scrot {}'.format(tempfile))
            self.pull_file(tempfile, filepath)
            self.delete_file(tempfile)
        except DeviceError as e:
            if "Can't open X dispay." not in e.message:
                raise e
            message = e.message.split('OUTPUT:', 1)[1].strip()
            self.logger.debug('Could not take screenshot: {}'.format(message))

    def is_screen_on(self):
        pass  # TODO

    def ensure_screen_is_on(self):
        pass  # TODO
示例#3
0
class LinuxDevice(BaseLinuxDevice):

    platform = 'linux'

    default_timeout = 30
    delay = 2
    long_delay = 3 * delay
    ready_timeout = 60

    parameters = [
        Parameter('host', mandatory=True, description='Host name or IP address for the device.'),
        Parameter('username', mandatory=True, description='User name for the account on the device.'),
        Parameter('password', description='Password for the account on the device (for password-based auth).'),
        Parameter('keyfile', description='Keyfile to be used for key-based authentication.'),
        Parameter('port', kind=int, default=22, description='SSH port number on the device.'),
        Parameter('password_prompt', default='[sudo] password',
                  description='Prompt presented by sudo when requesting the password.'),

        Parameter('use_telnet', kind=boolean, default=False,
                  description='Optionally, telnet may be used instead of ssh, though this is discouraged.'),
        Parameter('boot_timeout', kind=int, default=120,
                  description='How long to try to connect to the device after a reboot.'),

        Parameter('working_directory', default=None,
                  description='''
                  Working directory to be used by WA. This must be in a location where the specified user
                  has write permissions. This will default to /home/<username>/wa (or to /root/wa, if
                  username is 'root').
                  '''),
        Parameter('binaries_directory', default='/usr/local/bin',
                  description='Location of executable binaries on this device (must be in PATH).'),
    ]

    @property
    def is_rooted(self):
        if self._is_rooted is None:
            # First check if the user is root
            try:
                self.execute('test $(id -u) = 0')
                self._is_root_user = True
                self._is_rooted = True
                return self._is_rooted
            except DeviceError:
                self._is_root_user = False

            # Otherwise, check if the user has sudo rights
            try:
                self.execute('ls /', as_root=True)
                self._is_rooted = True
            except DeviceError:
                self._is_rooted = False
        return self._is_rooted

    def __init__(self, *args, **kwargs):
        super(LinuxDevice, self).__init__(*args, **kwargs)
        self.shell = None
        self.local_binaries_directory = None
        self._is_rooted = None

    def validate(self):
        if self.working_directory is None:  # pylint: disable=access-member-before-definition
            if self.username == 'root':
                self.working_directory = '/root/wa'  # pylint: disable=attribute-defined-outside-init
            else:
                self.working_directory = '/home/{}/wa'.format(self.username)  # pylint: disable=attribute-defined-outside-init
        self.local_binaries_directory = self.path.join(self.working_directory, 'bin')

    def initialize(self, context, *args, **kwargs):
        self.execute('mkdir -p {}'.format(self.local_binaries_directory))
        self.execute('mkdir -p {}'.format(self.binaries_directory))
        self.execute('export PATH={}:$PATH'.format(self.local_binaries_directory))
        self.execute('export PATH={}:$PATH'.format(self.binaries_directory))
        super(LinuxDevice, self).initialize(context, *args, **kwargs)

    # Power control

    def reset(self):
        self.execute('reboot', as_root=True)
        self._is_ready = False

    def hard_reset(self):
        super(LinuxDevice, self).hard_reset()
        self._is_ready = False

    def boot(self, hard=False, **kwargs):
        if hard:
            self.hard_reset()
        else:
            self.reset()
        self.logger.debug('Waiting for device...')
        start_time = time.time()
        while (time.time() - start_time) < self.boot_timeout:
            try:
                s = socket.create_connection((self.host, self.port), timeout=5)
                s.close()
                break
            except socket.timeout:
                pass
            except socket.error:
                time.sleep(5)
        else:
            raise DeviceError('Could not connect to {} after reboot'.format(self.host))

    def connect(self):  # NOQA pylint: disable=R0912
        self.shell = SshShell(password_prompt=self.password_prompt,
                              timeout=self.default_timeout, telnet=self.use_telnet)
        self.shell.login(self.host, self.username, self.password, self.keyfile, self.port)
        self._is_ready = True

    def disconnect(self):  # NOQA pylint: disable=R0912
        self.shell.logout()
        self._is_ready = False

    # Execution

    def has_root(self):
        try:
            self.execute('ls /', as_root=True)
            return True
        except DeviceError as e:
            if 'not in the sudoers file' not in e.message:
                raise e
            return False

    def execute(self, command, timeout=default_timeout, check_exit_code=True, background=False,
                as_root=False, strip_colors=True, **kwargs):
        """
        Execute the specified command on the device using adb.

        Parameters:

            :param command: The command to be executed. It should appear exactly
                            as if you were typing it into a shell.
            :param timeout: Time, in seconds, to wait for adb to return before aborting
                            and raising an error. Defaults to ``AndroidDevice.default_timeout``.
            :param check_exit_code: If ``True``, the return code of the command on the Device will
                                    be check and exception will be raised if it is not 0.
                                    Defaults to ``True``.
            :param background: If ``True``, will execute create a new ssh shell rather than using
                               the default session and will return it immediately. If this is ``True``,
                               ``timeout``, ``strip_colors`` and (obvisously) ``check_exit_code`` will
                               be ignored; also, with this, ``as_root=True``  is only valid if ``username``
                               for the device was set to ``root``.
            :param as_root: If ``True``, will attempt to execute command in privileged mode. The device
                            must be rooted, otherwise an error will be raised. Defaults to ``False``.

                            Added in version 2.1.3

        :returns: If ``background`` parameter is set to ``True``, the subprocess object will
                  be returned; otherwise, the contents of STDOUT from the device will be returned.

        """
        self._check_ready()
        try:
            if background:
                if as_root and self.username != 'root':
                    raise DeviceError('Cannot execute in background with as_root=True unless user is root.')
                return self.shell.background(command)
            else:
                # If we're already the root user, don't bother with sudo
                if self._is_root_user:
                    as_root = False
                return self.shell.execute(command, timeout, check_exit_code, as_root, strip_colors)
        except CalledProcessError as e:
            raise DeviceError(e)

    def kick_off(self, command):
        """
        Like execute but closes adb session and returns immediately, leaving the command running on the
        device (this is different from execute(background=True) which keeps adb connection open and returns
        a subprocess object).

        """
        self._check_ready()
        command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
        return self.shell.execute(command)

    def get_pids_of(self, process_name):
        """Returns a list of PIDs of all processes with the specified name."""
        # result should be a column of PIDs with the first row as "PID" header
        result = self.execute('ps -C {} -o pid'.format(process_name),  # NOQA
                              check_exit_code=False).strip().split()
        if len(result) >= 2:  # at least one row besides the header
            return map(int, result[1:])
        else:
            return []

    def ps(self, **kwargs):
        command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname'
        lines = iter(convert_new_lines(self.execute(command)).split('\n'))
        lines.next()  # header

        result = []
        for line in lines:
            parts = re.split(r'\s+', line, maxsplit=8)
            if parts:
                result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))

        if not kwargs:
            return result
        else:
            filtered_result = []
            for entry in result:
                if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
                    filtered_result.append(entry)
            return filtered_result

    # File management

    def push_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221
        self._check_ready()
        try:
            if not as_root or self.username == 'root':
                self.shell.push_file(source, dest, timeout=timeout)
            else:
                tempfile = self.path.join(self.working_directory, self.path.basename(dest))
                self.shell.push_file(source, tempfile, timeout=timeout)
                self.shell.execute('cp -r {} {}'.format(tempfile, dest), timeout=timeout, as_root=True)
        except CalledProcessError as e:
            raise DeviceError(e)

    def pull_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221
        self._check_ready()
        try:
            if not as_root or self.username == 'root':
                self.shell.pull_file(source, dest, timeout=timeout)
            else:
                tempfile = self.path.join(self.working_directory, self.path.basename(source))
                self.shell.execute('cp -r {} {}'.format(source, tempfile), timeout=timeout, as_root=True)
                self.shell.execute('chown -R {} {}'.format(self.username, tempfile), timeout=timeout, as_root=True)
                self.shell.pull_file(tempfile, dest, timeout=timeout)
        except CalledProcessError as e:
            raise DeviceError(e)

    def delete_file(self, filepath, as_root=False):  # pylint: disable=W0221
        self.execute('rm -rf {}'.format(filepath), as_root=as_root)

    def file_exists(self, filepath):
        output = self.execute('if [ -e \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath))
        # output from ssh my contain part of the expression in the buffer,
        # split out everything except the last word.
        return boolean(output.split()[-1])  # pylint: disable=maybe-no-member

    def listdir(self, path, as_root=False, **kwargs):
        contents = self.execute('ls -1 {}'.format(path), as_root=as_root).strip()
        if not contents:
            return []
        return [x.strip() for x in contents.split('\n')]  # pylint: disable=maybe-no-member

    def install(self, filepath, timeout=default_timeout, with_name=None):  # pylint: disable=W0221
        if self.is_rooted:
            destpath = self.path.join(self.binaries_directory,
                                      with_name and with_name or self.path.basename(filepath))
            self.push_file(filepath, destpath, as_root=True)
            self.execute('chmod a+x {}'.format(destpath), timeout=timeout, as_root=True)
        else:
            destpath = self.path.join(self.local_binaries_directory,
                                      with_name and with_name or self.path.basename(filepath))
            self.push_file(filepath, destpath)
            self.execute('chmod a+x {}'.format(destpath), timeout=timeout)
        return destpath

    install_executable = install  # compatibility

    def uninstall(self, name):
        if self.is_rooted:
            path = self.path.join(self.binaries_directory, name)
            self.delete_file(path, as_root=True)
        else:
            path = self.path.join(self.local_binaries_directory, name)
            self.delete_file(path)

    uninstall_executable = uninstall  # compatibility

    def is_installed(self, name):
        try:
            self.execute('which {}'.format(name))
            return True
        except DeviceError:
            return False

    # misc

    def ping(self):
        try:
            # May be triggered inside initialize()
            self.shell.execute('ls /', timeout=5)
        except (TimeoutError, CalledProcessError):
            raise DeviceNotRespondingError(self.host)

    def capture_screen(self, filepath):
        if not self.is_installed('scrot'):
            self.logger.debug('Could not take screenshot as scrot is not installed.')
            return
        try:
            tempfile = self.path.join(self.working_directory, os.path.basename(filepath))
            self.execute('DISPLAY=:0.0 scrot {}'.format(tempfile))
            self.pull_file(tempfile, filepath)
            self.delete_file(tempfile)
        except DeviceError as e:
            if "Can't open X dispay." not in e.message:
                raise e
            message = e.message.split('OUTPUT:', 1)[1].strip()
            self.logger.debug('Could not take screenshot: {}'.format(message))

    def is_screen_on(self):
        pass  # TODO

    def ensure_screen_is_on(self):
        pass  # TODO