Exemple #1
0
    def turn_off_tv(self):
        # Load the public and private keys
        with open(self.adb_key_filepath) as f:
            priv = f.read()
        with open(self.adb_key_filepath + '.pub') as f:
            pub = f.read()
        signer = PythonRSASigner(pub, priv)

        # Connect
        android_tv = AdbDeviceTcp(self.tv_ip_address,
                                  self.adb_port,
                                  default_transport_timeout_s=9.)
        android_tv.connect(rsa_keys=[signer], auth_timeout_s=0.1)

        # Send a shell command
        logger.info('Shutting off TV via shell command')
        tv_off_command_key = '26'
        android_tv.shell('input keyevent %s' % tv_off_command_key)

        android_tv.close()
class AndroidTV(ImageSourceBase):
    """ Works via ADB requires USB debugging enabled and require adbkey file for authentication (generate new and copy files to device
    or use old ones usually found at [~/.android/adbkey, %HOMEDRIVE%%HOMEPATH%\.android\adbkey]
    Works with all android based TVs
    """
    def __init__(self, ip, adbkeypath='adbkey', port=5555):
        self.signer = self._get_signer(adbkeypath)
        self.ip = ip
        self.port = port
        self.conn = AdbDeviceTcp(ip, port, default_timeout_s=9.)

    ## context manager
    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def connect(self):
        self.conn.connect(rsa_keys=[self.signer], auth_timeout_s=0.1)

    def close(self):
        self.conn.close()

    def get_ss(self):
        """ Take screenshot and create image (PIL) object"""
        raw_bytes = self.conn.shell('screencap -p', decode=False).replace(
            b'\x0D\x0A', b'\x0A')
        return Image.open(BytesIO(raw_bytes))

    def get_theme_color(self):
        return self.get_dom_color_from_image(self.get_ss())
        #return self.get_avg_color_from_image(self.get_ss())

    def _get_signer(self, adbkeypath):
        with open(adbkeypath) as f:
            return PythonRSASigner('', f.read())
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; may be an IP address or a host name
    port : int
        The device port to which we are connecting (default is 5555)
    adbkey : str
        The path to the ``adbkey`` file for ADB authentication

    """
    def __init__(self, host, port, adbkey=''):
        self.host = host
        self.port = int(port)
        self.adbkey = adbkey
        self._adb = AdbDeviceTcp(host=self.host,
                                 port=self.port,
                                 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

        """
        try:
            with _acquire(self._adb_lock):
                # Catch exceptions
                try:
                    # Connect with authentication
                    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)

                        self._adb.connect(rsa_keys=[signer],
                                          auth_timeout_s=auth_timeout_s)

                    # Connect without authentication
                    else:
                        self._adb.connect(auth_timeout_s=auth_timeout_s)

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

                except OSError as exc:
                    if self._available or always_log_errors:
                        if exc.strerror is None:
                            exc.strerror = "Timed out trying to connect to ADB device."
                        _LOGGER.warning("Couldn't connect to %s:%d.  %s: %s",
                                        self.host, self.port,
                                        exc.__class__.__name__, exc.strerror)

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

                except Exception as exc:  # pylint: disable=broad-except
                    if self._available or always_log_errors:
                        _LOGGER.warning("Couldn't connect to %s:%d.  %s: %s",
                                        self.host, self.port,
                                        exc.__class__.__name__, exc)

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

        except LockNotAcquiredException:
            _LOGGER.warning(
                "Couldn't connect to %s:%d because adb-shell lock not acquired.",
                self.host, self.port)
            self.close()
            self._available = False
            return False

    def pull(self, local_path, device_path):
        """Pull a file from the device using the Python ADB implementation.

        Parameters
        ----------
        local_path : str
            The path where the file will be saved
        device_path : str
            The file on the device that will be pulled

        """
        if not self.available:
            _LOGGER.debug(
                "ADB command not sent to %s:%d because adb-shell connection is not established: pull(%s, %s)",
                self.host, self.port, local_path, device_path)
            return

        with _acquire(self._adb_lock):
            _LOGGER.debug(
                "Sending command to %s:%d via adb-shell: pull(%s, %s)",
                self.host, self.port, local_path, device_path)
            self._adb.pull(device_path, local_path)
            return

    def push(self, local_path, device_path):
        """Push a file to the device using the Python ADB implementation.

        Parameters
        ----------
        local_path : str
            The file that will be pushed to the device
        device_path : str
            The path where the file will be saved on the device

        """
        if not self.available:
            _LOGGER.debug(
                "ADB command not sent to %s:%d because adb-shell connection is not established: push(%s, %s)",
                self.host, self.port, local_path, device_path)
            return

        with _acquire(self._adb_lock):
            _LOGGER.debug(
                "Sending command to %s:%d via adb-shell: push(%s, %s)",
                self.host, self.port, local_path, device_path)
            self._adb.push(local_path, device_path)
            return

    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:%d because adb-shell connection is not established: %s",
                self.host, self.port, cmd)
            return None

        with _acquire(self._adb_lock):
            _LOGGER.debug("Sending command to %s:%d via adb-shell: %s",
                          self.host, self.port, cmd)
            return self._adb.shell(cmd)
