Skip to content

Redmi K80 HyperOS 2.0 problems #333

@testerlife

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    📨 Inbox

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions