Files
Android-pyApi/android_pyapi/android/net.py
T
shuiyu1123 fbb8263b13 feat: 第三轮深度完善 — SQLite/JSON/网络/媒体/文本 真实实现
SQLite真实数据库:
- SQLiteDatabase: execSQL/insert/insertOrThrow/replace/update/delete/query 用sqlite3
- 修复getVersion/setVersion用PRAGMA user_version
- 修复getMaximumSize/getPageSize/setMaximumSize/setPageSize用PRAGMA
- SQLiteStatement: execute/simpleQueryForLong/simpleQueryForString/execQuery
- SQLiteOpenHelper: onCreate/onUpgrade/onDowngrade回调正确触发
- 修复SQLiteCursor.moveToPosition越界处理
- 修复ContentValues值对提取bug

JSON真实实现:
- JSONObject: isNull/append/putOnce/putOpt/getNames
- JSONArray: 完整put/get/opt/length/toString

网络真实实现:
- NetworkInterface: getByName/getNetworkInterfaces/getHardwareAddress 用/sys/class/net+fcntl.ioctl
- InterfaceAddress: 新增类
- ConnectivityManager: getActiveNetwork/getAllNetworks 用LinuxNetwork
- WifiManager: getScanResults用nmcli, setWifiEnabled用nmcli/rfkill
- BluetoothAdapter: getAddress/getName 用bluetoothctl

媒体真实实现:
- MediaPlayer: prepare检查文件, getCurrentPosition实时计算, getDuration/getVideoWidth用ffprobe
- MediaRecorder: setVideoSize/setVideoFrameRate/setVideoEncodingBitRate配置存储
- MediaMetadataRetriever: extractMetadata用ffprobe, getFrameAtTime用ffmpeg, getEmbeddedPicture用ffmpeg

文本真实实现:
- TextUtils: split用re.split, concat/indexOf/lastIndexOf/urlEncode/urlDecode
- SpannableString: setSpan/getSpans/getSpanFlags/nextSpanTransition 真实实现
- SpannableStringBuilder: 修复append重载bug

Provider增强:
- Settings: putBoolean/getBoolean
- MediaStore: getContentUri/VOLUME常量
- ContactsContract: 补全常量和CommonDataKinds

Util增强:
- Log: w/e/wtf支持Throwable参数, 修复wtf重复定义bug
- TypedValue: 修复complexToDimension/complexToFloat/getFloat实现

修复:
- __init__.py: 补全NetworkInterface/InetAddress/MediaMetadataRetriever/SpannableStringBuilder导出

统计: 221模块, 3666类, 31005方法, 183529行
2026-06-25 00:40:59 +00:00

1559 lines
52 KiB
Python