Exemple #4
0
class GridInputter:
    # fractions of the screen that the grid takes up
    START_X = 0.0
    START_Y = 0.22
    END_X = 1.0
    END_Y = 0.78

    GEV_MULTIPLIER = 17.4

    def __init__(self, grid_size, host='localhost', port=5555):
        self.dev = AdbDeviceTcp(host, port, default_transport_timeout_s=9.)
        self.dev.connect()
        self.swidth, self.sheight = self.screen_dimensions()
        self.grid_size = grid_size

    def __del__(self):
        self.dev.close()

    def screen_dimensions(self):
        line = self.dev.shell('wm size')
        result = re.match(r"Physical size: (?P<height>\d+)x(?P<width>\d+)\n",
                          line)
        return int(result.group('width')), int(result.group('height'))

    def input_swipe(self, p1, p2, time=100):
        start_pix = self.get_pixels(p1)
        end_pix = self.get_pixels(p2)

        self.dev.shell(
            f'input swipe {start_pix[0]} {start_pix[1]} {end_pix[0]} {end_pix[1]} {time}'
        )

    def simplify_path(self, path):
        path = path[:]
        new_path = []
        i = 0
        while i + 2 < len(path):
            if path[i + 0][0] == path[i + 1][0] == path[i + 2][0]:
                # we can remove the middle one
                path.remove(path[i + 1])
            elif path[i + 0][1] == path[i + 1][1] == path[i + 2][1]:
                path.remove(path[i + 1])
            else:
                new_path.append(path[i + 0])
                i += 1
        new_path += path[i:]

        # lastly, the game wants to help us complete paths so if the last two are neighbors we remove the last one
        if len(new_path) >= 3:
            if abs(new_path[-1][0] -
                   new_path[-2][0]) + abs(new_path[-1][1] -
                                          new_path[-2][1]) == 1:
                new_path = new_path[:-1]
        return new_path

    def get_pixels(self, point):
        # translate grid point to pixel offset
        x, y = point

        dx = self.swidth / self.grid_size
        dy = self.sheight * (self.END_Y - self.START_Y) / self.grid_size

        px = x * dx + dx / 2
        py = y * dy + dy / 2 + self.START_Y * self.sheight

        #print(f"{x,y} becomes {px,py}")
        return round(px), round(py)
