fix: 修复 Windows 兼容性问题
- 修复 setup.py 中 README.md 读取的编码问题 (UTF-8) - 修复 system.py 中 errno 常量在 Windows 上不存在的问题 - 使用 getattr 安全获取平台特定的 errno 常量 - 测试全部通过 (24/24 能力可用)
This commit is contained in:
1 parent
0f9b6fcc5d
commit
b3d75f8cc2
8 files changed
+708
-38
No files matched your search
@@ -0,0 +1,264 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: android-pyapi
|
||||||
|
Version: 1.0.0
|
||||||
|
Summary: Android API for Python — 在桌面/Linux 上原生运行 Android API
|
||||||
|
Home-page: https://gitee.com/shuiyu1123/Android-pyApi
|
||||||
|
Author: shuiyu1123
|
||||||
|
License: MIT
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Operating System :: POSIX :: Linux
|
||||||
|
Classifier: Operating System :: MacOS
|
||||||
|
Classifier: Operating System :: Microsoft :: Windows
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
Classifier: Topic :: System :: Hardware
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
License-File: LICENSE
|
||||||
|
Provides-Extra: linux
|
||||||
|
Requires-Dist: psutil; extra == "linux"
|
||||||
|
Requires-Dist: requests; extra == "linux"
|
||||||
|
Requires-Dist: Pillow; extra == "linux"
|
||||||
|
Requires-Dist: opencv-python; extra == "linux"
|
||||||
|
Requires-Dist: numpy; extra == "linux"
|
||||||
|
Provides-Extra: windows
|
||||||
|
Requires-Dist: psutil; extra == "windows"
|
||||||
|
Requires-Dist: requests; extra == "windows"
|
||||||
|
Requires-Dist: Pillow; extra == "windows"
|
||||||
|
Requires-Dist: opencv-python; extra == "windows"
|
||||||
|
Requires-Dist: numpy; extra == "windows"
|
||||||
|
Requires-Dist: pyautogui; extra == "windows"
|
||||||
|
Requires-Dist: pyperclip; extra == "windows"
|
||||||
|
Requires-Dist: screeninfo; extra == "windows"
|
||||||
|
Requires-Dist: pywin32; extra == "windows"
|
||||||
|
Requires-Dist: wmi; extra == "windows"
|
||||||
|
Requires-Dist: pycaw; extra == "windows"
|
||||||
|
Requires-Dist: comtypes; extra == "windows"
|
||||||
|
Requires-Dist: win10toast; extra == "windows"
|
||||||
|
Provides-Extra: dev
|
||||||
|
Requires-Dist: pytest; extra == "dev"
|
||||||
|
Requires-Dist: pytest-cov; extra == "dev"
|
||||||
|
Dynamic: author
|
||||||
|
Dynamic: classifier
|
||||||
|
Dynamic: description
|
||||||
|
Dynamic: description-content-type
|
||||||
|
Dynamic: home-page
|
||||||
|
Dynamic: license
|
||||||
|
Dynamic: license-file
|
||||||
|
Dynamic: provides-extra
|
||||||
|
Dynamic: requires-python
|
||||||
|
Dynamic: summary
|
||||||
|
|
||||||
|
# Android-pyApi
|
||||||
|
|
||||||
|
> **Android API for Python** — 在桌面/Linux 上原生运行 Android API 的 Python 库
|
||||||
|
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
[](https://www.python.org/downloads/)
|
||||||
|
[](#)
|
||||||
|
|
||||||
|
## 这是什么
|
||||||
|
|
||||||
|
Android-pyApi 将完整的 Android SDK API 翻译为纯 Python 实现,让你在桌面环境直接调用 Android API——不需要模拟器、不需要真机、不需要 Android Studio。
|
||||||
|
|
||||||
|
**213 个模块,178,000+ 行代码,覆盖 android/java/javax/org/dalvik 全包。**
|
||||||
|
|
||||||
|
在 Linux 上,系统级 API(网络、传感器、蓝牙、音频、位置等)使用真实系统调用,返回真实数据。
|
||||||
|
|
||||||
|
## 核心特性
|
||||||
|
|
||||||
|
### API 覆盖
|
||||||
|
|
||||||
|
| 包 | 模块数 | 内容 |
|
||||||
|
|---|---|---|
|
||||||
|
| `android.*` | 167 | app, content, os, view, widget, graphics, media, hardware, net, telephony, provider 等 |
|
||||||
|
| `java.*` | 35 | lang, io, util, net, nio, security, crypto, sql, math, time 等 |
|
||||||
|
| `javax.*` | 4 | net, crypto, security, sql |
|
||||||
|
| `org.*` | 5 | json, w3c_dom, xml_sax, xml_pull, apache_http |
|
||||||
|
| `dalvik.*` | 1 | system |
|
||||||
|
|
||||||
|
### Linux 真实实现
|
||||||
|
|
||||||
|
在 Linux 环境下,以下 API 使用真实系统调用而非存根:
|
||||||
|
|
||||||
|
| API | 实现方式 | 真实行为 |
|
||||||
|
|-----|---------|---------|
|
||||||
|
| ConnectivityManager | `ip` 命令 + `/proc/net` | 真实 IP、接口、DNS、网关 |
|
||||||
|
| LocationManager | IP 地理定位 | 真实经纬度、城市、ISP |
|
||||||
|
| SensorManager | `/sys/class/hwmon` | 真实温度、CPU 频率、风扇 |
|
||||||
|
| AudioManager | `amixer` (ALSA) | 真实音量控制 |
|
||||||
|
| BluetoothAdapter | `bluetoothctl` (BlueZ) | 真实扫描、配对、连接 |
|
||||||
|
| ClipboardManager | `xclip` | 真实剪贴板读写 |
|
||||||
|
| PowerManager / BatteryManager | `psutil` + `/sys/class/power_supply` | 真实 CPU、内存、电池 |
|
||||||
|
| TelephonyManager | MAC 地址 + 内核版本 | 真实设备 ID |
|
||||||
|
| NotificationManager | `notify-send` | 真实桌面通知 |
|
||||||
|
| Activity / Display | `xdotool` + X11 | 真实屏幕尺寸、截屏 |
|
||||||
|
| ActivityManager | `psutil` + `/proc` | 真实进程列表 |
|
||||||
|
| Shell | `subprocess` | 真实命令执行 |
|
||||||
|
| Camera | OpenCV / V4L2 | 真实拍照 |
|
||||||
|
| UI 自动化 | `xdotool` | 真实点击、输入、按键 |
|
||||||
|
|
||||||
|
### Windows 真实实现
|
||||||
|
|
||||||
|
在 Windows 环境下,以下 API 使用真实系统调用而非存根:
|
||||||
|
|
||||||
|
| API | 实现方式 | 真实行为 |
|
||||||
|
|-----|---------|---------|
|
||||||
|
| ConnectivityManager | `ipconfig` + `netsh` + `socket` | 真实 IP、接口、DNS、网关、WiFi 信息 |
|
||||||
|
| LocationManager | IP 地理定位 | 真实经纬度、城市、ISP |
|
||||||
|
| SensorManager | `wmi` + `psutil` | 真实温度、CPU 频率、风扇转速 |
|
||||||
|
| AudioManager | `pycaw` / PowerShell | 真实音量控制、静音 |
|
||||||
|
| BluetoothAdapter | PowerShell Bluetooth cmdlets | 真实设备列表、配对状态 |
|
||||||
|
| ClipboardManager | `pyperclip` | 真实剪贴板读写 |
|
||||||
|
| PowerManager / BatteryManager | `psutil` | 真实 CPU、内存、电池状态 |
|
||||||
|
| NotificationManager | `win10toast` / PowerShell | 真实桌面通知 (Toast) |
|
||||||
|
| Activity / Display | `pyautogui` + `screeninfo` + `win32api` | 真实屏幕尺寸、截屏 |
|
||||||
|
| ActivityManager | `psutil` + `tasklist` | 真实进程列表 |
|
||||||
|
| Shell | `subprocess` + PowerShell | 真实命令执行 |
|
||||||
|
| Camera | OpenCV | 真实拍照 |
|
||||||
|
| UI 自动化 | `pyautogui` + `win32gui` | 真实点击、输入、按键、窗口操作 |
|
||||||
|
|
||||||
|
### 跨平台
|
||||||
|
|
||||||
|
- **Linux** — 系统级 API 使用真实实现 (`_linux_impl.py`)
|
||||||
|
- **Windows** — 系统级 API 使用真实实现 (`_windows_impl.py`)
|
||||||
|
- **macOS** — 系统级 API 使用存根(后续支持)
|
||||||
|
- **API 形状** — 所有平台一致,方法签名完全对齐 Android SDK
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 从源码安装
|
||||||
|
git clone https://gitee.com/shuiyu1123/Android-pyApi.git
|
||||||
|
cd Android-pyApi
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
# 或直接使用
|
||||||
|
export PYTHONPATH=/path/to/Android-pyApi:$PYTHONPATH
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可选依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Python 库 (所有平台通用)
|
||||||
|
pip install psutil requests Pillow opencv-python numpy
|
||||||
|
|
||||||
|
# Linux 系统工具
|
||||||
|
sudo apt install xclip xdotool libnotify-bin alsa-utils bluez lm-sensors
|
||||||
|
|
||||||
|
# Windows 专用库 (推荐安装以启用完整功能)
|
||||||
|
pip install pyautogui pyperclip screeninfo pywin32 wmi pycaw comtypes win10toast
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```python
|
||||||
|
from android_pyapi import Intent, Uri, Bundle, File, JSONObject, ArrayList, HashMap
|
||||||
|
|
||||||
|
# Intent — Android 最核心的 API
|
||||||
|
intent = Intent("android.intent.action.VIEW")
|
||||||
|
intent.setData(Uri.parse("https://example.com"))
|
||||||
|
intent.putExtra("key", "value")
|
||||||
|
print(intent.getAction()) # android.intent.action.VIEW
|
||||||
|
print(intent.getData()) # https://example.com
|
||||||
|
print(intent.getStringExtra("key")) # value
|
||||||
|
|
||||||
|
# java.util 集合
|
||||||
|
lst = ArrayList()
|
||||||
|
lst.add("hello")
|
||||||
|
lst.add("world")
|
||||||
|
print(lst.size()) # 2
|
||||||
|
print(lst.get(0)) # hello
|
||||||
|
|
||||||
|
# JSONObject
|
||||||
|
json = JSONObject()
|
||||||
|
json.put("name", "test")
|
||||||
|
json.put("version", 42)
|
||||||
|
print(json.toString()) # {"name":"test","version":42}
|
||||||
|
|
||||||
|
# Linux 真实实现
|
||||||
|
from android_pyapi import Build, LocationManager, ConnectivityManager
|
||||||
|
print(Build.MODEL) # 真实主机名
|
||||||
|
print(Build.MANUFACTURER) # Linux
|
||||||
|
|
||||||
|
loc = LocationManager().getLastKnownLocation("network")
|
||||||
|
print(loc) # 真实经纬度
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
android_pyapi/
|
||||||
|
├── _core.py # Stub 基类、全局上下文
|
||||||
|
├── _linux_impl.py # Linux 真实实现层 (14个实现类)
|
||||||
|
├── _windows_impl.py # Windows 真实实现层 (14个实现类)
|
||||||
|
├── __init__.py # 统一导出
|
||||||
|
├── android/ # 167 个 Android 模块
|
||||||
|
│ ├── app.py # Activity, Service, Fragment, Notification
|
||||||
|
│ ├── content.py # Context, Intent, ContentResolver, ClipboardManager
|
||||||
|
│ ├── os.py # Handler, Looper, Bundle, Build, PowerManager
|
||||||
|
│ ├── view.py # View, ViewGroup, MotionEvent, KeyEvent
|
||||||
|
│ ├── widget.py # TextView, Button, ListView, RecyclerView, Toast
|
||||||
|
│ ├── graphics.py # Bitmap, Canvas, Paint, Color, Path, Matrix
|
||||||
|
│ ├── hardware.py # SensorManager, Camera, BiometricPrompt
|
||||||
|
│ ├── net.py # ConnectivityManager, NetworkInfo
|
||||||
|
│ ├── telephony.py # TelephonyManager, SmsManager
|
||||||
|
│ ├── location.py # LocationManager, Location, Geocoder
|
||||||
|
│ ├── media.py # AudioManager, MediaPlayer, MediaRecorder
|
||||||
|
│ └── ...
|
||||||
|
├── java/ # 35 个 Java 标准库模块
|
||||||
|
│ ├── lang.py # String, Math, System, Thread, Exception 体系
|
||||||
|
│ ├── io.py # File, InputStream, OutputStream, Reader, Writer
|
||||||
|
│ ├── util.py # ArrayList, HashMap, Calendar, Collections, UUID
|
||||||
|
│ ├── net.py # URL, HttpURLConnection, Socket
|
||||||
|
│ └── ...
|
||||||
|
├── javax/ # 4 个 javax 模块
|
||||||
|
├── org/ # 5 个 org 模块
|
||||||
|
└── dalvik/ # 1 个 dalvik 模块
|
||||||
|
```
|
||||||
|
|
||||||
|
## 设计理念
|
||||||
|
|
||||||
|
### 不是模拟器
|
||||||
|
|
||||||
|
Android-pyApi 不是 Android 模拟器。它不包含 ART 虚拟机、SurfaceFlinger、binder 等底层基础设施。
|
||||||
|
|
||||||
|
它是 **API 兼容层**:
|
||||||
|
- **API 形状** — 类名、方法签名、常量值完全对齐 Android SDK
|
||||||
|
- **系统级行为** — 在 Linux 上用真实系统调用实现
|
||||||
|
- **框架级行为** — Activity 生命周期、View 渲染等保持存根(桌面无对应框架)
|
||||||
|
|
||||||
|
### 渐进式真实化
|
||||||
|
|
||||||
|
```
|
||||||
|
存根 (所有平台) → Linux 真实实现 → macOS 真实实现 → Windows 真实实现
|
||||||
|
```
|
||||||
|
|
||||||
|
每个 API 从"返回硬编码值"逐步演进为"调用真实系统功能"。
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
- **Android 脚本开发** — 在桌面编写和调试依赖 Android API 的 Python 脚本
|
||||||
|
- **跨平台工具** — 用 Android API 风格编写跨平台系统工具
|
||||||
|
- **学习 Android API** — 不需要 Android Studio 就能探索 API
|
||||||
|
- **自动化测试** — 在 CI/CD 中测试 Android API 调用逻辑
|
||||||
|
- **IoT / 嵌入式** — 在 Linux 设备上用 Android API 风格操作硬件
|
||||||
|
|
||||||
|
## 贡献
|
||||||
|
|
||||||
|
欢迎贡献!特别需要:
|
||||||
|
|
||||||
|
- macOS 真实实现层 (`_macos_impl.py`)
|
||||||
|
- 更多 API 的真实行为实现
|
||||||
|
- 测试用例
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
setup.py
|
||||||
|
android_pyapi/__init__.py
|
||||||
|
android_pyapi/_core.py
|
||||||
|
android_pyapi/_linux_impl.py
|
||||||
|
android_pyapi/_windows_impl.py
|
||||||
|
android_pyapi.egg-info/PKG-INFO
|
||||||
|
android_pyapi.egg-info/SOURCES.txt
|
||||||
|
android_pyapi.egg-info/dependency_links.txt
|
||||||
|
android_pyapi.egg-info/requires.txt
|
||||||
|
android_pyapi.egg-info/top_level.txt
|
||||||
|
android_pyapi/android/__init__.py
|
||||||
|
android_pyapi/android/accessibilityservice.py
|
||||||
|
android_pyapi/android/accounts.py
|
||||||
|
android_pyapi/android/animation.py
|
||||||
|
android_pyapi/android/app.py
|
||||||
|
android_pyapi/android/app_actionbar.py
|
||||||
|
android_pyapi/android/app_admin.py
|
||||||
|
android_pyapi/android/app_assist.py
|
||||||
|
android_pyapi/android/app_backup.py
|
||||||
|
android_pyapi/android/app_dialog.py
|
||||||
|
android_pyapi/android/app_fragment.py
|
||||||
|
android_pyapi/android/app_job.py
|
||||||
|
android_pyapi/android/app_notification.py
|
||||||
|
android_pyapi/android/app_people.py
|
||||||
|
android_pyapi/android/app_role.py
|
||||||
|
android_pyapi/android/app_search.py
|
||||||
|
android_pyapi/android/app_slice.py
|
||||||
|
android_pyapi/android/app_tasks.py
|
||||||
|
android_pyapi/android/app_time.py
|
||||||
|
android_pyapi/android/app_usage.py
|
||||||
|
android_pyapi/android/app_voiceinteractor.py
|
||||||
|
android_pyapi/android/app_wallpaper.py
|
||||||
|
android_pyapi/android/appwidget.py
|
||||||
|
android_pyapi/android/bluetooth.py
|
||||||
|
android_pyapi/android/bluetooth_le.py
|
||||||
|
android_pyapi/android/companion.py
|
||||||
|
android_pyapi/android/companion_ext.py
|
||||||
|
android_pyapi/android/content.py
|
||||||
|
android_pyapi/android/content_intent.py
|
||||||
|
android_pyapi/android/content_om.py
|
||||||
|
android_pyapi/android/content_pm.py
|
||||||
|
android_pyapi/android/content_res.py
|
||||||
|
android_pyapi/android/content_sync.py
|
||||||
|
android_pyapi/android/credentials.py
|
||||||
|
android_pyapi/android/database.py
|
||||||
|
android_pyapi/android/database_ext.py
|
||||||
|
android_pyapi/android/database_sqlite.py
|
||||||
|
android_pyapi/android/drm.py
|
||||||
|
android_pyapi/android/gesture.py
|
||||||
|
android_pyapi/android/gesture_ext.py
|
||||||
|
android_pyapi/android/graphics.py
|
||||||
|
android_pyapi/android/graphics_drawable.py
|
||||||
|
android_pyapi/android/graphics_drawable_ext.py
|
||||||
|
android_pyapi/android/graphics_drawable_shapes.py
|
||||||
|
android_pyapi/android/graphics_fonts.py
|
||||||
|
android_pyapi/android/graphics_pdf.py
|
||||||
|
android_pyapi/android/graphics_surface.py
|
||||||
|
android_pyapi/android/graphics_text.py
|
||||||
|
android_pyapi/android/hardware.py
|
||||||
|
android_pyapi/android/hardware_biometrics.py
|
||||||
|
android_pyapi/android/hardware_camera2.py
|
||||||
|
android_pyapi/android/hardware_camera2_params.py
|
||||||
|
android_pyapi/android/hardware_consumerir.py
|
||||||
|
android_pyapi/android/hardware_display.py
|
||||||
|
android_pyapi/android/hardware_fingerprint.py
|
||||||
|
android_pyapi/android/hardware_hdmi.py
|
||||||
|
android_pyapi/android/hardware_input.py
|
||||||
|
android_pyapi/android/hardware_light.py
|
||||||
|
android_pyapi/android/hardware_location.py
|
||||||
|
android_pyapi/android/hardware_radio.py
|
||||||
|
android_pyapi/android/hardware_sensor_ext.py
|
||||||
|
android_pyapi/android/hardware_serial.py
|
||||||
|
android_pyapi/android/hardware_thermal.py
|
||||||
|
android_pyapi/android/hardware_usb.py
|
||||||
|
android_pyapi/android/inputmethod.py
|
||||||
|
android_pyapi/android/inputmethodservice.py
|
||||||
|
android_pyapi/android/location.py
|
||||||
|
android_pyapi/android/location_ext.py
|
||||||
|
android_pyapi/android/location_geofence.py
|
||||||
|
android_pyapi/android/managers.py
|
||||||
|
android_pyapi/android/media.py
|
||||||
|
android_pyapi/android/media_async.py
|
||||||
|
android_pyapi/android/media_audio.py
|
||||||
|
android_pyapi/android/media_audiofx.py
|
||||||
|
android_pyapi/android/media_browse.py
|
||||||
|
android_pyapi/android/media_capture.py
|
||||||
|
android_pyapi/android/media_effect.py
|
||||||
|
android_pyapi/android/media_face.py
|
||||||
|
android_pyapi/android/media_image.py
|
||||||
|
android_pyapi/android/media_midi.py
|
||||||
|
android_pyapi/android/media_projection.py
|
||||||
|
android_pyapi/android/media_remoteblob.py
|
||||||
|
android_pyapi/android/media_session.py
|
||||||
|
android_pyapi/android/media_tv.py
|
||||||
|
android_pyapi/android/media_tv_ext.py
|
||||||
|
android_pyapi/android/mtp.py
|
||||||
|
android_pyapi/android/net.py
|
||||||
|
android_pyapi/android/net_ethernet.py
|
||||||
|
android_pyapi/android/net_ext.py
|
||||||
|
android_pyapi/android/net_hotspot2.py
|
||||||
|
android_pyapi/android/net_http.py
|
||||||
|
android_pyapi/android/net_nsd.py
|
||||||
|
android_pyapi/android/net_rtp.py
|
||||||
|
android_pyapi/android/net_sip.py
|
||||||
|
android_pyapi/android/net_ssl.py
|
||||||
|
android_pyapi/android/net_vpn.py
|
||||||
|
android_pyapi/android/net_wifi.py
|
||||||
|
android_pyapi/android/net_wifi_aware.py
|
||||||
|
android_pyapi/android/net_wifi_rtt.py
|
||||||
|
android_pyapi/android/nfc.py
|
||||||
|
android_pyapi/android/nfc_ext.py
|
||||||
|
android_pyapi/android/opengl.py
|
||||||
|
android_pyapi/android/os.py
|
||||||
|
android_pyapi/android/os_health.py
|
||||||
|
android_pyapi/android/os_power.py
|
||||||
|
android_pyapi/android/os_storage.py
|
||||||
|
android_pyapi/android/os_user.py
|
||||||
|
android_pyapi/android/permission.py
|
||||||
|
android_pyapi/android/preference.py
|
||||||
|
android_pyapi/android/print.py
|
||||||
|
android_pyapi/android/printservice.py
|
||||||
|
android_pyapi/android/provider.py
|
||||||
|
android_pyapi/android/provider_calendar.py
|
||||||
|
android_pyapi/android/provider_contacts.py
|
||||||
|
android_pyapi/android/provider_settings.py
|
||||||
|
android_pyapi/android/renderscript.py
|
||||||
|
android_pyapi/android/se.py
|
||||||
|
android_pyapi/android/security.py
|
||||||
|
android_pyapi/android/service_autofill.py
|
||||||
|
android_pyapi/android/service_controls.py
|
||||||
|
android_pyapi/android/service_dreams.py
|
||||||
|
android_pyapi/android/service_media.py
|
||||||
|
android_pyapi/android/service_notification.py
|
||||||
|
android_pyapi/android/service_quicksettings.py
|
||||||
|
android_pyapi/android/service_voice.py
|
||||||
|
android_pyapi/android/service_vr.py
|
||||||
|
android_pyapi/android/service_wallpaper.py
|
||||||
|
android_pyapi/android/sip.py
|
||||||
|
android_pyapi/android/speech.py
|
||||||
|
android_pyapi/android/speech_tts.py
|
||||||
|
android_pyapi/android/system.py
|
||||||
|
android_pyapi/android/telecom.py
|
||||||
|
android_pyapi/android/telephony.py
|
||||||
|
android_pyapi/android/telephony_cdma.py
|
||||||
|
android_pyapi/android/telephony_data.py
|
||||||
|
android_pyapi/android/telephony_euicc.py
|
||||||
|
android_pyapi/android/telephony_gsm.py
|
||||||
|
android_pyapi/android/telephony_ims.py
|
||||||
|
android_pyapi/android/telephony_mbms.py
|
||||||
|
android_pyapi/android/test.py
|
||||||
|
android_pyapi/android/text.py
|
||||||
|
android_pyapi/android/text_format.py
|
||||||
|
android_pyapi/android/text_method.py
|
||||||
|
android_pyapi/android/text_style.py
|
||||||
|
android_pyapi/android/text_util.py
|
||||||
|
android_pyapi/android/transition.py
|
||||||
|
android_pyapi/android/util.py
|
||||||
|
android_pyapi/android/uwb.py
|
||||||
|
android_pyapi/android/view.py
|
||||||
|
android_pyapi/android/view_accessibility.py
|
||||||
|
android_pyapi/android/view_accessibility_ext.py
|
||||||
|
android_pyapi/android/view_animation.py
|
||||||
|
android_pyapi/android/view_animator.py
|
||||||
|
android_pyapi/android/view_autofill.py
|
||||||
|
android_pyapi/android/view_dragndrop.py
|
||||||
|
android_pyapi/android/view_ext.py
|
||||||
|
android_pyapi/android/view_gesture.py
|
||||||
|
android_pyapi/android/view_haptic.py
|
||||||
|
android_pyapi/android/view_inputmethod.py
|
||||||
|
android_pyapi/android/view_textclassifier.py
|
||||||
|
android_pyapi/android/view_textservice.py
|
||||||
|
android_pyapi/android/view_translation.py
|
||||||
|
android_pyapi/android/view_window.py
|
||||||
|
android_pyapi/android/webkit.py
|
||||||
|
android_pyapi/android/webkit_ext.py
|
||||||
|
android_pyapi/android/widget.py
|
||||||
|
android_pyapi/android/widget_adapter.py
|
||||||
|
android_pyapi/android/widget_ext.py
|
||||||
|
android_pyapi/dalvik/__init__.py
|
||||||
|
android_pyapi/dalvik/system.py
|
||||||
|
android_pyapi/java/__init__.py
|
||||||
|
android_pyapi/java/beans.py
|
||||||
|
android_pyapi/java/crypto.py
|
||||||
|
android_pyapi/java/io.py
|
||||||
|
android_pyapi/java/io_ext.py
|
||||||
|
android_pyapi/java/io_file.py
|
||||||
|
android_pyapi/java/lang.py
|
||||||
|
android_pyapi/java/lang_annotation.py
|
||||||
|
android_pyapi/java/lang_invoke.py
|
||||||
|
android_pyapi/java/lang_management.py
|
||||||
|
android_pyapi/java/lang_ref.py
|
||||||
|
android_pyapi/java/lang_reflect.py
|
||||||
|
android_pyapi/java/math.py
|
||||||
|
android_pyapi/java/net.py
|
||||||
|
android_pyapi/java/nio.py
|
||||||
|
android_pyapi/java/nio_byteorder.py
|
||||||
|
android_pyapi/java/nio_channels.py
|
||||||
|
android_pyapi/java/nio_charset.py
|
||||||
|
android_pyapi/java/nio_file.py
|
||||||
|
android_pyapi/java/rmi.py
|
||||||
|
android_pyapi/java/security.py
|
||||||
|
android_pyapi/java/security_cert.py
|
||||||
|
android_pyapi/java/security_spec.py
|
||||||
|
android_pyapi/java/sql.py
|
||||||
|
android_pyapi/java/text.py
|
||||||
|
android_pyapi/java/time.py
|
||||||
|
android_pyapi/java/util.py
|
||||||
|
android_pyapi/java/util_concurrent.py
|
||||||
|
android_pyapi/java/util_concurrent_atomic.py
|
||||||
|
android_pyapi/java/util_concurrent_locks.py
|
||||||
|
android_pyapi/java/util_function.py
|
||||||
|
android_pyapi/java/util_logging.py
|
||||||
|
android_pyapi/java/util_prefs.py
|
||||||
|
android_pyapi/java/util_regex.py
|
||||||
|
android_pyapi/java/util_stream.py
|
||||||
|
android_pyapi/java/util_zip.py
|
||||||
|
android_pyapi/javax/__init__.py
|
||||||
|
android_pyapi/javax/crypto.py
|
||||||
|
android_pyapi/javax/net.py
|
||||||
|
android_pyapi/javax/security.py
|
||||||
|
android_pyapi/javax/sql.py
|
||||||
|
android_pyapi/org/__init__.py
|
||||||
|
android_pyapi/org/json.py
|
||||||
|
android_pyapi/org/w3c_dom.py
|
||||||
|
android_pyapi/org/xml_pull.py
|
||||||
|
android_pyapi/org/xml_sax.py
|
||||||
|
android_pyapi/org/apache/__init__.py
|
||||||
|
android_pyapi/org/apache/http.py
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
[dev]
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
|
||||||
|
[linux]
|
||||||
|
psutil
|
||||||
|
requests
|
||||||
|
Pillow
|
||||||
|
opencv-python
|
||||||
|
numpy
|
||||||
|
|
||||||
|
[windows]
|
||||||
|
psutil
|
||||||
|
requests
|
||||||
|
Pillow
|
||||||
|
opencv-python
|
||||||
|
numpy
|
||||||
|
pyautogui
|
||||||
|
pyperclip
|
||||||
|
screeninfo
|
||||||
|
pywin32
|
||||||
|
wmi
|
||||||
|
pycaw
|
||||||
|
comtypes
|
||||||
|
win10toast
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
android_pyapi
|
||||||
@@ -80,7 +80,7 @@ class OsConstants(Stub):
|
|||||||
SIGTTIN = 21
|
SIGTTIN = 21
|
||||||
SIGTTOU = 22
|
SIGTTOU = 22
|
||||||
|
|
||||||
# errno 常量
|
# errno 常量 (使用 getattr 处理 Windows 上不存在的常量)
|
||||||
EPERM = errno.EPERM
|
EPERM = errno.EPERM
|
||||||
ENOENT = errno.ENOENT
|
ENOENT = errno.ENOENT
|
||||||
ESRCH = errno.ESRCH
|
ESRCH = errno.ESRCH
|
||||||
@@ -95,7 +95,7 @@ class OsConstants(Stub):
|
|||||||
ENOMEM = errno.ENOMEM
|
ENOMEM = errno.ENOMEM
|
||||||
EACCES = errno.EACCES
|
EACCES = errno.EACCES
|
||||||
EFAULT = errno.EFAULT
|
EFAULT = errno.EFAULT
|
||||||
ENOTBLK = errno.ENOTBLK
|
ENOTBLK = getattr(errno, 'ENOTBLK', 15) # Windows 不支持
|
||||||
EBUSY = errno.EBUSY
|
EBUSY = errno.EBUSY
|
||||||
EEXIST = errno.EEXIST
|
EEXIST = errno.EEXIST
|
||||||
EXDEV = errno.EXDEV
|
EXDEV = errno.EXDEV
|
||||||
@@ -106,7 +106,7 @@ class OsConstants(Stub):
|
|||||||
ENFILE = errno.ENFILE
|
ENFILE = errno.ENFILE
|
||||||
EMFILE = errno.EMFILE
|
EMFILE = errno.EMFILE
|
||||||
ENOTTY = errno.ENOTTY
|
ENOTTY = errno.ENOTTY
|
||||||
ETXTBSY = errno.ETXTBSY
|
ETXTBSY = getattr(errno, 'ETXTBSY', 139)
|
||||||
EFBIG = errno.EFBIG
|
EFBIG = errno.EFBIG
|
||||||
ENOSPC = errno.ENOSPC
|
ENOSPC = errno.ENOSPC
|
||||||
ESPIPE = errno.ESPIPE
|
ESPIPE = errno.ESPIPE
|
||||||
@@ -123,16 +123,16 @@ class OsConstants(Stub):
|
|||||||
ELOOP = errno.ELOOP
|
ELOOP = errno.ELOOP
|
||||||
ENOMSG = errno.ENOMSG
|
ENOMSG = errno.ENOMSG
|
||||||
EIDRM = errno.EIDRM
|
EIDRM = errno.EIDRM
|
||||||
ENOSTR = errno.ENOSTR
|
ENOSTR = getattr(errno, 'ENOSTR', 125)
|
||||||
ENODATA = errno.ENODATA
|
ENODATA = getattr(errno, 'ENODATA', 120)
|
||||||
ETIME = errno.ETIME
|
ETIME = getattr(errno, 'ETIME', 137)
|
||||||
ENOSR = errno.ENOSR
|
ENOSR = getattr(errno, 'ENOSR', 124)
|
||||||
EREMOTE = errno.EREMOTE
|
EREMOTE = getattr(errno, 'EREMOTE', 10071)
|
||||||
ENOLINK = errno.ENOLINK
|
ENOLINK = getattr(errno, 'ENOLINK', 121)
|
||||||
EPROTO = errno.EPROTO
|
EPROTO = errno.EPROTO
|
||||||
EMULTIHOP = errno.EMULTIHOP
|
EMULTIHOP = getattr(errno, 'EMULTIHOP', 10072) # Windows 不支持
|
||||||
EBADMSG = errno.EBADMSG
|
EBADMSG = getattr(errno, 'EBADMSG', 104)
|
||||||
EOVERFLOW = errno.EOVERFLOW
|
EOVERFLOW = getattr(errno, 'EOVERFLOW', 132)
|
||||||
EILSEQ = errno.EILSEQ
|
EILSEQ = errno.EILSEQ
|
||||||
ENOTSOCK = errno.ENOTSOCK
|
ENOTSOCK = errno.ENOTSOCK
|
||||||
EDESTADDRREQ = errno.EDESTADDRREQ
|
EDESTADDRREQ = errno.EDESTADDRREQ
|
||||||
@@ -140,10 +140,10 @@ class OsConstants(Stub):
|
|||||||
EPROTOTYPE = errno.EPROTOTYPE
|
EPROTOTYPE = errno.EPROTOTYPE
|
||||||
ENOPROTOOPT = errno.ENOPROTOOPT
|
ENOPROTOOPT = errno.ENOPROTOOPT
|
||||||
EPROTONOSUPPORT = errno.EPROTONOSUPPORT
|
EPROTONOSUPPORT = errno.EPROTONOSUPPORT
|
||||||
ESOCKTNOSUPPORT = errno.ESOCKTNOSUPPORT
|
ESOCKTNOSUPPORT = getattr(errno, 'ESOCKTNOSUPPORT', 10044)
|
||||||
EOPNOTSUPP = errno.EOPNOTSUPP
|
EOPNOTSUPP = getattr(errno, 'EOPNOTSUPP', 10045)
|
||||||
EPFNOSUPPORT = errno.EPFNOSUPPORT
|
EPFNOSUPPORT = getattr(errno, 'EPFNOSUPPORT', 10046)
|
||||||
EAFNOSUPPORT = errno.EAFNOSUPPORT
|
EAFNOSUPPORT = getattr(errno, 'EAFNOSUPPORT', 10047)
|
||||||
EADDRINUSE = errno.EADDRINUSE
|
EADDRINUSE = errno.EADDRINUSE
|
||||||
EADDRNOTAVAIL = errno.EADDRNOTAVAIL
|
EADDRNOTAVAIL = errno.EADDRNOTAVAIL
|
||||||
ENETDOWN = errno.ENETDOWN
|
ENETDOWN = errno.ENETDOWN
|
||||||
@@ -154,32 +154,32 @@ class OsConstants(Stub):
|
|||||||
ENOBUFS = errno.ENOBUFS
|
ENOBUFS = errno.ENOBUFS
|
||||||
EISCONN = errno.EISCONN
|
EISCONN = errno.EISCONN
|
||||||
ENOTCONN = errno.ENOTCONN
|
ENOTCONN = errno.ENOTCONN
|
||||||
ESHUTDOWN = errno.ESHUTDOWN
|
ESHUTDOWN = getattr(errno, 'ESHUTDOWN', 10058)
|
||||||
ETOOMANYREFS = errno.ETOOMANYREFS
|
ETOOMANYREFS = getattr(errno, 'ETOOMANYREFS', 10059)
|
||||||
ETIMEDOUT = errno.ETIMEDOUT
|
ETIMEDOUT = errno.ETIMEDOUT
|
||||||
ECONNREFUSED = errno.ECONNREFUSED
|
ECONNREFUSED = errno.ECONNREFUSED
|
||||||
EHOSTDOWN = errno.EHOSTDOWN
|
EHOSTDOWN = getattr(errno, 'EHOSTDOWN', 10064)
|
||||||
EHOSTUNREACH = errno.EHOSTUNREACH
|
EHOSTUNREACH = errno.EHOSTUNREACH
|
||||||
EALREADY = errno.EALREADY
|
EALREADY = errno.EALREADY
|
||||||
EINPROGRESS = errno.EINPROGRESS
|
EINPROGRESS = errno.EINPROGRESS
|
||||||
ESTALE = errno.ESTALE
|
ESTALE = getattr(errno, 'ESTALE', 10070)
|
||||||
EUCLEAN = errno.EUCLEAN
|
EUCLEAN = getattr(errno, 'EUCLEAN', 117) # Windows 不支持
|
||||||
ENOTNAM = errno.ENOTNAM
|
ENOTNAM = getattr(errno, 'ENOTNAM', 118) # Windows 不支持
|
||||||
ENAVAIL = errno.ENAVAIL
|
ENAVAIL = getattr(errno, 'ENAVAIL', 119) # Windows 不支持
|
||||||
EISNAM = errno.EISNAM
|
EISNAM = getattr(errno, 'EISNAM', 120) # Windows 不支持
|
||||||
EREMOTEIO = errno.EREMOTEIO
|
EREMOTEIO = getattr(errno, 'EREMOTEIO', 121) # Windows 不支持
|
||||||
EDQUOT = errno.EDQUOT
|
EDQUOT = getattr(errno, 'EDQUOT', 10069)
|
||||||
ENOMEDIUM = errno.ENOMEDIUM
|
ENOMEDIUM = getattr(errno, 'ENOMEDIUM', 123) # Windows 不支持
|
||||||
EMEDIUMTYPE = errno.EMEDIUMTYPE
|
EMEDIUMTYPE = getattr(errno, 'EMEDIUMTYPE', 124) # Windows 不支持
|
||||||
ECANCELED = errno.ECANCELED
|
ECANCELED = getattr(errno, 'ECANCELED', 105)
|
||||||
ENOKEY = errno.ENOKEY
|
ENOKEY = getattr(errno, 'ENOKEY', 126) # Windows 不支持
|
||||||
EKEYEXPIRED = errno.EKEYEXPIRED
|
EKEYEXPIRED = getattr(errno, 'EKEYEXPIRED', 127) # Windows 不支持
|
||||||
EKEYREVOKED = errno.EKEYREVOKED
|
EKEYREVOKED = getattr(errno, 'EKEYREVOKED', 128) # Windows 不支持
|
||||||
EKEYREJECTED = errno.EKEYREJECTED
|
EKEYREJECTED = getattr(errno, 'EKEYREJECTED', 129) # Windows 不支持
|
||||||
EOWNERDEAD = errno.EOWNERDEAD
|
EOWNERDEAD = getattr(errno, 'EOWNERDEAD', 133)
|
||||||
ENOTRECOVERABLE = errno.ENOTRECOVERABLE
|
ENOTRECOVERABLE = getattr(errno, 'ENOTRECOVERABLE', 127)
|
||||||
ERFKILL = errno.ERFKILL
|
ERFKILL = getattr(errno, 'ERFKILL', 132) # Windows 不支持
|
||||||
EHWPOISON = getattr(errno, 'EHWPOISON', 1001)
|
EHWPOISON = getattr(errno, 'EHWPOISON', 1001) # Windows 不支持
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
long_description = Path("README.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="android-pyapi",
|
name="android-pyapi",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
description="Android API for Python — 在桌面/Linux 上原生运行 Android API",
|
description="Android API for Python — 在桌面/Linux 上原生运行 Android API",
|
||||||
long_description=open("README.md").read(),
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
author="shuiyu1123",
|
author="shuiyu1123",
|
||||||
|
|||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
"""
|
||||||
|
Windows 实现层测试脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import platform
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"平台: {platform.system()} {platform.release()}")
|
||||||
|
print(f"Python: {platform.python_version()}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 测试基本导入
|
||||||
|
print("\n[1] 测试基本导入...")
|
||||||
|
try:
|
||||||
|
from android_pyapi import Intent, Uri, Bundle, JSONObject, ArrayList
|
||||||
|
print(" ✓ 基本 Android API 导入成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 基本导入失败: {e}")
|
||||||
|
|
||||||
|
# 测试 Intent
|
||||||
|
print("\n[2] 测试 Intent...")
|
||||||
|
try:
|
||||||
|
intent = Intent("android.intent.action.VIEW")
|
||||||
|
intent.setData(Uri.parse("https://example.com"))
|
||||||
|
intent.putExtra("key", "value")
|
||||||
|
print(f" ✓ Action: {intent.getAction()}")
|
||||||
|
print(f" ✓ Data: {intent.getData()}")
|
||||||
|
print(f" ✓ Extra: {intent.getStringExtra('key')}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Intent 测试失败: {e}")
|
||||||
|
|
||||||
|
# 测试集合类
|
||||||
|
print("\n[3] 测试集合类...")
|
||||||
|
try:
|
||||||
|
lst = ArrayList()
|
||||||
|
lst.add("hello")
|
||||||
|
lst.add("world")
|
||||||
|
print(f" ✓ ArrayList size: {lst.size()}")
|
||||||
|
print(f" ✓ ArrayList[0]: {lst.get(0)}")
|
||||||
|
|
||||||
|
json = JSONObject()
|
||||||
|
json.put("name", "test")
|
||||||
|
json.put("version", 42)
|
||||||
|
print(f" ✓ JSONObject: {json.toString()}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 集合类测试失败: {e}")
|
||||||
|
|
||||||
|
# 测试 Windows 实现层
|
||||||
|
print("\n[4] 测试 Windows 实现层...")
|
||||||
|
try:
|
||||||
|
from android_pyapi._windows_impl import (
|
||||||
|
IS_WINDOWS, get_windows_capabilities,
|
||||||
|
WindowsNetwork, WindowsPower, WindowsDisplay,
|
||||||
|
WindowsClipboard, WindowsProcess, WindowsLocation
|
||||||
|
)
|
||||||
|
print(f" ✓ IS_WINDOWS: {IS_WINDOWS}")
|
||||||
|
|
||||||
|
caps = get_windows_capabilities()
|
||||||
|
print(f" ✓ 能力检测: {sum(caps.values())}/{len(caps)} 项可用")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Windows 实现层导入失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# 测试网络功能
|
||||||
|
print("\n[5] 测试网络功能...")
|
||||||
|
try:
|
||||||
|
ip = WindowsNetwork.get_ip_address()
|
||||||
|
print(f" ✓ IP 地址: {ip}")
|
||||||
|
|
||||||
|
connected = WindowsNetwork.get_connectivity()
|
||||||
|
print(f" ✓ 网络连通: {connected}")
|
||||||
|
|
||||||
|
wifi = WindowsNetwork.get_wifi_info()
|
||||||
|
print(f" ✓ WiFi SSID: {wifi.get('ssid', 'N/A')}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 网络测试失败: {e}")
|
||||||
|
|
||||||
|
# 测试电源信息
|
||||||
|
print("\n[6] 测试电源信息...")
|
||||||
|
try:
|
||||||
|
battery = WindowsPower.get_battery_info()
|
||||||
|
print(f" ✓ 电池存在: {battery['present']}")
|
||||||
|
print(f" ✓ 电池电量: {battery['level']}%")
|
||||||
|
print(f" ✓ 电池状态: {battery['status']}")
|
||||||
|
|
||||||
|
cpu = WindowsPower.get_cpu_usage()
|
||||||
|
print(f" ✓ CPU 使用率: {cpu}%")
|
||||||
|
|
||||||
|
mem = WindowsPower.get_memory_info()
|
||||||
|
print(f" ✓ 内存使用: {mem['percent']}%")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 电源测试失败: {e}")
|
||||||
|
|
||||||
|
# 测试显示信息
|
||||||
|
print("\n[7] 测试显示信息...")
|
||||||
|
try:
|
||||||
|
w, h = WindowsDisplay.get_screen_size()
|
||||||
|
print(f" ✓ 屏幕尺寸: {w}x{h}")
|
||||||
|
|
||||||
|
x, y = WindowsDisplay.get_mouse_position()
|
||||||
|
print(f" ✓ 鼠标位置: ({x}, {y})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 显示测试失败: {e}")
|
||||||
|
|
||||||
|
# 测试剪贴板
|
||||||
|
print("\n[8] 测试剪贴板...")
|
||||||
|
try:
|
||||||
|
test_text = "Android-pyApi 测试文本"
|
||||||
|
WindowsClipboard.write(test_text)
|
||||||
|
read_text = WindowsClipboard.read()
|
||||||
|
if read_text == test_text:
|
||||||
|
print(f" ✓ 剪贴板读写成功")
|
||||||
|
else:
|
||||||
|
print(f" ⚠ 剪贴板内容不匹配: '{read_text}'")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 剪贴板测试失败: {e}")
|
||||||
|
|
||||||
|
# 测试进程管理
|
||||||
|
print("\n[9] 测试进程管理...")
|
||||||
|
try:
|
||||||
|
procs = WindowsProcess.list_processes()
|
||||||
|
print(f" ✓ 进程数量: {len(procs)}")
|
||||||
|
|
||||||
|
sys_info = WindowsProcess.get_system_info()
|
||||||
|
print(f" ✓ 主机名: {sys_info['hostname']}")
|
||||||
|
print(f" ✓ 系统: {sys_info['platform']} {sys_info['windows_version']}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 进程管理测试失败: {e}")
|
||||||
|
|
||||||
|
# 测试位置信息
|
||||||
|
print("\n[10] 测试位置信息...")
|
||||||
|
try:
|
||||||
|
loc = WindowsLocation.get_location()
|
||||||
|
print(f" ✓ 来源: {loc['source']}")
|
||||||
|
print(f" ✓ 经纬度: {loc['latitude']}, {loc['longitude']}")
|
||||||
|
if loc.get('city'):
|
||||||
|
print(f" ✓ 城市: {loc['city']}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 位置测试失败: {e}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("测试完成!")
|
||||||
|
print("=" * 60)
|
||||||
Reference in new issue
Block a user