"""
Android Network 组件 — ConnectivityManager, NetworkInfo, Uri_Builder 等
方法签名完全对齐 Android SDK
"""
import os
import sys
import socket
import struct
import threading
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
from urllib.parse import urlparse, urlunparse, quote, unquote
from .._core import (
Stub, AndroidOnlyError, android_context,
resolve_android_path, to_android_path,
_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 is defined in android.content; re-export the reference here.
from .content import Uri
Uri_Builder = Uri.Builder
# ═══════════════════════════════════════════════════════════════
# LocalSocket / LocalSocketAddress
# ═══════════════════════════════════════════════════════════════
class LocalSocketAddress(Stub):
"""Android LocalSocketAddress"""
class Namespace(Stub):
ABSTRACT = 0
RESERVED = 1
FILESYSTEM = 2
def __init__(self, name: str, namespace: int = Namespace.ABSTRACT):
super().__init__()
self._name = name
self._namespace = namespace
def getName(self) -> str:
return self._name
def getNamespace(self) -> int:
return self._namespace
class LocalSocket(Stub):
"""Android LocalSocket — 基于 Unix domain socket 实现"""
def __init__(self, sock_type: int = 1): # SOCK_STREAM
super().__init__()
self._sock: Optional[socket.socket] = None
self._sock_type = sock_type
self._input_stream: Optional[bytes] = b""
self._output_stream: Optional[bytes] = b""
self._closed = False
self._so_timeout: float = 0.0
self._address: Optional[LocalSocketAddress] = None
def connect(self, address: LocalSocketAddress, timeout: int = 0):
self._address = address
ns = address.getNamespace()
if ns == LocalSocketAddress.Namespace.ABSTRACT:
sock_path = "\0" + address.getName()
elif ns == LocalSocketAddress.Namespace.FILESYSTEM:
sock_path = address.getName()
else:
sock_path = os.path.join("/dev/socket", address.getName())
try:
self._sock = socket.socket(socket.AF_UNIX, self._sock_type)
if timeout > 0:
self._sock.settimeout(timeout / 1000.0)
self._sock.connect(sock_path)
except (OSError, socket.error) as e:
raise AndroidOnlyError(f"LocalSocket.connect failed: {e}")
def bind(self, address: LocalSocketAddress):
self._address = address
ns = address.getNamespace()
if ns == LocalSocketAddress.Namespace.ABSTRACT:
sock_path = "\0" + address.getName()
elif ns == LocalSocketAddress.Namespace.FILESYSTEM:
sock_path = address.getName()
else:
sock_path = os.path.join("/dev/socket", address.getName())
try:
self._sock = socket.socket(socket.AF_UNIX, self._sock_type)
self._sock.bind(sock_path)
except (OSError, socket.error) as e:
raise AndroidOnlyError(f"LocalSocket.bind failed: {e}")
def getInputStream(self):
return self._input_stream
def getOutputStream(self):
return self._output_stream
def close(self):
self._closed = True
if self._sock:
try:
self._sock.close()
except Exception:
pass
self._sock = None
def setSoTimeout(self, timeout: int):
self._so_timeout = timeout / 1000.0
if self._sock:
self._sock.settimeout(self._so_timeout)
def getSoTimeout(self) -> int:
return int(self._so_timeout * 1000)
def shutdownInput(self):
if self._sock:
try:
self._sock.shutdown(socket.SHUT_RD)
except Exception:
pass
def shutdownOutput(self):
if self._sock:
try:
self._sock.shutdown(socket.SHUT_WR)
except Exception:
pass
def isClosed(self) -> bool:
return self._closed
def isConnected(self) -> bool:
return self._sock is not None
def getRemoteSocketAddress(self) -> Optional[LocalSocketAddress]:
return self._address
def toString(self) -> str:
return f"LocalSocket(address={self._address})"
# ═══════════════════════════════════════════════════════════════
# NetworkUtils
# ═══════════════════════════════════════════════════════════════
class NetworkUtils(Stub):
"""NetworkUtils — 网络工具"""
@staticmethod
def getNetworkPartOfAddress(addr: str, prefix_len: int) -> str:
return addr
@staticmethod
def intToInetAddress(host_address: int) -> str:
try:
return socket.inet_ntoa(struct.pack("!I", host_address & 0xFFFFFFFF))
except Exception:
return "0.0.0.0"
@staticmethod
def inetAddressToInt(addr: str) -> int:
try:
return struct.unpack("!I", socket.inet_aton(addr))[0]
except Exception:
return 0
@staticmethod
def isIPv6ULA(addr: str) -> bool:
return addr.startswith("fc") or addr.startswith("fd")
@staticmethod
def isIPv6GlobalMulticast(addr: str) -> bool:
return addr.startswith("ff")
@staticmethod
def prefixLengthToSubnetMask(prefix_len: int) -> int:
return (0xFFFFFFFF << (32 - prefix_len)) & 0xFFFFFFFF if prefix_len <= 32 else 0
# ═══════════════════════════════════════════════════════════════
# InetAddresses
# ═══════════════════════════════════════════════════════════════
class InetAddresses(Stub):
"""InetAddresses — 地址解析工具"""
@staticmethod
def isNumeric(address: str) -> bool:
try:
socket.inet_aton(address)
return True
except OSError:
return False
@staticmethod
def parseNumericAddress(address: str) -> str:
return address
@staticmethod
def getAddressFamily(address: str) -> int:
return 2 # AF_INET
# ═══════════════════════════════════════════════════════════════
# NetworkSpecifier / MatchAllNetworkSpecifier
# ═══════════════════════════════════════════════════════════════
class NetworkSpecifier(Stub):
"""NetworkSpecifier — 网络说明符基类"""
def __init__(self):
super().__init__()
def satisfiedBy(self, other: "NetworkSpecifier") -> bool:
return self is other
def equals(self, other) -> bool:
return self is other
def hashCode(self) -> int:
return hash(self.__class__.__name__) & 0x7FFFFFFF
class MatchAllNetworkSpecifier(NetworkSpecifier):
"""匹配所有网络的说明符"""
def __init__(self):
super().__init__()
def satisfiedBy(self, other: NetworkSpecifier) -> bool:
return True
def toString(self) -> str:
return "MatchAllNetworkSpecifier"
class WifiAwareNetworkSpecifier(NetworkSpecifier):
"""WiFi Aware 网络说明符"""
def __init__(self, role: int, client_id: int, peer_id: int, peer_mac: Optional[str] = None):
super().__init__()
self._role = role
self._client_id = client_id
self._peer_id = peer_id
self._peer_mac = peer_mac
def getRole(self) -> int:
return self._role
def getClientId(self) -> int:
return self._client_id
def toString(self) -> str:
return f"WifiAwareNetworkSpecifier(role={self._role}, client={self._client_id})"
class EthernetNetworkSpecifier(NetworkSpecifier):
"""以太网网络说明符"""
def __init__(self, data: Optional[str] = None):
super().__init__()
self._data = data
def getData(self) -> Optional[str]:
return self._data
def toString(self) -> str:
return f"EthernetNetworkSpecifier(data={self._data})"
class TelephonyNetworkSpecifier(NetworkSpecifier):
"""电话网络说明符"""
def __init__(self, subscription_id: int = 0):
super().__init__()
self._subscription_id = subscription_id
def getSubscriptionId(self) -> int:
return self._subscription_id
def toString(self) -> str:
return f"TelephonyNetworkSpecifier(subId={self._subscription_id})"
# ═══════════════════════════════════════════════════════════════
# NetworkState
# ═══════════════════════════════════════════════════════════════
class NetworkState(Stub):
"""NetworkState — 网络状态"""
def __init__(self, network: Optional["Network"] = None,
network_info: Optional["NetworkInfo"] = None,
link_properties: Optional["LinkProperties"] = None,
network_capabilities: Optional["NetworkCapabilities"] = None,
subscriber_id: Optional[str] = None):
super().__init__()
self.network = network
self.network_info = network_info
self.link_properties = link_properties
self.network_capabilities = network_capabilities
self.subscriber_id = subscriber_id
def getNetwork(self) -> Optional["Network"]:
return self.network
def getNetworkInfo(self) -> Optional["NetworkInfo"]:
return self.network_info
def getLinkProperties(self) -> Optional["LinkProperties"]:
return self.link_properties
def getNetworkCapabilities(self) -> Optional["NetworkCapabilities"]:
return self.network_capabilities
def toString(self) -> str:
return f"NetworkState(network={self.network})"
# ═══════════════════════════════════════════════════════════════
# NetworkInfo
# ═══════════════════════════════════════════════════════════════
class NetworkInfo(Stub):
"""NetworkInfo — 网络连接信息"""
class DetailedState(Stub):
CONNECTED = "CONNECTED"
CONNECTING = "CONNECTING"
DISCONNECTED = "DISCONNECTED"
SUSPENDED = "SUSPENDED"
IDLE = "IDLE"
FAILED = "FAILED"
BLOCKED = "BLOCKED"
VERIFYING_POOR_CONNECTION = "VERIFYING_POOR_CONNECTION"
CAPTIVE_PORTAL_CHECK = "CAPTIVE_PORTAL_CHECK"
OBTAINING_IPADDR = "OBTAINING_IPADDR"
class State(Stub):
CONNECTED = "CONNECTED"
CONNECTING = "CONNECTING"
DISCONNECTED = "DISCONNECTED"
SUSPENDED = "SUSPENDED"
UNKNOWN = "UNKNOWN"
def __init__(self, type_: int = 0, subtype: int = 0,
state: str = "DISCONNECTED", detailed_state: str = "DISCONNECTED",
type_name: str = "", subtype_name: str = "",
extra_info: Optional[str] = None, reason: Optional[str] = None,
failover: bool = False, roaming: bool = False,
is_available: bool = True):
super().__init__()
self._type = type_
self._subtype = subtype
self._state = state
self._detailed_state = detailed_state
self._type_name = type_name or self._default_type_name(type_)
self._subtype_name = subtype_name
self._extra_info = extra_info
self._reason = reason
self._failover = failover
self._roaming = roaming
self._is_available = is_available
self._is_connected = (state == "CONNECTED")
self._is_connecting = (state == "CONNECTING")
def _default_type_name(self, type_: int) -> str:
names = {
0: "MOBILE", 1: "WIFI", 6: "WIMAX", 7: "BLUETOOTH",
9: "ETHERNET", 17: "VPN",
}
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:
return self._type
def getSubtype(self) -> int:
return self._subtype
def isConnected(self) -> bool:
return self._state == "CONNECTED"
def isConnectedOrConnecting(self) -> bool:
return self._state in ("CONNECTED", "CONNECTING")
def getState(self) -> str:
return self._state
def getDetailedState(self) -> str:
return self._detailed_state
def getTypeName(self) -> str:
return self._type_name
def getSubtypeName(self) -> str:
return self._subtype_name
def getExtraInfo(self) -> Optional[str]:
return self._extra_info
def getReason(self) -> Optional[str]:
return self._reason
def getFailover(self) -> bool:
return self._failover
def getRoaming(self) -> bool:
return self._roaming
def isAvailable(self) -> bool:
return self._is_available
def isFailover(self) -> bool:
return self._failover
def isRoaming(self) -> bool:
return self._roaming
def toString(self) -> str:
return (f"NetworkInfo(type={self._type_name}[{self._type}], "
f"state={self._state}/{self._detailed_state}, "
f"roaming={self._roaming}, available={self._is_available})")
def hashCode(self) -> int:
return self._type
def equals(self, other) -> bool:
if isinstance(other, NetworkInfo):
return self._type == other._type and self._state == other._state
return False
# ═══════════════════════════════════════════════════════════════
# Network
# ═══════════════════════════════════════════════════════════════
class Network(Stub):
"""Network — 网络句柄"""
def __init__(self, net_id: int = 0):
super().__init__()
self._net_id = net_id
def getNetworkHandle(self) -> int:
return self._net_id
def getNetworkCapabilities(self) -> Optional["NetworkCapabilities"]:
return None
def getLinkProperties(self) -> Optional["LinkProperties"]:
return None
def bindSocket(self, socket_obj: Any):
pass
def bindSocketToNetwork(self, socket_obj: Any) -> bool:
return True
def equals(self, other) -> bool:
if isinstance(other, Network):
return self._net_id == other._net_id
return False
def hashCode(self) -> int:
return self._net_id
def toString(self) -> str:
return f"Network({self._net_id})"
# ═══════════════════════════════════════════════════════════════
# NetworkCapabilities
# ═══════════════════════════════════════════════════════════════
class NetworkCapabilities(Stub):
"""NetworkCapabilities — 网络能力"""
NET_CAPABILITY_INTERNET = 12
NET_CAPABILITY_NOT_METERED = 11
NET_CAPABILITY_NOT_ROAMING = 18
NET_CAPABILITY_VALIDATED = 16
NET_CAPABILITY_CAPTIVE_PORTAL = 17
NET_CAPABILITY_NOT_VPN = 15
NET_CAPABILITY_MMS = 0
NET_CAPABILITY_SUPL = 1
NET_CAPABILITY_DUN = 2
NET_CAPABILITY_FOTA = 3
NET_CAPABILITY_IMS = 4
NET_CAPABILITY_CBS = 5
NET_CAPABILITY_WIFI_P2P = 6
NET_CAPABILITY_IA = 7
NET_CAPABILITY_RCS = 8
NET_CAPABILITY_XCAP = 9
NET_CAPABILITY_EIMS = 10
NET_CAPABILITY_NOT_CONGESTED = 13
NET_CAPABILITY_NOT_RESTRICTED = 14
NET_CAPABILITY_TRUSTED = 19
NET_CAPABILITY_NOT_VCN_MANAGED = 20
NET_CAPABILITY_PRIORITIZE_LATENCY = 21
NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 22
TRANSPORT_WIFI = 1
TRANSPORT_CELLULAR = 0
TRANSPORT_ETHERNET = 3
TRANSPORT_BLUETOOTH = 2
TRANSPORT_VPN = 4
TRANSPORT_WIFI_AWARE = 5
TRANSPORT_LOWPAN = 6
TRANSPORT_TEST = 7
SIGNAL_STRENGTH_UNSPECIFIED = -2147483648
def __init__(self):
super().__init__()
self._capabilities: Set[int] = set()
self._transports: Set[int] = set()
self._link_upstream_bw = 0
self._link_downstream_bw = 0
self._signal_strength = self.SIGNAL_STRENGTH_UNSPECIFIED
self._network_specifier: Optional[NetworkSpecifier] = None
self._owner_uid = -1
self._ssid: Optional[str] = None
self._administrator_uids: List[int] = []
self._enterprise_ids: List[int] = []
def hasCapability(self, capability: int) -> bool:
return capability in self._capabilities
def hasTransport(self, transport: int) -> bool:
return transport in self._transports
def addCapability(self, capability: int) -> "NetworkCapabilities":
self._capabilities.add(capability)
return self
def removeCapability(self, capability: int) -> "NetworkCapabilities":
self._capabilities.discard(capability)
return self
def getCapabilities(self) -> List[int]:
return sorted(self._capabilities)
def getLinkUpstreamBandwidthKbps(self) -> int:
return self._link_upstream_bw
def getLinkDownstreamBandwidthKbps(self) -> int:
return self._link_downstream_bw
def setLinkUpstreamBandwidthKbps(self, bw: int):
self._link_upstream_bw = bw
def setLinkDownstreamBandwidthKbps(self, bw: int):
self._link_downstream_bw = bw
def addTransportType(self, transport: int) -> "NetworkCapabilities":
self._transports.add(transport)
return self
def removeTransportType(self, transport: int) -> "NetworkCapabilities":
self._transports.discard(transport)
return self
def getTransportInfo(self) -> Optional[Any]:
return None
def setNetworkSpecifier(self, specifier: NetworkSpecifier):
self._network_specifier = specifier
def getNetworkSpecifier(self) -> Optional[NetworkSpecifier]:
return self._network_specifier
def getSignalStrength(self) -> int:
return self._signal_strength
def setSignalStrength(self, strength: int):
self._signal_strength = strength
def getOwnerUid(self) -> int:
return self._owner_uid
def setOwnerUid(self, uid: int):
self._owner_uid = uid
def getSsid(self) -> Optional[str]:
return self._ssid
def setSsid(self, ssid: str):
self._ssid = ssid
def getAdministratorUids(self) -> List[int]:
return list(self._administrator_uids)
def setAdministratorUids(self, uids: List[int]):
self._administrator_uids = list(uids)
def equals(self, other) -> bool:
if isinstance(other, NetworkCapabilities):
return (self._capabilities == other._capabilities
and self._transports == other._transports)
return False
def hashCode(self) -> int:
return hash(frozenset(self._capabilities)) ^ hash(frozenset(self._transports))
def toString(self) -> str:
caps = ",".join(str(c) for c in sorted(self._capabilities))
trans = ",".join(str(t) for t in sorted(self._transports))
return f"NetworkCapabilities(caps=[{caps}], transports=[{trans}])"
# ═══════════════════════════════════════════════════════════════
# NetworkRequest
# ═══════════════════════════════════════════════════════════════
class NetworkRequest(Stub):
"""NetworkRequest — 网络请求"""
class Builder(Stub):
"""Builder 模式"""
def __init__(self):
super().__init__()
self._capabilities: Set[int] = set()
self._transports: Set[int] = set()
self._network_specifier: Optional[NetworkSpecifier] = None
self._clear_capabilities: Set[int] = set()
def addCapability(self, capability: int) -> "NetworkRequest.Builder":
self._capabilities.add(capability)
return self
def removeCapability(self, capability: int) -> "NetworkRequest.Builder":
self._capabilities.discard(capability)
self._clear_capabilities.add(capability)
return self
def addTransportType(self, transport: int) -> "NetworkRequest.Builder":
self._transports.add(transport)
return self
def removeTransportType(self, transport: int) -> "NetworkRequest.Builder":
self._transports.discard(transport)
return self
def setNetworkSpecifier(self, specifier: NetworkSpecifier) -> "NetworkRequest.Builder":
self._network_specifier = specifier
return self
def build(self) -> "NetworkRequest":
request = NetworkRequest()
request._capabilities = set(self._capabilities)
request._transports = set(self._transports)
request._network_specifier = self._network_specifier
request._request_id = id(request)
return request
def __init__(self):
super().__init__()
self._capabilities: Set[int] = set()
self._transports: Set[int] = set()
self._network_specifier: Optional[NetworkSpecifier] = None
self._request_id = 0
def hasCapability(self, capability: int) -> bool:
return capability in self._capabilities
def hasTransport(self, transport: int) -> bool:
return transport in self._transports
def getCapabilities(self) -> Set[int]:
return set(self._capabilities)
def getTransports(self) -> Set[int]:
return set(self._transports)
def getNetworkSpecifier(self) -> Optional[NetworkSpecifier]:
return self._network_specifier
def getRequestId(self) -> int:
return self._request_id
def toString(self) -> str:
return f"NetworkRequest(id={self._request_id})"
# ═══════════════════════════════════════════════════════════════
# NetworkCallback
# ═══════════════════════════════════════════════════════════════
class NetworkCallback(Stub):
"""NetworkCallback — 网络回调"""
def __init__(self, flags: int = 0):
super().__init__()
self._flags = flags
def onAvailable(self, network: Network):
pass
def onLost(self, network: Network):
pass
def onCapabilitiesChanged(self, network: Network, capabilities: NetworkCapabilities):
pass
def onLinkPropertiesChanged(self, network: Network, link_properties: "LinkProperties"):
pass
def onNetworkSuspended(self, network: Network):
pass
def onResumed(self, network: Network):
pass
def onBlockedStatusChanged(self, network: Network, blocked: bool):
pass
def onLosing(self, network: Network, max_ms_to_live: int):
pass
def onPreCheck(self, network: Network) -> bool:
return True
def onUnavailable(self):
pass
# ═══════════════════════════════════════════════════════════════
# LinkAddress
# ═══════════════════════════════════════════════════════════════
class LinkAddress(Stub):
"""LinkAddress — 链接地址"""
def __init__(self, address: str = "0.0.0.0",
prefix_length: int = 24,
flags: int = 0,
scope: int = 0):
super().__init__()
self._address = address
self._prefix_length = prefix_length
self._flags = flags
self._scope = scope
self._is_ipv6 = ":" in address
def getAddress(self) -> str:
return self._address
def getPrefixLength(self) -> int:
return self._prefix_length
def getFlags(self) -> int:
return self._flags
def getScope(self) -> int:
return self._scope
def isIpv6(self) -> bool:
return self._is_ipv6
def toString(self) -> str:
return f"{self._address}/{self._prefix_length}"
def equals(self, other) -> bool:
if isinstance(other, LinkAddress):
return self._address == other._address
return False
def hashCode(self) -> int:
return hash(self._address) & 0x7FFFFFFF
# ═══════════════════════════════════════════════════════════════
# RouteInfo
# ═══════════════════════════════════════════════════════════════
class RouteInfo(Stub):
"""RouteInfo — 路由信息"""
RTN_UNICAST = 1
RTN_UNREACHABLE = 2
RTN_BROADCAST = 3
RTN_THROW = 4
RTN_OTHER = 5
def __init__(self, destination: Optional[LinkAddress] = None,
gateway: str = "",
interface_name: str = "",
is_default: bool = False,
type_: int = RTN_UNICAST):
super().__init__()
self._destination = destination
self._gateway = gateway
self._interface_name = interface_name
self._is_default = is_default
self._type = type_
def getDestination(self) -> Optional[LinkAddress]:
return self._destination
def getGateway(self) -> str:
return self._gateway
def getInterface(self) -> str:
return self._interface_name
def getType(self) -> int:
return self._type
def isDefaultRoute(self) -> bool:
return self._is_default
def isIPv4Default(self) -> bool:
return self._is_default and self._destination is not None and not self._destination.isIpv6()
def isIPv6Default(self) -> bool:
return self._is_default and self._destination is not None and self._destination.isIpv6()
def toString(self) -> str:
dest = str(self._destination) if self._destination else "0.0.0.0/0"
gw = self._gateway or "0.0.0.0"
return f"RouteInfo({dest} -> {gw} via {self._interface_name})"
def equals(self, other) -> bool:
if isinstance(other, RouteInfo):
return (self._gateway == other._gateway
and self._interface_name == other._interface_name)
return False
def hashCode(self) -> int:
return hash(self._gateway) & 0x7FFFFFFF
# ═══════════════════════════════════════════════════════════════
# LinkProperties
# ═══════════════════════════════════════════════════════════════
class LinkProperties(Stub):
"""LinkProperties — 链接属性"""
def __init__(self):
super().__init__()
self._link_addresses: List[LinkAddress] = []
self._routes: List[RouteInfo] = []
self._dns_servers: List[str] = []
self._interface_name: str = ""
self._mtu: int = 1500
self._http_proxy: Optional[ProxyInfo] = None
self._private_dns_server_name: str = ""
self._private_dns_active: bool = False
self._domains: str = ""
self._pcscf: List[str] = []
def getLinkAddresses(self) -> List[LinkAddress]:
return list(self._link_addresses)
def getRoutes(self) -> List[RouteInfo]:
return list(self._routes)
def getDnsServers(self) -> List[str]:
return list(self._dns_servers)
def getInterfaceName(self) -> str:
return self._interface_name
def setInterfaceName(self, name: str):
self._interface_name = name
def addLinkAddress(self, address: LinkAddress):
if address not in self._link_addresses:
self._link_addresses.append(address)
def removeLinkAddress(self, address: LinkAddress):
self._link_addresses = [a for a in self._link_addresses if a != address]
def addRoute(self, route: RouteInfo):
if route not in self._routes:
self._routes.append(route)
def removeRoute(self, route: RouteInfo):
self._routes = [r for r in self._routes if r != route]
def addDnsServer(self, dns: str):
if dns not in self._dns_servers:
self._dns_servers.append(dns)
def removeDnsServer(self, dns: str):
self._dns_servers = [d for d in self._dns_servers if d != dns]
def getMtu(self) -> int:
return self._mtu
def setMtu(self, mtu: int):
self._mtu = mtu
def getHttpProxy(self) -> Optional["ProxyInfo"]:
return self._http_proxy
def setHttpProxy(self, proxy: "ProxyInfo"):
self._http_proxy = proxy
def getPrivateDnsServerName(self) -> str:
return self._private_dns_server_name
def setPrivateDnsServerName(self, name: str):
self._private_dns_server_name = name
self._private_dns_active = bool(name)
def isPrivateDnsActive(self) -> bool:
return self._private_dns_active
def setLinkAddresses(self, addresses: List[LinkAddress]):
self._link_addresses = list(addresses)
def setDnsServers(self, dns_servers: List[str]):
self._dns_servers = list(dns_servers)
def getDomains(self) -> str:
return self._domains
def setDomains(self, domains: str):
self._domains = domains
def getPcscf(self) -> List[str]:
return list(self._pcscf)
def addPcscf(self, addr: str):
if addr not in self._pcscf:
self._pcscf.append(addr)
def setMtuV4(int_mtu: int):
pass
def setMtuV6(int_mtu: int):
pass
def clearLinkAddresses(self):
self._link_addresses.clear()
def clearRoutes(self):
self._routes.clear()
def clearDnsServers(self):
self._dns_servers.clear()
def toString(self) -> str:
iface = self._interface_name or "none"
return f"LinkProperties(iface={iface}, mtu={self._mtu}, dns={self._dns_servers})"
def equals(self, other) -> bool:
if isinstance(other, LinkProperties):
return (self._interface_name == other._interface_name
and self._mtu == other._mtu)
return False
def hashCode(self) -> int:
return hash(self._interface_name) & 0x7FFFFFFF
# ═══════════════════════════════════════════════════════════════
# DnsResolver
# ═══════════════════════════════════════════════════════════════
class DnsResolver(Stub):
"""DnsResolver — DNS 解析器"""
CLASS_IN = 1
TYPE_A = 1
TYPE_AAAA = 28
@staticmethod
def getInstance() -> "DnsResolver":
return DnsResolver()
def query(self, hostname: str, callback: Any, flags: int = 0):
try:
result = socket.getaddrinfo(hostname, None)
callback.onAnswer(None, 0, result)
except socket.gaierror:
callback.onAnswer(None, -1, None)
# ═══════════════════════════════════════════════════════════════
# DhcpInfo
# ═══════════════════════════════════════════════════════════════
class DhcpInfo(Stub):
"""DhcpInfo — DHCP 信息"""
def __init__(self):
super().__init__()
self.ipAddress = 0
self.gateway = 0
self.netmask = 0
self.dns1 = 0
self.dns2 = 0
self.serverAddress = 0
self.leaseDuration = 0
def toString(self) -> str:
return (f"DhcpInfo(ip={self.ipAddress}, gw={self.gateway}, "
f"mask={self.netmask}, dns1={self.dns1}, dns2={self.dns2})")
# ═══════════════════════════════════════════════════════════════
# SSLSessionCache
# ═══════════════════════════════════════════════════════════════
class SSLSessionCache(Stub):
"""SSLSessionCache — SSL 会话缓存"""
def __init__(self, context_or_dir: Any):
super().__init__()
self._dir: Optional[str] = None
if isinstance(context_or_dir, str):
self._dir = context_or_dir
else:
self._dir = os.path.join(_app_data_dir, "ssl_cache")
def getDir(self) -> Optional[str]:
return self._dir
# ═══════════════════════════════════════════════════════════════
# TrafficStats
# ═══════════════════════════════════════════════════════════════
class TrafficStats(Stub):
"""TrafficStats — 流量统计"""
UID_TETHERING = "UNSUPPORTED"
UNSUPPORTED = -1
TAG_SYSTEM_UPDATE = 0
_cached_stats = None
_cache_time = 0
@staticmethod
def _read_net_dev():
"""读取 /proc/net/dev 获取网络流量"""
try:
with open("/proc/net/dev", "r") as f:
lines = f.readlines()[2:] # 跳过前两行表头
total_rx = 0
total_tx = 0
for line in lines:
parts = line.split()
ifname = parts[0].rstrip(":")
if ifname == "lo":
continue
rx_bytes = int(parts[1])
tx_bytes = int(parts[9])
total_rx += rx_bytes
total_tx += tx_bytes
return {"rx_bytes": total_rx, "tx_bytes": total_tx,
"rx_packets": 0, "tx_packets": 0}
except Exception:
return {"rx_bytes": 0, "tx_bytes": 0, "rx_packets": 0, "tx_packets": 0}
@staticmethod
def getMobileRxBytes():
return 0 # 桌面无移动数据
@staticmethod
def getMobileTxBytes():
return 0
@staticmethod
def getMobileRxPackets():
return 0
@staticmethod
def getMobileTxPackets():
return 0
@staticmethod
def getTotalRxBytes():
return TrafficStats._read_net_dev()["rx_bytes"]
@staticmethod
def getTotalTxBytes():
return TrafficStats._read_net_dev()["tx_bytes"]
@staticmethod
def getTotalRxPackets():
return TrafficStats._read_net_dev()["rx_packets"]
@staticmethod
def getTotalTxPackets():
return TrafficStats._read_net_dev()["tx_packets"]
@staticmethod
def getUidRxBytes(uid):
try:
with open(f"/proc/uid_stat/{uid}/tcp_rcv", "r") as f:
return int(f.read().strip())
except Exception:
return 0
@staticmethod
def getUidTxBytes(uid):
try:
with open(f"/proc/uid_stat/{uid}/tcp_snd", "r") as f:
return int(f.read().strip())
except Exception:
return 0
@staticmethod
def getUidRxPackets(uid):
return TrafficStats.getUidRxBytes(uid)
@staticmethod
def getUidTxPackets(uid):
return TrafficStats.getUidTxBytes(uid)
@staticmethod
def getThreadStatsTag():
return getattr(TrafficStats, '_thread_tag', 0)
@staticmethod
def setThreadStatsTag(tag):
TrafficStats._thread_tag = tag
@staticmethod
def clearThreadStatsTag():
TrafficStats._thread_tag = 0
@staticmethod
def getUidTcpTxBytes(uid: int) -> int:
return 0
@staticmethod
def getUidTcpRxBytes(uid: int) -> int:
return 0
@staticmethod
def getUidUdpTxBytes(uid: int) -> int:
return 0
@staticmethod
def getUidUdpRxBytes(uid: int) -> int:
return 0
@staticmethod
def setThreadStatsUid(uid: int):
pass
@staticmethod
def clearThreadStatsUid():
pass
@staticmethod
def incrementOperationCount(uid: int, tag: int, count: int):
pass
# ═══════════════════════════════════════════════════════════════
# Proxy / ProxyInfo
# ═══════════════════════════════════════════════════════════════
class Proxy(Stub):
"""Proxy — 系统代理信息"""
@staticmethod
def getDefaultHost() -> Optional[str]:
return None
@staticmethod
def getDefaultPort() -> int:
return -1
class ProxyInfo(Stub):
"""ProxyInfo — 代理详细信息"""
def __init__(self, host: str = "", port: int = 0,
pac_file_url: Optional[str] = None,
username: str = "", password: str = "",
exclusion_list: Optional[List[str]] = None):
super().__init__()
self._host = host
self._port = port
self._pac_file_url = pac_file_url
self._username = username
self._password = password
self._exclusion_list = list(exclusion_list) if exclusion_list else []
self._valid = bool(host) or bool(pac_file_url)
def getHost(self) -> str:
return self._host
def getPort(self) -> int:
return self._port
def getPacFileUrl(self) -> Optional[str]:
return self._pac_file_url
def getUsername(self) -> str:
return self._username
def getPassword(self) -> str:
return self._password
def getExclusionList(self) -> List[str]:
return list(self._exclusion_list)
def isValid(self) -> bool:
return self._valid
def makeProxy(self, url: str):
return self
def equals(self, other) -> bool:
if isinstance(other, ProxyInfo):
return self._host == other._host and self._port == other._port
return False
def hashCode(self) -> int:
return hash(self._host) ^ self._port
def toString(self) -> str:
pac = f" pac={self._pac_file_url}" if self._pac_file_url else ""
return f"ProxyInfo(host={self._host}:{self._port}{pac})"
# ═══════════════════════════════════════════════════════════════
# ConnectivityManager
# ═══════════════════════════════════════════════════════════════
class ConnectivityManager(Stub):
"""ConnectivityManager — 连接管理器"""
TYPE_WIFI = 1
TYPE_MOBILE = 0
TYPE_BLUETOOTH = 7
TYPE_ETHERNET = 9
TYPE_VPN = 17
TYPE_WIMAX = 6
TYPE_DUMMY = 8
TYPE_PROXY = 16
CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"
DEFAULT_NETWORK_PREFERENCE = 1
def __init__(self):
super().__init__()
self._networks: Dict[int, "Network"] = {}
self._callbacks: List[Tuple[NetworkCallback, Optional[NetworkRequest]]] = []
self._active_network_info = NetworkInfo(
type_=self.TYPE_WIFI, state="CONNECTED", detailed_state="CONNECTED",
type_name="WIFI", is_available=True,
)
self._net_id_counter = 0
self._network_caps: Dict[int, NetworkCapabilities] = {}
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]:
if _USE_LINUX:
return self._get_linux_active_info()
return self._active_network_info
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)
if network:
return NetworkInfo(type_=network_type, state="CONNECTED", is_available=True)
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]:
return self._active_network_info
def getAllNetworks(self) -> List[Network]:
if _USE_LINUX:
networks: List[Network] = []
try:
interfaces = LinuxNetwork.get_interfaces()
idx = 0
for iface in interfaces:
name = iface.get("ifname") or iface.get("name", "")
if not name or name == "lo":
continue
idx += 1
net = Network(idx)
networks.append(net)
# 若无真实接口, 回退到内部缓存
if networks:
return networks
except Exception:
pass
return list(self._networks.values())
def getActiveNetwork(self) -> Optional[Network]:
if _USE_LINUX:
try:
interfaces = LinuxNetwork.get_interfaces()
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":
net = Network(1)
self._networks[1] = net
return net
# 回退: 若有外网连通性, 仍返回一个活跃 Network
if LinuxNetwork.get_connectivity():
net = Network(1)
self._networks[1] = net
return net
except Exception:
pass
if self._networks:
nid = next(iter(self._networks))
return self._networks[nid]
net = Network(1)
self._networks[1] = net
return net
def getActiveNetworkForUid(self, uid: int) -> Optional[Network]:
return self.getActiveNetwork()
def getNetworkCapabilities(self, network: Network) -> Optional[NetworkCapabilities]:
caps = self._network_caps.get(network.getNetworkHandle())
if caps is None:
caps = NetworkCapabilities()
caps.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
caps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
caps.addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
caps.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
self._network_caps[network.getNetworkHandle()] = caps
return caps
def getLinkProperties(self, network: Network) -> Optional[LinkProperties]:
lp = self._link_props.get(network.getNetworkHandle())
if lp is None:
lp = LinkProperties()
self._link_props[network.getNetworkHandle()] = lp
return lp
def requestNetwork(self, request: NetworkRequest, callback: NetworkCallback, timeout_ms: int = 0,
handler: Any = None, network_specifier: int = 0) -> None:
self._callbacks.append((callback, request))
net = self.getActiveNetwork()
if net:
try:
callback.onAvailable(net)
except Exception:
pass
def registerDefaultNetworkCallback(self, callback: NetworkCallback, handler: Any = None) -> None:
self._callbacks.append((callback, None))
net = self.getActiveNetwork()
if net:
try:
callback.onAvailable(net)
except Exception:
pass
def registerNetworkCallback(self, request: NetworkRequest, callback: NetworkCallback,
handler: Any = None) -> None:
self._callbacks.append((callback, request))
net = self.getActiveNetwork()
if net:
try:
callback.onAvailable(net)
except Exception:
pass
def unregisterNetworkCallback(self, callback_or_request: Any):
if isinstance(callback_or_request, NetworkCallback):
self._callbacks = [
(cb, req) for cb, req in self._callbacks
if cb is not callback_or_request
]
elif isinstance(callback_or_request, NetworkRequest):
self._callbacks = [
(cb, req) for cb, req in self._callbacks
if req is not callback_or_request
]
def bindProcessToNetwork(self, network: Optional[Network]) -> bool:
return True
def getProcessDefaultNetwork(self) -> Optional[Network]:
return self.getActiveNetwork()
@staticmethod
def setProcessDefaultNetwork(network: Optional[Network]) -> bool:
return True
def getAllNetworkInfo(self) -> List[NetworkInfo]:
return [self._active_network_info]
def getNetworkInfoForProcess(self) -> Optional[NetworkInfo]:
return self._active_network_info
def isDefaultNetworkActive(self) -> bool:
return True
def addDefaultNetworkActiveListener(self, listener: Any):
pass
def removeDefaultNetworkActiveListener(self, listener: Any):
pass
def reportBadNetwork(self, network: Network):
pass
def setNetworkPreference(self, preference: int):
pass
def getNetworkPreference(self) -> int:
return self.DEFAULT_NETWORK_PREFERENCE
def startCaptivePortalApp(self, network: Network):
pass
def setProcessDefaultNetworkForUid(self, uid: int) -> bool:
return True
def registerNetworkAgent(self, agent: Any) -> int:
return 0
def toString(self) -> str:
return f"ConnectivityManager(networks={len(self._networks)})"
# ═══════════════════════════════════════════════════════════════
# __all__
# ═══════════════════════════════════════════════════════════════
__all__ = [
"Uri",
"Uri_Builder",
"ConnectivityManager",
"NetworkInfo",
"Network",
"NetworkCapabilities",
"NetworkRequest",
"NetworkCallback",
"LinkProperties",
"LinkAddress",
"RouteInfo",
"DnsResolver",
"DhcpInfo",
"InetAddresses",
"SSLSessionCache",
"TrafficStats",
"Proxy",
"ProxyInfo",
"NetworkSpecifier",
"MatchAllNetworkSpecifier",
"WifiAwareNetworkSpecifier",
"EthernetNetworkSpecifier",
"TelephonyNetworkSpecifier",
"NetworkState",
"LocalSocket",
"LocalSocketAddress",
"NetworkUtils",
]