Exemple #5
0
class AdbConnect:
    def __init__(self, ip):
        self.ip = ip
        self.device = None
        self.user_root = '/storage/emulated/0/'
        self.current_dir = None
        self.dirs = []
        self.files = []
        if sys.platform.startswith('win'):
            import winreg
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
            )
            self.default_download_dir = Path(
                winreg.QueryValueEx(key, "Desktop")[0]).as_posix()
        else:
            self.default_download_dir = os.path.expanduser('~')
        self.current_down_dir = self.default_download_dir

    def set_ip(self, ip):
        self.ip = ip

    def connect(self):
        with open(os.path.join(os.path.expanduser('~'),
                               '.android/adbkey')) as f:
            p = f.read()
        signer = PythonRSASigner('', p)
        self.device = AdbDeviceTcp(self.ip, 5555, default_timeout_s=9.)
        self.device.connect(rsa_keys=[signer], auth_timeout_s=0.1)
        # ls -l
        self.ls()
        print('connect')

    def disconnect(self):
        if self.device is not None:
            self.device.close()
        print('close')

    def check_dir_or_file(self, names):
        detailed_names = re.split('\n', names)[1:-1]
        # print(detailed_names)
        # 7 -> support space in filename
        self.dirs, self.files = [], []
        for e in detailed_names:
            if e.startswith('d'):
                self.dirs.append(re.split('\s+', e, maxsplit=7)[-1])
            else:
                self.files.append(re.split('\s+', e, maxsplit=7)[-1])

    def ls(self, dir_name=None):
        if dir_name is None:
            self.current_dir = self.user_root
        else:
            self.current_dir = dir_name
        dir_file_names = self.device.shell('ls -la "' + self.current_dir + '"')
        self.check_dir_or_file(dir_file_names)
        # print('dirs', self.dirs, 'files', self.files, sep='\n')

    def change_directory(self, dir_name):
        if dir_name == '.':
            # do nothing
            pass
        elif dir_name == '..':
            self.current_dir = Path(self.current_dir).parent.as_posix()
        else:
            self.current_dir = Path(os.path.join(self.current_dir,
                                                 dir_name)).as_posix()
        self.ls(self.current_dir)
        # print(self.current_dir, 'dirs', self.dirs, 'files', self.files, sep='\n')

    def pull_file(self, filename):
        download_dir = self.current_down_dir
        d_thread = DownloadThread(f=self.device.pull,
                                  filename=Path(
                                      os.path.join(self.current_dir,
                                                   filename)).as_posix(),
                                  local_dir=download_dir)
        d_thread.start()
        # self.device.pull(
        #     Path(os.path.join(self.current_dir, filename)).as_posix(),
        #     os.path.join(download_dir, filename)
        # )

    def thread_upload(self, local_files):
        # remote_dir -> current dir
        u_thread = UploadThread(f=self.device.push,
                                filenames=local_files,
                                remote_dir=self.current_dir)
        u_thread.start()
