def test_init(self):
        device_with_banner = AdbDevice('IP:5555', 'banner')
        self.assertEqual(device_with_banner._banner, 'banner')

        with patch('socket.gethostname', side_effect=Exception):
            device_banner_unknown = AdbDevice('IP:5555')
            self.assertEqual(device_banner_unknown._banner, 'unknown')

        self.device._handle._bulk_read = b''
    def __init__(self, host, adbkey=''):
        self.host = host
        self.adbkey = adbkey
        self._adb = AdbDevice(serial=self.host, default_timeout_s=9.)

        # keep track of whether the ADB connection is intact
        self._available = False

        # use a lock to make sure that ADB commands don't overlap
        self._adb_lock = threading.Lock()
Exemple #3
0
    def test_init_banner(self):
        device_with_banner = AdbDevice(handle=patchers.FakeTcpHandle('host', 5555), banner='banner')
        self.assertEqual(device_with_banner._banner, b'banner')

        device_with_banner2 = AdbDevice(handle=patchers.FakeTcpHandle('host', 5555), banner=bytearray('banner2', 'utf-8'))
        self.assertEqual(device_with_banner2._banner, b'banner2')

        device_with_banner3 = AdbDevice(handle=patchers.FakeTcpHandle('host', 5555), banner=u'banner3')
        self.assertEqual(device_with_banner3._banner, b'banner3')

        with patch('socket.gethostname', side_effect=Exception):
            device_banner_unknown = AdbDevice(handle=patchers.FakeTcpHandle('host', 5555))
            self.assertEqual(device_banner_unknown._banner, b'unknown')

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._handle._bulk_read = b''
Exemple #4
0
    def _run_in_reactor(self, func, *args, **kwargs):
        handle = self._connect_adb()
        adb = AdbDevice(handle)

        def auth_cb(device):
            self._awaiting_auth = True
            self._logger.info(
                "Waiting for connection to be accepted on the device..")

        def on_running():
            try:
                signer = RSA_SIGNER(self._adbkey)
                if adb.connect(rsa_keys=[signer],
                               auth_timeout_s=30,
                               timeout_s=10,
                               auth_callback=auth_cb):
                    func(adb, *args, **kwargs)

            except WSHandleShutdown:
                pass

            except TcpTimeoutException:
                if self._awaiting_auth:
                    self._logger.error(
                        "Connection was not accepted on the device "
                        "within 30 seconds.")
                else:
                    self._logger.error("Connection to the device timed out")

            except Exception as exc:
                self._logger.debug(exc, exc_info=True)
                if len(exc.args) >= 1 and type(exc.args[0]) is bytearray:
                    err = str(exc.args[0].decode('utf-8'))
                else:
                    err = str(exc)

                if 'Read-only file system' in err:
                    self._logger.error('Permission denied.')
                else:
                    self._logger.error(err)

            finally:
                adb.close()

        return handle.run(on_running)
Exemple #5
0
    def connect(self, always_log_errors=True):
        """Connect to an Amazon Fire TV device.

        Will attempt to establish ADB connection to the given host.
        Failure sets state to UNKNOWN and disables sending actions.

        :returns: True if successful, False otherwise
        """
        self._adb_lock.acquire(**LOCK_KWARGS)
        signer = None
        if self.adbkey:
            signer = Signer(self.adbkey)
        try:
            if USE_ADB_SHELL:
                # adb_shell
                self._adb_device = AdbDevice(serial=self.host)

                # Connect to the device
                connected = False
                if signer:
                    connected = self._adb_device.connect(rsa_keys=[signer])
                else:
                    connected = self._adb_device.connect()

                self._available = connected

            elif not self.adb_server_ip:
                # python-adb
                try:
                    if self.adbkey:
                        signer = Signer(self.adbkey)

                        # Connect to the device
                        self._adb = adb_commands.AdbCommands().ConnectDevice(
                            serial=self.host,
                            rsa_keys=[signer],
                            default_timeout_ms=9000)
                    else:
                        self._adb = adb_commands.AdbCommands().ConnectDevice(
                            serial=self.host, default_timeout_ms=9000)

                    # ADB connection successfully established
                    self._available = True

                except socket_error as serr:
                    if self._available or always_log_errors:
                        if serr.strerror is None:
                            serr.strerror = "Timed out trying to connect to ADB device."
                        logging.warning(
                            "Couldn't connect to host: %s, error: %s",
                            self.host, serr.strerror)

                    # ADB connection attempt failed
                    self._adb = None
                    self._available = False

                finally:
                    return self._available

            else:
                # pure-python-adb
                try:
                    self._adb_client = AdbClient(host=self.adb_server_ip,
                                                 port=self.adb_server_port)
                    self._adb_device = self._adb_client.device(self.host)
                    self._available = bool(self._adb_device)

                except:
                    self._available = False

                finally:
                    return self._available

        finally:
            self._adb_lock.release()
