- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
643 lines
21 KiB
Python
643 lines
21 KiB
Python
"""
|
|
Android HTTP API — AndroidHttpClient, HttpUrlConnection, HttpResponseCache, SSLCertificateSocketFactory, TimedText
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import io
|
|
import json
|
|
import time
|
|
import socket
|
|
import hashlib
|
|
import tempfile
|
|
import threading
|
|
import urllib.request
|
|
import urllib.error
|
|
import urllib.parse
|
|
import ssl as _ssl
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional, Set, Tuple, Callable, BinaryIO
|
|
from http.client import HTTPResponse, HTTPConnection
|
|
from urllib.request import Request as _UrlRequest
|
|
|
|
from .._core import Stub, AndroidOnlyError, _app_data_dir
|
|
|
|
from ..java.lang import RuntimeException
|
|
|
|
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# 常量
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
HTTP_GET = "GET"
|
|
HTTP_POST = "POST"
|
|
HTTP_PUT = "PUT"
|
|
HTTP_DELETE = "DELETE"
|
|
HTTP_HEAD = "HEAD"
|
|
HTTP_OPTIONS = "OPTIONS"
|
|
HTTP_PATCH = "PATCH"
|
|
HTTP_TRACE = "TRACE"
|
|
|
|
HTTP_100_CONTINUE = 100
|
|
HTTP_101_SWITCHING_PROTOCOLS = 101
|
|
HTTP_200_OK = 200
|
|
HTTP_201_CREATED = 201
|
|
HTTP_202_ACCEPTED = 202
|
|
HTTP_204_NO_CONTENT = 204
|
|
HTTP_301_MOVED_PERMANENTLY = 301
|
|
HTTP_302_FOUND = 302
|
|
HTTP_304_NOT_MODIFIED = 304
|
|
HTTP_307_TEMPORARY_REDIRECT = 307
|
|
HTTP_400_BAD_REQUEST = 400
|
|
HTTP_401_UNAUTHORIZED = 401
|
|
HTTP_403_FORBIDDEN = 403
|
|
HTTP_404_NOT_FOUND = 404
|
|
HTTP_405_METHOD_NOT_ALLOWED = 405
|
|
HTTP_408_REQUEST_TIMEOUT = 408
|
|
HTTP_409_CONFLICT = 409
|
|
HTTP_410_GONE = 410
|
|
HTTP_429_TOO_MANY_REQUESTS = 429
|
|
HTTP_500_INTERNAL_SERVER_ERROR = 500
|
|
HTTP_502_BAD_GATEWAY = 502
|
|
HTTP_503_SERVICE_UNAVAILABLE = 503
|
|
HTTP_504_GATEWAY_TIMEOUT = 504
|
|
|
|
CHUNK_SIZE = 8192
|
|
DEFAULT_CONNECT_TIMEOUT = 30000
|
|
DEFAULT_READ_TIMEOUT = 30000
|
|
MAX_REDIRECTS = 20
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SSLCertificateSocketFactory
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class SSLCertificateSocketFactory(Stub):
|
|
"""SSLCertificateSocketFactory — SSL 证书套接字工厂"""
|
|
|
|
SSL_SECURITY_PROTOCOL_TLS = 1
|
|
SSL_SECURITY_PROTOCOL_SSLv3 = 2
|
|
SSL_SECURITY_PROTOCOL_DTLS = 3
|
|
SSL_SECURITY_PROTOCOL_TLSv1 = 4
|
|
SSL_SECURITY_PROTOCOL_TLSv1_1 = 5
|
|
SSL_SECURITY_PROTOCOL_TLSv1_2 = 6
|
|
SSL_SECURITY_PROTOCOL_TLSv1_3 = 7
|
|
|
|
def __init__(self, handshakeTimeoutMillis: int = 0):
|
|
super().__init__()
|
|
self._handshake_timeout = handshakeTimeoutMillis
|
|
self._security_protocol = SSLCertificateSocketFactory.SSL_SECURITY_PROTOCOL_TLS
|
|
self._trust_all = False
|
|
self._key_manager: Optional[Any] = None
|
|
self._trust_manager: Optional[Any] = None
|
|
self._secure_random: Optional[Any] = None
|
|
self._default_ssl_context = _ssl.create_default_context()
|
|
|
|
@staticmethod
|
|
def getDefault(handshakeTimeoutMillis: int = 0) -> "SSLCertificateSocketFactory":
|
|
return SSLCertificateSocketFactory(handshakeTimeoutMillis)
|
|
|
|
@staticmethod
|
|
def getSocketFactory() -> "SSLCertificateSocketFactory":
|
|
return SSLCertificateSocketFactory()
|
|
|
|
def createSocket(self, s: Optional[socket.socket] = None, host: str = "", port: int = 443, autoClose: bool = True) -> socket.socket:
|
|
if s is None:
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
context = _ssl.create_default_context()
|
|
if self._trust_all:
|
|
context.check_hostname = False
|
|
context.verify_mode = _ssl.CERT_NONE
|
|
return context.wrap_socket(s, server_hostname=host if host else None)
|
|
except Exception:
|
|
return s
|
|
|
|
def setTrustAll(self, trustAll: bool) -> None:
|
|
self._trust_all = trustAll
|
|
|
|
def setKeyManager(self, km: Any) -> None:
|
|
self._key_manager = km
|
|
|
|
def setTrustManager(self, tm: Any) -> None:
|
|
self._trust_manager = tm
|
|
|
|
def setSecureRandom(self, sr: Any) -> None:
|
|
self._secure_random = sr
|
|
|
|
def getSecurityProtocol(self) -> int:
|
|
return self._security_protocol
|
|
|
|
def setSecurityProtocol(self, protocol: int) -> None:
|
|
self._security_protocol = protocol
|
|
|
|
def getHandshakeTimeout(self) -> int:
|
|
return self._handshake_timeout
|
|
|
|
def toString(self) -> str:
|
|
return f"SSLCertificateSocketFactory(protocol={self._security_protocol})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# HttpResponseCache
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class HttpResponseCache(Stub):
|
|
"""HttpResponseCache — HTTP 响应缓存"""
|
|
|
|
_instance: Optional["HttpResponseCache"] = None
|
|
|
|
def __init__(self, directory: str, maxSize: int):
|
|
super().__init__()
|
|
self._directory = directory
|
|
self._max_size = maxSize
|
|
self._current_size = 0
|
|
self._cache: Dict[str, Tuple[str, float, int]] = {}
|
|
self._hit_count = 0
|
|
self._miss_count = 0
|
|
self._network_count = 0
|
|
self._write_success_count = 0
|
|
self._write_abort_count = 0
|
|
self._lock = threading.Lock()
|
|
os.makedirs(directory, exist_ok=True)
|
|
|
|
@staticmethod
|
|
def getDefault() -> Optional["HttpResponseCache"]:
|
|
return HttpResponseCache._instance
|
|
|
|
@staticmethod
|
|
def install(directory: str, maxSize: int) -> "HttpResponseCache":
|
|
cache = HttpResponseCache(directory, maxSize)
|
|
HttpResponseCache._instance = cache
|
|
return cache
|
|
|
|
def get(self, key: str) -> Optional[Tuple[str, float]]:
|
|
with self._lock:
|
|
entry = self._cache.get(key)
|
|
if entry:
|
|
data, expiry, size = entry
|
|
if time.time() < expiry:
|
|
self._hit_count += 1
|
|
return (data, expiry)
|
|
else:
|
|
del self._cache[key]
|
|
self._current_size -= size
|
|
self._miss_count += 1
|
|
return None
|
|
self._miss_count += 1
|
|
return None
|
|
|
|
def put(self, key: str, data: str, ttl_seconds: int = 300) -> None:
|
|
with self._lock:
|
|
old = self._cache.get(key)
|
|
if old:
|
|
self._current_size -= old[2]
|
|
size = len(data.encode("utf-8"))
|
|
expires = time.time() + ttl_seconds
|
|
self._cache[key] = (data, expires, size)
|
|
self._current_size += size
|
|
self._write_success_count += 1
|
|
self._evict_if_needed()
|
|
|
|
def _evict_if_needed(self) -> None:
|
|
while self._current_size > self._max_size and self._cache:
|
|
oldest_key = min(self._cache.keys(), key=lambda k: self._cache[k][1])
|
|
oldest = self._cache.pop(oldest_key)
|
|
self._current_size -= oldest[2]
|
|
self._write_abort_count += 1
|
|
|
|
def flush(self) -> None:
|
|
pass
|
|
|
|
def delete(self) -> None:
|
|
with self._lock:
|
|
self._cache.clear()
|
|
self._current_size = 0
|
|
HttpResponseCache._instance = None
|
|
|
|
def maxSize(self) -> int:
|
|
return self._max_size
|
|
|
|
def size(self) -> int:
|
|
with self._lock:
|
|
return self._current_size
|
|
|
|
def hitCount(self) -> int:
|
|
return self._hit_count
|
|
|
|
def missCount(self) -> int:
|
|
return self._miss_count
|
|
|
|
def networkCount(self) -> int:
|
|
return self._network_count
|
|
|
|
def writeSuccessCount(self) -> int:
|
|
return self._write_success_count
|
|
|
|
def writeAbortCount(self) -> int:
|
|
return self._write_abort_count
|
|
|
|
def isCached(self, key: str) -> bool:
|
|
with self._lock:
|
|
return key in self._cache
|
|
|
|
def clear(self) -> None:
|
|
with self._lock:
|
|
self._cache.clear()
|
|
self._current_size = 0
|
|
|
|
def toString(self) -> str:
|
|
return (
|
|
f"HttpResponseCache(size={self._current_size}/{self._max_size}, "
|
|
f"hits={self._hit_count}, misses={self._miss_count})"
|
|
)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TimedText
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TimedText(Stub):
|
|
"""TimedText — 定时文本(字幕)"""
|
|
|
|
def __init__(self, text: str = "", startMs: int = 0, endMs: int = 0):
|
|
super().__init__()
|
|
self._text = text
|
|
self._start_ms = startMs
|
|
self._end_ms = endMs
|
|
self._style: Optional[Dict[str, Any]] = None
|
|
self._subtitle_id = 0
|
|
self._track_id = 0
|
|
self._mime_type: Optional[str] = None
|
|
self._language: Optional[str] = None
|
|
self._flow = False
|
|
|
|
def getText(self) -> str:
|
|
return self._text
|
|
|
|
def setText(self, text: str) -> None:
|
|
self._text = text
|
|
|
|
def getStartMs(self) -> int:
|
|
return self._start_ms
|
|
|
|
def setStartMs(self, startMs: int) -> None:
|
|
self._start_ms = startMs
|
|
|
|
def getEndMs(self) -> int:
|
|
return self._end_ms
|
|
|
|
def setEndMs(self, endMs: int) -> None:
|
|
self._end_ms = endMs
|
|
|
|
def getDurationMs(self) -> int:
|
|
return self._end_ms - self._start_ms
|
|
|
|
def getStyle(self) -> Optional[Dict[str, Any]]:
|
|
return self._style
|
|
|
|
def setStyle(self, style: Dict[str, Any]) -> None:
|
|
self._style = dict(style)
|
|
|
|
def getLanguage(self) -> Optional[str]:
|
|
return self._language
|
|
|
|
def setLanguage(self, lang: str) -> None:
|
|
self._language = lang
|
|
|
|
def getMimeType(self) -> Optional[str]:
|
|
return self._mime_type
|
|
|
|
def setMimeType(self, mimeType: str) -> None:
|
|
self._mime_type = mimeType
|
|
|
|
def isFlow(self) -> bool:
|
|
return self._flow
|
|
|
|
def setFlow(self, flow: bool) -> None:
|
|
self._flow = flow
|
|
|
|
def toString(self) -> str:
|
|
return f"TimedText(text={self._text[:50]}, start={self._start_ms}, end={self._end_ms})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# HttpUrlConnection (扩展)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class HttpUrlConnection(Stub):
|
|
"""HttpURLConnection (扩展) — HTTP 连接管理"""
|
|
|
|
def __init__(self, url: str = ""):
|
|
super().__init__()
|
|
self._url = url
|
|
self._method = HTTP_GET
|
|
self._connect_timeout = DEFAULT_CONNECT_TIMEOUT
|
|
self._read_timeout = DEFAULT_READ_TIMEOUT
|
|
self._request_properties: Dict[str, str] = {}
|
|
self._follow_redirects = True
|
|
self._instance_follow_redirects = True
|
|
self._use_caches = True
|
|
self._allow_user_interaction = False
|
|
self._do_input = True
|
|
self._do_output = False
|
|
self._if_modified_since = 0
|
|
self._fixed_length = -1
|
|
self._fixed_stream_mode = False
|
|
self._chunk_length = 0
|
|
self._response_code = -1
|
|
self._response_message: Optional[str] = None
|
|
self._content_length = -1
|
|
self._content_type: Optional[str] = None
|
|
self._content_encoding: Optional[str] = None
|
|
self._expiration: Optional[int] = None
|
|
self._date: Optional[int] = None
|
|
self._last_modified: Optional[int] = None
|
|
self._header_fields: Dict[str, List[str]] = {}
|
|
self._input_stream: Optional[BinaryIO] = None
|
|
self._output_stream: Optional[BinaryIO] = None
|
|
self._connected = False
|
|
self._sslsocket_factory: Optional[SSLCertificateSocketFactory] = None
|
|
|
|
def setRequestMethod(self, method: str) -> None:
|
|
self._method = method.upper()
|
|
|
|
def getRequestMethod(self) -> str:
|
|
return self._method
|
|
|
|
def setConnectTimeout(self, timeoutMillis: int) -> None:
|
|
self._connect_timeout = timeoutMillis
|
|
|
|
def getConnectTimeout(self) -> int:
|
|
return self._connect_timeout
|
|
|
|
def setReadTimeout(self, timeoutMillis: int) -> None:
|
|
self._read_timeout = timeoutMillis
|
|
|
|
def getReadTimeout(self) -> int:
|
|
return self._read_timeout
|
|
|
|
def setRequestProperty(self, key: str, value: str) -> None:
|
|
self._request_properties[key] = value
|
|
|
|
def addRequestProperty(self, key: str, value: str) -> None:
|
|
if key in self._request_properties:
|
|
self._request_properties[key] += ", " + value
|
|
else:
|
|
self._request_properties[key] = value
|
|
|
|
def getRequestProperty(self, key: str) -> Optional[str]:
|
|
return self._request_properties.get(key)
|
|
|
|
def getRequestProperties(self) -> Dict[str, str]:
|
|
return dict(self._request_properties)
|
|
|
|
def setInstanceFollowRedirects(self, follow: bool) -> None:
|
|
self._instance_follow_redirects = follow
|
|
|
|
def getInstanceFollowRedirects(self) -> bool:
|
|
return self._instance_follow_redirects
|
|
|
|
def setFollowRedirects(self, follow: bool) -> None:
|
|
self._follow_redirects = follow
|
|
|
|
def getFollowRedirects(self) -> bool:
|
|
return self._follow_redirects
|
|
|
|
def setUseCaches(self, use: bool) -> None:
|
|
self._use_caches = use
|
|
|
|
def getUseCaches(self) -> bool:
|
|
return self._use_caches
|
|
|
|
def setAllowUserInteraction(self, allow: bool) -> None:
|
|
self._allow_user_interaction = allow
|
|
|
|
def getAllowUserInteraction(self) -> bool:
|
|
return self._allow_user_interaction
|
|
|
|
def setDoInput(self, do: bool) -> None:
|
|
self._do_input = do
|
|
|
|
def getDoInput(self) -> bool:
|
|
return self._do_input
|
|
|
|
def setDoOutput(self, do: bool) -> None:
|
|
self._do_output = do
|
|
|
|
def getDoOutput(self) -> bool:
|
|
return self._do_output
|
|
|
|
def setIfModifiedSince(self, ifModifiedSince: int) -> None:
|
|
self._if_modified_since = ifModifiedSince
|
|
|
|
def getIfModifiedSince(self) -> int:
|
|
return self._if_modified_since
|
|
|
|
def setFixedLengthStreamingMode(self, contentLength: int) -> None:
|
|
self._fixed_length = contentLength
|
|
self._fixed_stream_mode = True
|
|
|
|
def setChunkedStreamingMode(self, chunklen: int) -> None:
|
|
self._chunk_length = chunklen
|
|
|
|
def getHeaderField(self, name: str) -> Optional[str]:
|
|
values = self._header_fields.get(name)
|
|
return values[0] if values else None
|
|
|
|
def getHeaderFields(self) -> Dict[str, List[str]]:
|
|
return dict(self._header_fields)
|
|
|
|
def getHeaderFieldKey(self, n: int) -> Optional[str]:
|
|
keys = list(self._header_fields.keys())
|
|
if 0 <= n < len(keys):
|
|
return keys[n]
|
|
return None
|
|
|
|
def getHeaderFieldInt(self, name: str, default: int) -> int:
|
|
val = self.getHeaderField(name)
|
|
if val:
|
|
try:
|
|
return int(val)
|
|
except ValueError:
|
|
pass
|
|
return default
|
|
|
|
def getHeaderFieldLong(self, name: str, default: int) -> int:
|
|
return self.getHeaderFieldInt(name, default)
|
|
|
|
def getHeaderFieldDate(self, name: str, default: int) -> int:
|
|
return default
|
|
|
|
def getContentLength(self) -> int:
|
|
return self._content_length
|
|
|
|
def getContentLengthLong(self) -> int:
|
|
return self._content_length
|
|
|
|
def getContentType(self) -> Optional[str]:
|
|
return self._content_type
|
|
|
|
def getContentEncoding(self) -> Optional[str]:
|
|
return self._content_encoding
|
|
|
|
def getExpiration(self) -> int:
|
|
return self._expiration or 0
|
|
|
|
def getDate(self) -> int:
|
|
return self._date or 0
|
|
|
|
def getLastModified(self) -> int:
|
|
return self._last_modified or 0
|
|
|
|
def getResponseCode(self) -> int:
|
|
if not self._connected:
|
|
self.connect()
|
|
return self._response_code
|
|
|
|
def getResponseMessage(self) -> Optional[str]:
|
|
return self._response_message
|
|
|
|
def getInputStream(self) -> Optional[BinaryIO]:
|
|
if not self._connected:
|
|
self.connect()
|
|
return self._input_stream
|
|
|
|
def getOutputStream(self) -> Optional[BinaryIO]:
|
|
if not self._connected:
|
|
self.connect()
|
|
return self._output_stream
|
|
|
|
def getErrorStream(self) -> Optional[BinaryIO]:
|
|
return None
|
|
|
|
def connect(self) -> None:
|
|
if self._connected:
|
|
return
|
|
self._connected = True
|
|
try:
|
|
req = _UrlRequest(self._url, method=self._method.encode() if hasattr(self._method, 'encode') else self._method.encode())
|
|
for k, v in self._request_properties.items():
|
|
req.add_header(k, v)
|
|
self._response_code = 200
|
|
self._response_message = "OK"
|
|
self._content_type = "text/plain"
|
|
self._content_length = 0
|
|
except Exception as e:
|
|
self._response_code = 0
|
|
self._response_message = str(e)
|
|
|
|
def disconnect(self) -> None:
|
|
self._connected = False
|
|
self._input_stream = None
|
|
self._output_stream = None
|
|
|
|
def getURL(self) -> str:
|
|
return self._url
|
|
|
|
def getPermission(self) -> Optional[Any]:
|
|
return None
|
|
|
|
def toString(self) -> str:
|
|
return f"HttpURLConnection(url={self._url}, method={self._method})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# AndroidHttpClient
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class AndroidHttpClient(Stub):
|
|
"""AndroidHttpClient — Android HTTP 客户端 (扩展)"""
|
|
|
|
def __init__(self, userAgent: str = "AndroidHttpClient"):
|
|
super().__init__()
|
|
self._user_agent = userAgent
|
|
self._connect_timeout = DEFAULT_CONNECT_TIMEOUT
|
|
self._read_timeout = DEFAULT_READ_TIMEOUT
|
|
self._follow_redirects = True
|
|
self._compression_enabled = True
|
|
self._cookie_store: Dict[str, Dict[str, str]] = {}
|
|
self._auth_cache: Dict[str, Tuple[str, str]] = {}
|
|
self._lock = threading.Lock()
|
|
|
|
@staticmethod
|
|
def newInstance(userAgent: str = "AndroidHttpClient") -> "AndroidHttpClient":
|
|
return AndroidHttpClient(userAgent)
|
|
|
|
def execute(self, request: Any) -> Any:
|
|
return None
|
|
|
|
def executeHttp(self, request: Any, context: Any) -> Any:
|
|
return None
|
|
|
|
def execute(self, host: Any, request: Any) -> Any:
|
|
return None
|
|
|
|
def execute(self, host: Any, request: Any, context: Any) -> Any:
|
|
return None
|
|
|
|
def getConnectionManager(self) -> Any:
|
|
return None
|
|
|
|
def getCredentialsProvider(self) -> Any:
|
|
return None
|
|
|
|
def getCookieStore(self) -> Any:
|
|
return None
|
|
|
|
def getAuthSchemeRegistry(self) -> Any:
|
|
return None
|
|
|
|
def enableCurlLogging(self, tag: str, level: int) -> None:
|
|
pass
|
|
|
|
def disableCurlLogging(self) -> None:
|
|
pass
|
|
|
|
def setUserAgent(self, userAgent: str) -> None:
|
|
self._user_agent = userAgent
|
|
|
|
def getConnectTimeout(self) -> int:
|
|
return self._connect_timeout
|
|
|
|
def setConnectTimeout(self, timeout: int) -> None:
|
|
self._connect_timeout = timeout
|
|
|
|
def getReadTimeout(self) -> int:
|
|
return self._read_timeout
|
|
|
|
def setReadTimeout(self, timeout: int) -> None:
|
|
self._read_timeout = timeout
|
|
|
|
def isCompressionEnabled(self) -> bool:
|
|
return self._compression_enabled
|
|
|
|
def setCompressionEnabled(self, enabled: bool) -> None:
|
|
self._compression_enabled = enabled
|
|
|
|
def clearCredentials(self) -> None:
|
|
with self._lock:
|
|
self._auth_cache.clear()
|
|
|
|
def clearCookies(self) -> None:
|
|
with self._lock:
|
|
self._cookie_store.clear()
|
|
|
|
def close(self) -> None:
|
|
pass
|
|
|
|
@staticmethod
|
|
def modifyRequestToAcceptGzipResponse(request: Any) -> Any:
|
|
return request
|
|
|
|
@staticmethod
|
|
def getUngzippedContent(entity: Any) -> Any:
|
|
return entity
|
|
|
|
def toString(self) -> str:
|
|
return f"AndroidHttpClient(user_agent={self._user_agent})" |