Exemple #6
0
class cADB_Helper:

    def __init__(self):
        self.aGlobalRSA_KEYS:List               = []
        self.oDevice:Optional[AdbDeviceTcp]     = None
        self.uHost:str                          = ''
        self.Load_RSA_KEYS()

    def Connect(self,uHost:str, uPort:str ,fTimeOut:float) -> cADB_Helper:
        self.uHost   = uHost
        self.oDevice = AdbDeviceTcp(uHost, int(uPort), default_transport_timeout_s=fTimeOut)
        self.oDevice.connect(rsa_keys=self.aGlobalRSA_KEYS, auth_timeout_s=20.1)
        return self
    def Load_RSA_KEYS(self) -> None:
        # default adb key path
        aKeyPathes:List[cPath] = [cPath(OS_GetSystemUserPath() + '.android/adbkey')]

        #default Android Path
        if Globals.uPlatform==u'android':
            aKeyPathes.append(cPath(OS_GetSystemUserPath()+'misc/adb/adb_keys/adbkey'))

        #Download path
        aKeyPathes.append(Globals.oPathUserDownload+"/adbkey")

        for oPath in aKeyPathes:
            if oPath.Exists():
                try:
                    with open(str(oPath)) as f:
                        oPriv   = f.read()
                        oSigner = PythonRSASigner('', oPriv)
                        self.aGlobalRSA_KEYS.append(oSigner)
                        Logger.info("RSA Keyfiles loaded from "+str(oPath))
                except Exception as e:
                    Logger.error("Error Loading RSA Keys from "+str(oPath)+" "+str(e))
            else:
                Logger.debug("No RSA Keyfiles at "+oPath)

    # noinspection PyUnusedLocal
    def Shell(self, uCommand:str, fTimeOut:float=1.0) -> str:
        """Run command on the device, returning the output."""
        return self.oDevice.shell(uCommand)

    def GetAppList(self, uCommand:str, fTimeOut:float=1.0) -> str:
        """fetches the list of all installed apps from an Android device
           The result will be saved for later calls
         """
        uAppList: str = GetVar("ADB_APPLIST_" + self.uHost)
        if uAppList=='':
            uAppList = self.Shell(uCommand=uCommand, fTimeOut=fTimeOut)
            # Logger.debug("GetAppList:"+str(uAppList))
            # print (uAppList)
            SetVar("ADB_APPLIST_" + self.uHost, uAppList)
        return uAppList

    def GetAppName(self, uCommand:str, fTimeOut:float=1.0) -> str:
        uAppList:str=GetVar("ADB_APPLIST_"+self.uHost)
        return self.FindPackageName(uAppName=uCommand,uAppList=uAppList)

    # noinspection PyMethodMayBeStatic
    def GetAppIntent(self, uCommand:str, fTimeOut:float=1.0) ->str:

        uAppName:str
        uAppDump:str
        uIntent:str = u''
        uSearch:str
        uAppName,uAppDump = uCommand.split("|||")
        uSearch = uAppName+"/."
        aDumpLines:List[str] = uAppDump.splitlines()

        for uLine in aDumpLines:
            if uSearch in uLine:
                uIntent = uLine
            if "android.intent.category.LAUNCHER" in uLine or "android.intent.category.LEANBACK_LAUNCHER" in uLine:
                break
        if uIntent:
            uIntent = uIntent.strip().split(" ")[1]
        else:
            uSearch = uAppName
            for uLine in aDumpLines:
                if uSearch in uLine:
                    uIntent = uLine
                if "android.intent.category.LAUNCHER" in uLine or "android.intent.category.LEANBACK_LAUNCHER" in uLine:
                    break
            if uIntent:
                if "cmp=" in uIntent:
                    uIntent = uIntent.strip().split("cmp=")[1]
                    uIntent = uIntent[:-1]
                    # something hacky until we identify why this is wrong identified from the dump
                    uIntent=uIntent.replace(".nvidia","")
                else:
                    uIntent = uIntent.strip().split(" ")[1]
        return uIntent

    def Close(self) -> None:
        self.oDevice.close()

    # noinspection PyMethodMayBeStatic
    def FindPackageName(self,*,uAppList:str,uAppName:str) -> str:

        """  ^(?=.*amazon)(?=.*music).*$ """

        uPackageName:str = uAppName

        try:
            oResult=re.compile(uAppName,re.MULTILINE).search(uAppList)
            if oResult:
                # noinspection PyUnresolvedReferences
                uResult=uAppList[oResult.regs[0][0]:oResult.regs[0][1]]
                uPackageName = uResult.split(u"=")[-1]
        except Exception as e:
            Logger.info("FindPackageName: couldn't validate Appname as regex, returning the default value: %s (%s)" % (uAppName,str(e)))
        return uPackageName
Exemple #7
0
class SX5_Manager(object):
    """ """

    # ************************************************* #
    # **************** Private Methods **************** #
    # ************************************************* #
    def __init__(self):
        """ Constructor"""
        self._sx5_shell_values_dict = {
            'SupercapVoltage_mV': {
                'command':
                'cat /sys/bus/platform/devices/vendor:supercap/voltage',
                'value:': ""
            },
            'CapokFlag': {
                'command':
                'cat /sys/bus/platform/devices/vendor:supercap/capok',
                'value': ""
            },
            'BatteryCharge_%': {
                'command': 'cat sys/class/power_supply/bq27750-0/capacity',
                'value': ""
            },
            'BatteryVoltage_uV': {
                'command': 'cat sys/class/power_supply/bq27750-0/voltage_now',
                'value': ""
            }
        }

        self._sx5_config_dict: dict
        self._sx5_device: AdbDeviceTcp

        pass

    # ---------------------------------------------------------------- #
    # ----------------------- Private Methods ------------------------ #
    # ---------------------------------------------------------------- #
    def _parse_config_file(self):
        """"""
        self._sx5_config_dict = XmlDictConfig(
            ElementTree.parse('Config.xml').getroot())['LoopTest']

        pass

    def _adb_init(self):
        """"""
        count = 0
        res = -1
        num_of_try = 60

        # Start Timer
        timer = Timer()
        timer.start()

        while (count < num_of_try):
            if timer.elapsed_time_s(2) >= 1:
                print("- Waiting for SX5 device " + "." * count, end='\r')
                res = subprocess.run(
                    "adb devices", text=True, capture_output=True).stdout.find(
                        self._sx5_config_dict['SX5']['device'])
                if (res != -1):
                    timer.stop()
                    break
                else:
                    timer.reset()

                count += 1
        if res == -1:
            sys.stdout.write("\033[K")
            print("No Device Found")
            raise ADB_Error
        else:
            sys.stdout.write("\033[K")
            self._sx5_device = AdbDeviceTcp(
                host=self._sx5_config_dict['SX5']['ip'],
                port=int(self._sx5_config_dict['SX5']['port']),
                default_transport_timeout_s=9.)
        pass

    def _adb_tcp_connect(self):
        """"""
        try:
            self._sx5_device.connect(auth_timeout_s=0.1)
            print("- SX5 Connected")
        except:
            print(
                "Timeout expired: check if device is on and if the IP is correct"
            )
            raise
        pass

    def _adb_tcp_disconnect(self):
        """"""
        self._sx5_device.close()
        pass

    # ---------------------------------------------------------------- #
    # ------------------------ Public Methods ------------------------ #
    # ---------------------------------------------------------------- #

    def init(self):
        """ Initialize class and connect SX5 to ADB over TCP-IP protocol """
        # Parse config file
        self._parse_config_file()

        # Initialize adb connection
        try:
            self._adb_init()
        except ADB_Error:
            sys.exit()
        try:
            self._adb_tcp_connect()
        except:
            subprocess.run("adb disconnect")
            subprocess.run("adb tcpip {port}".format(
                port=int(self._sx5_config_dict['SX5']['port'])))
            #self._adb_init()
            self._adb_tcp_connect()
        pass

    def _read_shell(self, cmd: str):
        """"""
        max_attempt = 10
        attempt_count = 0
        stdout = ""

        while attempt_count < max_attempt:
            try:
                stdout = self._sx5_device.shell(cmd)
            except:
                # Try to establish a new connection if no response...
                try:
                    self._sx5_device = AdbDeviceTcp(
                        host=self._sx5_config_dict['SX5']['ip'],
                        port=int(self._sx5_config_dict['SX5']['port']),
                        default_transport_timeout_s=9.)
                except:
                    pass
                try:
                    self._sx5_device.connect(auth_timeout_s=0.2)
                except:
                    pass

                attempt_count += 1
            else:
                break

        if attempt_count >= max_attempt:
            raise exceptions.TcpTimeoutException
        pass

        return stdout

    def read_sx5_value(self, value: str):
        """ """
        if value in self._sx5_shell_values_dict.keys():
            self._sx5_shell_values_dict[value]['value'] = self._read_shell(
                self._sx5_shell_values_dict[value]['command'])
        return

    def update_all_sx5_values(self):
        """ """
        # Variables
        concat_cmd = ""
        stdout = ""

        for key in self._sx5_shell_values_dict:
            concat_cmd += self._sx5_shell_values_dict[key]['command'] + " && "

        # Delete last "&&"
        concat_cmd = concat_cmd[:-3]

        # Read output from adb shell
        stdout = self._read_shell(concat_cmd)

        keys_list = list(self._sx5_shell_values_dict.keys())
        stdout_splitted = stdout.split("\n")[:-1]

        # Add each element of stdout to the proper value of shel dictionary
        for index, item in enumerate(stdout_splitted):
            self._sx5_shell_values_dict[
                keys_list[index]]["value"] = stdout_splitted[index]
        return

    @property
    def supercap_voltage_mV(self):
        return int(self._sx5_shell_values_dict['SupercapVoltage_mV']
                   ['value'].strip(" mV\n"))

    @property
    def capok_flag(self):
        return bool(
            int(self._sx5_shell_values_dict['CapokFlag']['value'].strip("\n")))

    @property
    def battery_charge_pct(self):
        return int(self._sx5_shell_values_dict['BatteryCharge_%']
                   ['value'].strip("\n"))

    @property
    def battery_voltage_uV(self):
        return int(self._sx5_shell_values_dict['BatteryVoltage_uV']
                   ['value'].strip(" mV\n"))

    @property
    def battery_voltage_mV(self):
        return self.battery_voltage_uV / 1000