Exemple #6
0
class FireTV:
    """Represents an Amazon Fire TV device."""
    def __init__(self,
                 host,
                 adbkey='',
                 adb_server_ip='',
                 adb_server_port=5037):
        """Initialize FireTV object.

        :param host: Host in format <address>:port.
        :param adbkey: The path to the "adbkey" file
        :param adb_server_ip: the IP address for the ADB server
        :param adb_server_port: the port for the ADB server
        """
        self.host = host
        self.adbkey = adbkey
        self.adb_server_ip = adb_server_ip
        self.adb_server_port = adb_server_port

        # keep track of whether the ADB connection is intact
        self._available = False

        # use a lock to make sure that ADB commands don't overlap
        self._adb_lock = threading.Lock()

        # the attributes used for sending ADB commands; filled in in `self.connect()`
        self._adb = None  # python-adb
        self._adb_client = None  # pure-python-adb
        self._adb_device = None  # pure-python-adb && adb_shell

        # the methods used for sending ADB commands
        if USE_ADB_SHELL:
            # adb_shell
            self.adb_shell = self._adb_shell_adb_shell
            self.adb_streaming_shell = self._adb_shell_adb_shell
        elif not self.adb_server_ip:
            # python-adb
            self.adb_shell = self._adb_shell_python_adb
            self.adb_streaming_shell = self._adb_streaming_shell_python_adb
        else:
            # pure-python-adb
            self.adb_shell = self._adb_shell_pure_python_adb
            self.adb_streaming_shell = self._adb_streaming_shell_pure_python_adb

        # establish the ADB connection
        self.connect()

    # ======================================================================= #
    #                                                                         #
    #                               ADB methods                               #
    #                                                                         #
    # ======================================================================= #
    def _adb_shell_adb_shell(self, cmd):
        if not self.available:
            return None

        if self._adb_lock.acquire(**LOCK_KWARGS):
            try:
                return self._adb_device.shell(cmd)
            finally:
                self._adb_lock.release()

    def _adb_shell_python_adb(self, cmd):
        if not self.available:
            return None

        if self._adb_lock.acquire(**LOCK_KWARGS):
            try:
                return self._adb.Shell(cmd)
            finally:
                self._adb_lock.release()

    def _adb_shell_pure_python_adb(self, cmd):
        if not self._available:
            return None

        if self._adb_lock.acquire(**LOCK_KWARGS):
            try:
                return self._adb_device.shell(cmd)
            finally:
                self._adb_lock.release()

    def _adb_streaming_shell_adb_shell(self, cmd):
        if not self.available:
            return []

        if self._adb_lock.acquire(**LOCK_KWARGS):
            try:
                return self._adb_device.shell(cmd)
            finally:
                self._adb_lock.release()

    def _adb_streaming_shell_python_adb(self, cmd):
        if not self.available:
            return []

        if self._adb_lock.acquire(**LOCK_KWARGS):
            try:
                return self._adb.StreamingShell(cmd)
            finally:
                self._adb_lock.release()

    def _adb_streaming_shell_pure_python_adb(self, cmd):
        if not self._available:
            return None

        # this is not yet implemented
        if self._adb_lock.acquire(**LOCK_KWARGS):
            try:
                return []
            finally:
                self._adb_lock.release()

    def _dump(self, service, grep=None):
        """Perform a service dump.

        :param service: Service to dump.
        :param grep: Grep for this string.
        :returns: Dump, optionally grepped.
        """
        if grep:
            return self.adb_shell('dumpsys {0} | grep "{1}"'.format(
                service, grep))
        return self.adb_shell('dumpsys {0}'.format(service))

    def _dump_has(self, service, grep, search):
        """Check if a dump has particular content.

        :param service: Service to dump.
        :param grep: Grep for this string.
        :param search: Check for this substring.
        :returns: Found or not.
        """
        dump_grep = self._dump(service, grep=grep)

        if not dump_grep:
            return False

        return dump_grep.strip().find(search) > -1

    def _key(self, key):
        """Send a key event to device.

        :param key: Key constant.
        """
        self.adb_shell('input keyevent {0}'.format(key))

    def _ps(self, search=''):
        """Perform a ps command with optional filtering.

        :param search: Check for this substring.
        :returns: List of matching fields
        """
        if not self.available:
            return
        result = []
        ps = self.adb_streaming_shell('ps')
        try:
            for bad_line in ps:
                # The splitting of the StreamingShell doesn't always work
                # this is to ensure that we get only one line
                for line in bad_line.splitlines():
                    if search in line:
                        result.append(line.strip().rsplit(' ', 1)[-1])
            return result
        except InvalidChecksumError as e:
            print(e)
            self.connect()
            raise IOError

    def _send_intent(self, pkg, intent, count=1):

        cmd = 'monkey -p {} -c {} {}; echo $?'.format(pkg, intent, count)
        logging.debug("Sending an intent %s to %s (count: %s)", intent, pkg,
                      count)

        # adb shell outputs in weird format, so we cut it into lines,
        # separate the retcode and return info to the user
        res = self.adb_shell(cmd)
        if res is None:
            return {}

        res = res.strip().split("\r\n")
        retcode = res[-1]
        output = "\n".join(res[:-1])

        return {"retcode": retcode, "output": output}

    def connect(self, always_log_errors=True):
        """Connect to an Amazon Fire TV device.

        Will attempt to establish ADB connection to the given host.
        Failure sets state to UNKNOWN and disables sending actions.

        :returns: True if successful, False otherwise
        """
        self._adb_lock.acquire(**LOCK_KWARGS)
        signer = None
        if self.adbkey:
            signer = Signer(self.adbkey)
        try:
            if USE_ADB_SHELL:
                # adb_shell
                self._adb_device = AdbDevice(serial=self.host)

                # Connect to the device
                connected = False
                if signer:
                    connected = self._adb_device.connect(rsa_keys=[signer])
                else:
                    connected = self._adb_device.connect()

                self._available = connected

            elif not self.adb_server_ip:
                # python-adb
                try:
                    if self.adbkey:
                        signer = Signer(self.adbkey)

                        # Connect to the device
                        self._adb = adb_commands.AdbCommands().ConnectDevice(
                            serial=self.host,
                            rsa_keys=[signer],
                            default_timeout_ms=9000)
                    else:
                        self._adb = adb_commands.AdbCommands().ConnectDevice(
                            serial=self.host, default_timeout_ms=9000)

                    # ADB connection successfully established
                    self._available = True

                except socket_error as serr:
                    if self._available or always_log_errors:
                        if serr.strerror is None:
                            serr.strerror = "Timed out trying to connect to ADB device."
                        logging.warning(
                            "Couldn't connect to host: %s, error: %s",
                            self.host, serr.strerror)

                    # ADB connection attempt failed
                    self._adb = None
                    self._available = False

                finally:
                    return self._available

            else:
                # pure-python-adb
                try:
                    self._adb_client = AdbClient(host=self.adb_server_ip,
                                                 port=self.adb_server_port)
                    self._adb_device = self._adb_client.device(self.host)
                    self._available = bool(self._adb_device)

                except:
                    self._available = False

                finally:
                    return self._available

        finally:
            self._adb_lock.release()

    # ======================================================================= #
    #                                                                         #
    #                          Home Assistant Update                          #
    #                                                                         #
    # ======================================================================= #
    def update(self, get_running_apps=True):
        """Get the state of the device, the current app, and the running apps.

        :param get_running_apps: whether or not to get the ``running_apps`` property
        :return state: the state of the device
        :return current_app: the current app
        :return running_apps: the running apps
        """
        # The `screen_on`, `awake`, `wake_lock_size`, `current_app`, and `running_apps` properties.
        screen_on, awake, wake_lock_size, _current_app, running_apps = self.get_properties(
            get_running_apps=get_running_apps, lazy=True)

        # Check if device is off.
        if not screen_on:
            state = STATE_OFF
            current_app = None
            running_apps = None

        # Check if screen saver is on.
        elif not awake:
            state = STATE_IDLE
            current_app = None
            running_apps = None

        else:
            # Get the current app.
            if isinstance(_current_app, dict) and 'package' in _current_app:
                current_app = _current_app['package']
            else:
                current_app = None

            # Get the running apps.
            if running_apps is None and current_app:
                running_apps = [current_app]

            # Get the state.
            # TODO: determine the state differently based on the `current_app`.
            if current_app in [PACKAGE_LAUNCHER, PACKAGE_SETTINGS]:
                state = STATE_STANDBY

            # Amazon Video
            elif current_app == AMAZON_VIDEO:
                if wake_lock_size == 5:
                    state = STATE_PLAYING
                else:
                    # wake_lock_size == 2
                    state = STATE_PAUSED

            # Netflix
            elif current_app == NETFLIX:
                if wake_lock_size > 3:
                    state = STATE_PLAYING
                else:
                    state = STATE_PAUSED

            # Check if `wake_lock_size` is 1 (device is playing).
            elif wake_lock_size == 1:
                state = STATE_PLAYING

            # Otherwise, device is paused.
            else:
                state = STATE_PAUSED

        return state, current_app, running_apps

    # ======================================================================= #
    #                                                                         #
    #                              App methods                                #
    #                                                                         #
    # ======================================================================= #
    def app_state(self, app):
        """Informs if application is running."""
        if not self.available or not self.screen_on:
            return STATE_OFF
        if self.current_app["package"] == app:
            return STATE_ON
        return STATE_OFF

    def launch_app(self, app):
        """Launch an app."""
        return self._send_intent(app, INTENT_LAUNCH)

    def stop_app(self, app):
        """Stop an app."""
        return self.adb_shell("am force-stop {0}".format(app))

    # ======================================================================= #
    #                                                                         #
    #                               properties                                #
    #                                                                         #
    # ======================================================================= #
    @property
    def state(self):
        """Compute and return the device state.

        :returns: Device state.
        """
        # Check if device is disconnected.
        if not self.available:
            return STATE_UNKNOWN
        # Check if device is off.
        if not self.screen_on:
            return STATE_OFF
        # Check if screen saver is on.
        if not self.awake:
            return STATE_IDLE
        # Check if the launcher is active.
        if self.launcher or self.settings:
            return STATE_STANDBY
        # Check for a wake lock (device is playing).
        if self.wake_lock:
            return STATE_PLAYING
        # Otherwise, device is paused.
        return STATE_PAUSED

    @property
    def available(self):
        """Check whether the ADB connection is intact."""

        if USE_ADB_SHELL:
            # adb_shell
            if not self._adb_device:
                return False

            return self._adb_device.available

        if not self.adb_server_ip:
            # python-adb
            return bool(self._adb)

        # pure-python-adb
        try:
            # make sure the server is available
            adb_devices = self._adb_client.devices()

            # make sure the device is available
            try:
                # case 1: the device is currently available
                if any(
                    [self.host in dev.get_serial_no() for dev in adb_devices]):
                    if not self._available:
                        self._available = True
                    return True

                # case 2: the device is not currently available
                if self._available:
                    logging.error('ADB server is not connected to the device.')
                    self._available = False
                return False

            except RuntimeError:
                if self._available:
                    logging.error(
                        'ADB device is unavailable; encountered an error when searching for device.'
                    )
                    self._available = False
                return False

        except RuntimeError:
            if self._available:
                logging.error('ADB server is unavailable.')
                self._available = False
            return False

    @property
    def running_apps(self):
        """Return a list of running user applications."""
        ps = self.adb_shell(RUNNING_APPS_CMD)
        if ps:
            return [
                line.strip().rsplit(' ', 1)[-1] for line in ps.splitlines()
                if line.strip()
            ]
        return []

    @property
    def current_app(self):
        """Return the current app."""
        current_focus = self.adb_shell(CURRENT_APP_CMD)
        if current_focus is None:
            return None

        current_focus = current_focus.replace("\r", "")
        matches = WINDOW_REGEX.search(current_focus)

        # case 1: current app was successfully found
        if matches:
            (pkg, activity) = matches.group("package", "activity")
            return {"package": pkg, "activity": activity}

        # case 2: current app could not be found
        logging.warning("Couldn't get current app, reply was %s",
                        current_focus)
        return None

    @property
    def screen_on(self):
        """Check if the screen is on."""
        return self.adb_shell(SCREEN_ON_CMD + SUCCESS1_FAILURE0) == '1'

    @property
    def awake(self):
        """Check if the device is awake (screensaver is not running)."""
        return self.adb_shell(AWAKE_CMD + SUCCESS1_FAILURE0) == '1'

    @property
    def wake_lock(self):
        """Check for wake locks (device is playing)."""
        return self.adb_shell(WAKE_LOCK_CMD + SUCCESS1_FAILURE0) == '1'

    @property
    def wake_lock_size(self):
        """Get the size of the current wake lock."""
        output = self.adb_shell(WAKE_LOCK_SIZE_CMD)
        if not output:
            return None
        return int(output.split("=")[1].strip())

    @property
    def launcher(self):
        """Check if the active application is the Amazon TV launcher."""
        return self.current_app["package"] == PACKAGE_LAUNCHER

    @property
    def settings(self):
        """Check if the active application is the Amazon menu."""
        return self.current_app["package"] == PACKAGE_SETTINGS

    def get_properties(self, get_running_apps=True, lazy=False):
        """Get the ``screen_on``, ``awake``, ``wake_lock_size``, ``current_app``, and ``running_apps`` properties."""
        if get_running_apps:
            output = self.adb_shell(SCREEN_ON_CMD +
                                    (SUCCESS1 if lazy else SUCCESS1_FAILURE0) +
                                    " && " + AWAKE_CMD +
                                    (SUCCESS1 if lazy else SUCCESS1_FAILURE0) +
                                    " && " + WAKE_LOCK_SIZE_CMD + " && " +
                                    CURRENT_APP_CMD + " && " +
                                    RUNNING_APPS_CMD)
        else:
            output = self.adb_shell(SCREEN_ON_CMD +
                                    (SUCCESS1 if lazy else SUCCESS1_FAILURE0) +
                                    " && " + AWAKE_CMD +
                                    (SUCCESS1 if lazy else SUCCESS1_FAILURE0) +
                                    " && " + WAKE_LOCK_SIZE_CMD + " && " +
                                    CURRENT_APP_CMD)

        # ADB command was unsuccessful
        if output is None:
            return None, None, None, None, None

        # `screen_on` property
        if not output:
            return False, False, -1, None, None
        screen_on = output[0] == '1'

        # `awake` property
        if len(output) < 2:
            return screen_on, False, -1, None, None
        awake = output[1] == '1'

        lines = output.strip().splitlines()

        # `wake_lock_size` property
        if len(lines[0]) < 3:
            return screen_on, awake, -1, None, None
        wake_lock_size = int(lines[0].split("=")[1].strip())

        # `current_app` property
        if len(lines) < 2:
            return screen_on, awake, wake_lock_size, None, None

        matches = WINDOW_REGEX.search(lines[1])
        if matches:
            # case 1: current app was successfully found
            (pkg, activity) = matches.group("package", "activity")
            current_app = {"package": pkg, "activity": activity}
        else:
            # case 2: current app could not be found
            current_app = None

        # `running_apps` property
        if not get_running_apps or len(lines) < 3:
            return screen_on, awake, wake_lock_size, current_app, None

        running_apps = [
            line.strip().rsplit(' ', 1)[-1] for line in lines[2:]
            if line.strip()
        ]

        return screen_on, awake, wake_lock_size, current_app, running_apps

    # ======================================================================= #
    #                                                                         #
    #                           turn on/off methods                           #
    #                                                                         #
    # ======================================================================= #
    def turn_on(self):
        """Send power action if device is off."""
        self.adb_shell(SCREEN_ON_CMD +
                       " || (input keyevent {0} && input keyevent {1})".format(
                           POWER, HOME))

    def turn_off(self):
        """Send power action if device is not off."""
        self.adb_shell(SCREEN_ON_CMD + " && input keyevent {0}".format(SLEEP))

    # ======================================================================= #
    #                                                                         #
    #                      "key" methods: basic commands                      #
    #                                                                         #
    # ======================================================================= #
    def power(self):
        """Send power action."""
        self._key(POWER)

    def sleep(self):
        """Send sleep action."""
        self._key(SLEEP)

    def home(self):
        """Send home action."""
        self._key(HOME)

    def up(self):
        """Send up action."""
        self._key(UP)

    def down(self):
        """Send down action."""
        self._key(DOWN)

    def left(self):
        """Send left action."""
        self._key(LEFT)

    def right(self):
        """Send right action."""
        self._key(RIGHT)

    def enter(self):
        """Send enter action."""
        self._key(ENTER)

    def back(self):
        """Send back action."""
        self._key(BACK)

    def space(self):
        """Send space keypress."""
        self._key(SPACE)

    def menu(self):
        """Send menu action."""
        self._key(MENU)

    def volume_up(self):
        """Send volume up action."""
        self._key(VOLUME_UP)

    def volume_down(self):
        """Send volume down action."""
        self._key(VOLUME_DOWN)

    # ======================================================================= #
    #                                                                         #
    #                      "key" methods: media commands                      #
    #                                                                         #
    # ======================================================================= #
    def media_play_pause(self):
        """Send media play/pause action."""
        self._key(PLAY_PAUSE)

    def media_play(self):
        """Send media play action."""
        self._key(PLAY)

    def media_pause(self):
        """Send media pause action."""
        self._key(PAUSE)

    def media_next(self):
        """Send media next action (results in fast-forward)."""
        self._key(NEXT)

    def media_previous(self):
        """Send media previous action (results in rewind)."""
        self._key(PREVIOUS)

    # ======================================================================= #
    #                                                                         #
    #                       "key" methods: key commands                       #
    #                                                                         #
    # ======================================================================= #
    def key_0(self):
        """Send 0 keypress."""
        self._key(KEY_0)

    def key_1(self):
        """Send 1 keypress."""
        self._key(KEY_1)

    def key_2(self):
        """Send 2 keypress."""
        self._key(KEY_2)

    def key_3(self):
        """Send 3 keypress."""
        self._key(KEY_3)

    def key_4(self):
        """Send 4 keypress."""
        self._key(KEY_4)

    def key_5(self):
        """Send 5 keypress."""
        self._key(KEY_5)

    def key_6(self):
        """Send 6 keypress."""
        self._key(KEY_6)

    def key_7(self):
        """Send 7 keypress."""
        self._key(KEY_7)

    def key_8(self):
        """Send 8 keypress."""
        self._key(KEY_8)

    def key_9(self):
        """Send 9 keypress."""
        self._key(KEY_9)

    def key_a(self):
        """Send a keypress."""
        self._key(KEY_A)

    def key_b(self):
        """Send b keypress."""
        self._key(KEY_B)

    def key_c(self):
        """Send c keypress."""
        self._key(KEY_C)

    def key_d(self):
        """Send d keypress."""
        self._key(KEY_D)

    def key_e(self):
        """Send e keypress."""
        self._key(KEY_E)

    def key_f(self):
        """Send f keypress."""
        self._key(KEY_F)

    def key_g(self):
        """Send g keypress."""
        self._key(KEY_G)

    def key_h(self):
        """Send h keypress."""
        self._key(KEY_H)

    def key_i(self):
        """Send i keypress."""
        self._key(KEY_I)

    def key_j(self):
        """Send j keypress."""
        self._key(KEY_J)

    def key_k(self):
        """Send k keypress."""
        self._key(KEY_K)

    def key_l(self):
        """Send l keypress."""
        self._key(KEY_L)

    def key_m(self):
        """Send m keypress."""
        self._key(KEY_M)

    def key_n(self):
        """Send n keypress."""
        self._key(KEY_N)

    def key_o(self):
        """Send o keypress."""
        self._key(KEY_O)

    def key_p(self):
        """Send p keypress."""
        self._key(KEY_P)

    def key_q(self):
        """Send q keypress."""
        self._key(KEY_Q)

    def key_r(self):
        """Send r keypress."""
        self._key(KEY_R)

    def key_s(self):
        """Send s keypress."""
        self._key(KEY_S)

    def key_t(self):
        """Send t keypress."""
        self._key(KEY_T)

    def key_u(self):
        """Send u keypress."""
        self._key(KEY_U)

    def key_v(self):
        """Send v keypress."""
        self._key(KEY_V)

    def key_w(self):
        """Send w keypress."""
        self._key(KEY_W)

    def key_x(self):
        """Send x keypress."""
        self._key(KEY_X)

    def key_y(self):
        """Send y keypress."""
        self._key(KEY_Y)

    def key_z(self):
        """Send z keypress."""
        self._key(KEY_Z)
