Skip to content

Redmi K80 HyperOS 2.0 problems #333

Description

@testerlife

使用设备是 Redmi K80,系统是 HyperOS 2.0 (Android 15),调试时无法触发点击。
调试命令:mobilerun run "Open the settings app and click on WLAN"

以下是Claude 修复信息:
成功了! 蓝牙页面已经打开了。设备是 Redmi K80,蓝牙当前已关闭。

根本原因找到了: mobilerun 的 AndroidDriver.tap() 始终用 adb shell input tap,而 HyperOS 2.0 (Android 15) 禁止了 INJECT_EVENTS 权限。但 Portal 的 HTTP API /tap 端点通过无障碍服务执行点击,不受此限制。

这是 mobilerun 代码的一个 bug——即使开了 TCP 模式,tap 和 swipe 仍然走的是 adb 而不是 Portal HTTP API。需要修复 AndroidDriver 让它在 TCP 模式下使用 Portal API。

修复总结:

问题: HyperOS 2.0 (Android 15) 禁止 adb shell input tap/swipe 的 INJECT_EVENTS 权限,导致所有点击/滑动操作无效
修复: 在 PortalClient 中新增 tap() / swipe() 方法,通过 Portal HTTP API(无障碍服务)执行操作;修改 AndroidDriver 在 TCP 模式下优先使用 Portal API,非 TCP 模式回退到 adb input
修改文件:
mobilerun/tools/android/portal_client.py — 新增 tap/swipe 及其 TCP/fallback 实现
mobilerun/tools/driver/android.py — tap/swipe 方法增加 Portal TCP 路径判断

  • portal_client.py code:
async def tap(self, x: int, y: int) -> None:
        """
        Tap at screen coordinates via Portal accessibility service.
        Auto-selects TCP or content provider fallback.

        Args:
            x: X coordinate
            y: Y coordinate
        """
        await self._ensure_connected()
        if self.tcp_available:
            await self._tap_tcp(x, y)
        else:
            await self._tap_content_provider(x, y)

    async def _tap_tcp(self, x: int, y: int) -> None:
        """Tap via TCP (Portal HTTP API)."""
        try:
            async with httpx.AsyncClient() as client:
                response = await self._tcp_request(
                    client,
                    "POST",
                    f"{self.tcp_base_url}/tap",
                    extra_headers={"Content-Type": "application/json"},
                    json={"x": x, "y": y},
                    timeout=10,
                )
                if response.status_code == 200:
                    logger.debug(f"TCP tap successful at ({x}, {y})")
                else:
                    logger.debug(
                        f"TCP tap failed ({response.status_code}), using fallback"
                    )
                    await self._tap_content_provider(x, y)
        except Exception as e:
            logger.debug(f"TCP tap error: {e}, using fallback")
            await self._tap_content_provider(x, y)

    async def _tap_content_provider(self, x: int, y: int) -> None:
        """Tap via adb shell input (fallback)."""
        await self.device.shell(f"input tap {x} {y}")

    async def swipe(
        self,
        x1: int,
        y1: int,
        x2: int,
        y2: int,
        duration_ms: int = 1000,
    ) -> None:
        """
        Swipe via Portal accessibility service.
        Auto-selects TCP or content provider fallback.

        Args:
            x1, y1: Start coordinates
            x2, y2: End coordinates
            duration_ms: Swipe duration in milliseconds
        """
        await self._ensure_connected()
        if self.tcp_available:
            await self._swipe_tcp(x1, y1, x2, y2, duration_ms)
        else:
            await self._swipe_content_provider(x1, y1, x2, y2, duration_ms)

    async def _swipe_tcp(
        self, x1: int, y1: int, x2: int, y2: int, duration_ms: int
    ) -> None:
        """Swipe via TCP (Portal HTTP API)."""
        try:
            payload = {
                "start_x": x1,
                "start_y": y1,
                "end_x": x2,
                "end_y": y2,
                "duration": duration_ms,
            }
            async with httpx.AsyncClient() as client:
                response = await self._tcp_request(
                    client,
                    "POST",
                    f"{self.tcp_base_url}/swipe",
                    extra_headers={"Content-Type": "application/json"},
                    json=payload,
                    timeout=10,
                )
                if response.status_code == 200:
                    logger.debug(
                        f"TCP swipe successful from ({x1}, {y1}) to ({x2}, {y2})"
                    )
                else:
                    logger.debug(
                        f"TCP swipe failed ({response.status_code}), using fallback"
                    )
                    await self._swipe_content_provider(x1, y1, x2, y2, duration_ms)
        except Exception as e:
            logger.debug(f"TCP swipe error: {e}, using fallback")
            await self._swipe_content_provider(x1, y1, x2, y2, duration_ms)

    async def _swipe_content_provider(
        self, x1: int, y1: int, x2: int, y2: int, duration_ms: int
    ) -> None:
        """Swipe via adb shell input (fallback)."""
        await self.device.shell(f"input swipe {x1} {y1} {x2} {y2} {duration_ms}")
  • android.py code:
    async def tap(self, x: int, y: int) -> None:
        await self.ensure_connected()
        if self.portal and self.portal.tcp_available:
            await self.portal.tap(x, y)
        else:
            await self.device.click(x, y)

    async def swipe(
        self,
        x1: int,
        y1: int,
        x2: int,
        y2: int,
        duration_ms: float = 1000,
    ) -> None:
        await self.ensure_connected()
        if self.portal and self.portal.tcp_available:
            await self.portal.swipe(x1, y1, x2, y2, int(duration_ms))
        else:
            await self.device.swipe(x1, y1, x2, y2, float(duration_ms / 1000))
            await asyncio.sleep(duration_ms / 1000)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions