feat: Linux 真实实现层 — 从存根库变为可用生产力工具
新增 _linux_impl.py (800+行): - LinuxNetwork: 真实IP/接口/DNS/网关/MAC (ip命令+/proc/net) - LinuxBluetooth: 真实蓝牙状态/扫描/配对/连接 (bluetoothctl) - LinuxSensors: 真实温度/CPU频率/风扇 (/sys/class/hwmon) - LinuxAudio: 真实音量控制/静音 (amixer) - LinuxClipboard: 真实剪贴板读写 (xclip+tkinter回退) - LinuxPower: 真实CPU/内存/电池 (psutil+/sys/class/power_supply) - LinuxLocation: 真实IP地理定位 (ip-api.com) - LinuxNotify: 真实桌面通知 (notify-send) - LinuxDisplay: 真实屏幕尺寸/截屏/鼠标位置 (xdotool+X11) - LinuxUIAutomation: 真实点击/输入/按键/滚动 (xdotool) - LinuxCamera: 真实摄像头拍照 (opencv/V4L2) - LinuxProcess: 真实进程列表/系统信息/应用管理 (psutil+/proc) - LinuxShell: 真实命令执行 (subprocess) 改造10+核心类使用Linux真实实现: - BluetoothAdapter/SensorManager/AudioManager - ConnectivityManager/WifiManager/TelephonyManager - LocationManager/PowerManager/BatteryManager/Build - ActivityManager/Activity/Toast/Display/ClipboardManager - NotificationManager 15项真实行为测试全部通过: - 真实IP: 10.60.218.244 - 真实位置: 北京 39.9042, 116.407 - 真实主机名: work-41-vci-g427c - 真实内核: 5.15.120.byteatom-ck.14 - 真实内存: 1.8GB/3.8GB - 真实进程列表
This commit is contained in:
1 parent
8c3bd3c415
commit
690022cea3
17 files changed
+2083
-8
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -19,6 +19,12 @@ from .._core import (
|
|||||||
_sdcard_root, _app_data_dir, _package_name,
|
_sdcard_root, _app_data_dir, _package_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxNotify, LinuxShell, LinuxProcess, LinuxDisplay, LinuxUIAutomation, LinuxPower
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Intent
|
# Intent
|
||||||
@@ -834,10 +840,16 @@ class NotificationManager(Stub):
|
|||||||
|
|
||||||
def notify(self, id_: int, notification: Notification):
|
def notify(self, id_: int, notification: Notification):
|
||||||
self._notifications[id_] = notification
|
self._notifications[id_] = notification
|
||||||
sys.stderr.write(
|
if _USE_LINUX:
|
||||||
f"[NotificationManager] notify id={id_}: "
|
title = getattr(notification, "_title", "") or ""
|
||||||
f"title={notification._title} text={notification._text}\n"
|
text = getattr(notification, "_text", "") or ""
|
||||||
)
|
if title or text:
|
||||||
|
LinuxNotify.send(title, text)
|
||||||
|
else:
|
||||||
|
sys.stderr.write(
|
||||||
|
f"[NotificationManager] notify id={id_}: "
|
||||||
|
f"title={notification._title} text={notification._text}\n"
|
||||||
|
)
|
||||||
|
|
||||||
def notifyAsPackage(self, package_name: str, tag: Optional[str], id_: int, notification: Notification):
|
def notifyAsPackage(self, package_name: str, tag: Optional[str], id_: int, notification: Notification):
|
||||||
self.notify(id_, notification)
|
self.notify(id_, notification)
|
||||||
@@ -1821,6 +1833,48 @@ class Activity(Stub):
|
|||||||
def getTheme(self):
|
def getTheme(self):
|
||||||
return getattr(self, '_theme', 0)
|
return getattr(self, '_theme', 0)
|
||||||
|
|
||||||
|
def getDeviceInfo(self):
|
||||||
|
"""获取设备信息"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxProcess.get_system_info()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def executeShell(self, command):
|
||||||
|
"""执行 Shell 命令"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxShell.exec(command)
|
||||||
|
return {"success": False, "output": "Shell not available"}
|
||||||
|
|
||||||
|
def screenshot(self, path=None):
|
||||||
|
"""截屏"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxDisplay.screenshot(path)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getScreenSize(self):
|
||||||
|
"""获取屏幕尺寸"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxDisplay.get_screen_size()
|
||||||
|
return (1920, 1080)
|
||||||
|
|
||||||
|
def click(self, x, y):
|
||||||
|
"""点击屏幕坐标"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxUIAutomation.click(x, y)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def typeText(self, text):
|
||||||
|
"""输入文本"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxUIAutomation.type_text(text)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def pressKey(self, key):
|
||||||
|
"""按键"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxUIAutomation.press_key(key)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Service
|
# Service
|
||||||
@@ -2009,9 +2063,13 @@ class ActivityManager(Stub):
|
|||||||
self._memory_info = self.MemoryInfo()
|
self._memory_info = self.MemoryInfo()
|
||||||
|
|
||||||
def getMemoryInfo(self) -> "ActivityManager.MemoryInfo":
|
def getMemoryInfo(self) -> "ActivityManager.MemoryInfo":
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxPower.get_memory_info()
|
||||||
return self._memory_info
|
return self._memory_info
|
||||||
|
|
||||||
def getRunningAppProcesses(self) -> Optional[List["ActivityManager.RunningAppProcessInfo"]]:
|
def getRunningAppProcesses(self) -> Optional[List["ActivityManager.RunningAppProcessInfo"]]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxProcess.list_processes()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def killBackgroundProcesses(self, package_name: str):
|
def killBackgroundProcesses(self, package_name: str):
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ from typing import Any, Callable, Dict, List, Optional, Tuple
|
|||||||
|
|
||||||
from .._core import Stub, AndroidOnlyError
|
from .._core import Stub, AndroidOnlyError
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxProcess, LinuxPower
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"TaskManager", "TaskInfo", "TaskStackListener", "TaskSnapshot",
|
"TaskManager", "TaskInfo", "TaskStackListener", "TaskSnapshot",
|
||||||
"TaskDescription", "ActivityManager",
|
"TaskDescription", "ActivityManager",
|
||||||
@@ -908,9 +914,18 @@ class ActivityManager(Stub):
|
|||||||
return list(self._services.values())[:maxNum]
|
return list(self._services.values())[:maxNum]
|
||||||
|
|
||||||
def getRunningAppProcesses(self) -> List[RunningAppProcessInfo]:
|
def getRunningAppProcesses(self) -> List[RunningAppProcessInfo]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxProcess.list_processes()
|
||||||
return list(self._processes.values())
|
return list(self._processes.values())
|
||||||
|
|
||||||
def getMemoryInfo(self, outInfo: MemoryInfo):
|
def getMemoryInfo(self, outInfo: MemoryInfo):
|
||||||
|
if _USE_LINUX:
|
||||||
|
mem = LinuxPower.get_memory_info()
|
||||||
|
outInfo.availMem = mem.get("available", 0)
|
||||||
|
outInfo.totalMem = mem.get("total", 0)
|
||||||
|
outInfo.threshold = int(mem.get("total", 0) * 0.1)
|
||||||
|
outInfo.lowMemory = mem.get("percent", 0) > 90.0
|
||||||
|
return outInfo
|
||||||
import psutil
|
import psutil
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ from .._core import (
|
|||||||
_sdcard_root, _app_data_dir, _package_name,
|
_sdcard_root, _app_data_dir, _package_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxBluetooth, HAS_BLUETOOTHCTL
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# BluetoothProfile
|
# BluetoothProfile
|
||||||
@@ -645,14 +652,20 @@ class BluetoothAdapter(Stub):
|
|||||||
return BluetoothAdapter._default_adapter
|
return BluetoothAdapter._default_adapter
|
||||||
|
|
||||||
def isEnabled(self) -> bool:
|
def isEnabled(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxBluetooth.get_state() == LinuxBluetooth.STATE_ON
|
||||||
return self._enabled
|
return self._enabled
|
||||||
|
|
||||||
def enable(self) -> bool:
|
def enable(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxBluetooth.enable()
|
||||||
self._enabled = True
|
self._enabled = True
|
||||||
self._state = self.STATE_ON
|
self._state = self.STATE_ON
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def disable(self) -> bool:
|
def disable(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxBluetooth.disable()
|
||||||
self._enabled = False
|
self._enabled = False
|
||||||
self._state = self.STATE_OFF
|
self._state = self.STATE_OFF
|
||||||
return True
|
return True
|
||||||
@@ -668,13 +681,36 @@ class BluetoothAdapter(Stub):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def getBondedDevices(self) -> Set[BluetoothDevice]:
|
def getBondedDevices(self) -> Set[BluetoothDevice]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
devices = set()
|
||||||
|
for d in LinuxBluetooth.get_bonded_devices():
|
||||||
|
dev = BluetoothDevice(d.get("address", "00:00:00:00:00:00"))
|
||||||
|
dev._name = d.get("name", "Unknown")
|
||||||
|
dev._bond_state = BluetoothDevice.BOND_BONDED
|
||||||
|
devices.add(dev)
|
||||||
|
return devices
|
||||||
return set(self._bonded_devices)
|
return set(self._bonded_devices)
|
||||||
|
|
||||||
|
def getBondedDeviceAddresses(self) -> Set[str]:
|
||||||
|
"""获取已配对设备地址集合"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return {d.get("address", "") for d in LinuxBluetooth.get_bonded_devices()}
|
||||||
|
return {d.getAddress() for d in self._bonded_devices}
|
||||||
|
|
||||||
def startDiscovery(self) -> bool:
|
def startDiscovery(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
try:
|
||||||
|
LinuxBluetooth.scan(timeout=5)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
self._discovering = True
|
self._discovering = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cancelDiscovery(self) -> bool:
|
def cancelDiscovery(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
# bluetoothctl scan off 在 LinuxBluetooth.scan 内部已处理
|
||||||
|
return True
|
||||||
self._discovering = False
|
self._discovering = False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -694,6 +730,8 @@ class BluetoothAdapter(Stub):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def getState(self) -> int:
|
def getState(self) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxBluetooth.get_state()
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def listenUsingRfcommWithServiceRecord(self, name: str, uuid_str: str) -> BluetoothServerSocket:
|
def listenUsingRfcommWithServiceRecord(self, name: str, uuid_str: str) -> BluetoothServerSocket:
|
||||||
@@ -704,6 +742,18 @@ class BluetoothAdapter(Stub):
|
|||||||
return BluetoothServerSocket()
|
return BluetoothServerSocket()
|
||||||
|
|
||||||
def getRemoteDevice(self, address: str) -> Optional[BluetoothDevice]:
|
def getRemoteDevice(self, address: str) -> Optional[BluetoothDevice]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
# 从已配对设备中查找
|
||||||
|
for d in LinuxBluetooth.get_bonded_devices():
|
||||||
|
if d.get("address", "").upper() == address.upper():
|
||||||
|
dev = BluetoothDevice(d.get("address", address))
|
||||||
|
dev._name = d.get("name", "Unknown")
|
||||||
|
dev._bond_state = BluetoothDevice.BOND_BONDED
|
||||||
|
return dev
|
||||||
|
# 未配对但地址合法则返回一个设备对象
|
||||||
|
if BluetoothAdapter.checkBluetoothAddress(address):
|
||||||
|
return BluetoothDevice(address)
|
||||||
|
return None
|
||||||
if not BluetoothAdapter.checkBluetoothAddress(address):
|
if not BluetoothAdapter.checkBluetoothAddress(address):
|
||||||
return None
|
return None
|
||||||
return BluetoothDevice(address)
|
return BluetoothDevice(address)
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ from .._core import (
|
|||||||
_sdcard_root, _app_data_dir, _package_name,
|
_sdcard_root, _app_data_dir, _package_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxClipboard, LinuxNotify, LinuxLocation, LinuxProcess
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Uri
|
# Uri
|
||||||
@@ -466,6 +472,7 @@ class Context(Stub):
|
|||||||
INPUT_METHOD_SERVICE = "input_method"
|
INPUT_METHOD_SERVICE = "input_method"
|
||||||
CAMERA_SERVICE = "camera"
|
CAMERA_SERVICE = "camera"
|
||||||
BATTERY_SERVICE = "battery"
|
BATTERY_SERVICE = "battery"
|
||||||
|
CLIPBOARD_SERVICE = "clipboard"
|
||||||
|
|
||||||
# Mode 常量
|
# Mode 常量
|
||||||
MODE_PRIVATE = 0x0000
|
MODE_PRIVATE = 0x0000
|
||||||
@@ -642,6 +649,8 @@ class Context(Stub):
|
|||||||
if name == Context.BATTERY_SERVICE:
|
if name == Context.BATTERY_SERVICE:
|
||||||
from .os import BatteryManager
|
from .os import BatteryManager
|
||||||
return BatteryManager()
|
return BatteryManager()
|
||||||
|
if name == Context.CLIPBOARD_SERVICE:
|
||||||
|
return ClipboardManager()
|
||||||
return self._services.get(name)
|
return self._services.get(name)
|
||||||
|
|
||||||
def startService(self, intent: Any) -> Optional[Any]:
|
def startService(self, intent: Any) -> Optional[Any]:
|
||||||
@@ -1708,6 +1717,29 @@ class SyncResult(Stub):
|
|||||||
return f"SyncResult({self.stats.numEntries} entries)"
|
return f"SyncResult({self.stats.numEntries} entries)"
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# ClipboardManager
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
class ClipboardManager(Stub):
|
||||||
|
"""ClipboardManager — 剪贴板管理器"""
|
||||||
|
|
||||||
|
def getText(self):
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxClipboard.read()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def setText(self, text):
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxClipboard.write(text)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def hasText(self):
|
||||||
|
if _USE_LINUX:
|
||||||
|
return len(LinuxClipboard.read()) > 0
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# 替换 _core.py 中的 _GlobalContext
|
# 替换 _core.py 中的 _GlobalContext
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
@@ -1748,4 +1780,5 @@ __all__ = [
|
|||||||
"DialogInterface",
|
"DialogInterface",
|
||||||
"SyncAdapter",
|
"SyncAdapter",
|
||||||
"SyncResult",
|
"SyncResult",
|
||||||
|
"ClipboardManager",
|
||||||
]
|
]
|
||||||
@@ -10,6 +10,13 @@ from typing import Any, Callable, Dict, List, Optional, Tuple
|
|||||||
|
|
||||||
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL
|
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxBluetooth, LinuxSensors, HAS_BLUETOOTHCTL
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Sensor
|
# Sensor
|
||||||
@@ -251,14 +258,89 @@ class SensorManager(Stub):
|
|||||||
self._sensors[type_] = Sensor(type_)
|
self._sensors[type_] = Sensor(type_)
|
||||||
|
|
||||||
def getDefaultSensor(self, type_: int) -> Optional[Sensor]:
|
def getDefaultSensor(self, type_: int) -> Optional[Sensor]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
sensor = self._build_linux_sensor(type_)
|
||||||
|
if sensor is not None:
|
||||||
|
return sensor
|
||||||
return self._sensors.get(type_)
|
return self._sensors.get(type_)
|
||||||
|
|
||||||
|
def _build_linux_sensor(self, type_: int) -> Optional[Sensor]:
|
||||||
|
"""根据 Linux 真实传感器数据构造 Sensor 对象"""
|
||||||
|
try:
|
||||||
|
if type_ == Sensor.TYPE_AMBIENT_TEMPERATURE:
|
||||||
|
temps = LinuxSensors.get_temperatures()
|
||||||
|
if temps:
|
||||||
|
t = temps[0]
|
||||||
|
return Sensor(Sensor.TYPE_AMBIENT_TEMPERATURE,
|
||||||
|
name=t.get("name", "Linux Temperature"),
|
||||||
|
vendor="Linux", resolution=0.01,
|
||||||
|
max_range=125.0, power=0.0, min_delay=1000)
|
||||||
|
return None
|
||||||
|
if type_ == Sensor.TYPE_LIGHT:
|
||||||
|
light = LinuxSensors.get_light_level()
|
||||||
|
if light is not None:
|
||||||
|
return Sensor(Sensor.TYPE_LIGHT, name="Linux Light Sensor",
|
||||||
|
vendor="Linux", resolution=0.001,
|
||||||
|
max_range=40000.0, power=0.0, min_delay=1000)
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
def getSensorList(self, type_: int) -> List[Sensor]:
|
def getSensorList(self, type_: int) -> List[Sensor]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self._get_linux_sensor_list(type_)
|
||||||
if type_ == -1: # ALL
|
if type_ == -1: # ALL
|
||||||
return list(self._sensors.values())
|
return list(self._sensors.values())
|
||||||
sensor = self._sensors.get(type_)
|
sensor = self._sensors.get(type_)
|
||||||
return [sensor] if sensor else []
|
return [sensor] if sensor else []
|
||||||
|
|
||||||
|
def _get_linux_sensor_list(self, type_: int) -> List[Sensor]:
|
||||||
|
"""从 Linux 真实传感器数据生成传感器列表"""
|
||||||
|
sensors: List[Sensor] = []
|
||||||
|
try:
|
||||||
|
if type_ == -1 or type_ == Sensor.TYPE_AMBIENT_TEMPERATURE:
|
||||||
|
for t in LinuxSensors.get_temperatures():
|
||||||
|
sensors.append(Sensor(
|
||||||
|
Sensor.TYPE_AMBIENT_TEMPERATURE,
|
||||||
|
name=t.get("name", "Linux Temperature"),
|
||||||
|
vendor="Linux", resolution=0.01,
|
||||||
|
max_range=125.0, power=0.0, min_delay=1000))
|
||||||
|
if type_ == -1 or type_ == Sensor.TYPE_LIGHT:
|
||||||
|
light = LinuxSensors.get_light_level()
|
||||||
|
if light is not None:
|
||||||
|
sensors.append(Sensor(
|
||||||
|
Sensor.TYPE_LIGHT, name="Linux Light Sensor",
|
||||||
|
vendor="Linux", resolution=0.001,
|
||||||
|
max_range=40000.0, power=0.0, min_delay=1000))
|
||||||
|
# CPU 频率作为附加传感器 (映射到压力传感器类型以承载真实数据)
|
||||||
|
if type_ == -1 or type_ == Sensor.TYPE_PRESSURE:
|
||||||
|
for f in LinuxSensors.get_cpu_frequency():
|
||||||
|
sensors.append(Sensor(
|
||||||
|
Sensor.TYPE_PRESSURE,
|
||||||
|
name=f"CPU{f.get('cpu', 0)} Frequency",
|
||||||
|
vendor="Linux", resolution=1.0,
|
||||||
|
max_range=10000.0, power=0.0, min_delay=1000))
|
||||||
|
# 风扇转速作为附加传感器 (映射到相对湿度类型以承载真实数据)
|
||||||
|
if type_ == -1 or type_ == Sensor.TYPE_RELATIVE_HUMIDITY:
|
||||||
|
for fan in LinuxSensors.get_fan_speeds():
|
||||||
|
sensors.append(Sensor(
|
||||||
|
Sensor.TYPE_RELATIVE_HUMIDITY,
|
||||||
|
name=f"Fan {fan.get('name', '')}",
|
||||||
|
vendor="Linux", resolution=1.0,
|
||||||
|
max_range=10000.0, power=0.0, min_delay=1000))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# 若未取到真实数据则回退到模拟传感器
|
||||||
|
if not sensors:
|
||||||
|
if type_ == -1:
|
||||||
|
sensors = list(self._sensors.values())
|
||||||
|
else:
|
||||||
|
sensor = self._sensors.get(type_)
|
||||||
|
if sensor:
|
||||||
|
sensors = [sensor]
|
||||||
|
return sensors
|
||||||
|
|
||||||
def getSensors(self) -> List[Sensor]:
|
def getSensors(self) -> List[Sensor]:
|
||||||
return list(self._sensors.values())
|
return list(self._sensors.values())
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ from .._core import (
|
|||||||
_sdcard_root, _app_data_dir, _package_name,
|
_sdcard_root, _app_data_dir, _package_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxLocation
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Location
|
# Location
|
||||||
@@ -952,6 +958,15 @@ class LocationManager(Stub):
|
|||||||
self.PASSIVE_PROVIDER, self.FUSED_PROVIDER}
|
self.PASSIVE_PROVIDER, self.FUSED_PROVIDER}
|
||||||
|
|
||||||
def getLastKnownLocation(self, provider: str) -> Optional[Location]:
|
def getLastKnownLocation(self, provider: str) -> Optional[Location]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
data = LinuxLocation.get_location()
|
||||||
|
loc = Location(provider or "network")
|
||||||
|
loc.setLatitude(data.get("latitude", 0.0))
|
||||||
|
loc.setLongitude(data.get("longitude", 0.0))
|
||||||
|
loc.setAccuracy(data.get("accuracy", 0))
|
||||||
|
loc.setTime(int(datetime.now().timestamp() * 1000))
|
||||||
|
loc.setProvider(provider or "network")
|
||||||
|
return loc
|
||||||
if provider in self._last_known_locations:
|
if provider in self._last_known_locations:
|
||||||
return self._last_known_locations[provider]
|
return self._last_known_locations[provider]
|
||||||
loc = Location(provider)
|
loc = Location(provider)
|
||||||
@@ -969,6 +984,18 @@ class LocationManager(Stub):
|
|||||||
provider = provider_or_request
|
provider = provider_or_request
|
||||||
else:
|
else:
|
||||||
provider = self.GPS_PROVIDER
|
provider = self.GPS_PROVIDER
|
||||||
|
if _USE_LINUX and listener_or_callback is not None:
|
||||||
|
data = LinuxLocation.get_location()
|
||||||
|
loc = Location(provider or "network")
|
||||||
|
loc.setLatitude(data.get("latitude", 0.0))
|
||||||
|
loc.setLongitude(data.get("longitude", 0.0))
|
||||||
|
loc.setAccuracy(data.get("accuracy", 0))
|
||||||
|
loc.setTime(int(datetime.now().timestamp() * 1000))
|
||||||
|
loc.setProvider(provider or "network")
|
||||||
|
try:
|
||||||
|
listener_or_callback.onLocationChanged(loc)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if listener_or_callback is not None:
|
if listener_or_callback is not None:
|
||||||
if provider not in self._listeners:
|
if provider not in self._listeners:
|
||||||
self._listeners[provider] = []
|
self._listeners[provider] = []
|
||||||
@@ -984,9 +1011,13 @@ class LocationManager(Stub):
|
|||||||
del self._listeners[provider]
|
del self._listeners[provider]
|
||||||
|
|
||||||
def isProviderEnabled(self, provider: str) -> bool:
|
def isProviderEnabled(self, provider: str) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return provider == "network"
|
||||||
return provider in self._enabled_providers
|
return provider in self._enabled_providers
|
||||||
|
|
||||||
def getProviders(self, criteria: Optional[Criteria] = None, enabled_only: bool = False) -> List[str]:
|
def getProviders(self, criteria: Optional[Criteria] = None, enabled_only: bool = False) -> List[str]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return ["network"]
|
||||||
providers = list(self._providers.keys())
|
providers = list(self._providers.keys())
|
||||||
if enabled_only:
|
if enabled_only:
|
||||||
providers = [p for p in providers if p in self._enabled_providers]
|
providers = [p for p in providers if p in self._enabled_providers]
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ from .._core import Stub, AndroidOnlyError, _app_data_dir, _sdcard_root
|
|||||||
|
|
||||||
from ..java.lang import RuntimeException
|
from ..java.lang import RuntimeException
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxNetwork, HAS_IP
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||||
|
|
||||||
|
|
||||||
@@ -517,14 +524,58 @@ class ConnectivityManager(Stub):
|
|||||||
self._active_network: Optional[Any] = None
|
self._active_network: Optional[Any] = None
|
||||||
self._all_networks: List[Any] = []
|
self._all_networks: List[Any] = []
|
||||||
|
|
||||||
|
def _get_linux_active_info(self):
|
||||||
|
"""从 Linux 真实网络信息构造活跃 NetworkInfo"""
|
||||||
|
try:
|
||||||
|
from .net import NetworkInfo
|
||||||
|
interfaces = LinuxNetwork.get_interfaces()
|
||||||
|
# 有真实网络接口 (非环回) 且 operstate=up 即视为已连接
|
||||||
|
connected = any(
|
||||||
|
(iface.get("ifname") or iface.get("name", "")) != "lo"
|
||||||
|
and iface.get("operstate", "").lower() == "up"
|
||||||
|
for iface in interfaces
|
||||||
|
)
|
||||||
|
if not connected:
|
||||||
|
connected = LinuxNetwork.get_connectivity()
|
||||||
|
ip = LinuxNetwork.get_ip_address()
|
||||||
|
iface_name = ""
|
||||||
|
net_type = ConnectivityManager.TYPE_ETHERNET
|
||||||
|
for iface in interfaces:
|
||||||
|
name = iface.get("ifname") or iface.get("name", "")
|
||||||
|
state = iface.get("operstate", "")
|
||||||
|
if name and name != "lo" and state.lower() == "up":
|
||||||
|
iface_name = iface_name or name
|
||||||
|
if name and name.startswith(("wl", "wlan", "wlp")):
|
||||||
|
net_type = ConnectivityManager.TYPE_WIFI
|
||||||
|
iface_name = name
|
||||||
|
data = {
|
||||||
|
"connected": connected,
|
||||||
|
"ip": ip,
|
||||||
|
"interface": iface_name,
|
||||||
|
"type_name": "WIFI" if net_type == ConnectivityManager.TYPE_WIFI else "ETHERNET",
|
||||||
|
}
|
||||||
|
return NetworkInfo.from_linux_data(data, network_type=net_type)
|
||||||
|
except Exception:
|
||||||
|
return self._active_network_info
|
||||||
|
|
||||||
def getActiveNetworkInfo(self) -> Optional[Any]:
|
def getActiveNetworkInfo(self) -> Optional[Any]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self._get_linux_active_info()
|
||||||
return self._active_network_info
|
return self._active_network_info
|
||||||
|
|
||||||
def getNetworkInfo(self, networkType: int) -> Optional[Any]:
|
def getNetworkInfo(self, networkType: int) -> Optional[Any]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
info = self._get_linux_active_info()
|
||||||
|
if info is not None and info.getType() == networkType:
|
||||||
|
return info
|
||||||
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getNetworkInfo(self, network: Any) -> Optional[Any]:
|
def getConnectivityStatus(self) -> bool:
|
||||||
return None
|
"""返回当前网络连通性 (True=已连接)"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxNetwork.get_connectivity()
|
||||||
|
return False
|
||||||
|
|
||||||
def getAllNetworkInfo(self) -> List[Any]:
|
def getAllNetworkInfo(self) -> List[Any]:
|
||||||
return self._all_networks
|
return self._all_networks
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
|||||||
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL, HAS_PYMUPDF, resolve_android_path
|
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL, HAS_PYMUPDF, resolve_android_path
|
||||||
from .graphics import PointF
|
from .graphics import PointF
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxAudio, HAS_AMIXER
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# AudioAttributes
|
# AudioAttributes
|
||||||
@@ -596,15 +603,37 @@ class AudioManager(Stub):
|
|||||||
self._parameters: Dict[str, str] = {}
|
self._parameters: Dict[str, str] = {}
|
||||||
|
|
||||||
def getStreamVolume(self, streamType: int) -> int:
|
def getStreamVolume(self, streamType: int) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
# STREAM_MUSIC 及其它流类型映射到 Linux 主音量
|
||||||
|
return LinuxAudio.get_volume()
|
||||||
return self._stream_volumes.get(streamType, 0)
|
return self._stream_volumes.get(streamType, 0)
|
||||||
|
|
||||||
def getStreamMaxVolume(self, streamType: int) -> int:
|
def getStreamMaxVolume(self, streamType: int) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return 100
|
||||||
return 15
|
return 15
|
||||||
|
|
||||||
def setStreamVolume(self, streamType: int, index: int, flags: int = 0):
|
def setStreamVolume(self, streamType: int, index: int, flags: int = 0):
|
||||||
|
if _USE_LINUX:
|
||||||
|
# STREAM_MUSIC 及其它流类型映射到 Linux 主音量
|
||||||
|
LinuxAudio.set_volume(index)
|
||||||
|
return
|
||||||
if streamType in self._stream_volumes:
|
if streamType in self._stream_volumes:
|
||||||
self._stream_volumes[streamType] = max(0, min(15, index))
|
self._stream_volumes[streamType] = max(0, min(15, index))
|
||||||
|
|
||||||
|
def isMicrophoneMute(self) -> bool:
|
||||||
|
"""麦克风是否静音"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxAudio.get_mute()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def setMicrophoneMute(self, on: bool):
|
||||||
|
"""设置麦克风静音"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
LinuxAudio.set_mute(on)
|
||||||
|
return
|
||||||
|
# 存根: 无操作
|
||||||
|
|
||||||
def adjustVolume(self, direction: int, flags: int = 0):
|
def adjustVolume(self, direction: int, flags: int = 0):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ from .._core import (
|
|||||||
_sdcard_root, _app_data_dir, _package_name,
|
_sdcard_root, _app_data_dir, _package_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxNetwork, HAS_IP
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Uri (re-export from content)
|
# Uri (re-export from content)
|
||||||
@@ -379,6 +386,38 @@ class NetworkInfo(Stub):
|
|||||||
}
|
}
|
||||||
return names.get(type_, "UNKNOWN")
|
return names.get(type_, "UNKNOWN")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_linux_data(cls, data: Optional[dict] = None,
|
||||||
|
network_type: int = 9) -> "NetworkInfo":
|
||||||
|
"""从 Linux 真实网络数据构造 NetworkInfo
|
||||||
|
|
||||||
|
data 字典可包含: ip, interface, connected, type_name 等
|
||||||
|
"""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
connected = bool(data.get("connected", False))
|
||||||
|
state = "CONNECTED" if connected else "DISCONNECTED"
|
||||||
|
detailed_state = "CONNECTED" if connected else "DISCONNECTED"
|
||||||
|
type_name = data.get("type_name") or cls._default_type_name_static(network_type)
|
||||||
|
extra_info = data.get("ip") or data.get("interface")
|
||||||
|
return cls(
|
||||||
|
type_=network_type,
|
||||||
|
state=state,
|
||||||
|
detailed_state=detailed_state,
|
||||||
|
type_name=type_name,
|
||||||
|
extra_info=extra_info,
|
||||||
|
is_available=True,
|
||||||
|
roaming=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _default_type_name_static(type_: int) -> str:
|
||||||
|
names = {
|
||||||
|
0: "MOBILE", 1: "WIFI", 6: "WIMAX", 7: "BLUETOOTH",
|
||||||
|
9: "ETHERNET", 17: "VPN",
|
||||||
|
}
|
||||||
|
return names.get(type_, "UNKNOWN")
|
||||||
|
|
||||||
def getType(self) -> int:
|
def getType(self) -> int:
|
||||||
return self._type
|
return self._type
|
||||||
|
|
||||||
@@ -1230,15 +1269,71 @@ class ConnectivityManager(Stub):
|
|||||||
self._network_caps: Dict[int, NetworkCapabilities] = {}
|
self._network_caps: Dict[int, NetworkCapabilities] = {}
|
||||||
self._link_props: Dict[int, LinkProperties] = {}
|
self._link_props: Dict[int, LinkProperties] = {}
|
||||||
|
|
||||||
|
def _get_linux_active_info(self) -> Optional[NetworkInfo]:
|
||||||
|
"""从 Linux 真实网络信息构造活跃 NetworkInfo"""
|
||||||
|
try:
|
||||||
|
interfaces = LinuxNetwork.get_interfaces()
|
||||||
|
# 有真实网络接口 (非环回) 且 operstate=up 即视为已连接
|
||||||
|
connected = any(
|
||||||
|
(iface.get("ifname") or iface.get("name", "")) != "lo"
|
||||||
|
and iface.get("operstate", "").lower() == "up"
|
||||||
|
for iface in interfaces
|
||||||
|
)
|
||||||
|
# 若有活跃接口但 get_connectivity 为 False (外网不可达), 仍视为已连接
|
||||||
|
if not connected:
|
||||||
|
connected = LinuxNetwork.get_connectivity()
|
||||||
|
ip = LinuxNetwork.get_ip_address()
|
||||||
|
iface_name = ""
|
||||||
|
for iface in interfaces:
|
||||||
|
name = iface.get("ifname") or iface.get("name", "")
|
||||||
|
state = iface.get("operstate", "")
|
||||||
|
if name and name != "lo" and state.lower() == "up":
|
||||||
|
iface_name = name
|
||||||
|
break
|
||||||
|
# 判断网络类型 (无线接口 -> WIFI, 否则 ETHERNET)
|
||||||
|
net_type = self.TYPE_ETHERNET
|
||||||
|
for iface in interfaces:
|
||||||
|
name = iface.get("ifname") or iface.get("name", "")
|
||||||
|
if name and name.startswith(("wl", "wlan", "wlp")):
|
||||||
|
net_type = self.TYPE_WIFI
|
||||||
|
iface_name = iface_name or name
|
||||||
|
break
|
||||||
|
data = {
|
||||||
|
"connected": connected,
|
||||||
|
"ip": ip,
|
||||||
|
"interface": iface_name,
|
||||||
|
"type_name": "WIFI" if net_type == self.TYPE_WIFI else "ETHERNET",
|
||||||
|
}
|
||||||
|
return NetworkInfo.from_linux_data(data, network_type=net_type)
|
||||||
|
except Exception:
|
||||||
|
return self._active_network_info
|
||||||
|
|
||||||
def getActiveNetworkInfo(self) -> Optional[NetworkInfo]:
|
def getActiveNetworkInfo(self) -> Optional[NetworkInfo]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self._get_linux_active_info()
|
||||||
return self._active_network_info
|
return self._active_network_info
|
||||||
|
|
||||||
def getNetworkInfo(self, network_type: int) -> Optional[NetworkInfo]:
|
def getNetworkInfo(self, network_type: int) -> Optional[NetworkInfo]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
info = self._get_linux_active_info()
|
||||||
|
if info is not None and info.getType() == network_type:
|
||||||
|
return info
|
||||||
|
# 请求的类型与活跃网络不同时, 返回断开状态
|
||||||
|
return NetworkInfo(
|
||||||
|
type_=network_type, state="DISCONNECTED",
|
||||||
|
detailed_state="DISCONNECTED", is_available=False,
|
||||||
|
)
|
||||||
network = self._networks.get(network_type)
|
network = self._networks.get(network_type)
|
||||||
if network:
|
if network:
|
||||||
return NetworkInfo(type_=network_type, state="CONNECTED", is_available=True)
|
return NetworkInfo(type_=network_type, state="CONNECTED", is_available=True)
|
||||||
return self._active_network_info
|
return self._active_network_info
|
||||||
|
|
||||||
|
def getConnectivityStatus(self) -> bool:
|
||||||
|
"""返回当前网络连通性 (True=已连接)"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxNetwork.get_connectivity()
|
||||||
|
return True
|
||||||
|
|
||||||
def getNetworkInfoForUid(self, network: Network, uid: int) -> Optional[NetworkInfo]:
|
def getNetworkInfoForUid(self, network: Network, uid: int) -> Optional[NetworkInfo]:
|
||||||
return self._active_network_info
|
return self._active_network_info
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
|||||||
|
|
||||||
from .._core import Stub, AndroidOnlyError
|
from .._core import Stub, AndroidOnlyError
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxNetwork, HAS_IP
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Network", "NetworkCapabilities", "NetworkRequest", "NetworkSpecifier",
|
"Network", "NetworkCapabilities", "NetworkRequest", "NetworkSpecifier",
|
||||||
"MatchAllNetworkSpecifier", "NetworkAgent", "NetworkInfo",
|
"MatchAllNetworkSpecifier", "NetworkAgent", "NetworkInfo",
|
||||||
@@ -1291,17 +1298,64 @@ class ConnectivityManager(Stub):
|
|||||||
def getActiveNetwork(self) -> Optional[Network]:
|
def getActiveNetwork(self) -> Optional[Network]:
|
||||||
return self._active_network
|
return self._active_network
|
||||||
|
|
||||||
|
def _get_linux_active_info(self):
|
||||||
|
"""从 Linux 真实网络信息构造活跃 NetworkInfo"""
|
||||||
|
try:
|
||||||
|
from .net import NetworkInfo
|
||||||
|
interfaces = LinuxNetwork.get_interfaces()
|
||||||
|
# 有真实网络接口 (非环回) 且 operstate=up 即视为已连接
|
||||||
|
connected = any(
|
||||||
|
(iface.get("ifname") or iface.get("name", "")) != "lo"
|
||||||
|
and iface.get("operstate", "").lower() == "up"
|
||||||
|
for iface in interfaces
|
||||||
|
)
|
||||||
|
if not connected:
|
||||||
|
connected = LinuxNetwork.get_connectivity()
|
||||||
|
ip = LinuxNetwork.get_ip_address()
|
||||||
|
iface_name = ""
|
||||||
|
net_type = ConnectivityManager.TYPE_ETHERNET
|
||||||
|
for iface in interfaces:
|
||||||
|
name = iface.get("ifname") or iface.get("name", "")
|
||||||
|
state = iface.get("operstate", "")
|
||||||
|
if name and name != "lo" and state.lower() == "up":
|
||||||
|
iface_name = iface_name or name
|
||||||
|
if name and name.startswith(("wl", "wlan", "wlp")):
|
||||||
|
net_type = ConnectivityManager.TYPE_WIFI
|
||||||
|
iface_name = name
|
||||||
|
data = {
|
||||||
|
"connected": connected,
|
||||||
|
"ip": ip,
|
||||||
|
"interface": iface_name,
|
||||||
|
"type_name": "WIFI" if net_type == ConnectivityManager.TYPE_WIFI else "ETHERNET",
|
||||||
|
}
|
||||||
|
return NetworkInfo.from_linux_data(data, network_type=net_type)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
def getActiveNetworkInfo(self) -> Optional[NetworkInfo]:
|
def getActiveNetworkInfo(self) -> Optional[NetworkInfo]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self._get_linux_active_info()
|
||||||
if self._active_network:
|
if self._active_network:
|
||||||
return self._network_infos.get(self._active_network.getNetId())
|
return self._network_infos.get(self._active_network.getNetId())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getNetworkInfo(self, networkType: int) -> Optional[NetworkInfo]:
|
def getNetworkInfo(self, networkType: int) -> Optional[NetworkInfo]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
info = self._get_linux_active_info()
|
||||||
|
if info is not None and info.getType() == networkType:
|
||||||
|
return info
|
||||||
|
return None
|
||||||
for info in self._network_infos.values():
|
for info in self._network_infos.values():
|
||||||
if info.getType() == networkType:
|
if info.getType() == networkType:
|
||||||
return info
|
return info
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getConnectivityStatus(self) -> bool:
|
||||||
|
"""返回当前网络连通性 (True=已连接)"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxNetwork.get_connectivity()
|
||||||
|
return self._active_network is not None
|
||||||
|
|
||||||
def getNetworkInfoForNetwork(self, network: Network) -> Optional[NetworkInfo]:
|
def getNetworkInfoForNetwork(self, network: Network) -> Optional[NetworkInfo]:
|
||||||
return self._network_infos.get(network.getNetId())
|
return self._network_infos.get(network.getNetId())
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ from .._core import (
|
|||||||
_sdcard_root, _app_data_dir, _package_name,
|
_sdcard_root, _app_data_dir, _package_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxNetwork, HAS_IP
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# SupplicantState
|
# SupplicantState
|
||||||
@@ -620,6 +627,8 @@ class WifiManager(Stub):
|
|||||||
self._p2p_channels: List[int] = []
|
self._p2p_channels: List[int] = []
|
||||||
|
|
||||||
def getConnectionInfo(self) -> Optional[WifiInfo]:
|
def getConnectionInfo(self) -> Optional[WifiInfo]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self._get_linux_connection_info()
|
||||||
if self._connection_info is None:
|
if self._connection_info is None:
|
||||||
info = WifiInfo()
|
info = WifiInfo()
|
||||||
info._ssid = '"mock_wifi"'
|
info._ssid = '"mock_wifi"'
|
||||||
@@ -632,6 +641,34 @@ class WifiManager(Stub):
|
|||||||
self._connection_info = info
|
self._connection_info = info
|
||||||
return self._connection_info
|
return self._connection_info
|
||||||
|
|
||||||
|
def getWifiInfo(self) -> Optional[WifiInfo]:
|
||||||
|
"""getWifiInfo — getConnectionInfo 的别名"""
|
||||||
|
return self.getConnectionInfo()
|
||||||
|
|
||||||
|
def _get_linux_connection_info(self) -> WifiInfo:
|
||||||
|
"""从 LinuxNetwork.get_wifi_info() 构造 WifiInfo"""
|
||||||
|
info = WifiInfo()
|
||||||
|
try:
|
||||||
|
data = LinuxNetwork.get_wifi_info() or {}
|
||||||
|
ssid = data.get("ssid", "")
|
||||||
|
info._ssid = f'"{ssid}"' if ssid else "<unknown ssid>"
|
||||||
|
info._bssid = data.get("bssid", "00:00:00:00:00:00")
|
||||||
|
# signal 通常是 0-100, 转换为 RSSI (dBm, 约 -100..0)
|
||||||
|
signal = data.get("signal", 0)
|
||||||
|
if isinstance(signal, (int, float)) and signal > 0:
|
||||||
|
info._rssi = int(-100 + (signal / 100.0) * 60)
|
||||||
|
else:
|
||||||
|
info._rssi = int(signal) if signal else WifiInfo.INVALID_RSSI
|
||||||
|
iface = data.get("interface", "")
|
||||||
|
if iface:
|
||||||
|
info._frequency = 2412 # 默认 2.4GHz
|
||||||
|
info._link_speed = data.get("link_speed", 0) or 0
|
||||||
|
info._network_id = 0
|
||||||
|
info._supplicant_state = SupplicantState(SupplicantState.COMPLETED)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return info
|
||||||
|
|
||||||
def getScanResults(self) -> List[ScanResult]:
|
def getScanResults(self) -> List[ScanResult]:
|
||||||
return list(self._scan_results)
|
return list(self._scan_results)
|
||||||
|
|
||||||
@@ -644,8 +681,26 @@ class WifiManager(Stub):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def isWifiEnabled(self) -> bool:
|
def isWifiEnabled(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self._linux_has_wireless_interface()
|
||||||
return self._wifi_enabled
|
return self._wifi_enabled
|
||||||
|
|
||||||
|
def _linux_has_wireless_interface(self) -> bool:
|
||||||
|
"""检查是否存在无线网络接口"""
|
||||||
|
try:
|
||||||
|
for iface in LinuxNetwork.get_interfaces():
|
||||||
|
name = iface.get("ifname") or iface.get("name", "")
|
||||||
|
if name and name.startswith(("wl", "wlan", "wlp")):
|
||||||
|
return True
|
||||||
|
# 回退: 检查 /sys/class/net 下的无线接口
|
||||||
|
import os as _os
|
||||||
|
for name in _os.listdir("/sys/class/net/") if _os.path.isdir("/sys/class/net") else []:
|
||||||
|
if name.startswith(("wl", "wlan", "wlp")):
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
def getWifiState(self) -> int:
|
def getWifiState(self) -> int:
|
||||||
return self._wifi_state
|
return self._wifi_state
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ from .._core import (
|
|||||||
HAS_PSUTIL,
|
HAS_PSUTIL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxPower, LinuxProcess, HAS_PSUTIL as _HAS_PSUTIL
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Build
|
# Build
|
||||||
@@ -118,6 +125,50 @@ class Build(Stub):
|
|||||||
def getFingerprint() -> str:
|
def getFingerprint() -> str:
|
||||||
return Build.FINGERPRINT
|
return Build.FINGERPRINT
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _populate_from_linux(cls):
|
||||||
|
"""从 LinuxProcess.get_system_info() 填充真实设备信息"""
|
||||||
|
try:
|
||||||
|
info = LinuxProcess.get_system_info() or {}
|
||||||
|
import platform as _platform
|
||||||
|
import socket as _socket
|
||||||
|
hostname = info.get("hostname") or _socket.gethostname()
|
||||||
|
system = info.get("platform") or _platform.system()
|
||||||
|
arch = info.get("arch") or _platform.machine()
|
||||||
|
kernel = info.get("kernel") or _platform.release()
|
||||||
|
cls.MODEL = hostname
|
||||||
|
cls.MANUFACTURER = "Linux"
|
||||||
|
cls.BRAND = system
|
||||||
|
cls.DEVICE = arch
|
||||||
|
cls.BOARD = arch
|
||||||
|
cls.HARDWARE = arch
|
||||||
|
cls.HOST = hostname
|
||||||
|
cls.PRODUCT = hostname
|
||||||
|
cls.DISPLAY = kernel
|
||||||
|
cls.ID = kernel
|
||||||
|
# FINGERPRINT: brand/device/device:version/id/build:user/release-keys
|
||||||
|
cls.FINGERPRINT = (
|
||||||
|
f"{system}/{hostname}/{arch}:Linux/{kernel}/{hostname}:user/release-keys"
|
||||||
|
)
|
||||||
|
# CPU ABI 用架构映射
|
||||||
|
_abi_map = {
|
||||||
|
"x86_64": "x86_64", "amd64": "x86_64",
|
||||||
|
"aarch64": "arm64-v8a", "arm64": "arm64-v8a",
|
||||||
|
"x86": "x86", "i386": "x86", "i686": "x86",
|
||||||
|
"armv7l": "armeabi-v7a", "arm": "armeabi-v7a",
|
||||||
|
}
|
||||||
|
cls.CPU_ABI = _abi_map.get(arch, arch)
|
||||||
|
cls.SUPPORTED_ABIS = [cls.CPU_ABI]
|
||||||
|
cls.SUPPORTED_64_BIT_ABIS = [cls.CPU_ABI] if "64" in cls.CPU_ABI else []
|
||||||
|
cls.SUPPORTED_32_BIT_ABIS = [cls.CPU_ABI] if "64" not in cls.CPU_ABI else []
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# 在 Linux 上用真实系统信息填充 Build
|
||||||
|
if _USE_LINUX:
|
||||||
|
Build._populate_from_linux()
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# SystemClock
|
# SystemClock
|
||||||
@@ -1299,6 +1350,20 @@ class PowerManager(Stub):
|
|||||||
return PowerManagerWakeLock(level_and_flags, tag)
|
return PowerManagerWakeLock(level_and_flags, tag)
|
||||||
|
|
||||||
def isInteractive(self) -> bool:
|
def isInteractive(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self.isScreenOn()
|
||||||
|
return self._is_interactive
|
||||||
|
|
||||||
|
def isScreenOn(self) -> bool:
|
||||||
|
"""屏幕是否点亮 — 在 Linux 上检查 DISPLAY 环境变量"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
import os as _os_mod
|
||||||
|
display = _os_mod.environ.get("DISPLAY", "")
|
||||||
|
if display:
|
||||||
|
return True
|
||||||
|
# WAYLAND_DISPLAY 作为回退
|
||||||
|
wayland = _os_mod.environ.get("WAYLAND_DISPLAY", "")
|
||||||
|
return bool(wayland)
|
||||||
return self._is_interactive
|
return self._is_interactive
|
||||||
|
|
||||||
def isPowerSaveMode(self) -> bool:
|
def isPowerSaveMode(self) -> bool:
|
||||||
@@ -1478,8 +1543,58 @@ class BatteryManager(Stub):
|
|||||||
self._current_now = -500000
|
self._current_now = -500000
|
||||||
self._current_avg = -450000
|
self._current_avg = -450000
|
||||||
self._energy_counter = 15000000
|
self._energy_counter = 15000000
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
|
||||||
|
_LINUX_STATUS_MAP = {
|
||||||
|
"charging": 2, # BATTERY_STATUS_CHARGING
|
||||||
|
"discharging": 3, # BATTERY_STATUS_DISCHARGING
|
||||||
|
"not charging": 4, # BATTERY_STATUS_NOT_CHARGING
|
||||||
|
"full": 5, # BATTERY_STATUS_FULL
|
||||||
|
"unknown": 1, # BATTERY_STATUS_UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_linux_battery_info(self) -> dict:
|
||||||
|
"""从 LinuxPower 获取真实电池信息"""
|
||||||
|
try:
|
||||||
|
return LinuxPower.get_battery_info() or {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _refresh_from_linux(self):
|
||||||
|
"""用 Linux 真实电池信息刷新内部状态"""
|
||||||
|
info = self._get_linux_battery_info()
|
||||||
|
if not info:
|
||||||
|
return
|
||||||
|
self._present = bool(info.get("present", False))
|
||||||
|
level = info.get("level", -1)
|
||||||
|
if self._present and isinstance(level, (int, float)) and level >= 0:
|
||||||
|
self._level = int(level)
|
||||||
|
self._capacity = int(level)
|
||||||
|
else:
|
||||||
|
# 无电池或电量未知
|
||||||
|
self._level = -1
|
||||||
|
self._capacity = -1
|
||||||
|
status_str = str(info.get("status", "unknown")).lower()
|
||||||
|
self._status = self._LINUX_STATUS_MAP.get(status_str, self.BATTERY_STATUS_UNKNOWN)
|
||||||
|
voltage = info.get("voltage", 0)
|
||||||
|
if isinstance(voltage, (int, float)):
|
||||||
|
# LinuxPower 返回伏特, Android 期望毫伏
|
||||||
|
self._voltage = int(voltage * 1000)
|
||||||
|
temperature = info.get("temperature", 0)
|
||||||
|
if isinstance(temperature, (int, float)):
|
||||||
|
# LinuxPower 返回摄氏度, Android 期望十分之一度
|
||||||
|
self._temperature = int(temperature * 10)
|
||||||
|
self._technology = info.get("technology", "") or "Li-ion"
|
||||||
|
# 充电状态决定 plugged
|
||||||
|
if self._status == self.BATTERY_STATUS_CHARGING:
|
||||||
|
self._plugged = self.BATTERY_PLUGGED_AC
|
||||||
|
else:
|
||||||
|
self._plugged = 0
|
||||||
|
|
||||||
def getIntProperty(self, prop_id: int) -> int:
|
def getIntProperty(self, prop_id: int) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
if prop_id == self.BATTERY_PROPERTY_CAPACITY:
|
if prop_id == self.BATTERY_PROPERTY_CAPACITY:
|
||||||
return self._capacity
|
return self._capacity
|
||||||
if prop_id == self.BATTERY_PROPERTY_STATUS:
|
if prop_id == self.BATTERY_PROPERTY_STATUS:
|
||||||
@@ -1498,9 +1613,57 @@ class BatteryManager(Stub):
|
|||||||
return self.getIntProperty(prop_id)
|
return self.getIntProperty(prop_id)
|
||||||
|
|
||||||
def isCharging(self) -> bool:
|
def isCharging(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
return self._status == self.BATTERY_STATUS_CHARGING
|
return self._status == self.BATTERY_STATUS_CHARGING
|
||||||
|
|
||||||
|
def isPresent(self) -> bool:
|
||||||
|
"""电池是否存在"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
return self._present
|
||||||
|
|
||||||
|
def getLevel(self) -> int:
|
||||||
|
"""电池电量 (0-100, -1 表示未知)"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
return self._level
|
||||||
|
|
||||||
|
def getVoltage(self) -> int:
|
||||||
|
"""电池电压 (毫伏)"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
return self._voltage
|
||||||
|
|
||||||
|
def getTemperature(self) -> int:
|
||||||
|
"""电池温度 (十分之一摄氏度)"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
return self._temperature
|
||||||
|
|
||||||
|
def getTechnology(self) -> str:
|
||||||
|
"""电池技术"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
return self._technology
|
||||||
|
|
||||||
|
def getPlugType(self) -> int:
|
||||||
|
"""充电类型"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
return self._plugged
|
||||||
|
|
||||||
|
def getStatus(self) -> int:
|
||||||
|
"""电池状态"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
return self._status
|
||||||
|
|
||||||
def computeChargeTimeRemaining(self) -> int:
|
def computeChargeTimeRemaining(self) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
# 无电池或已充满时返回 -1
|
||||||
|
if not self._present or self._status == self.BATTERY_STATUS_FULL:
|
||||||
|
return -1
|
||||||
return 1800000 # 30 minutes
|
return 1800000 # 30 minutes
|
||||||
|
|
||||||
def toString(self) -> str:
|
def toString(self) -> str:
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ from .._core import Stub, AndroidOnlyError, _app_data_dir
|
|||||||
|
|
||||||
from ..java.lang import RuntimeException
|
from ..java.lang import RuntimeException
|
||||||
|
|
||||||
|
# Linux 真实实现导入
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxPower, LinuxProcess, HAS_PSUTIL as _HAS_PSUTIL
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||||
|
|
||||||
|
|
||||||
@@ -505,6 +512,54 @@ class BatteryManager(Stub):
|
|||||||
self._discharge_time_remaining = -1
|
self._discharge_time_remaining = -1
|
||||||
self._is_capacity_estimated = False
|
self._is_capacity_estimated = False
|
||||||
self._capacity_percentage = 100.0
|
self._capacity_percentage = 100.0
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
|
|
||||||
|
_LINUX_STATUS_MAP = {
|
||||||
|
"charging": BATTERY_STATUS_CHARGING,
|
||||||
|
"discharging": BATTERY_STATUS_DISCHARGING,
|
||||||
|
"not charging": BATTERY_STATUS_NOT_CHARGING,
|
||||||
|
"full": BATTERY_STATUS_FULL,
|
||||||
|
"unknown": BATTERY_STATUS_UNKNOWN,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_linux_battery_info(self) -> dict:
|
||||||
|
"""从 LinuxPower 获取真实电池信息"""
|
||||||
|
try:
|
||||||
|
return LinuxPower.get_battery_info() or {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _refresh_from_linux(self):
|
||||||
|
"""用 Linux 真实电池信息刷新内部状态"""
|
||||||
|
info = self._get_linux_battery_info()
|
||||||
|
if not info:
|
||||||
|
return
|
||||||
|
self._present = bool(info.get("present", False))
|
||||||
|
level = info.get("level", -1)
|
||||||
|
if self._present and isinstance(level, (int, float)) and level >= 0:
|
||||||
|
self._battery_level = int(level)
|
||||||
|
self._capacity_percentage = float(level)
|
||||||
|
# BATTERY_PROPERTY_CAPACITY 在 Android 中返回百分比 (0-100)
|
||||||
|
self._capacity = int(level)
|
||||||
|
else:
|
||||||
|
# 无电池或电量未知
|
||||||
|
self._battery_level = -1
|
||||||
|
self._capacity_percentage = 0.0
|
||||||
|
self._capacity = -1
|
||||||
|
status_str = str(info.get("status", "unknown")).lower()
|
||||||
|
self._status = self._LINUX_STATUS_MAP.get(status_str, BATTERY_STATUS_UNKNOWN)
|
||||||
|
voltage = info.get("voltage", 0)
|
||||||
|
if isinstance(voltage, (int, float)):
|
||||||
|
self._voltage = int(voltage * 1000) # V -> mV
|
||||||
|
temperature = info.get("temperature", 0)
|
||||||
|
if isinstance(temperature, (int, float)):
|
||||||
|
self._temperature = temperature # 摄氏度
|
||||||
|
self._technology = info.get("technology", "") or "Li-ion"
|
||||||
|
if self._status == BATTERY_STATUS_CHARGING:
|
||||||
|
self._plugged = BATTERY_PLUGGED_AC
|
||||||
|
else:
|
||||||
|
self._plugged = 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getInstance(context: Any) -> "BatteryManager":
|
def getInstance(context: Any) -> "BatteryManager":
|
||||||
@@ -513,6 +568,8 @@ class BatteryManager(Stub):
|
|||||||
return BatteryManager._instance
|
return BatteryManager._instance
|
||||||
|
|
||||||
def getIntProperty(self, id: int) -> int:
|
def getIntProperty(self, id: int) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
properties = {
|
properties = {
|
||||||
BATTERY_PROPERTY_CAPACITY: self._capacity,
|
BATTERY_PROPERTY_CAPACITY: self._capacity,
|
||||||
BATTERY_PROPERTY_CHARGE_COUNTER: self._charge_counter,
|
BATTERY_PROPERTY_CHARGE_COUNTER: self._charge_counter,
|
||||||
@@ -521,6 +578,9 @@ class BatteryManager(Stub):
|
|||||||
BATTERY_PROPERTY_ENERGY_COUNTER: self._energy_counter,
|
BATTERY_PROPERTY_ENERGY_COUNTER: self._energy_counter,
|
||||||
BATTERY_PROPERTY_CHARGE_TIME_REMAINING: self._charge_time_remaining,
|
BATTERY_PROPERTY_CHARGE_TIME_REMAINING: self._charge_time_remaining,
|
||||||
BATTERY_PROPERTY_DISCHARGE_TIME_REMAINING: self._discharge_time_remaining,
|
BATTERY_PROPERTY_DISCHARGE_TIME_REMAINING: self._discharge_time_remaining,
|
||||||
|
BATTERY_PROPERTY_STATUS: self._status,
|
||||||
|
BATTERY_PROPERTY_TEMPERATURE: int(self._temperature),
|
||||||
|
BATTERY_PROPERTY_VOLTAGE: self._voltage,
|
||||||
}
|
}
|
||||||
return properties.get(id, 0)
|
return properties.get(id, 0)
|
||||||
|
|
||||||
@@ -528,9 +588,13 @@ class BatteryManager(Stub):
|
|||||||
return self.getIntProperty(id)
|
return self.getIntProperty(id)
|
||||||
|
|
||||||
def isCharging(self) -> bool:
|
def isCharging(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
return self._status == BATTERY_STATUS_CHARGING or self._status == BATTERY_STATUS_FULL
|
return self._status == BATTERY_STATUS_CHARGING or self._status == BATTERY_STATUS_FULL
|
||||||
|
|
||||||
def getBatteryLevel(self) -> int:
|
def getBatteryLevel(self) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
self._refresh_from_linux()
|
||||||
return self._battery_level
|
return self._battery_level
|
||||||
|
|
||||||
def getInt(intent: Any, key: str, defaultValue: int) -> int:
|
def getInt(intent: Any, key: str, defaultValue: int) -> int:
|
||||||
@@ -687,9 +751,11 @@ class PowerManager(Stub):
|
|||||||
self._shutdown_requested = False
|
self._shutdown_requested = False
|
||||||
self._thermal_status = THERMAL_STATUS_NONE
|
self._thermal_status = THERMAL_STATUS_NONE
|
||||||
self._power_save_state = PowerSaveState()
|
self._power_save_state = PowerSaveState()
|
||||||
self._current_wake_up_reason = WAKE_REASON_UNKNOWN
|
self._current_wake_up_reason = PowerManager.WAKE_REASON_UNKNOWN
|
||||||
|
|
||||||
def isInteractive(self) -> bool:
|
def isInteractive(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self.isScreenOn()
|
||||||
return self._is_interactive
|
return self._is_interactive
|
||||||
|
|
||||||
def isPowerSaveMode(self) -> bool:
|
def isPowerSaveMode(self) -> bool:
|
||||||
@@ -705,6 +771,13 @@ class PowerManager(Stub):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def isScreenOn(self) -> bool:
|
def isScreenOn(self) -> bool:
|
||||||
|
if _USE_LINUX:
|
||||||
|
import os as _os_mod
|
||||||
|
display = _os_mod.environ.get("DISPLAY", "")
|
||||||
|
if display:
|
||||||
|
return True
|
||||||
|
wayland = _os_mod.environ.get("WAYLAND_DISPLAY", "")
|
||||||
|
return bool(wayland)
|
||||||
return self._is_screen_on
|
return self._is_screen_on
|
||||||
|
|
||||||
def isWakeLockLevelSupported(self, level: int) -> bool:
|
def isWakeLockLevelSupported(self, level: int) -> bool:
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ from .._core import (
|
|||||||
_sdcard_root, _app_data_dir, _package_name,
|
_sdcard_root, _app_data_dir, _package_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxNetwork, LinuxProcess, LinuxLocation
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# CellIdentity 基类集合
|
# CellIdentity 基类集合
|
||||||
@@ -553,6 +559,9 @@ class TelephonyManager(Stub):
|
|||||||
self._context = context
|
self._context = context
|
||||||
|
|
||||||
def getNetworkOperatorName(self) -> str:
|
def getNetworkOperatorName(self) -> str:
|
||||||
|
if _USE_LINUX:
|
||||||
|
loc = LinuxLocation.get_location()
|
||||||
|
return loc.get("isp", "") or ""
|
||||||
return "MockOperator"
|
return "MockOperator"
|
||||||
|
|
||||||
def getNetworkOperator(self) -> str:
|
def getNetworkOperator(self) -> str:
|
||||||
@@ -562,9 +571,15 @@ class TelephonyManager(Stub):
|
|||||||
return "us"
|
return "us"
|
||||||
|
|
||||||
def getNetworkType(self) -> int:
|
def getNetworkType(self) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
if LinuxNetwork.get_connectivity():
|
||||||
|
return self.NETWORK_TYPE_IWLAN
|
||||||
|
return self.NETWORK_TYPE_UNKNOWN
|
||||||
return self.NETWORK_TYPE_LTE
|
return self.NETWORK_TYPE_LTE
|
||||||
|
|
||||||
def getPhoneType(self) -> int:
|
def getPhoneType(self) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return self.PHONE_TYPE_NONE
|
||||||
return self.PHONE_TYPE_GSM
|
return self.PHONE_TYPE_GSM
|
||||||
|
|
||||||
def getDataNetworkType(self) -> int:
|
def getDataNetworkType(self) -> int:
|
||||||
@@ -574,6 +589,8 @@ class TelephonyManager(Stub):
|
|||||||
return self.NETWORK_TYPE_LTE
|
return self.NETWORK_TYPE_LTE
|
||||||
|
|
||||||
def getSimOperator(self) -> str:
|
def getSimOperator(self) -> str:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return ""
|
||||||
return "310260"
|
return "310260"
|
||||||
|
|
||||||
def getSimOperatorName(self) -> str:
|
def getSimOperatorName(self) -> str:
|
||||||
@@ -583,18 +600,29 @@ class TelephonyManager(Stub):
|
|||||||
return "us"
|
return "us"
|
||||||
|
|
||||||
def getSimSerialNumber(self) -> Optional[str]:
|
def getSimSerialNumber(self) -> Optional[str]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return ""
|
||||||
return "89014103211118510720"
|
return "89014103211118510720"
|
||||||
|
|
||||||
def getSubscriberId(self) -> Optional[str]:
|
def getSubscriberId(self) -> Optional[str]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return ""
|
||||||
return "310260123456789"
|
return "310260123456789"
|
||||||
|
|
||||||
def getDeviceId(self) -> Optional[str]:
|
def getDeviceId(self) -> Optional[str]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxNetwork.get_mac_address() or None
|
||||||
return "000000000000000"
|
return "000000000000000"
|
||||||
|
|
||||||
def getDeviceSoftwareVersion(self) -> Optional[str]:
|
def getDeviceSoftwareVersion(self) -> Optional[str]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
info = LinuxProcess.get_system_info()
|
||||||
|
return info.get("kernel", "")
|
||||||
return "00.00.00.00"
|
return "00.00.00.00"
|
||||||
|
|
||||||
def getLine1Number(self) -> Optional[str]:
|
def getLine1Number(self) -> Optional[str]:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return ""
|
||||||
return "15551234567"
|
return "15551234567"
|
||||||
|
|
||||||
def getGroupIdLevel1(self) -> Optional[str]:
|
def getGroupIdLevel1(self) -> Optional[str]:
|
||||||
@@ -619,6 +647,9 @@ class TelephonyManager(Stub):
|
|||||||
return self.SIM_STATE_READY
|
return self.SIM_STATE_READY
|
||||||
|
|
||||||
def getCellLocation(self) -> Any:
|
def getCellLocation(self) -> Any:
|
||||||
|
# 桌面无基站,Linux 下返回 None
|
||||||
|
if _USE_LINUX:
|
||||||
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getAllCellInfo(self) -> Optional[List[CellInfo]]:
|
def getAllCellInfo(self) -> Optional[List[CellInfo]]:
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|||||||
|
|
||||||
from .._core import Stub, AndroidOnlyError, android_context, HAS_PYAUTOGUI
|
from .._core import Stub, AndroidOnlyError, android_context, HAS_PYAUTOGUI
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxDisplay, LinuxUIAutomation, HAS_XDOTOOL
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# Gravity
|
# Gravity
|
||||||
@@ -363,6 +369,12 @@ class View(Stub):
|
|||||||
def performClick(self) -> bool:
|
def performClick(self) -> bool:
|
||||||
return self.callOnClick()
|
return self.callOnClick()
|
||||||
|
|
||||||
|
def screenshot(self, path=None):
|
||||||
|
"""截屏"""
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxDisplay.screenshot(path)
|
||||||
|
return None
|
||||||
|
|
||||||
def performLongClick(self) -> bool:
|
def performLongClick(self) -> bool:
|
||||||
if self._on_long_click_listener:
|
if self._on_long_click_listener:
|
||||||
self._on_long_click_listener(self)
|
self._on_long_click_listener(self)
|
||||||
@@ -3260,9 +3272,13 @@ class Display(Stub):
|
|||||||
return self._display_id
|
return self._display_id
|
||||||
|
|
||||||
def getWidth(self) -> int:
|
def getWidth(self) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxDisplay.get_screen_size()[0]
|
||||||
return self._width
|
return self._width
|
||||||
|
|
||||||
def getHeight(self) -> int:
|
def getHeight(self) -> int:
|
||||||
|
if _USE_LINUX:
|
||||||
|
return LinuxDisplay.get_screen_size()[1]
|
||||||
return self._height
|
return self._height
|
||||||
|
|
||||||
def getRotation(self) -> int:
|
def getRotation(self) -> int:
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ from .view import (
|
|||||||
ViewGroup as VG,
|
ViewGroup as VG,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .._linux_impl import IS_LINUX, LinuxNotify, LinuxClipboard
|
||||||
|
_USE_LINUX = IS_LINUX
|
||||||
|
except ImportError:
|
||||||
|
_USE_LINUX = False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# TextView
|
# TextView
|
||||||
@@ -3589,9 +3595,15 @@ class Toast(Stub):
|
|||||||
return toast
|
return toast
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
pass
|
if _USE_LINUX:
|
||||||
|
LinuxNotify.send("Toast", self._text)
|
||||||
|
else:
|
||||||
|
print(f"[Toast] {self._text}")
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
|
if _USE_LINUX:
|
||||||
|
# 桌面通知自动消失,无需操作
|
||||||
|
return
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def setGravity(self, gravity: int, x_offset: int, y_offset: int):
|
def setGravity(self, gravity: int, x_offset: int, y_offset: int):
|
||||||
|
|||||||
Reference in new issue
Block a user