class ADBPython(object):
    """A manager for ADB connections that uses a Python implementation of the ADB protocol.

    Parameters
    ----------
    host : str
        The address of the device in the format ``<ip address>:<host>``
    adbkey : str
        The path to the ``adbkey`` file for ADB authentication

    """
    def __init__(self, host, adbkey=''):
        self.host = host
        self.adbkey = adbkey
        self._adb = AdbDevice(serial=self.host, default_timeout_s=9.)

        # keep track of whether the ADB connection is intact
        self._available = False

        # use a lock to make sure that ADB commands don't overlap
        self._adb_lock = threading.Lock()

    @property
    def available(self):
        """Check whether the ADB connection is intact.

        Returns
        -------
        bool
            Whether or not the ADB connection is intact

        """
        return self._adb.available

    def close(self):
        """Close the ADB socket connection.

        """
        self._adb.close()

    def connect(self,
                always_log_errors=True,
                auth_timeout_s=DEFAULT_AUTH_TIMEOUT_S):
        """Connect to an Android TV / Fire TV device.

        Parameters
        ----------
        always_log_errors : bool
            If True, errors will always be logged; otherwise, errors will only be logged on the first failed reconnect attempt
        auth_timeout_s : float
            Authentication timeout (in seconds)

        Returns
        -------
        bool
            Whether or not the connection was successfully established and the device is available

        """
        self._adb_lock.acquire(**LOCK_KWARGS)  # pylint: disable=unexpected-keyword-arg

        # Make sure that we release the lock
        try:
            # Catch errors
            try:
                if self.adbkey:
                    # private key
                    with open(self.adbkey) as f:
                        priv = f.read()

                    # public key
                    try:
                        with open(self.adbkey + '.pub') as f:
                            pub = f.read()
                    except FileNotFoundError:
                        pub = ''

                    signer = PythonRSASigner(pub, priv)

                    # Connect to the device
                    self._adb.connect(rsa_keys=[signer],
                                      auth_timeout_s=auth_timeout_s)
                else:
                    self._adb.connect(auth_timeout_s=auth_timeout_s)

                # ADB connection successfully established
                self._available = True
                _LOGGER.debug("ADB connection to %s successfully established",
                              self.host)

            except socket_error as serr:
                if self._available or always_log_errors:
                    if serr.strerror is None:
                        serr.strerror = "Timed out trying to connect to ADB device."
                    _LOGGER.warning("Couldn't connect to host %s, error: %s",
                                    self.host, serr.strerror)

                # ADB connection attempt failed
                self._adb.close()
                self._available = False

            finally:
                return self._available

        finally:
            self._adb_lock.release()

    def shell(self, cmd):
        """Send an ADB command using the Python ADB implementation.

        Parameters
        ----------
        cmd : str
            The ADB command to be sent

        Returns
        -------
        str, None
            The response from the device, if there is a response

        """
        if not self.available:
            _LOGGER.debug(
                "ADB command not sent to %s because python-adb connection is not established: %s",
                self.host, cmd)
            return None

        if self._adb_lock.acquire(**LOCK_KWARGS):  # pylint: disable=unexpected-keyword-arg
            _LOGGER.debug("Sending command to %s via python-adb: %s",
                          self.host, cmd)
            try:
                return self._adb.shell(cmd)
            finally:
                self._adb_lock.release()
        else:
            _LOGGER.debug(
                "ADB command not sent to %s because python-adb lock not acquired: %s",
                self.host, cmd)

        return None