class AdbConnector:
    """
    wraps adb_shell functions for simpler automation
    """
    def __init__(self, ip=None, need_auth=True, device_name="", auto_reconnect_seconds=60, verbose=False):
        self.ip = ip
        self.device_name = device_name
        self.need_auth = need_auth
        self.auto_reconnect_seconds = auto_reconnect_seconds
        self.verbose=verbose

        self._event_handler = AdbConnectorEventManager()
        self._last_connected_time = time.time()
        self._device_resolution = None
        self._device = None

        self.width = self.screen_width()
        self.height = self.screen_height()
        print(f">>> Target phone sreen size is [{self.width} x {self.height}]")

    def _connect(self):
        """
        in my experience, it was better to connect, send commands, disconnect right away
        rather than connect once and send many commands for hours => getting very slow at some point
        Now added auto-reconnect feature with argument auto_reconnect_seconds defaulting to 1 min
        """
        now = time.time()
        if self._device and self._last_connected_time + self.auto_reconnect_seconds < now:
            self._disconnect()

        if self._device:
            return


        if self.verbose:
            print(f">>> connecting to {self.ip}")
        self._last_connected_time = now
        self._device = AdbDeviceTcp(self.ip, default_timeout_s=self.auto_reconnect_seconds)
        if not self.need_auth:
            self._device.connect(auth_timeout_s=0.1)
        else:
            with open(os.path.expanduser('~/.android/adbkey')) as f:
                private_key = f.read()
            signer = PythonRSASigner('', private_key)
            self._device.connect(rsa_keys=[signer], auth_timeout_s=0.1)

        if self.verbose:
            print(f">>> connected")

    def _disconnect(self):
        self._device.close()
        self._device = None
        if self.verbose:
            print(">>> disconnected")

    def _shell(self, command, **kwargs):
        if self.verbose:
            print(f">>> shell {command}")
        return self._device.shell(command, **kwargs)

    def _pull(self, from_file, to_file):
        if self.verbose:
            print(f">>> pull {from_file} to {to_file}")
        return self._device.pull(from_file, to_file)


    def tap(self, x, y, wait_ms = 100):
        """
        tap the screen and force wait 100ms by default to simulate real taps if several in a row
        :param x:
        :param y:
        :param wait_ms:
        :return:
        """
        assert 0 <= x < self.width, "out of bound"
        assert 0 <= y < self.height, "out of bound"
        self._connect()
        self._shell(f'{CMD_SHELL_TAP} {x:.0f} {y:.0f}')
        self.wait(wait_ms)

    def press(self, x, y, time_ms = 0, wait_ms = 0):
        """
        long tap, implemented with a static swipe
        :param x:
        :param y:
        :param time_ms:
        :param wait_ms:
        :return:
        """
        self._connect()
        self._shell(f'{CMD_SHELL_SWIPE} {x:.0f} {y:.0f} {x:.0f} {y:.0f} {time_ms}')
        self.wait(wait_ms)

    def swipe(self, x1, y1, x2, y2, time_ms = 0, wait_ms = 0):
        """
        swipe from point (x1, y1) to point (x2, y2) in the span of time_ms
        careful, swipe has inertia, so if it is used to scroll a screen for example,
        screen will most likely keep scrolling at the end of the swipe
        To avoid that effect, use the event_move() instead
        :param x1:
        :param y1:
        :param x2:
        :param y2:
        :param time_ms:
        :param wait_ms:
        :return:
        """
        self._connect()
        print(x1, y1, x2, y2)
        self._shell(f'{CMD_SHELL_SWIPE} {x1:.0f} {y1:.0f} {x2:.0f} {y2:.0f} {time_ms}')
        self.wait(wait_ms)

    @staticmethod
    def wait(time_ms):
        """
        to wait some time. Not relying on adb, just convenient
        :param time_ms:
        :return:
        """
        if time_ms > 0:
            time.sleep(time_ms / 1000.0)

    @DeprecationWarning
    def event_press(self, slot, x, y):
        self._event_handler.event_press(slot, x, y)

    @DeprecationWarning
    def event_move(self, slot, x, y):
        self._event_handler.event_move(slot, x, y)

    @DeprecationWarning
    def event_release(self, slot):
        self._event_handler.event_release(slot)

    @DeprecationWarning
    def event_flush(self):
        self._connect()
        self._device.shell(self._event_handler.event_flush(self.device_name))

    # too much info: debug only
    def print_all_process_info(self):
        self._connect()
        processes_with_focus = self._shell(CMD_WINDOWS_DUMP)
        print(processes_with_focus)

    # todo: does not work anymore with latest android versions?
    @DeprecationWarning
    def process_has_focus(self, process_name):
        self._connect()
        all_processes = self._shell(f"{CMD_WINDOWS_DUMP} | grep -i {process_name} | grep -i mcurrentfocus")
        return len(all_processes) > 0

    # todo: needs PR merged in adb_shell module
    def listen(self):
        self._connect()
        for line in self._device.streaming_shell(CMD_GET_EVENT):
            print(line)

    def _get_screen_resolution(self):
        if not self._device_resolution:
            self._connect()
            header_width_x_height = self._shell(CMD_WM_SIZE)
            self._device_resolution = tuple(map(int, re.findall("\d+", header_width_x_height)))
        return self._device_resolution

    def screen_width(self):
        return self._get_screen_resolution()[0]

    def screen_height(self):
        return self._get_screen_resolution()[1]

    def get_screenshot(self, raw=False, pull=True) -> Image:
        if pull:
            if raw:
                return self._get_screenshot_raw_pull_file()
            return self._get_screenshot_png_pull_file()
        if raw:
            raise Exception("Not implemented")
        return self._get_screenshot_png_stream()

    @staticmethod
    def get_temp_remote_filepath(extension):
        random_part = next(tempfile._get_candidate_names())
        return os.path.join(REMOTE_SCREENSHOT_DIRECTORY, f"screenshot_adb_{random_part}.{extension}")

    @staticmethod
    def get_temp_local_filepath(extension):
        if not os.path.exists(OUTPUT_DIR):
            os.mkdir(OUTPUT_DIR)
        return os.path.realpath(f"{OUTPUT_DIR}/screenshot.{extension}")

    def _get_screenshot_png_pull_file(self) -> Image:
        self._connect()
        png_remote_filepath = self.get_temp_remote_filepath("png")
        png_local_filepath = self.get_temp_local_filepath("png")
        self._shell(f"{CMD_SCREENSHOT_PNG} {png_remote_filepath}")
        self._pull(png_remote_filepath, png_local_filepath)
        self._shell(f"rm {png_remote_filepath}")
        return Image.open(png_local_filepath)

    def _get_screenshot_raw_pull_file(self) -> Image:
        self._connect()
        raw_remote_filepath = self.get_temp_remote_filepath("raw")
        raw_local_filepath = self.get_temp_local_filepath("raw")
        self._shell(f"{CMD_SCREENSHOT_RAW} {raw_remote_filepath}")
        self._pull(raw_remote_filepath, raw_local_filepath)
        self._shell(f"rm {raw_remote_filepath}")
        with open(raw_local_filepath, 'rb') as f:
            raw = f.read()
        return Image.frombuffer('RGBA', (self.screen_height(), self.screen_width()), raw[12:], 'raw', 'RGBX', 0, 1)

    # todo: use exec-out instead of shell
    def _get_screenshot_png_stream(self) -> Image:
        self._connect()
        raw = self._shell(CMD_SCREENSHOT_PNG, decode=False)
        image = Image.open(io.BytesIO(raw))
        return image