Exemple #1
0
def scan_phone_tcp(to_search_path, remote_ip, adb_key_file, max_files=None):
    """
    Search in the given directory for .jpg files and copy them to a temporarily folder.
    By default the 5555 port is used.
    After comparison, they're deleted from the original path.
    :param to_search_path: The path where the files are located.
    :param remote_ip: The IP of the device where the files are located.
    :param adb_key_file: The ADB key of the device to be connected.
    :param max_files: The number of files to be moved.
    :return: True
    """
    android_images = []
    with open(adb_key_file) as f:
        priv = f.read()
    signer = PythonRSASigner('', priv)
    device = AdbDeviceTcp(remote_ip, 5555, default_transport_timeout_s=100.)
    if device.connect(rsa_keys=[signer], auth_timeout_s=100.):
        if device.available:
            print("Connected to selected device.\n---")
        directory_scan = device.list(to_search_path, None, 9000)
        if max_files is None:
            max_files = len(directory_scan)
        for file in directory_scan:
            if os.path.splitext(file.filename.decode('utf-8'))[1] == ".jpg":
                save = AndroidPhoto()
                save.name = file.filename.decode("utf-8")
                save.size = file.size
                save.path = to_search_path
                android_images.append(save)
                if len(android_images) >= max_files:
                    break
    print(f"There're listed {len(android_images)} files.\n---")
    for image in android_images:
        device.pull(image.path + image.name, os.path.join(temp_directory, image.name),
                    progress_callback=log_pull_status,
                    transport_timeout_s=100, read_timeout_s=100)
        if image.size == os.path.getsize(os.path.join(temp_directory, image.name)):
            device.shell('rm -f ' + to_search_path + image.name)
            print("\r\r" + image.name + " is now in the temp folder.")
    print("---\nAll files are now in the temp folder.\n---")
    return True
Exemple #2
0
def get_apk_file(adbkey_path, device_ip, device_port, package_name):
    with open(adbkey_path) as f:
        priv = f.read()
    signer = PythonRSASigner('', priv)
    device = AdbDeviceTcp(device_ip, device_port, default_timeout_s=9.)
    device.connect(rsa_keys=[signer], auth_timeout_s=0.1)

    # Send a shell command
    # apk file (base.apk)
    print("<*> Copying APK file to /data/local/tmp/apkSneeze/base.apk (mobile device)...")
    shell_cmd = "su - root -c '\
	mkdir -p /data/local/tmp/apkSneeze && \
	cp /data/app/{}*/base.apk /data/local/tmp/apkSneeze && \
	chmod 777 /data/local/tmp/apkSneeze/base.apk'".format(package_name)

    device.shell(shell_cmd)
    print("<*> Downloading APK file to {}...".format(DEFAULT_APP_NAME))
    device.pull("/data/local/tmp/apkSneeze/base.apk", DEFAULT_APP_NAME)
    print("<*> Download done, check {}".format(DEFAULT_APP_NAME))
    print("<*> Deleting APK file from /data/local/tmp/apkSneeze/base.apk (mobile device)...")
    device.shell("su - root -c 'rm /data/local/tmp/apkSneeze/base.apk'")
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)
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