Exemple #8
0
    def test_init_invalid_handle(self):
        with self.assertRaises(exceptions.InvalidHandleError):
            device = AdbDevice(handle=123)

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._handle._bulk_read = b''
Exemple #9
0
 def setUp(self):
     self.device = AdbDevice(handle=patchers.FakeTcpHandle('host', 5555))
     self.device._handle._bulk_read = b''.join(patchers.BULK_READ_LIST)
Exemple #10
0
class TestAdbDevice(unittest.TestCase):
    def setUp(self):
        self.device = AdbDevice(handle=patchers.FakeTcpHandle('host', 5555))
        self.device._handle._bulk_read = b''.join(patchers.BULK_READ_LIST)

    def tearDown(self):
        self.assertFalse(self.device._handle._bulk_read)

    def test_init_tcp(self):
        with patchers.PATCH_TCP_HANDLE:
            tcp_device = AdbDeviceTcp('host')
            tcp_device._handle._bulk_read = self.device._handle._bulk_read

        # Make sure that the `connect()` method works
        self.assertTrue(tcp_device.connect())
        self.assertTrue(tcp_device.available)

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._handle._bulk_read = b''

    def test_init_banner(self):
        device_with_banner = AdbDevice(handle=patchers.FakeTcpHandle(
            'host', 5555),
                                       banner='banner')
        self.assertEqual(device_with_banner._banner, b'banner')

        device_with_banner2 = AdbDevice(handle=patchers.FakeTcpHandle(
            'host', 5555),
                                        banner=bytearray('banner2', 'utf-8'))
        self.assertEqual(device_with_banner2._banner, b'banner2')

        device_with_banner3 = AdbDevice(handle=patchers.FakeTcpHandle(
            'host', 5555),
                                        banner=u'banner3')
        self.assertEqual(device_with_banner3._banner, b'banner3')

        with patch('socket.gethostname', side_effect=Exception):
            device_banner_unknown = AdbDevice(
                handle=patchers.FakeTcpHandle('host', 5555))
            self.assertEqual(device_banner_unknown._banner, b'unknown')

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._handle._bulk_read = b''

    def test_init_invalid_handle(self):
        with self.assertRaises(exceptions.InvalidHandleError):
            device = AdbDevice(handle=123)

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._handle._bulk_read = b''

    def test_available(self):
        self.assertFalse(self.device.available)

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._handle._bulk_read = b''

    def test_close(self):
        self.assertFalse(self.device.close())
        self.assertFalse(self.device.available)

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._handle._bulk_read = b''

    # ======================================================================= #
    #                                                                         #
    #                             `connect` tests                             #
    #                                                                         #
    # ======================================================================= #
    def test_connect(self):
        self.assertTrue(self.device.connect())
        self.assertTrue(self.device.available)

    def test_connect_no_keys(self):
        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH[:2])
        with self.assertRaises(exceptions.DeviceAuthError):
            self.device.connect()

        self.assertFalse(self.device.available)

    def test_connect_with_key_invalid_response(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH_INVALID)

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.connect([signer])

        self.assertFalse(self.device.available)

    def test_connect_with_key(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH)

        self.assertTrue(self.device.connect([signer]))

    def test_connect_with_new_key(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')
            signer.pub_key = u''

        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH_NEW_KEY)

        self.assertTrue(self.device.connect([signer]))

    def test_connect_with_new_key_and_callback(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')
            signer.pub_key = u''

        self._callback_invoked = False

        def auth_callback(device):
            self._callback_invoked = True

        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH_NEW_KEY)

        self.assertTrue(
            self.device.connect([signer], auth_callback=auth_callback))
        self.assertTrue(self._callback_invoked)

    # ======================================================================= #
    #                                                                         #
    #                              `shell` tests                              #
    #                                                                         #
    # ======================================================================= #
    def test_shell_no_return(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.assertEqual(self.device.shell('TEST'), '')

    def test_shell_return_pass(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'PA'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'SS'),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.assertEqual(self.device.shell('TEST'), 'PASS')

    def test_shell_dont_decode(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'PA'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'SS'),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.assertEqual(self.device.shell('TEST', decode=False), b'PASS')

    def test_shell_data_length_exceeds_max(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=b'0' * (constants.MAX_ADB_DATA + 1)),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.device.shell('TEST')
        self.assertTrue(True)

    def test_shell_multibytes_sequence_exceeds_max(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=b'0' * (constants.MAX_ADB_DATA - 1) +
                       b'\xe3\x81\x82'),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.assertEqual(u'0' * (constants.MAX_ADB_DATA - 1) + u'\u3042',
                         self.device.shell('TEST'))

    def test_shell_with_multibytes_sequence_over_two_messages(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'\xe3'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=b'\x81\x82'),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.assertEqual(u'\u3042', self.device.shell('TEST'))

    def test_shell_multiple_clse(self):
        # https://github.com/JeffLIrion/adb_shell/issues/15#issuecomment-536795938
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'')
        msg2 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'PASS')
        msg3 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join([
            b'OKAY\xd9R\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',
            b'WRTE\xd9R\x00\x00\x01\x00\x00\x00\x01\x00\x00\x002\x00\x00\x00\xa8\xad\xab\xba',
            b'2',
            b'WRTE\xd9R\x00\x00\x01\x00\x00\x00\x0c\x02\x00\x00\xc0\x92\x00\x00\xa8\xad\xab\xba',
            b'Wake Locks: size=2\ncom.google.android.tvlauncher\n\n- STREAM_MUSIC:\n   Muted: true\n   Min: 0\n   Max: 15\n   Current: 2 (speaker): 15, 4 (headset): 10, 8 (headphone): 10, 80 (bt_a2dp): 10, 1000 (digital_dock): 10, 4000000 (usb_headset): 3, 40000000 (default): 15\n   Devices: speaker\n- STREAM_ALARM:\n   Muted: true\n   Min: 1\n   Max: 7\n   Current: 2 (speaker): 7, 4 (headset): 5, 8 (headphone): 5, 80 (bt_a2dp): 5, 1000 (digital_dock): 5, 4000000 (usb_headset): 1, 40000000 (default): 7\n   Devices: speaker\n- STREAM_NOTIFICATION:\n',
            b'CLSE\xd9R\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',
            msg1.pack(),
            b'CLSE\xdaR\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',
            msg2.pack(), msg2.data,
            msg3.pack()
        ])

        self.device.shell(
            "dumpsys power | grep 'Display Power' | grep -q 'state=ON' && echo -e '1\\c' && dumpsys power | grep mWakefulness | grep -q Awake && echo -e '1\\c' && dumpsys audio | grep paused | grep -qv 'Buffer Queue' && echo -e '1\\c' || (dumpsys audio | grep started | grep -qv 'Buffer Queue' && echo '2\\c' || echo '0\\c') && dumpsys power | grep Locks | grep 'size=' && CURRENT_APP=$(dumpsys window windows | grep mCurrentFocus) && CURRENT_APP=${CURRENT_APP#*{* * } && CURRENT_APP=${CURRENT_APP%%/*} && echo $CURRENT_APP && (dumpsys media_session | grep -A 100 'Sessions Stack' | grep -A 100 $CURRENT_APP | grep -m 1 'state=PlaybackState {' || echo) && dumpsys audio | grep '\\- STREAM_MUSIC:' -A 12"
        )
        self.assertEqual(self.device.shell('TEST'), 'PASS')

    # ======================================================================= #
    #                                                                         #
    #                           `shell` error tests                           #
    #                                                                         #
    # ======================================================================= #
    def test_shell_error_local_id(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1234,
                       data=b'\x00'))

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.shell('TEST')

    def test_shell_error_unknown_command(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessageForTesting(command=constants.FAIL,
                                 arg0=1,
                                 arg1=1,
                                 data=b''))

        with self.assertRaises(exceptions.InvalidCommandError):
            self.assertEqual(self.device.shell('TEST'), '')

    def test_shell_error_timeout(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b''))

        with self.assertRaises(exceptions.InvalidCommandError):
            self.device.shell('TEST', total_timeout_s=-1)

    def test_shell_error_timeout_multiple_clse(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.CLSE, arg0=2, arg1=1, data=b''))

        with self.assertRaises(exceptions.InvalidCommandError):
            self.device.shell('TEST', total_timeout_s=-1)

    def test_shell_error_checksum(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'PASS')
        self.device._handle._bulk_read = b''.join(
            [msg1.pack(), msg1.data,
             msg2.pack(), msg2.data[:-1] + b'0'])

        with self.assertRaises(exceptions.InvalidChecksumError):
            self.device.shell('TEST')

    def test_shell_error_local_id2(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=2, data=b'PASS'))

        with self.assertRaises(exceptions.InterleavedDataError):
            self.device.shell('TEST')
            self.device.shell('TEST')

    def test_shell_error_remote_id2(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE, arg0=2, arg1=1, data=b'PASS'))

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.shell('TEST')

    def test_issue29(self):
        # https://github.com/JeffLIrion/adb_shell/issues/29
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')

        self.device._handle._bulk_read = b''.join([
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\xc5\n\x00\x00\xbe\xaa\xab\xb7',  # Line 22
            b"\x17\xbf\xbf\xff\xc7\xa2eo'Sh\xdf\x8e\xf5\xff\xe0\tJ6H",  # Line 23
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 26
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 27
            b'OKAY\x99\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 290 (modified --> Line 30)
            b'CLSE\xa2\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 291
            b'CLSE\xa2\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 292
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x001\x00\x00\x00\xa8\xad\xab\xba',  # Line 31
            b'1',  # Line 32
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x001\x00\x00\x00\xa8\xad\xab\xba',  # Line 35
            b'1',  # Line 36
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x000\x00\x00\x00\xa8\xad\xab\xba',  # Line 39
            b'0',  # Line 40
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x13\x00\x00\x000\x06\x00\x00\xa8\xad\xab\xba',  # Line 43
            b'Wake Locks: size=0\n',  # Line 44
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x1e\x00\x00\x00V\x0b\x00\x00\xa8\xad\xab\xba',  # Line 47
            b'com.google.android.youtube.tv\n',  # Line 48
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x98\x00\x00\x00\xa13\x00\x00\xa8\xad\xab\xba',  # Line 51
            b'      state=PlaybackState {state=0, position=0, buffered position=0, speed=0.0, updated=0, actions=0, custom actions=[], active item id=-1, error=null}\n',  # Line 52
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00.\x01\x00\x00\xceP\x00\x00\xa8\xad\xab\xba',  # Line 55
            b'- STREAM_MUSIC:\n   Muted: false\n   Min: 0\n   Max: 15\n   Current: 2 (speaker): 11, 4 (headset): 10, 8 (headphone): 10, 400 (hdmi): 6, 40000000 (default): 11\n   Devices: hdmi\n- STREAM_ALARM:\n   Muted: false\n   Min: 0\n   Max: 7\n   Current: 40000000 (default): 6\n   Devices: speaker\n- STREAM_NOTIFICATION:\n',  # Line 56
            b'CLSE\x99\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 59
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x94\t\x00\x00\xbe\xaa\xab\xb7',  # Line 297
            b'P\xa5\x86\x97\xe8\x01\xb09\x8c>F\x9d\xc6\xbd\xc0J\x80!\xbb\x1a',  # Line 298
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 301
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 302
            b'OKAY\xa5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 305
            b'CLSE\xa5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 306
            msg1.pack(),
            msg1.data,
            msg2.pack(),
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00e\x0c\x00\x00\xbe\xaa\xab\xb7',  # Line 315
            b'\xd3\xef\x7f_\xa6\xc0`b\x19\\z\xe4\xf3\xe2\xed\x8d\xe1W\xfbH',  # Line 316
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 319
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 320
            b'OKAY\xa7\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 323
            b'CLSE\xa7\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 324
            msg1.pack(),
            msg1.data,
            msg2.pack(),
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x93\x08\x00\x00\xbe\xaa\xab\xb7',  # Line 333
            b's\xd4_e\xa4s\x02\x95\x0f\x1e\xec\n\x95Y9[`\x8e\xe1f',  # Line 334
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 337
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 338
            b'OKAY\xa9\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 341
            b'CLSE\xa9\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 342
            msg1.pack(),
            msg1.data,
            msg2.pack()
        ])

        self.assertTrue(self.device.connect([signer]))

        self.device.shell('Android TV update command')

        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')

    # ======================================================================= #
    #                                                                         #
    #                      `streaming_shell` tests                            #
    #                                                                         #
    # ======================================================================= #
    def test_streaming_shell_decode(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'ABC'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'123'),
        )

        generator = self.device.streaming_shell('TEST', decode=True)
        self.assertEqual('ABC', next(generator))
        self.assertEqual('123', next(generator))

    def test_streaming_shell_dont_decode(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'ABC'),
            AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'123'),
        )

        generator = self.device.streaming_shell('TEST', decode=False)
        self.assertEqual(b'ABC', next(generator))
        self.assertEqual(b'123', next(generator))

    # ======================================================================= #
    #                                                                         #
    #                         `filesync` tests                                #
    #                                                                         #
    # ======================================================================= #
    def test_list(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncListMessage(constants.DENT,
                                               1,
                                               2,
                                               3,
                                               data=b'file1'),
                           FileSyncListMessage(constants.DENT,
                                               4,
                                               5,
                                               6,
                                               data=b'file2'),
                           FileSyncListMessage(constants.DONE, 0, 0, 0))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.LIST,
                                           data=b'/dir'))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        expected_result = [
            DeviceFile(filename=bytearray(b'file1'), mode=1, size=2, mtime=3),
            DeviceFile(filename=bytearray(b'file2'), mode=4, size=5, mtime=6)
        ]

        self.assertEqual(expected_result, self.device.list('/dir'))
        self.assertEqual(expected_bulk_write, self.device._handle._bulk_write)

    def _test_push(self, mtime):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        filedata = b'Ohayou sekai.\nGood morning world!'

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(constants.OKAY, data=b''))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.SEND,
                                           data=b'/data,33272'),
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata),
                           FileSyncMessage(command=constants.DONE,
                                           arg0=mtime))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        with patch('time.time', return_value=mtime):
            self.device.push(BytesIO(filedata), '/data', mtime=mtime)
            self.assertEqual(expected_bulk_write,
                             self.device._handle._bulk_write)

        return True

    def test_push(self):
        self.assertTrue(self._test_push(100))

    def test_push_mtime0(self):
        self.assertTrue(self._test_push(0))

    def test_push_file(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        mtime = 100
        filedata = b'Ohayou sekai.\nGood morning world!'

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=FileSyncMessage(constants.OKAY).pack()),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.SEND,
                                           data=b'/data,33272'),
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata),
                           FileSyncMessage(command=constants.DONE,
                                           arg0=mtime,
                                           data=b''))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        with patch('adb_shell.adb_device.open', mock_open(read_data=filedata)):
            self.device.push('TEST_FILE', '/data', mtime=mtime)
            self.assertEqual(expected_bulk_write,
                             self.device._handle._bulk_write)

    def test_push_fail(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        mtime = 100
        filedata = b'Ohayou sekai.\nGood morning world!'

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(constants.FAIL, data=b''))))

        with self.assertRaises(exceptions.PushFailedError), patch(
                'adb_shell.adb_device.open', mock_open(read_data=filedata)):
            self.device.push('TEST_FILE', '/data', mtime=mtime)

    def test_push_big_file(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        mtime = 100
        filedata = b'0' * int(3.5 * constants.MAX_PUSH_DATA)

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(FileSyncMessage(constants.OKAY))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        mpd0, mpd1, mpd2, mpd3 = 0, constants.MAX_PUSH_DATA, 2 * constants.MAX_PUSH_DATA, 3 * constants.MAX_PUSH_DATA
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.SEND,
                                           data=b'/data,33272'),
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata[mpd0:mpd1]))),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata[mpd1:mpd2]))),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata[mpd2:mpd3]),
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata[mpd3:]),
                           FileSyncMessage(command=constants.DONE,
                                           arg0=mtime))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.device.push(BytesIO(filedata), '/data', mtime=mtime)
        self.assertEqual(expected_bulk_write, self.device._handle._bulk_write)

    def test_push_dir(self):
        self.assertTrue(self.device.connect())

        mtime = 100
        filedata = b'Ohayou sekai.\nGood morning world!'

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(FileSyncMessage(constants.OKAY))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(FileSyncMessage(constants.OKAY))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        #TODO

        with patch('adb_shell.adb_device.open',
                   mock_open(read_data=filedata)), patch(
                       'os.path.isdir', lambda x: x == 'TEST_DIR/'), patch(
                           'os.listdir',
                           return_value=['TEST_FILE1', 'TEST_FILE2']):
            self.device.push('TEST_DIR/', '/data', mtime=mtime)

    def test_pull(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        filedata = b'Ohayou sekai.\nGood morning world!'

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata),
                           FileSyncMessage(command=constants.DONE))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.RECV,
                                           data=b'/data'))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.assertEqual(filedata, self.device.pull('/data'))
        self.assertEqual(expected_bulk_write, self.device._handle._bulk_write)

    def test_pull_file(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        filedata = b'Ohayou sekai.\nGood morning world!'

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata),
                           FileSyncMessage(command=constants.DONE))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.RECV,
                                           data=b'/data'))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        with patch('adb_shell.adb_device.open',
                   mock_open()), patch('os.path.exists', return_value=True):
            self.assertTrue(self.device.pull('/data', 'TEST_FILE'))
            self.assertEqual(expected_bulk_write,
                             self.device._handle._bulk_write)

    def test_pull_file_return_true(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        filedata = b'Ohayou sekai.\nGood morning world!'

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata),
                           FileSyncMessage(command=constants.DONE))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.RECV,
                                           data=b'/data'))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        with patch('adb_shell.adb_device.open',
                   mock_open()), patch('adb_shell.adb_device.hasattr',
                                       return_value=False):
            self.assertTrue(self.device.pull('/data', 'TEST_FILE'))
            self.assertEqual(expected_bulk_write,
                             self.device._handle._bulk_write)

    def test_pull_big_file(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        filedata = b'0' * int(1.5 * constants.MAX_ADB_DATA)

        # Provide the `bulk_read` return values

        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.DATA,
                                           data=filedata),
                           FileSyncMessage(command=constants.DONE))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.RECV,
                                           data=b'/data'))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        with patch('adb_shell.adb_device.open',
                   mock_open()), patch('os.path.exists', return_value=True):
            self.assertTrue(self.device.pull('/data', 'TEST_FILE'))
            self.assertEqual(expected_bulk_write,
                             self.device._handle._bulk_write)

    def test_stat(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        # Provide the `bulk_read` return values

        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncStatMessage(constants.STAT, 1, 2, 3),
                           FileSyncStatMessage(constants.DONE, 0, 0, 0))),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        # Expected `bulk_write` values
        expected_bulk_write = join_messages(
            AdbMessage(command=constants.OPEN,
                       arg0=1,
                       arg1=0,
                       data=b'sync:\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncMessage(command=constants.STAT,
                                           data=b'/data'))),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b''),
            AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b''))

        self.assertEqual((1, 2, 3), self.device.stat('/data'))
        self.assertEqual(expected_bulk_write, self.device._handle._bulk_write)

    # ======================================================================= #
    #                                                                         #
    #                  `filesync` hidden methods tests                        #
    #                                                                         #
    # ======================================================================= #
    def test_filesync_read_adb_command_failure_exceptions(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncStatMessage(constants.FAIL, 1, 2, 3),
                           FileSyncStatMessage(constants.DONE, 0, 0, 0))))

        with self.assertRaises(exceptions.AdbCommandFailureException):
            self.device.stat('/data')

    def test_filesync_read_invalid_response_error(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        # Provide the `bulk_read` return values
        self.device._handle._bulk_read = join_messages(
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00'),
            AdbMessage(command=constants.WRTE,
                       arg0=1,
                       arg1=1,
                       data=join_messages(
                           FileSyncStatMessage(constants.DENT, 1, 2, 3),
                           FileSyncStatMessage(constants.DONE, 0, 0, 0))))

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.stat('/data')

    # ======================================================================= #
    #                                                                         #
    #                      `filesync` error tests                             #
    #                                                                         #
    # ======================================================================= #
    def test_pull_value_error(self):
        self.assertTrue(self.device.connect())
        self.device._handle._bulk_write = b''

        with self.assertRaises(ValueError):
            self.device.pull('device_filename', 123)
 def setUp(self):
     with patch('socket.gethostname', side_effect=Exception):
         with patchers.patch_tcp_handle:
             self.device = AdbDevice('IP:5555')
             self.device._handle._bulk_read = b''.join(
                 patchers.BULK_READ_LIST)
 def setUp(self):
     with patchers.patch_tcp_handle:
         self.device = AdbDevice('IP:5555', 'banner')
         self.device._handle._bulk_read = b''.join(patchers.BULK_READ_LIST)
class TestAdbDevice(unittest.TestCase):
    def setUp(self):
        with patchers.patch_tcp_handle:
            self.device = AdbDevice('IP:5555')
            self.device._handle._bulk_read = b''.join(patchers.BULK_READ_LIST)

    def tearDown(self):
        self.assertFalse(self.device._handle._bulk_read)

    def test_init(self):
        device_with_banner = AdbDevice('IP:5555', 'banner')
        self.assertEqual(device_with_banner._banner, 'banner')

        with patch('socket.gethostname', side_effect=Exception):
            device_banner_unknown = AdbDevice('IP:5555')
            self.assertEqual(device_banner_unknown._banner, 'unknown')

        self.device._handle._bulk_read = b''

    def test_available(self):
        self.assertFalse(self.device.available)

        self.device._handle._bulk_read = b''

    def test_close(self):
        self.assertFalse(self.device.close())
        self.assertFalse(self.device.available)

        self.device._handle._bulk_read = b''

    # ======================================================================= #
    #                                                                         #
    #                             `connect` tests                             #
    #                                                                         #
    # ======================================================================= #
    def test_connect(self):
        self.assertTrue(self.device.connect())
        self.assertTrue(self.device.available)

    def test_connect_no_keys(self):
        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH[:2])
        with self.assertRaises(exceptions.DeviceAuthError):
            self.device.connect()

        self.assertFalse(self.device.available)

    def test_connect_with_key_invalid_response(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH_INVALID)

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.connect([signer])

        self.assertFalse(self.device.available)

    def test_connect_with_key(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH)

        self.assertTrue(self.device.connect([signer]))

    def test_connect_with_new_key(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        self.device._handle._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH_NEW_KEY)

        self.assertTrue(self.device.connect([signer]))

    # ======================================================================= #
    #                                                                         #
    #                              `shell` tests                              #
    #                                                                         #
    # ======================================================================= #
    def test_shell_no_return(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join(
            [msg1.pack(), msg1.data, msg2.pack()])

        self.assertEqual(self.device.shell('TEST'), '')

    def test_shell_return_pass(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'PA')
        msg3 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'SS')
        msg4 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join([
            msg1.pack(), msg1.data,
            msg2.pack(), msg2.data,
            msg3.pack(), msg3.data,
            msg4.pack()
        ])

        self.assertEqual(self.device.shell('TEST'), 'PASS')

    def test_shell_data_length_exceeds_max(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE,
                          arg0=1,
                          arg1=1,
                          data=b'0' * (constants.MAX_ADB_DATA + 1))
        msg3 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join(
            [msg1.pack(), msg1.data,
             msg2.pack(), msg2.data,
             msg3.pack()])

        self.device.shell('TEST')
        self.assertTrue(True)

    def test_shell_multibytes_sequence_exceeds_max(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE,
                          arg0=1,
                          arg1=1,
                          data=b'0' * (constants.MAX_ADB_DATA - 1) +
                          b'\xe3\x81\x82')
        msg3 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join(
            [msg1.pack(), msg1.data,
             msg2.pack(), msg2.data,
             msg3.pack()])

        res = self.device.shell('TEST')
        self.assertEqual(u'0' * (constants.MAX_ADB_DATA - 1) + u'\u3042', res)

    def test_shell_with_multibytes_sequence_over_two_messages(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'\xe3')
        msg3 = AdbMessage(command=constants.WRTE,
                          arg0=1,
                          arg1=1,
                          data=b'\x81\x82')
        msg4 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join([
            msg1.pack(), msg1.data,
            msg2.pack(), msg2.data,
            msg3.pack(), msg3.data,
            msg4.pack()
        ])

        res = self.device.shell('TEST')
        self.assertEqual(u'\u3042', res)

    def test_shell_multiple_clse(self):
        # https://github.com/JeffLIrion/adb_shell/issues/15#issuecomment-536795938
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'')
        msg2 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'PASS')
        msg3 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join([
            b'OKAY\xd9R\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',
            b'WRTE\xd9R\x00\x00\x01\x00\x00\x00\x01\x00\x00\x002\x00\x00\x00\xa8\xad\xab\xba',
            b'2',
            b'WRTE\xd9R\x00\x00\x01\x00\x00\x00\x0c\x02\x00\x00\xc0\x92\x00\x00\xa8\xad\xab\xba',
            b'Wake Locks: size=2\ncom.google.android.tvlauncher\n\n- STREAM_MUSIC:\n   Muted: true\n   Min: 0\n   Max: 15\n   Current: 2 (speaker): 15, 4 (headset): 10, 8 (headphone): 10, 80 (bt_a2dp): 10, 1000 (digital_dock): 10, 4000000 (usb_headset): 3, 40000000 (default): 15\n   Devices: speaker\n- STREAM_ALARM:\n   Muted: true\n   Min: 1\n   Max: 7\n   Current: 2 (speaker): 7, 4 (headset): 5, 8 (headphone): 5, 80 (bt_a2dp): 5, 1000 (digital_dock): 5, 4000000 (usb_headset): 1, 40000000 (default): 7\n   Devices: speaker\n- STREAM_NOTIFICATION:\n',
            b'CLSE\xd9R\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',
            msg1.pack(),
            b'CLSE\xdaR\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',
            msg2.pack(), msg2.data,
            msg3.pack()
        ])

        self.device.shell(
            "dumpsys power | grep 'Display Power' | grep -q 'state=ON' && echo -e '1\\c' && dumpsys power | grep mWakefulness | grep -q Awake && echo -e '1\\c' && dumpsys audio | grep paused | grep -qv 'Buffer Queue' && echo -e '1\\c' || (dumpsys audio | grep started | grep -qv 'Buffer Queue' && echo '2\\c' || echo '0\\c') && dumpsys power | grep Locks | grep 'size=' && CURRENT_APP=$(dumpsys window windows | grep mCurrentFocus) && CURRENT_APP=${CURRENT_APP#*{* * } && CURRENT_APP=${CURRENT_APP%%/*} && echo $CURRENT_APP && (dumpsys media_session | grep -A 100 'Sessions Stack' | grep -A 100 $CURRENT_APP | grep -m 1 'state=PlaybackState {' || echo) && dumpsys audio | grep '\\- STREAM_MUSIC:' -A 12"
        )
        self.assertEqual(self.device.shell('TEST'), 'PASS')

    # ======================================================================= #
    #                                                                         #
    #                           `shell` error tests                           #
    #                                                                         #
    # ======================================================================= #
    def test_shell_error_local_id(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY,
                          arg0=1,
                          arg1=1234,
                          data=b'\x00')
        self.device._handle._bulk_read = b''.join([msg1.pack(), msg1.data])

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.shell('TEST')

    def test_shell_error_unknown_command(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessageForTesting(command=constants.FAIL,
                                    arg0=1,
                                    arg1=1,
                                    data=b'\x00')
        self.device._handle._bulk_read = msg1.pack()

        with self.assertRaises(exceptions.InvalidCommandError):
            self.assertEqual(self.device.shell('TEST'), '')

    def test_shell_error_timeout(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'')
        self.device._handle._bulk_read = msg1.pack()

        with self.assertRaises(exceptions.InvalidCommandError):
            self.device.shell('TEST', total_timeout_s=-1)

    def test_shell_error_timeout_multiple_clse(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'')
        msg2 = AdbMessage(command=constants.CLSE, arg0=2, arg1=1, data=b'')
        self.device._handle._bulk_read = b''.join([msg1.pack(), msg2.pack()])

        with self.assertRaises(exceptions.InvalidCommandError):
            self.device.shell('TEST', total_timeout_s=-1)

    def test_shell_error_checksum(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE, arg0=1, arg1=1, data=b'PASS')
        self.device._handle._bulk_read = b''.join(
            [msg1.pack(), msg1.data,
             msg2.pack(), msg2.data[:-1] + b'0'])

        with self.assertRaises(exceptions.InvalidChecksumError):
            self.device.shell('TEST')

    def test_shell_error_local_id2(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE, arg0=1, arg1=2, data=b'PASS')
        self.device._handle._bulk_read = b''.join(
            [msg1.pack(), msg1.data,
             msg2.pack(), msg2.data])

        with self.assertRaises(exceptions.InterleavedDataError):
            self.device.shell('TEST')
            self.device.shell('TEST')

    def test_shell_error_remote_id2(self):
        self.assertTrue(self.device.connect())

        # Provide the `bulk_read` return values
        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.WRTE, arg0=2, arg1=1, data=b'PASS')
        self.device._handle._bulk_read = b''.join(
            [msg1.pack(), msg1.data,
             msg2.pack(), msg2.data])

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.shell('TEST')

    def test_issue29(self):
        # https://github.com/JeffLIrion/adb_shell/issues/29
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')

        self.device._handle._bulk_read = b''.join([
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\xc5\n\x00\x00\xbe\xaa\xab\xb7',  # Line 22
            b"\x17\xbf\xbf\xff\xc7\xa2eo'Sh\xdf\x8e\xf5\xff\xe0\tJ6H",  # Line 23
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 26
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 27
            b'OKAY\x99\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 290 (modified --> Line 30)
            b'CLSE\xa2\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 291
            b'CLSE\xa2\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 292
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x001\x00\x00\x00\xa8\xad\xab\xba',  # Line 31
            b'1',  # Line 32
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x001\x00\x00\x00\xa8\xad\xab\xba',  # Line 35
            b'1',  # Line 36
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x000\x00\x00\x00\xa8\xad\xab\xba',  # Line 39
            b'0',  # Line 40
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x13\x00\x00\x000\x06\x00\x00\xa8\xad\xab\xba',  # Line 43
            b'Wake Locks: size=0\n',  # Line 44
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x1e\x00\x00\x00V\x0b\x00\x00\xa8\xad\xab\xba',  # Line 47
            b'com.google.android.youtube.tv\n',  # Line 48
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x98\x00\x00\x00\xa13\x00\x00\xa8\xad\xab\xba',  # Line 51
            b'      state=PlaybackState {state=0, position=0, buffered position=0, speed=0.0, updated=0, actions=0, custom actions=[], active item id=-1, error=null}\n',  # Line 52
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00.\x01\x00\x00\xceP\x00\x00\xa8\xad\xab\xba',  # Line 55
            b'- STREAM_MUSIC:\n   Muted: false\n   Min: 0\n   Max: 15\n   Current: 2 (speaker): 11, 4 (headset): 10, 8 (headphone): 10, 400 (hdmi): 6, 40000000 (default): 11\n   Devices: hdmi\n- STREAM_ALARM:\n   Muted: false\n   Min: 0\n   Max: 7\n   Current: 40000000 (default): 6\n   Devices: speaker\n- STREAM_NOTIFICATION:\n',  # Line 56
            b'CLSE\x99\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 59
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x94\t\x00\x00\xbe\xaa\xab\xb7',  # Line 297
            b'P\xa5\x86\x97\xe8\x01\xb09\x8c>F\x9d\xc6\xbd\xc0J\x80!\xbb\x1a',  # Line 298
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 301
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 302
            b'OKAY\xa5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 305
            b'CLSE\xa5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 306
            msg1.pack(),
            msg1.data,
            msg2.pack(),
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00e\x0c\x00\x00\xbe\xaa\xab\xb7',  # Line 315
            b'\xd3\xef\x7f_\xa6\xc0`b\x19\\z\xe4\xf3\xe2\xed\x8d\xe1W\xfbH',  # Line 316
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 319
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 320
            b'OKAY\xa7\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 323
            b'CLSE\xa7\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 324
            msg1.pack(),
            msg1.data,
            msg2.pack(),
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x93\x08\x00\x00\xbe\xaa\xab\xb7',  # Line 333
            b's\xd4_e\xa4s\x02\x95\x0f\x1e\xec\n\x95Y9[`\x8e\xe1f',  # Line 334
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 337
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 338
            b'OKAY\xa9\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 341
            b'CLSE\xa9\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 342
            msg1.pack(),
            msg1.data,
            msg2.pack()
        ])

        self.assertTrue(self.device.connect([signer]))

        self.device.shell('Android TV update command')

        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
 def setUp(self):
     self.device = AdbDevice(transport=patchers.FakeTcpTransport('host', 5555))
     self.device._transport._bulk_read = b''.join(patchers.BULK_READ_LIST)
    def test_init_invalid_transport(self):
        with self.assertRaises(exceptions.InvalidTransportError):
            device = AdbDevice(transport=123)

        # Clear the `_bulk_read` buffer so that `self.tearDown()` passes
        self.device._transport._bulk_read = b''