From ff8cdd77c3f9510b6c2f053825e279b90f497ed4 Mon Sep 17 00:00:00 2001 From: chengma Date: Sun, 9 Aug 2026 22:18:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Wi-Fi=20=E6=81=A2=E5=A4=8D=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=90=8E=E7=BB=A7=E7=BB=AD=E6=90=9C=E7=B4=A2=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=20(#66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/android_device_service.py | 8 ++++ client/src/settings_ui_event.py | 42 +++++++++++++++--- client/test/test_settings_ui_event.py | 61 +++++++++++++++++++++------ docs/client/05-ui-specification.md | 5 +++ 4 files changed, 97 insertions(+), 19 deletions(-) diff --git a/client/src/android_device_service.py b/client/src/android_device_service.py index 0decd6e..4b18755 100644 --- a/client/src/android_device_service.py +++ b/client/src/android_device_service.py @@ -40,6 +40,14 @@ class AndroidWifiConversionResult: devices: List[AndroidDevice] +@dataclass(frozen=True) +class AndroidDeviceSearchResult: + """一次设备搜索的结果,可同时携带已保存设备恢复警告。""" + + devices: List[AndroidDevice] + restore_warning: str = "" + + CommandRunner = Callable[[Sequence[str], float], subprocess.CompletedProcess] ProgressCallback = Callable[[str], None] Sleeper = Callable[[float], None] diff --git a/client/src/settings_ui_event.py b/client/src/settings_ui_event.py index 50c6fd2..eead193 100644 --- a/client/src/settings_ui_event.py +++ b/client/src/settings_ui_event.py @@ -18,6 +18,7 @@ from PyQt5.QtCore import ( from .android_device_service import ( AndroidDeviceConversionCancelled, AndroidDeviceSearchError, + AndroidDeviceSearchResult, AndroidDeviceService, PDD_PACKAGE_NAME, ) @@ -130,11 +131,18 @@ class AndroidDeviceSearchWorker(QObject): def run(self) -> None: try: try: + restore_warning = "" if self._restore_serial: - devices = self._service.restore_saved_device( - self._restore_serial, - lambda: self._cancelled, - ) + try: + devices = self._service.restore_saved_device( + self._restore_serial, + lambda: self._cancelled, + ) + except AndroidDeviceSearchError as exc: + restore_warning = str(exc) + devices = self._service.search( + lambda: self._cancelled + ) else: devices = self._service.search(lambda: self._cancelled) except AndroidDeviceSearchError as exc: @@ -145,7 +153,9 @@ class AndroidDeviceSearchWorker(QObject): self.failed.emit(str(exc) or "无法搜索 Android 设备") else: if not self._cancelled: - self.succeeded.emit(devices) + self.succeeded.emit( + AndroidDeviceSearchResult(devices, restore_warning) + ) finally: self.completed.emit() @@ -897,7 +907,7 @@ class SettingsPageEventBinder(QObject): return AndroidDeviceInfo(serial) if serial else None @pyqtSlot(object) - def _on_search_succeeded(self, devices) -> None: + def _on_search_succeeded(self, result: AndroidDeviceSearchResult) -> None: if self._closing: return @@ -909,7 +919,7 @@ class SettingsPageEventBinder(QObject): android_version=device.android_version, status=device.status, ) - for device in devices + for device in result.devices ] self._page.set_android_devices(rows) saved_device = self._page.deviceTableModel.device_for_serial( @@ -919,6 +929,24 @@ class SettingsPageEventBinder(QObject): self._page.deviceTableModel.set_checked_serial( self._saved_android_serial ) + + if result.restore_warning: + action = ( + "自动恢复未完成" + if self._search_is_startup_restore + else "搜索完成" + ) + found_text = ( + f"找到 {len(rows)} 台 Android 设备" + if rows + else "未找到其他 Android 设备" + ) + self._page.deviceStatusLabel.setText( + f"{action}:{found_text};已保存设备 " + f"{self._saved_android_serial} 恢复失败:" + f"{result.restore_warning};配置已保留" + ) + elif saved_device is not None and saved_device.status == "device": action = ( "恢复完成" if self._search_is_startup_restore diff --git a/client/test/test_settings_ui_event.py b/client/test/test_settings_ui_event.py index 322b453..f5dd9f7 100644 --- a/client/test/test_settings_ui_event.py +++ b/client/test/test_settings_ui_event.py @@ -556,13 +556,14 @@ class SettingsPageEventTest(unittest.TestCase): page.eventBinder.shutdown() page.deleteLater() - def test_manual_wifi_reconnect_failure_keeps_saved_device(self): + def test_manual_wifi_reconnect_failure_still_lists_usb_device(self): wifi_serial = "192.168.0.173:5555" self.repository.set(SELECTED_ANDROID_SERIAL_KEY, wifi_serial) service = SequenceAndroidDeviceService( [ [AndroidDevice(wifi_serial, "Wi-Fi", "Phone", "14")], AndroidDeviceSearchError("目标设备拒绝连接"), + [AndroidDevice("USB-001", "USB", "Pixel 8", "14")], ] ) page = SettingsPage( @@ -574,16 +575,20 @@ class SettingsPageEventTest(unittest.TestCase): self._wait_until(lambda: page.eventBinder._search_thread is None) page.searchButton.click() - self._wait_until(lambda: service.call_count == 2) + self._wait_until(lambda: service.call_count == 3) self._wait_until(lambda: page.eventBinder._search_thread is None) self.assertEqual( self.repository.get(SELECTED_ANDROID_SERIAL_KEY), wifi_serial, ) - self.assertEqual(page.deviceTableModel.checked_serial, wifi_serial) - self.assertIn("无法重新连接", page.deviceStatusLabel.text()) - self.assertIn("已保存配置未更改", page.deviceStatusLabel.text()) + self.assertIsNotNone( + page.deviceTableModel.device_for_serial("USB-001") + ) + self.assertEqual(page.deviceTableModel.checked_serial, "") + self.assertIn("找到 1 台 Android 设备", page.deviceStatusLabel.text()) + self.assertIn("恢复失败", page.deviceStatusLabel.text()) + self.assertIn("配置已保留", page.deviceStatusLabel.text()) page.eventBinder.shutdown() page.deleteLater() @@ -591,9 +596,38 @@ class SettingsPageEventTest(unittest.TestCase): wifi_serial = "192.168.0.173:5555" self.repository.set(SELECTED_ANDROID_SERIAL_KEY, wifi_serial) service = SequenceAndroidDeviceService( - [AndroidDeviceSearchError("目标设备拒绝连接")] + [AndroidDeviceSearchError("目标设备拒绝连接"), []] ) + page = SettingsPage( + settings_repository=self.repository, + admin_gateway=MockAdminGateway(), + android_device_service=service, + ) + self._wait_until(lambda: service.call_count == 2) + self._wait_until(lambda: page.eventBinder._search_thread is None) + + self.assertEqual( + self.repository.get(SELECTED_ANDROID_SERIAL_KEY), + wifi_serial, + ) + self.assertIn("自动恢复未完成", page.deviceStatusLabel.text()) + self.assertIn("未找到其他 Android 设备", page.deviceStatusLabel.text()) + self.assertIn("配置已保留", page.deviceStatusLabel.text()) + self.assertTrue(page.searchButton.isEnabled()) + page.eventBinder.shutdown() + page.deleteLater() + + def test_search_failure_after_restore_failure_preserves_previous_rows(self): + wifi_serial = "192.168.0.173:5555" + self.repository.set(SELECTED_ANDROID_SERIAL_KEY, wifi_serial) + service = SequenceAndroidDeviceService( + [ + [AndroidDevice(wifi_serial, "Wi-Fi", "Phone", "14")], + AndroidDeviceSearchError("目标设备拒绝连接"), + AndroidDeviceSearchError("adb devices 执行失败"), + ] + ) page = SettingsPage( settings_repository=self.repository, admin_gateway=MockAdminGateway(), @@ -602,13 +636,16 @@ class SettingsPageEventTest(unittest.TestCase): self._wait_until(lambda: service.call_count == 1) self._wait_until(lambda: page.eventBinder._search_thread is None) - self.assertEqual( - self.repository.get(SELECTED_ANDROID_SERIAL_KEY), - wifi_serial, + page.searchButton.click() + self._wait_until(lambda: service.call_count == 3) + self._wait_until(lambda: page.eventBinder._search_thread is None) + + self.assertIsNotNone( + page.deviceTableModel.device_for_serial(wifi_serial) ) - self.assertIn("自动恢复失败", page.deviceStatusLabel.text()) - self.assertIn("可点击搜索重试", page.deviceStatusLabel.text()) - self.assertTrue(page.searchButton.isEnabled()) + self.assertEqual(page.deviceTableModel.checked_serial, wifi_serial) + self.assertIn("搜索失败", page.deviceStatusLabel.text()) + self.assertIn("adb devices 执行失败", page.deviceStatusLabel.text()) page.eventBinder.shutdown() page.deleteLater() diff --git a/docs/client/05-ui-specification.md b/docs/client/05-ui-specification.md index 86c7bd0..5c4072b 100644 --- a/docs/client/05-ui-specification.md +++ b/docs/client/05-ui-specification.md @@ -251,6 +251,11 @@ class TaskTableModel(QAbstractTableModel): - PDD 包名; - “测试连接”; - 当前设备型号、Android 版本和 PDD 当前状态摘要。 +- 点击设备“搜索”时可以先尝试恢复已保存的 Wi-Fi ADB,但单台设备恢复失败 + 不能中断普通设备枚举。表格仍展示 `adb devices -l` 实际找到的 USB 和 Wi-Fi + 设备,状态文字同时说明恢复失败原因以及“已保存配置仍保留”。 +- 只有普通设备枚举本身失败时才显示“搜索失败”并保留旧表格;程序不得因为 + 设备暂时离线自动删除已保存配置。 ### 自动化